From 51cd271b6ffe12e8764c178f85cdbc4521edaf6f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 23 Jun 2026 00:05:41 +0200 Subject: [PATCH] libremidi update to v5.4.3 and added virtual midi out as default --- include/libremidi/api-c.h | 17 +- include/libremidi/api.hpp | 12 +- include/libremidi/backends.hpp | 51 +- include/libremidi/backends/alsa_raw.hpp | 7 +- .../libremidi/backends/alsa_raw/config.hpp | 7 +- .../libremidi/backends/alsa_raw/helpers.hpp | 30 +- .../libremidi/backends/alsa_raw/midi_in.hpp | 53 +- .../libremidi/backends/alsa_raw/midi_out.hpp | 8 +- .../libremidi/backends/alsa_raw/observer.hpp | 181 ++-- include/libremidi/backends/alsa_raw_ump.hpp | 13 +- .../backends/alsa_raw_ump/config.hpp | 8 +- .../backends/alsa_raw_ump/helpers.hpp | 4 +- .../backends/alsa_raw_ump/midi_in.hpp | 66 +- .../backends/alsa_raw_ump/midi_out.hpp | 11 +- .../backends/alsa_raw_ump/observer.hpp | 13 +- include/libremidi/backends/alsa_seq.hpp | 15 +- .../libremidi/backends/alsa_seq/config.hpp | 2 +- .../libremidi/backends/alsa_seq/helpers.hpp | 55 +- .../libremidi/backends/alsa_seq/midi_in.hpp | 55 +- .../libremidi/backends/alsa_seq/midi_out.hpp | 24 +- .../libremidi/backends/alsa_seq/observer.hpp | 297 ++++-- .../backends/alsa_seq/shared_handler.hpp | 18 +- include/libremidi/backends/alsa_seq_ump.hpp | 17 +- .../backends/alsa_seq_ump/config.hpp | 2 +- .../backends/alsa_seq_ump/midi_out.hpp | 17 +- .../libremidi/backends/android/android.hpp | 33 + include/libremidi/backends/android/config.hpp | 23 + .../libremidi/backends/android/helpers.cpp | 239 +++++ .../libremidi/backends/android/helpers.hpp | 40 + .../libremidi/backends/android/midi_in.hpp | 165 ++++ .../libremidi/backends/android/midi_out.hpp | 126 +++ .../libremidi/backends/android/observer.hpp | 93 ++ include/libremidi/backends/coremidi.hpp | 8 +- .../libremidi/backends/coremidi/config.hpp | 2 +- .../backends/coremidi/error_domain.hpp | 2 +- .../libremidi/backends/coremidi/helpers.hpp | 40 +- .../libremidi/backends/coremidi/midi_in.hpp | 16 +- .../libremidi/backends/coremidi/midi_out.hpp | 28 +- .../libremidi/backends/coremidi/observer.hpp | 75 +- include/libremidi/backends/coremidi_ump.hpp | 8 +- .../backends/coremidi_ump/config.hpp | 2 +- .../backends/coremidi_ump/midi_in.hpp | 16 +- .../backends/coremidi_ump/midi_out.hpp | 21 +- .../backends/coremidi_ump/observer.hpp | 13 +- include/libremidi/backends/dummy.hpp | 44 +- include/libremidi/backends/emscripten.hpp | 8 +- .../libremidi/backends/emscripten/config.hpp | 2 +- .../libremidi/backends/emscripten/helpers.hpp | 2 +- .../backends/emscripten/midi_access.hpp | 22 +- .../libremidi/backends/emscripten/midi_in.cpp | 9 +- .../libremidi/backends/emscripten/midi_in.hpp | 4 +- .../backends/emscripten/midi_out.cpp | 20 +- .../backends/emscripten/midi_out.hpp | 4 +- .../backends/emscripten/observer.cpp | 5 +- .../backends/emscripten/observer.hpp | 2 +- include/libremidi/backends/jack.hpp | 8 +- include/libremidi/backends/jack/config.hpp | 2 +- .../libremidi/backends/jack/error_domain.hpp | 30 +- include/libremidi/backends/jack/helpers.hpp | 151 ++- include/libremidi/backends/jack/midi_in.hpp | 18 +- include/libremidi/backends/jack/midi_out.hpp | 86 +- include/libremidi/backends/jack/observer.hpp | 63 +- .../backends/jack/shared_handler.hpp | 25 +- include/libremidi/backends/jack_ump.hpp | 34 + .../libremidi/backends/jack_ump/config.hpp | 33 + .../libremidi/backends/jack_ump/midi_in.hpp | 119 +++ .../libremidi/backends/jack_ump/midi_out.hpp | 184 ++++ .../libremidi/backends/jack_ump/observer.hpp | 243 +++++ .../backends/jack_ump/shared_handler.hpp | 0 include/libremidi/backends/kdmapi.hpp | 43 + include/libremidi/backends/kdmapi/config.hpp | 23 + include/libremidi/backends/kdmapi/helpers.hpp | 178 ++++ include/libremidi/backends/kdmapi/midi_in.hpp | 11 + .../libremidi/backends/kdmapi/midi_out.hpp | 173 ++++ .../libremidi/backends/kdmapi/observer.hpp | 86 ++ include/libremidi/backends/keyboard.hpp | 8 +- .../libremidi/backends/keyboard/config.hpp | 206 +++-- .../libremidi/backends/keyboard/midi_in.hpp | 18 +- include/libremidi/backends/linux/alsa.hpp | 120 ++- .../libremidi/backends/linux/dylib_loader.hpp | 3 +- include/libremidi/backends/linux/helpers.hpp | 2 +- include/libremidi/backends/linux/pipewire.hpp | 79 +- include/libremidi/backends/linux/udev.hpp | 139 ++- include/libremidi/backends/net/config.hpp | 7 +- include/libremidi/backends/net/helpers.hpp | 31 +- include/libremidi/backends/net/midi_in.hpp | 11 +- include/libremidi/backends/net/midi_out.hpp | 4 +- include/libremidi/backends/net/observer.hpp | 4 +- include/libremidi/backends/network.hpp | 8 +- include/libremidi/backends/network_ump.hpp | 8 +- include/libremidi/backends/pipewire.hpp | 8 +- .../libremidi/backends/pipewire/config.hpp | 2 +- .../libremidi/backends/pipewire/context.hpp | 48 +- .../libremidi/backends/pipewire/helpers.hpp | 70 +- .../libremidi/backends/pipewire/midi_in.hpp | 13 +- .../libremidi/backends/pipewire/midi_out.hpp | 20 +- .../libremidi/backends/pipewire/observer.hpp | 32 +- .../backends/pipewire/shared_handler.hpp | 23 +- include/libremidi/backends/pipewire_ump.hpp | 39 + .../backends/pipewire_ump/config.hpp | 36 + .../backends/pipewire_ump/midi_in.hpp | 132 +++ .../backends/pipewire_ump/midi_out.hpp | 182 ++++ .../backends/pipewire_ump/observer.hpp | 70 ++ include/libremidi/backends/winmidi.hpp | 14 +- include/libremidi/backends/winmidi/config.hpp | 19 +- .../libremidi/backends/winmidi/helpers.hpp | 194 +++- .../libremidi/backends/winmidi/midi_in.hpp | 143 ++- .../libremidi/backends/winmidi/midi_out.hpp | 68 +- .../libremidi/backends/winmidi/observer.hpp | 270 ++++-- include/libremidi/backends/winmm.hpp | 8 +- include/libremidi/backends/winmm/config.hpp | 2 +- .../libremidi/backends/winmm/error_domain.hpp | 68 +- include/libremidi/backends/winmm/helpers.hpp | 6 +- include/libremidi/backends/winmm/midi_in.hpp | 21 +- include/libremidi/backends/winmm/midi_out.hpp | 14 +- include/libremidi/backends/winmm/observer.hpp | 25 +- include/libremidi/backends/winuwp.hpp | 10 +- include/libremidi/backends/winuwp/config.hpp | 2 +- include/libremidi/backends/winuwp/helpers.hpp | 8 +- include/libremidi/backends/winuwp/midi_in.hpp | 5 +- .../libremidi/backends/winuwp/midi_out.hpp | 2 +- .../libremidi/backends/winuwp/observer.hpp | 62 +- include/libremidi/client.cpp | 2 +- include/libremidi/client.hpp | 47 +- include/libremidi/cmidi2.hpp | 778 +++++++++------- include/libremidi/config.hpp | 109 ++- include/libremidi/configurations.hpp | 70 +- include/libremidi/defaults.hpp | 26 +- include/libremidi/detail/conversion.hpp | 306 ++++++- include/libremidi/detail/memory.hpp | 4 +- include/libremidi/detail/midi_api.hpp | 11 +- include/libremidi/detail/midi_in.hpp | 6 +- include/libremidi/detail/midi_out.hpp | 2 +- .../libremidi/detail/midi_stream_decoder.hpp | 91 +- include/libremidi/detail/observer.hpp | 4 +- include/libremidi/detail/semaphore.hpp | 1 - include/libremidi/detail/ump_stream.hpp | 3 +- include/libremidi/error.hpp | 25 +- include/libremidi/error_handler.hpp | 10 +- include/libremidi/input_configuration.hpp | 7 +- include/libremidi/libremidi-c.cpp | 41 +- include/libremidi/libremidi-c.h | 8 + include/libremidi/libremidi.cpp | 62 +- include/libremidi/libremidi.hpp | 20 +- include/libremidi/message.hpp | 15 +- include/libremidi/midi_in.cpp | 124 +-- include/libremidi/midi_out.cpp | 88 +- include/libremidi/observer.cpp | 53 +- include/libremidi/observer_configuration.hpp | 58 +- include/libremidi/output_configuration.hpp | 2 +- include/libremidi/port_comparison.hpp | 565 ++++++++++++ include/libremidi/port_information.hpp | 164 ++++ .../libremidi/protocols/remote_control.hpp | 858 ++++++++++++++++++ include/libremidi/reader.cpp | 118 +-- include/libremidi/reader.hpp | 4 +- include/libremidi/shared_context.hpp | 10 +- include/libremidi/system_error2.hpp | 50 +- include/libremidi/types.hpp | 70 ++ include/libremidi/ump.hpp | 54 +- include/libremidi/ump_events.hpp | 374 ++++++++ include/libremidi/writer.cpp | 54 +- include/libremidi/writer.hpp | 2 +- src/midi.cpp | 11 + src/midi.h | 5 + src/signal-manager.cpp | 2 + src/signal-manager.h | 1 + 166 files changed, 8135 insertions(+), 2009 deletions(-) create mode 100644 include/libremidi/backends/android/android.hpp create mode 100644 include/libremidi/backends/android/config.hpp create mode 100644 include/libremidi/backends/android/helpers.cpp create mode 100644 include/libremidi/backends/android/helpers.hpp create mode 100644 include/libremidi/backends/android/midi_in.hpp create mode 100644 include/libremidi/backends/android/midi_out.hpp create mode 100644 include/libremidi/backends/android/observer.hpp create mode 100644 include/libremidi/backends/jack_ump.hpp create mode 100644 include/libremidi/backends/jack_ump/config.hpp create mode 100644 include/libremidi/backends/jack_ump/midi_in.hpp create mode 100644 include/libremidi/backends/jack_ump/midi_out.hpp create mode 100644 include/libremidi/backends/jack_ump/observer.hpp create mode 100644 include/libremidi/backends/jack_ump/shared_handler.hpp create mode 100644 include/libremidi/backends/kdmapi.hpp create mode 100644 include/libremidi/backends/kdmapi/config.hpp create mode 100644 include/libremidi/backends/kdmapi/helpers.hpp create mode 100644 include/libremidi/backends/kdmapi/midi_in.hpp create mode 100644 include/libremidi/backends/kdmapi/midi_out.hpp create mode 100644 include/libremidi/backends/kdmapi/observer.hpp create mode 100644 include/libremidi/backends/pipewire_ump.hpp create mode 100644 include/libremidi/backends/pipewire_ump/config.hpp create mode 100644 include/libremidi/backends/pipewire_ump/midi_in.hpp create mode 100644 include/libremidi/backends/pipewire_ump/midi_out.hpp create mode 100644 include/libremidi/backends/pipewire_ump/observer.hpp create mode 100644 include/libremidi/port_comparison.hpp create mode 100644 include/libremidi/port_information.hpp create mode 100644 include/libremidi/protocols/remote_control.hpp create mode 100644 include/libremidi/types.hpp create mode 100644 include/libremidi/ump_events.hpp diff --git a/include/libremidi/api-c.h b/include/libremidi/api-c.h index db5f26a..510cd79 100644 --- a/include/libremidi/api-c.h +++ b/include/libremidi/api-c.h @@ -1,13 +1,15 @@ #pragma once +#ifndef LIBREMIDI_API_C_H +#define LIBREMIDI_API_C_H -#if __cplusplus +#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD) extern "C" { #endif //! MIDI API specifier arguments. //! To get information on which feature is supported by each back-end, check their backend file //! in e.g. backends/winmm.hpp, etc. -typedef enum libremidi_api +enum libremidi_api { UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */ @@ -22,6 +24,8 @@ typedef enum libremidi_api PIPEWIRE, /*!< PipeWire */ KEYBOARD, /*!< Computer keyboard input */ NETWORK, /*!< MIDI over IP */ + ANDROID_AMIDI, /*!< Android AMidi API */ + KDMAPI, /*!< OmniMIDI KDMAPI (Windows) */ // MIDI 2.0 APIs ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */ @@ -30,10 +34,15 @@ typedef enum libremidi_api WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */ KEYBOARD_UMP, /*!< Computer keyboard input */ NETWORK_UMP, /*!< MIDI2 over IP */ + JACK_UMP, /*!< MIDI2 over JACK, type "32 bit raw UMP". Requires PipeWire v1.4+. */ + PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */ DUMMY = 0xFFFF /*!< A compilable but non-functional API. */ -} libremidi_api; +}; -#if __cplusplus +typedef enum libremidi_api libremidi_api; + +#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD) } #endif +#endif diff --git a/include/libremidi/api.hpp b/include/libremidi/api.hpp index 208fd9d..2a957fc 100644 --- a/include/libremidi/api.hpp +++ b/include/libremidi/api.hpp @@ -2,11 +2,10 @@ #include #include -#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { //! MIDI API specifier arguments. //! To get information on which feature is supported by each back-end, check their backend file @@ -31,9 +30,6 @@ LIBREMIDI_EXPORT std::vector available_apis() noexcept; */ LIBREMIDI_EXPORT std::vector available_ump_apis() noexcept; -LIBREMIDI_EXPORT -libremidi::API midi_api(const std::any& conf); - //! A static function to determine the current version. LIBREMIDI_EXPORT std::string_view get_version() noexcept; @@ -64,10 +60,12 @@ inline constexpr libremidi::API default_api() noexcept return API::COREMIDI; #elif defined(_WIN32) return API::WINDOWS_MM; -#elif defined(__linux__) +#elif defined(LIBREMIDI_ALSA) return API::ALSA_SEQ; #elif defined(__emscripten__) return API::EMSCRIPTEN_WEBMIDI; +#elif defined(LIBREMIDI_ANDROID) + return API::ANDROID_AMIDI; #else return API::DUMMY; #endif @@ -83,7 +81,7 @@ inline constexpr libremidi::API default_api() noexcept return API::COREMIDI_UMP; #elif defined(_WIN32) return API::WINDOWS_MIDI_SERVICES; -#elif defined(__linux__) +#elif defined(LIBREMIDI_ALSA) return API::ALSA_SEQ_UMP; #elif defined(__emscripten__) return API::DUMMY; diff --git a/include/libremidi/backends.hpp b/include/libremidi/backends.hpp index fb289eb..5c445ed 100644 --- a/include/libremidi/backends.hpp +++ b/include/libremidi/backends.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #if !__has_include() && !__has_include() @@ -30,10 +29,16 @@ #if defined(LIBREMIDI_JACK) #include + #if defined(LIBREMIDI_HAS_JACK_GET_VERSION) + #include + #endif #endif #if defined(LIBREMIDI_PIPEWIRE) #include + #if defined(LIBREMIDI_PIPEWIRE_UMP) + #include + #endif #endif #if defined(LIBREMIDI_COREMIDI) @@ -45,6 +50,10 @@ #include #endif +#if defined(LIBREMIDI_KDMAPI) + #include +#endif + #if defined(LIBREMIDI_WINUWP) #include #endif @@ -68,7 +77,11 @@ #include #endif -namespace libremidi +#if defined(LIBREMIDI_ANDROID) + #include +#endif + +NAMESPACE_LIBREMIDI { // The order here will control the order of the API search in // the constructor. @@ -80,7 +93,7 @@ constexpr auto make_tl(unused, Args...) namespace midi1 { -static constexpr auto available_backends = make_tl( +LIBREMIDI_STATIC constexpr auto available_backends = make_tl( 0 #if defined(LIBREMIDI_ALSA) , @@ -98,6 +111,10 @@ static constexpr auto available_backends = make_tl( , winmm_backend{} #endif +#if defined(LIBREMIDI_KDMAPI) + , + kdmapi_backend{} +#endif #if defined(LIBREMIDI_WINUWP) , winuwp_backend{} @@ -121,6 +138,10 @@ static constexpr auto available_backends = make_tl( #if defined(LIBREMIDI_NETWORK) , net::backend{} +#endif +#if defined(LIBREMIDI_ANDROID) + , + android::backend{} #endif , dummy_backend{}); @@ -146,7 +167,7 @@ auto for_backend(libremidi::API api, F&& f) namespace midi2 { -static constexpr auto available_backends = make_tl( +LIBREMIDI_STATIC constexpr auto available_backends = make_tl( 0 #if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP , @@ -163,6 +184,14 @@ static constexpr auto available_backends = make_tl( #if defined(LIBREMIDI_NETWORK) , net_ump::backend{} +#endif +#if defined(LIBREMIDI_JACK) && defined(LIBREMIDI_HAS_JACK_GET_VERSION) + , + jack_ump::backend{} +#endif +#if defined(LIBREMIDI_PIPEWIRE_UMP) + , + pipewire_ump::backend{} #endif , dummy_backend{}); @@ -173,7 +202,7 @@ static_assert(std::tuple_size_v >= 1); template auto for_all_backends(F&& f) { - std::apply([&](auto&&... x) { (f(x), ...); }, available_backends); + std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends); } template @@ -202,10 +231,10 @@ auto for_backend(libremidi::API api, F&& f) midi2::for_backend(api, f); } -void for_input_configuration(auto f, std::any& api_conf) +void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -217,10 +246,10 @@ void for_input_configuration(auto f, std::any& api_conf) std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); } -void for_output_configuration(auto f, std::any& api_conf) +void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -232,10 +261,10 @@ void for_output_configuration(auto f, std::any& api_conf) std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); } -void for_observer_configuration(auto f, std::any& api_conf) +void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; diff --git a/include/libremidi/backends/alsa_raw.hpp b/include/libremidi/backends/alsa_raw.hpp index 06556b6..275bb06 100644 --- a/include/libremidi/backends/alsa_raw.hpp +++ b/include/libremidi/backends/alsa_raw.hpp @@ -3,12 +3,13 @@ #include #include +#include // Credits: greatly inspired from // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // Thanks Craig Stuart Sapp -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { struct backend { @@ -19,8 +20,8 @@ struct backend using midi_out_configuration = alsa_raw_output_configuration; using midi_observer_configuration = alsa_raw_observer_configuration; static const constexpr auto API = libremidi::API::ALSA_RAW; - static const constexpr auto name = "alsa_raw"; - static const constexpr auto display_name = "ALSA (raw)"; + static const constexpr std::string_view name = "alsa_raw"; + static const constexpr std::string_view display_name = "ALSA (raw)"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/alsa_raw/config.hpp b/include/libremidi/backends/alsa_raw/config.hpp index 446ce9b..b94e1e2 100644 --- a/include/libremidi/backends/alsa_raw/config.hpp +++ b/include/libremidi/backends/alsa_raw/config.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -9,12 +10,12 @@ #if __has_include() #include -namespace libremidi +NAMESPACE_LIBREMIDI { using poll_descriptors = pollfd; } #else -namespace libremidi +NAMESPACE_LIBREMIDI { struct poll_descriptors { @@ -25,7 +26,7 @@ struct poll_descriptors } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { /** diff --git a/include/libremidi/backends/alsa_raw/helpers.hpp b/include/libremidi/backends/alsa_raw/helpers.hpp index 1783a5a..c5483f5 100644 --- a/include/libremidi/backends/alsa_raw/helpers.hpp +++ b/include/libremidi/backends/alsa_raw/helpers.hpp @@ -2,10 +2,17 @@ #include #include #include +#include -#include +#include + +#include +#include +#include +#include #include #include +#include #include // Credits: greatly inspired from @@ -13,9 +20,7 @@ // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // Thanks Craig Stuart Sapp -namespace libremidi -{ -namespace +NAMESPACE_LIBREMIDI { struct alsa_raw_port_id { @@ -27,25 +32,25 @@ struct alsa_raw_port_id }; inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept { - return (uint64_t(id.card) << 32) + (uint64_t(id.dev) << 16) + id.port; + return (uint64_t(id.port) << 32) + (uint64_t(id.dev) << 16) + id.card; } inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept { alsa_raw_port_id ret; - ret.card = (p & 0x00'00'FF'FF'00'00'00'00) >> 32; + ret.port = (p & 0x00'00'FF'FF'00'00'00'00) >> 32; ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16; - ret.port = (p & 0x00'00'00'00'00'00'FF'FF); + ret.card = (p & 0x00'00'00'00'00'00'FF'FF); return ret; } static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3); -} namespace alsa_raw { struct alsa_raw_port_info { + // hw:1,2,0 std::string device; std::string card_name; std::string device_name; @@ -91,7 +96,6 @@ struct enumerator : handler{self} , configuration{self.configuration} { - } // 1: is an input / output @@ -114,8 +118,9 @@ struct enumerator else if (status < 0 && status != -ENXIO) { handler.libremidi_handle_error( - configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub) - + " : " + snd.strerror(status)); + configuration, + "Cannot get rawmidi information: " + device_identifier(card, device, sub) + " : " + + snd.strerror(status)); return status; } else @@ -201,7 +206,8 @@ inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name) if (status < 0) { self.handler.libremidi_handle_error( - self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status)); + self.configuration, + "cannot open control for card"s + name + " : " + snd.strerror(status)); } } diff --git a/include/libremidi/backends/alsa_raw/midi_in.hpp b/include/libremidi/backends/alsa_raw/midi_in.hpp index fda1b36..3ae0972 100644 --- a/include/libremidi/backends/alsa_raw/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw/midi_in.hpp @@ -10,7 +10,7 @@ #include #include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { class midi_in_impl : public midi1::in_api @@ -200,7 +200,7 @@ public: midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -220,7 +220,7 @@ public: private: void run_thread(auto parse_func) { - fds_.push_back(this->termination_event); + fds_.push_back(this->m_termination_event); const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); @@ -228,12 +228,12 @@ private: for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), period); + ssize_t err = poll(fds_.data(), fds_.size(), static_cast(period)); if (err == -EAGAIN) continue; else if (err < 0) return; - else if (termination_event.ready(fds_.back())) + else if (m_termination_event.ready(fds_.back())) break; err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); @@ -250,11 +250,11 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; + this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; } else { - this->thread_ = std::thread{ + this->m_thread = std::thread{ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; } return stdx::error{}; @@ -264,8 +264,7 @@ private: using namespace std::literals; libremidi_handle_error( - this->configuration, - "error starting MIDI input thread: "s + e.what()); + this->configuration, "error starting MIDI input thread: "s + e.what()); return e.code(); } return stdx::error{}; @@ -282,16 +281,16 @@ private: stdx::error close_port() override { - termination_event.notify(); - if (thread_.joinable()) - thread_.join(); - termination_event.consume(); // Reset to zero + m_termination_event.notify(); + if (m_thread.joinable()) + m_thread.join(); + m_termination_event.consume(); // Reset to zero return midi_in_impl::close_port(); } - std::thread thread_; - eventfd_notifier termination_event{}; + std::thread m_thread; + eventfd_notifier m_termination_event{}; }; class midi_in_alsa_raw_manual : public midi_in_impl @@ -316,19 +315,21 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer, fds); + }}); } else { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); + }}); } } @@ -342,7 +343,7 @@ private: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/alsa_raw/midi_out.hpp b/include/libremidi/backends/alsa_raw/midi_out.hpp index 227b883..7746718 100644 --- a/include/libremidi/backends/alsa_raw/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw/midi_out.hpp @@ -8,7 +8,7 @@ #include #include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { class midi_out_impl final : public midi1::out_api @@ -44,8 +44,7 @@ public: int status = snd.rawmidi.open(NULL, &midiport_, portname, mode); if (status < 0) { - libremidi_handle_error( - this->configuration, "cannot open device."); + libremidi_handle_error(this->configuration, "cannot open device."); return from_errc(status); } return stdx::error{}; @@ -87,8 +86,7 @@ public: { if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0) { - libremidi_handle_error( - this->configuration, "cannot write message."); + libremidi_handle_error(this->configuration, "cannot write message."); return from_errc(err); } diff --git a/include/libremidi/backends/alsa_raw/observer.hpp b/include/libremidi/backends/alsa_raw/observer.hpp index 68f2afb..d52fe8c 100644 --- a/include/libremidi/backends/alsa_raw/observer.hpp +++ b/include/libremidi/backends/alsa_raw/observer.hpp @@ -6,9 +6,10 @@ #if LIBREMIDI_HAS_UDEV #include #include - #include +#endif +#include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { template class observer_impl_base @@ -27,28 +28,39 @@ public: explicit observer_impl_base( observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} + { + } + + ~observer_impl_base() + { +#if LIBREMIDI_HAS_UDEV + m_termination_event.notify(); + + if (m_thread.joinable()) + m_thread.join(); +#endif + } + + // Must be called for child classes due to virtual function calls + void finish_init() { if (!configuration.has_callbacks()) return; - fds[0] = this->udev; - fds[1] = termination_event; - fds[2] = timer_fd; +#if LIBREMIDI_HAS_UDEV + m_fds[0] = this->m_udev; + m_fds[1] = m_termination_event; + m_fds[2] = m_timer_fd; +#endif // Set-up initial state if (configuration.notify_in_constructor) this->check_devices(); +#if LIBREMIDI_HAS_UDEV // Start thread - thread = std::thread{[this] { this->run(); }}; - } - - ~observer_impl_base() - { - termination_event.notify(); - - if (thread.joinable()) - thread.join(); + m_thread = std::thread{[this] { this->run(); }}; +#endif } std::vector get_input_ports() const noexcept override @@ -77,12 +89,12 @@ public: return ret; } -private: +#if LIBREMIDI_HAS_UDEV void run() { for (;;) { - if (int err = poll(fds, 3, -1); err < 0) + if (int err = poll(m_fds, 3, -1); err < 0) { if (err == -EAGAIN) continue; @@ -90,59 +102,87 @@ private: return; } - // Check udev - if (fds[0].revents & POLLIN) + // Check eventfd to see if we need to exit + if (m_fds[1].revents & POLLIN) { - udev_device* dev = udev.udev.monitor_receive_device(udev.monitor); + break; + } + + // Check udev + if (m_fds[0].revents & POLLIN) + { + udev_device* dev = m_udev.udev.monitor_receive_device(m_udev.monitor); if (!dev) continue; - std::string_view act = udev.udev.device_get_action(dev); - std::string_view ss = udev.udev.device_get_subsystem(dev); + std::string_view act = m_udev.udev.device_get_action(dev); + std::string_view ss = m_udev.udev.device_get_subsystem(dev); if (!act.empty() && ss == "snd_seq") { if (act == "add" || act == "remove") { // Check every 100 milliseconds for ten seconds - this->timer_fd.restart(configuration.poll_period.count()); - timer_check_counts = 100; + this->m_timer_fd.restart(configuration.poll_period.count()); + m_timer_check_counts = 100; } } - udev.udev.device_unref(dev); + m_udev.udev.device_unref(dev); - fds[0].revents = 0; - } - - // Check eventfd - if (fds[1].revents & POLLIN) - { - break; + m_fds[0].revents = 0; } // Check timer - if (fds[2].revents & POLLIN) + if (m_fds[2].revents & POLLIN) { - if (this->timer_check_counts-- <= 0) - this->timer_fd.cancel(); - fds[2].revents = 0; + if (this->m_timer_check_counts-- <= 0) + this->m_timer_fd.cancel(); + m_fds[2].revents = 0; check_devices(); } } } +#endif template - auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept + auto to_port_info(const alsa_raw::alsa_raw_port_info& p) const noexcept -> std::conditional_t { +#if LIBREMIDI_HAS_UDEV + auto [container, device, type, manufacturer, product, serial] + = get_udev_soundcard_info(m_udev, p.card); +#else + container_identifier container{}; + device_identifier device{}; + libremidi::transport_type type{}; + std::string manufacturer; + std::string product; + std::string serial; +#endif + std::string display_name = p.subdevice_name; + if (display_name.empty()) + display_name = p.device_name; + if (display_name.empty()) + display_name = p.card_name; + if (display_name.empty()) + display_name = product; + if (display_name.empty()) + display_name = "unknown"; + return { - {.client = 0, + {.api = get_current_api(), + .client = 0, + .container = container, + .device = device, .port = raw_to_port_handle({p.card, p.dev, p.sub}), - .manufacturer = p.card_name, + .manufacturer = manufacturer, + .product = product, + .serial = serial, .device_name = p.device_name, .port_name = p.subdevice_name, - .display_name = p.subdevice_name}}; + .display_name = std::move(display_name), + .type = type}}; } void check_devices() @@ -151,7 +191,7 @@ private: new_devs.enumerate_cards(); - for (auto& in_prev : current_inputs) + for (auto& in_prev : m_current_inputs) { if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev); it == new_devs.inputs.end()) @@ -165,8 +205,8 @@ private: for (auto& in_next : new_devs.inputs) { - if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next); - it == current_inputs.end()) + if (auto it = std::find(m_current_inputs.begin(), m_current_inputs.end(), in_next); + it == m_current_inputs.end()) { if (auto& cb = this->configuration.input_added) { @@ -175,7 +215,7 @@ private: } } - for (auto& out_prev : current_outputs) + for (auto& out_prev : m_current_outputs) { if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev); it == new_devs.outputs.end()) @@ -189,8 +229,8 @@ private: for (auto& out_next : new_devs.outputs) { - if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next); - it == current_outputs.end()) + if (auto it = std::find(m_current_outputs.begin(), m_current_outputs.end(), out_next); + it == m_current_outputs.end()) { if (auto& cb = this->configuration.output_added) { @@ -198,43 +238,34 @@ private: } } } - current_inputs = std::move(new_devs.inputs); - current_outputs = std::move(new_devs.outputs); + m_current_inputs = std::move(new_devs.inputs); + m_current_outputs = std::move(new_devs.outputs); } - udev_helper udev{}; - eventfd_notifier termination_event{}; - timerfd_timer timer_fd{}; - int timer_check_counts = 0; - std::thread thread; - std::vector current_inputs; - std::vector current_outputs; - - pollfd fds[3]{}; -}; -} -#else - #include -namespace libremidi::alsa_raw -{ -template -struct observer_impl_base : observer_dummy -{ - explicit observer_impl_base( - [[maybe_unused]] observer_configuration&& conf, - [[maybe_unused]] alsa_raw_observer_configuration&& apiconf) - : observer_dummy{dummy_configuration{}, dummy_configuration{}} - { - } -}; -} +#if LIBREMIDI_HAS_UDEV + udev_helper m_udev{}; + eventfd_notifier m_termination_event{}; + timerfd_timer m_timer_fd{}; + int m_timer_check_counts = 0; + std::thread m_thread; + pollfd m_fds[3]{}; #endif -namespace libremidi::alsa_raw + std::vector m_current_inputs; + std::vector m_current_outputs; +}; +} + +NAMESPACE_LIBREMIDI::alsa_raw { struct observer_impl : observer_impl_base { - using alsa_raw::observer_impl_base::observer_impl_base; + observer_impl(observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf) + : observer_impl_base{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; } }; } diff --git a/include/libremidi/backends/alsa_raw_ump.hpp b/include/libremidi/backends/alsa_raw_ump.hpp index 3fc7adf..fc1c4da 100644 --- a/include/libremidi/backends/alsa_raw_ump.hpp +++ b/include/libremidi/backends/alsa_raw_ump.hpp @@ -5,7 +5,9 @@ #include #include -namespace libremidi::alsa_raw_ump +#include + +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct backend { @@ -14,12 +16,11 @@ struct backend using midi_observer = observer_impl; using midi_in_configuration = alsa_raw_ump::input_configuration; using midi_out_configuration = alsa_raw_ump::output_configuration; - struct midi_observer_configuration : alsa_raw_observer_configuration - { - }; + using midi_observer_configuration = alsa_raw_ump::observer_configuration; + static const constexpr auto API = libremidi::API::ALSA_RAW_UMP; - static const constexpr auto name = "alsa_raw_ump"; - static const constexpr auto display_name = "ALSA (raw UMP)"; + static const constexpr std::string_view name = "alsa_raw_ump"; + static const constexpr std::string_view display_name = "ALSA (raw UMP)"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/alsa_raw_ump/config.hpp b/include/libremidi/backends/alsa_raw_ump/config.hpp index a89ccaf..d9e82f2 100644 --- a/include/libremidi/backends/alsa_raw_ump/config.hpp +++ b/include/libremidi/backends/alsa_raw_ump/config.hpp @@ -1,7 +1,10 @@ #pragma once #include -namespace libremidi::alsa_raw_ump +#include +#include + +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct input_configuration { @@ -18,8 +21,7 @@ struct output_configuration std::optional chunking; }; -struct observer_configuration +struct observer_configuration : public alsa_raw_observer_configuration { - std::chrono::milliseconds poll_period{100}; }; } diff --git a/include/libremidi/backends/alsa_raw_ump/helpers.hpp b/include/libremidi/backends/alsa_raw_ump/helpers.hpp index 5b16ca9..a4c2e5b 100644 --- a/include/libremidi/backends/alsa_raw_ump/helpers.hpp +++ b/include/libremidi/backends/alsa_raw_ump/helpers.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi::alsa_raw_ump +#include + +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct midi2_enumerator : alsa_raw::enumerator { diff --git a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp index e4b1640..6748286 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp @@ -10,7 +10,7 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { class midi_in_impl : public midi2::in_api @@ -44,7 +44,8 @@ public: SND_RAWMIDI_NONBLOCK; // fixme if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0) { - libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); + libremidi_handle_error( + this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); return from_errc(err); } @@ -143,7 +144,8 @@ public: while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0) { const auto to_ns = [this] { return absolute_timestamp(); }; - m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); + m_processing.on_bytes( + {words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -166,7 +168,8 @@ public: const auto to_ns = [ts] { return static_cast(ts.tv_sec) * 1'000'000'000 + static_cast(ts.tv_nsec); }; - m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); + m_processing.on_bytes( + {words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -194,7 +197,7 @@ public: libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -206,14 +209,14 @@ public: ~midi_in_impl_threaded() { // Close a connection if it exists. - this->close_port(); + midi_in_impl_threaded::close_port(); client_open_ = std::errc::not_connected; } private: void run_thread(auto parse_func) { - fds_.push_back(this->termination_event); + fds_.push_back(this->m_termination_event); const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); @@ -221,12 +224,12 @@ private: for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), period); + ssize_t err = poll(fds_.data(), fds_.size(), static_cast(period)); if (err == -EAGAIN) continue; else if (err < 0) return; - else if (termination_event.ready(fds_.back())) + else if (m_termination_event.ready(fds_.back())) break; err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); @@ -243,11 +246,11 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; + this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; } else { - this->thread_ = std::thread{ + this->m_thread = std::thread{ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; } return stdx::error{}; @@ -257,8 +260,7 @@ private: using namespace std::literals; libremidi_handle_error( - this->configuration, - "error starting MIDI input thread: "s + e.what()); + this->configuration, "error starting MIDI input thread: "s + e.what()); return e.code(); } return stdx::error{}; @@ -275,16 +277,16 @@ private: stdx::error close_port() override { - termination_event.notify(); - if (thread_.joinable()) - thread_.join(); - termination_event.consume(); // Reset to zero + m_termination_event.notify(); + if (m_thread.joinable()) + m_thread.join(); + m_termination_event.consume(); // Reset to zero return midi_in_impl::close_port(); } - std::thread thread_; - eventfd_notifier termination_event{}; + std::thread m_thread; + eventfd_notifier m_termination_event{}; }; class midi_in_impl_manual : public midi_in_impl @@ -299,7 +301,7 @@ public: ~midi_in_impl_manual() { // Close a connection if it exists. - this->close_port(); + midi_in_impl_manual::close_port(); client_open_ = std::errc::not_connected; } @@ -309,19 +311,21 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer, fds); + }}); } else { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); + }}); } } @@ -335,7 +339,7 @@ private: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp index f350cde..0ca5b87 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp @@ -1,14 +1,19 @@ #pragma once #include #include +#include #include +#include +#include #include -#include -#include +#include +#include +#include +#include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { class midi_out_impl final : public midi2::out_api diff --git a/include/libremidi/backends/alsa_raw_ump/observer.hpp b/include/libremidi/backends/alsa_raw_ump/observer.hpp index 03e3286..0c44805 100644 --- a/include/libremidi/backends/alsa_raw_ump/observer.hpp +++ b/include/libremidi/backends/alsa_raw_ump/observer.hpp @@ -2,13 +2,18 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { - class observer_impl : public alsa_raw::observer_impl_base { - using alsa_raw::observer_impl_base::observer_impl_base; +public: + observer_impl( + libremidi::observer_configuration&& conf, alsa_raw_ump::observer_configuration&& apiconf) + : observer_impl_base{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; } }; - } diff --git a/include/libremidi/backends/alsa_seq.hpp b/include/libremidi/backends/alsa_seq.hpp index 0d2c43b..bac33c3 100644 --- a/include/libremidi/backends/alsa_seq.hpp +++ b/include/libremidi/backends/alsa_seq.hpp @@ -16,7 +16,11 @@ #include #include -namespace libremidi::alsa_seq +#include + +#include + +NAMESPACE_LIBREMIDI::alsa_seq { struct backend @@ -29,13 +33,16 @@ struct backend using midi_out_configuration = alsa_seq::output_configuration; using midi_observer_configuration = alsa_seq::observer_configuration; static const constexpr auto API = libremidi::API::ALSA_SEQ; - static const constexpr auto name = "alsa_seq"; - static const constexpr auto display_name = "ALSA (sequencer)"; + static const constexpr std::string_view name = "alsa_seq"; + static const constexpr std::string_view display_name = "ALSA (sequencer)"; static inline bool available() noexcept { static const libasound& snd = libasound::instance(); - return snd.available && snd.seq.available; + if (!snd.available || !snd.seq.available) + return false; + + return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0; } }; diff --git a/include/libremidi/backends/alsa_seq/config.hpp b/include/libremidi/backends/alsa_seq/config.hpp index 8153fb3..26ff5d5 100644 --- a/include/libremidi/backends/alsa_seq/config.hpp +++ b/include/libremidi/backends/alsa_seq/config.hpp @@ -15,7 +15,7 @@ typedef struct snd_seq_addr } #endif -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { // Possible parameters: diff --git a/include/libremidi/backends/alsa_seq/helpers.hpp b/include/libremidi/backends/alsa_seq/helpers.hpp index 0500a28..8f5a72c 100644 --- a/include/libremidi/backends/alsa_seq/helpers.hpp +++ b/include/libremidi/backends/alsa_seq/helpers.hpp @@ -9,7 +9,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct event_handle { @@ -42,23 +42,39 @@ struct event_handle ~event_handle() { snd.seq.free_event(ev); } }; -namespace -{ inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept { - return (client << 32) + port; + return (port << 32) + client; } inline constexpr std::pair seq_from_port_handle(port_handle p) noexcept { - int client = p >> 32; - int port = p & 0xFFFFFFFF; + int port = p >> 32; + int client = p & 0xFFFFFFFF; return {client, port}; } +inline void for_all_clients( + const libasound& snd, snd_seq_t* seq, const std::function& func) +{ + snd_seq_client_info_t* cinfo{}; + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_t* pinfo{}; + snd_seq_port_info_alloca(&pinfo); + + snd.seq.client_info_set_client(cinfo, -1); + while (snd.seq.query_next_client(seq, cinfo) >= 0) + { + int client = snd.seq.client_info_get_client(cinfo); + if (client == 0) + continue; + func(*cinfo); + } +} + inline void for_all_ports( const libasound& snd, snd_seq_t* seq, - std::function func) + const std::function& func) { snd_seq_client_info_t* cinfo{}; snd_seq_client_info_alloca(&cinfo); @@ -82,6 +98,21 @@ inline void for_all_ports( } } +inline void for_all_ports( + const libasound& snd, snd_seq_t* seq, int client, + const std::function& func) +{ + snd_seq_port_info_t* pinfo{}; + snd_seq_port_info_alloca(&pinfo); + + snd.seq.port_info_set_client(pinfo, client); + snd.seq.port_info_set_port(pinfo, -1); + while (snd.seq.query_next_port(seq, pinfo) >= 0) + { + func(*pinfo); + } +} + // This function is used to count or get the pinfo structure for a given port // number. inline unsigned int iterate_port_info( @@ -125,7 +156,6 @@ inline unsigned int iterate_port_info( return count; return 0; } -} // A structure to hold variables related to the ALSA API // implementation. @@ -159,15 +189,15 @@ struct alsa_data snd.seq.set_client_name(seq, configuration.client_name.data()); #if __has_include() - if (snd.seq.ump.set_client_midi_version) + if (snd.seq.set_client_midi_version) { switch (configuration.midi_version) { case 1: - snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI); + snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI); break; case 2: - snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); + snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); break; } } @@ -272,8 +302,7 @@ struct alsa_data // Make subscription if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0) { - self.libremidi_handle_error( - self.configuration, "ALSA error allocation port subscription."); + self.libremidi_handle_error(self.configuration, "ALSA error allocation port subscription."); return err; } diff --git a/include/libremidi/backends/alsa_seq/midi_in.hpp b/include/libremidi/backends/alsa_seq/midi_in.hpp index 8065097..8d0be5d 100644 --- a/include/libremidi/backends/alsa_seq/midi_in.hpp +++ b/include/libremidi/backends/alsa_seq/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct dummy_processing { @@ -195,11 +195,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -212,7 +207,7 @@ public: .count(); } - int process_event(const snd_seq_event_t& ev) + int64_t process_event(const snd_seq_event_t& ev) { if constexpr (ConfigurationImpl::midi_version == 1) { @@ -258,17 +253,17 @@ public: return 0; } - int process_events() + int64_t process_events() { if constexpr (ConfigurationImpl::midi_version == 1) { snd_seq_event_t* ev{}; event_handle handle{snd}; - int result = 0; + int64_t result = 0; if ((result = snd.seq.event_input(seq, &ev)) > 0) { handle.reset(ev); - if (int err = process_event(*ev); err < 0) + if (auto err = process_event(*ev); err < 0) { return err; } @@ -353,7 +348,7 @@ public: midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { this->libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -364,7 +359,7 @@ public: ~midi_in_alsa_threaded() { - this->close_port(); + midi_in_alsa_threaded::close_port(); this->client_open_ = std::errc::not_connected; } @@ -399,7 +394,7 @@ private: { try { - this->thread = std::thread([this] { thread_handler(); }); + this->m_thread = std::thread([this] { thread_handler(); }); return stdx::error{}; } catch (const std::system_error& e) @@ -415,12 +410,12 @@ private: stdx::error stop_thread() { - termination_event.notify(); + m_termination_event.notify(); - if (this->thread.joinable()) - this->thread.join(); + if (this->m_thread.joinable()) + this->m_thread.join(); - termination_event.consume(); + m_termination_event.consume(); return stdx::error{}; } @@ -428,7 +423,7 @@ private: { int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1; auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd)); - poll_fds[0] = this->termination_event; + poll_fds[0] = this->m_termination_event; alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN); const auto period @@ -439,10 +434,10 @@ private: if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0) { // No data pending - if (poll(poll_fds, poll_fd_count, period) >= 0) + if (poll(poll_fds, poll_fd_count, static_cast(period)) >= 0) { // We got our stop-thread signal - if (termination_event.ready(poll_fds[0])) + if (m_termination_event.ready(poll_fds[0])) { break; } @@ -450,7 +445,7 @@ private: continue; } - int res{}; + int64_t res{}; if constexpr (ConfigurationImpl::midi_version == 1) { res = this->process_events(); @@ -467,8 +462,8 @@ private: } } - std::thread thread{}; - eventfd_notifier termination_event{}; + std::thread m_thread{}; + eventfd_notifier m_termination_event{}; }; template @@ -487,17 +482,17 @@ public: using poll_params = typename ConfigurationImpl::poll_parameters_type; this->configuration.manual_poll( poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) { - if constexpr (ConfigurationImpl::midi_version == 1) - return this->process_event(ev); + if constexpr (ConfigurationImpl::midi_version == 1) + return this->process_event(ev); #if __has_include() - else - return this->process_ump_event(ev); + else + return this->process_ump_event(ev); #endif - }}); + }}); return 0; } - ~midi_in_alsa_manual() { this->close_port(); } + ~midi_in_alsa_manual() { midi_in_alsa_manual::close_port(); } stdx::error open_port(const input_port& pt, std::string_view local_port_name) override { @@ -530,7 +525,7 @@ public: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr diff --git a/include/libremidi/backends/alsa_seq/midi_out.hpp b/include/libremidi/backends/alsa_seq/midi_out.hpp index 0eea4ba..0865c5e 100644 --- a/include/libremidi/backends/alsa_seq/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { class midi_out_impl final @@ -30,7 +30,7 @@ public: return; } - if (snd.midi.event_new(this->bufferSize, &this->coder) < 0) + if (snd.midi.event_new(this->m_bufferSize, &this->coder) < 0) { libremidi_handle_error(this->configuration, "error initializing MIDI event parser."); return; @@ -68,8 +68,9 @@ public: stdx::error open_port(const output_port& p, std::string_view portName) override { - unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); - if (nSrc < 1) + unsigned int n_src + = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); + if (n_src < 1) { libremidi_handle_error(this->configuration, "no MIDI output sources found!"); return make_error_code(std::errc::no_such_device); @@ -109,11 +110,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -122,9 +118,9 @@ public: stdx::error send_message(const unsigned char* message, std::size_t size) override { int64_t result{}; - if (size > this->bufferSize) + if (size > this->m_bufferSize) { - this->bufferSize = size; + this->m_bufferSize = size; result = snd.midi.event_resize_buffer(this->coder, size); if (result != 0) { @@ -146,8 +142,8 @@ public: // FIXME direct is set but snd_seq_event_output_direct is not used... snd_seq_ev_set_direct(&ev); - const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below - result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev); + const int64_t n_bytes = size; // signed to avoir potential overflow with size - offset below + result = snd.midi.event_encode(this->coder, message + offset, (long)(n_bytes - offset), &ev); if (result < 0) { libremidi_handle_warning(this->configuration, "event parsing error!"); @@ -174,6 +170,6 @@ public: } private: - uint64_t bufferSize{32}; + uint64_t m_bufferSize{32}; }; } diff --git a/include/libremidi/backends/alsa_seq/observer.hpp b/include/libremidi/backends/alsa_seq/observer.hpp index fb101cb..77a331f 100644 --- a/include/libremidi/backends/alsa_seq/observer.hpp +++ b/include/libremidi/backends/alsa_seq/observer.hpp @@ -7,20 +7,32 @@ #include -#include +#if LIBREMIDI_HAS_UDEV + #include +#endif + #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { +struct client_info +{ + std::string client_name; + int client_id{}; + std::optional card{}; +}; + struct port_info { std::string client_name; std::string port_name; int client{}; int port{}; - bool isInput{}; - bool isOutput{}; + bool is_input{}; + bool is_output{}; + std::optional card{}; + libremidi::transport_type type{}; }; template @@ -88,12 +100,69 @@ public: } } - std::optional get_info(int client, int port) const noexcept + std::optional + get_client_info(int client, snd_seq_client_info_t& cinfo) const noexcept { + client_info p; + p.client_id = client; + + if (auto name = snd.seq.client_info_get_name(&cinfo)) + p.client_name = name; + + if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0) + p.card = card; + + return p; + } + + std::optional get_info( + int client, int port, snd_seq_client_info_t& cinfo, + snd_seq_port_info_t& pinfo) const noexcept + { + const auto tp = snd.seq.port_info_get_type(&pinfo); + const auto cap = snd.seq.port_info_get_capability(&pinfo); + if ((cap & SND_SEQ_PORT_CAP_NO_EXPORT) != 0) + return std::nullopt; + port_info p; p.client = client; p.port = port; + bool ok = this->configuration.track_any; + + static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER + | SND_SEQ_PORT_TYPE_APPLICATION; + + if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) + { + p.type = libremidi::transport_type::hardware; + ok = true; + } + else if ((tp & virtual_port) && this->configuration.track_virtual) + { + p.type = libremidi::transport_type::software; + ok = true; + } + if (!ok) + return {}; + + if (auto name = snd.seq.client_info_get_name(&cinfo)) + p.client_name = name; + + if (auto name = snd.seq.port_info_get_name(&pinfo)) + p.port_name = name; + + if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0) + p.card = card; + + p.is_input = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ); + p.is_output = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE); + + return p; + } + + std::optional get_info(int client, int port) const noexcept + { snd_seq_client_info_t* cinfo; snd_seq_client_info_alloca(&cinfo); if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0) @@ -104,55 +173,57 @@ public: if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0) return std::nullopt; - const auto tp = snd.seq.port_info_get_type(pinfo); - bool ok = this->configuration.track_any; - - static constexpr auto virtual_port - = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER; - - if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) - ok = true; - else if ((tp & virtual_port) && this->configuration.track_virtual) - ok = true; - if (!ok) - return {}; - - if (auto name = snd.seq.client_info_get_name(cinfo)) - p.client_name = name; - - if (auto name = snd.seq.port_info_get_name(pinfo)) - p.port_name = name; - - auto cap = snd.seq.port_info_get_capability(pinfo); - p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ); - p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE); - - return p; + return get_info(client, port, *cinfo, *pinfo); } template - auto to_port_info(port_info p) const noexcept + auto to_port_info(const port_info& p) const noexcept -> std::conditional_t { static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle)); static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle)); + + container_identifier container{}; + device_identifier device{}; + libremidi::transport_type type = p.type; + std::string manufacturer; + std::string product; + std::string serial; +#if LIBREMIDI_HAS_UDEV + if (p.card) + { + auto res = get_udev_soundcard_info(m_udev, *p.card); + container = res.container; + device = res.path; + type = res.type; + manufacturer = res.vendor; + product = res.product; + serial = res.serial; + } +#endif return { - {.client = std::uintptr_t(this->seq), + {.api = get_current_api(), + .client = std::uintptr_t(this->seq), + .container = container, + .device = device, .port = alsa_seq::seq_to_port_handle(p.client, p.port), - .manufacturer = "", + .manufacturer = manufacturer, + .product = product, + .serial = serial, .device_name = p.client_name, .port_name = p.port_name, - .display_name = p.port_name}}; + .display_name = p.port_name, + .type = type}}; } void init_all_ports() { alsa_seq::for_all_ports( snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - register_port(clt, pt); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + register_port(clt, pt); + }); } libremidi::API get_current_api() const noexcept override @@ -168,12 +239,12 @@ public: std::vector ret; alsa_seq::for_all_ports( snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - if (auto p = get_info(clt, pt)) - if (p->isInput) - ret.push_back(to_port_info(*p)); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + if (auto p = get_info(clt, pt, client, port)) + if (p->is_input) + ret.push_back(to_port_info(*p)); + }); return ret; } @@ -182,12 +253,12 @@ public: std::vector ret; alsa_seq::for_all_ports( snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - if (auto p = get_info(clt, pt)) - if (p->isOutput) - ret.push_back(to_port_info(*p)); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + if (auto p = get_info(clt, pt, client, port)) + if (p->is_output) + ret.push_back(to_port_info(*p)); + }); return ret; } @@ -200,13 +271,13 @@ public: if (p.client == snd.seq.client_id(seq)) return; - knownClients_[{p.client, p.port}] = p; - if (p.isInput && configuration.input_added) + m_knownClients[{p.client, p.port}] = p; + if (p.is_input && configuration.input_added) { configuration.input_added(to_port_info(p)); } - if (p.isOutput && configuration.output_added) + if (p.is_output && configuration.output_added) { configuration.output_added(to_port_info(p)); } @@ -214,37 +285,61 @@ public: void unregister_port(int client, int port) { - auto it = knownClients_.find({client, port}); - if (it != knownClients_.end()) + auto it = m_knownClients.find({client, port}); + if (it != m_knownClients.end()) { auto p = it->second; - knownClients_.erase(it); + m_knownClients.erase(it); - if (p.isInput && configuration.input_removed) + if (p.is_input && configuration.input_removed) { configuration.input_removed(to_port_info(p)); } - if (p.isOutput && configuration.output_removed) + if (p.is_output && configuration.output_removed) { configuration.output_removed(to_port_info(p)); } } } - void handle_event(const snd_seq_event_t& ev) + void handle_event_direct(const snd_seq_event_t& ev) { switch (ev.type) { + case SND_SEQ_EVENT_CLIENT_START: { + // TODO + break; + } + case SND_SEQ_EVENT_CLIENT_EXIT: { + // TODO + break; + } + case SND_SEQ_EVENT_CLIENT_CHANGE: { + // TODO + break; + } +#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS + case SND_SEQ_EVENT_UMP_EP_CHANGE: { + // TODO + break; + } + case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: { + // TODO + break; + } +#endif case SND_SEQ_EVENT_PORT_START: { - register_port(ev.data.addr.client, ev.data.addr.port); + this->register_port(ev.data.addr.client, ev.data.addr.port); break; } case SND_SEQ_EVENT_PORT_EXIT: { - unregister_port(ev.data.addr.client, ev.data.addr.port); + this->unregister_port(ev.data.addr.client, ev.data.addr.port); break; } case SND_SEQ_EVENT_PORT_CHANGE: + // TODO + break; default: break; } @@ -263,7 +358,11 @@ public: } private: - std::map, port_info> knownClients_; + std::map, port_info> m_knownClients; + +#if LIBREMIDI_HAS_UDEV + udev_helper m_udev{}; +#endif }; template @@ -275,38 +374,87 @@ public: { // Create relevant descriptors auto& snd = alsa_data::snd; - const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN); - descriptors_.resize(N + 1); - snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN); - descriptors_.back() = this->termination_event; + + // 1. Descriptor count + const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN); + int total_descriptors = n; + total_descriptors++; // eventfd for terminating the thread + + // 2. Create storage + descriptors.resize(total_descriptors); + + // 3. Store descriptors + snd.seq.poll_descriptors(this->seq, descriptors.data(), n, POLLIN); + descriptors[n] = this->termination_event; // Start the listening thread - thread = std::thread{[this] { + thread = std::thread{[this, n] { auto& snd = alsa_data::snd; const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); for (;;) { - int err = poll(descriptors_.data(), descriptors_.size(), period); + int err = poll(descriptors.data(), descriptors.size(), static_cast(period)); if (err >= 0) { // We got our stop-thread signal - if (descriptors_.back().revents & POLLIN) + if (descriptors[n].revents & POLLIN) break; + // Put ALSA event in our queue snd_seq_event_t* ev{}; event_handle handle{snd}; while (snd.seq.event_input(this->seq, &ev) >= 0) { handle.reset(ev); - this->handle_event(*ev); + this->handle_event_delayed(*ev); + } + + // Process the events in a deferred way. + // This is because udev takes some milliseconds to populate its field after a + // port was added + auto tm = std::chrono::steady_clock::now(); + for (auto it = queued_events.begin(); it != queued_events.end();) + { + if ((tm - it->second) >= this->configuration.poll_period) + { + this->handle_event_direct(it->first); + it = queued_events.erase(it); + } + else + { + break; + } } } } }}; } + void handle_event_delayed(const snd_seq_event_t& ev) + { + switch (ev.type) + { + case SND_SEQ_EVENT_CLIENT_START: + case SND_SEQ_EVENT_CLIENT_EXIT: + case SND_SEQ_EVENT_CLIENT_CHANGE: + +#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS + case SND_SEQ_EVENT_UMP_EP_CHANGE: + case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: +#endif + + case SND_SEQ_EVENT_PORT_START: + case SND_SEQ_EVENT_PORT_EXIT: + case SND_SEQ_EVENT_PORT_CHANGE: + queued_events.emplace_back(ev, std::chrono::steady_clock::now()); + break; + default: + break; + } + } + ~observer_threaded() { termination_event.notify(); @@ -317,7 +465,8 @@ public: eventfd_notifier termination_event{}; std::thread thread; - std::vector descriptors_; + std::vector descriptors; + std::vector> queued_events; }; template @@ -329,16 +478,16 @@ public: { this->configuration.manual_poll( poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) { - this->handle_event(v); - return 0; - }}); + this->handle_event_direct(v); + return 0; + }}); } ~observer_manual() { this->configuration.stop_poll(this->vaddr); } }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr diff --git a/include/libremidi/backends/alsa_seq/shared_handler.hpp b/include/libremidi/backends/alsa_seq/shared_handler.hpp index f9434a0..7404a46 100644 --- a/include/libremidi/backends/alsa_seq/shared_handler.hpp +++ b/include/libremidi/backends/alsa_seq/shared_handler.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct shared_handler : public libremidi::shared_context @@ -61,7 +61,7 @@ struct shared_handler : public libremidi::shared_context { auto clt = std::make_shared(client_name); - auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) { + auto cb = [client = std::weak_ptr{clt}](libremidi::alsa_seq::poll_parameters params) { if (auto clt = client.lock()) { clt->events.push( @@ -74,8 +74,7 @@ struct shared_handler : public libremidi::shared_context auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) { if (auto clt = client.lock()) { - clt->events.push( - {.type = shared_handler::event_type::callback_removed, .payload = std::move(id)}); + clt->events.push({.type = shared_handler::event_type::callback_removed, .payload = id}); clt->queue_event.notify(); } return true; @@ -128,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::move(*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); @@ -146,15 +145,14 @@ struct shared_handler : public libremidi::shared_context } // Look for who's ready - for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++) + for (int64_t i = 0, n = std::ssize(fds) - 2; i < n; i++) { if (fds[i].revents & POLLIN) { // Read alsa event snd_seq_event_t* ev{}; event_handle handle{snd}; - int result = 0; - while ((result = snd.seq.event_input(client, &ev)) > 0) + while (snd.seq.event_input(client, &ev) > 0) { handle.reset(ev); @@ -181,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/alsa_seq_ump.hpp b/include/libremidi/backends/alsa_seq_ump.hpp index ae91f79..56b0f86 100644 --- a/include/libremidi/backends/alsa_seq_ump.hpp +++ b/include/libremidi/backends/alsa_seq_ump.hpp @@ -5,7 +5,11 @@ #include #include -namespace libremidi +#include + +#include + +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr @@ -38,7 +42,7 @@ inline std::unique_ptr make< } -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { struct backend { @@ -50,13 +54,16 @@ struct backend using midi_out_configuration = alsa_seq_ump::output_configuration; using midi_observer_configuration = alsa_seq_ump::observer_configuration; static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP; - static const constexpr auto name = "alsa_seq_ump"; - static const constexpr auto display_name = "ALSA (sequencer, UMP)"; + static const constexpr std::string_view name = "alsa_seq_ump"; + static const constexpr std::string_view display_name = "ALSA (sequencer, UMP)"; static inline bool available() noexcept { static const libasound& snd = libasound::instance(); - return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available; + if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available) + return false; + + return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0; } }; } diff --git a/include/libremidi/backends/alsa_seq_ump/config.hpp b/include/libremidi/backends/alsa_seq_ump/config.hpp index f57a306..661de4a 100644 --- a/include/libremidi/backends/alsa_seq_ump/config.hpp +++ b/include/libremidi/backends/alsa_seq_ump/config.hpp @@ -11,7 +11,7 @@ extern "C" { typedef struct snd_seq_ump_event snd_seq_ump_event_t; } -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { struct poll_parameters diff --git a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp index 46760d6..bb9be5c 100644 --- a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { class midi_out_impl final @@ -64,8 +64,9 @@ public: stdx::error open_port(const output_port& p, std::string_view portName) override { - unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); - if (nSrc < 1) + unsigned int n_src + = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); + if (n_src < 1) { libremidi_handle_error(this->configuration, "no MIDI output sources found!"); return make_error_code(std::errc::no_such_device); @@ -105,11 +106,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -133,9 +129,10 @@ public: libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return static_cast(-ret); } - return std::errc{0}; + static_assert(std::errc{0} == std::errc{}); + return std::errc{}; }; - segment_ump_stream(ump_stream, count, write_func, []() {}); + segment_ump_stream(ump_stream, count, write_func, []() { }); snd.seq.drain_output(this->seq); return stdx::error{}; diff --git a/include/libremidi/backends/android/android.hpp b/include/libremidi/backends/android/android.hpp new file mode 100644 index 0000000..9dc3a7c --- /dev/null +++ b/include/libremidi/backends/android/android.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct backend +{ + using midi_in = android::midi_in; + using midi_out = android::midi_out; + using midi_observer = android::observer; + using midi_in_configuration = android::input_configuration; + using midi_out_configuration = android::output_configuration; + using midi_observer_configuration = android::observer_configuration; + static const constexpr auto API = libremidi::API::ANDROID_AMIDI; + static const constexpr std::string_view name = "android"; + static const constexpr std::string_view display_name = "Android MIDI API"; + + static bool available() noexcept + { +#if defined(__ANDROID__) + return true; +#else + return false; +#endif + } +}; +} +} diff --git a/include/libremidi/backends/android/config.hpp b/include/libremidi/backends/android/config.hpp new file mode 100644 index 0000000..55d8ae0 --- /dev/null +++ b/include/libremidi/backends/android/config.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct input_configuration +{ + std::string_view client_name = "libremidi client"; +}; + +struct output_configuration +{ + std::string_view client_name = "libremidi client"; +}; + +struct observer_configuration +{ + std::string_view client_name = "libremidi client"; +}; +} +} \ No newline at end of file diff --git a/include/libremidi/backends/android/helpers.cpp b/include/libremidi/backends/android/helpers.cpp new file mode 100644 index 0000000..da90bf3 --- /dev/null +++ b/include/libremidi/backends/android/helpers.cpp @@ -0,0 +1,239 @@ +#include +#include +#include + +NAMESPACE_LIBREMIDI::android +{ +// Main source of knowledge is RtMidi implementation +// which was done by Yellow Labrador + +JNIEnv* context::get_thread_env() +{ + JNIEnv* env; + JavaVM* jvm; + jsize count; + if (JNI_GetCreatedJavaVMs(&jvm, 1, &count) != JNI_OK || count < 1) + { + LOGE("No JVM found"); + return nullptr; + } + + if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) + { + if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) + { + LOGE("Failed to attach thread"); + return nullptr; + } + } + + return env; +} + +jobject context::get_context(JNIEnv* env) +{ + auto activity_thread = env->FindClass("android/app/ActivityThread"); + auto current_activity_thread = env->GetStaticMethodID( + activity_thread, "currentActivityThread", "()Landroid/app/ActivityThread;"); + auto at = env->CallStaticObjectMethod(activity_thread, current_activity_thread); + + if (!at) + { + LOGE("Failed to get ActivityThread"); + return nullptr; + } + + auto get_application + = env->GetMethodID(activity_thread, "getApplication", "()Landroid/app/Application;"); + auto app = env->CallObjectMethod(at, get_application); + + if (!app) + { + LOGE("Failed to get Application"); + return nullptr; + } + + return app; +} + +jobject context::get_midi_manager(JNIEnv* env, jobject ctx) +{ + auto context_class = env->FindClass("android/content/Context"); + auto get_system_service = env->GetMethodID( + context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + auto midi_service_str = env->NewStringUTF("midi"); + auto service = env->CallObjectMethod(ctx, get_system_service, midi_service_str); + env->DeleteLocalRef(midi_service_str); + return service; +} + +void context::refresh_midi_devices(JNIEnv* env, jobject ctx, bool is_output) +{ + cleanup_devices(env); + + auto midi_service = get_midi_manager(env, ctx); + if (!midi_service) + return; + + auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager"); + auto get_devices_method + = env->GetMethodID(midi_mgr_class, "getDevices", "()[Landroid/media/midi/MidiDeviceInfo;"); + auto device_array = (jobjectArray)env->CallObjectMethod(midi_service, get_devices_method); + + auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto get_input_count = env->GetMethodID(device_info_class, "getInputPortCount", "()I"); + auto get_output_count = env->GetMethodID(device_info_class, "getOutputPortCount", "()I"); + + jsize count = env->GetArrayLength(device_array); + for (jsize i = 0; i < count; ++i) + { + auto device_info = env->GetObjectArrayElement(device_array, i); + int port_count = is_output ? env->CallIntMethod(device_info, get_input_count) + : env->CallIntMethod(device_info, get_output_count); + + if (port_count > 0) + { + midi_devices.push_back(env->NewGlobalRef(device_info)); + } + env->DeleteLocalRef(device_info); + } + + env->DeleteLocalRef(device_array); + env->DeleteLocalRef(midi_service); +} + +std::string context::port_name(JNIEnv* env, unsigned int port_number) +{ + if (port_number >= midi_devices.size()) + { + LOGE("Invalid port number"); + return ""; + } + + auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto get_props_method + = env->GetMethodID(device_info_class, "getProperties", "()Landroid/os/Bundle;"); + auto bundle = env->CallObjectMethod(midi_devices[port_number], get_props_method); + + auto bundle_class = env->FindClass("android/os/Bundle"); + auto get_string_method + = env->GetMethodID(bundle_class, "getString", "(Ljava/lang/String;)Ljava/lang/String;"); + auto key = env->NewStringUTF("name"); + auto name_str = (jstring)env->CallObjectMethod(bundle, get_string_method, key); + + std::string result; + if (name_str) + { + const char* name_chars = env->GetStringUTFChars(name_str, nullptr); + result = name_chars; + env->ReleaseStringUTFChars(name_str, name_chars); + env->DeleteLocalRef(name_str); + } + + env->DeleteLocalRef(key); + env->DeleteLocalRef(bundle); + + return result; +} + +void context::cleanup_devices(JNIEnv* env) +{ + for (jobject device : midi_devices) + { + env->DeleteGlobalRef(device); + } + midi_devices.clear(); +} + +void context::open_device(jobject device_info, void* target, bool is_output) +{ + auto env = get_thread_env(); + if (!env) + return; + + auto ctx = get_context(env); + if (!ctx) + return; + + auto midi_mgr = get_midi_manager(env, ctx); + if (!midi_mgr) + return; + + // Check if we're on the main thread + auto looper_class = env->FindClass("android/os/Looper"); + auto get_my_looper_method + = env->GetStaticMethodID(looper_class, "myLooper", "()Landroid/os/Looper;"); + auto my_looper = env->CallStaticObjectMethod(looper_class, get_my_looper_method); + + jobject handler = nullptr; + if (!my_looper) + { + LOGI("Not on a Looper thread, using main looper"); + auto get_main_looper_method + = env->GetStaticMethodID(looper_class, "getMainLooper", "()Landroid/os/Looper;"); + auto main_looper = env->CallStaticObjectMethod(looper_class, get_main_looper_method); + + auto handler_class = env->FindClass("android/os/Handler"); + auto handler_ctor = env->GetMethodID(handler_class, "", "(Landroid/os/Looper;)V"); + handler = env->NewObject(handler_class, handler_ctor, main_looper); + + env->DeleteLocalRef(main_looper); + } + + // Create the callback listener + auto callback_class = env->FindClass("dev/celtera/libremidi/MidiDeviceCallback"); + if (!callback_class) + { + LOGE("MidiDeviceCallback class not found - ensure the Java class is loaded"); + if (handler) + env->DeleteLocalRef(handler); + return; + } + + auto callback_ctor = env->GetMethodID(callback_class, "", "(JZ)V"); + auto callback + = env->NewObject(callback_class, callback_ctor, (jlong)target, (jboolean)is_output); + + // Open the device + auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager"); + auto open_device_method = env->GetMethodID( + midi_mgr_class, "openDevice", + "(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/" + "MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V"); + + env->CallVoidMethod(midi_mgr, open_device_method, device_info, callback, handler); + + env->DeleteLocalRef(callback); + if (handler) + env->DeleteLocalRef(handler); +} + +extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened( + JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output) +{ + if (!midi_device || target_ptr == 0) + { + LOGE("Invalid device or target pointer in callback"); + return; + } + + AMidiDevice* amidi_device = nullptr; + AMidiDevice_fromJava(env, midi_device, &amidi_device); + + if (!amidi_device) + { + LOGE("Failed to convert Java MIDI device to AMidiDevice"); + return; + } + + if (is_output) + { + midi_out::open_callback(reinterpret_cast(target_ptr), amidi_device); + } + else + { + midi_in::open_callback(reinterpret_cast(target_ptr), amidi_device); + } +} + +} diff --git a/include/libremidi/backends/android/helpers.hpp b/include/libremidi/backends/android/helpers.hpp new file mode 100644 index 0000000..7f4ce41 --- /dev/null +++ b/include/libremidi/backends/android/helpers.hpp @@ -0,0 +1,40 @@ +#pragma once +#include + +#include +#include + +#include +#include + +#include +#include + +#define LOG_TAG "libremidi" +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct context +{ + static inline std::string client_name; + static inline std::vector midi_devices; + + static JNIEnv* get_thread_env(); + static jobject get_context(JNIEnv* env); + static jobject get_midi_manager(JNIEnv* env, jobject context); + static void refresh_midi_devices(JNIEnv* env, jobject context, bool is_output); + static void open_device(jobject device_info, void* target, bool is_output); + static std::string port_name(JNIEnv* env, unsigned int port_number); + static void cleanup_devices(JNIEnv* env); +}; + +extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened( + JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output); +} +} diff --git a/include/libremidi/backends/android/midi_in.hpp b/include/libremidi/backends/android/midi_in.hpp new file mode 100644 index 0000000..78fca93 --- /dev/null +++ b/include/libremidi/backends/android/midi_in.hpp @@ -0,0 +1,165 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class midi_in + : public midi1::in_api + , public error_handler +{ +public: + explicit midi_in( + libremidi::input_configuration&& conf, libremidi::android::input_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + libremidi_handle_error( + configuration, "Android backend only supports one client name per process"); + return; + } + context::client_name = std::string(aconf.client_name); + client_open_ = stdx::error{}; + } + + ~midi_in() override { close_port(); } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + stdx::error open_port(const input_port& port, std::string_view /*name*/) override + { + if (is_port_open()) + { + libremidi_handle_error(configuration, "midi_in::open_port: a port is already open"); + return std::errc{}; + } + + const unsigned int port_number = port.port; + auto env = context::get_thread_env(); + if (!env) + { + libremidi_handle_error(configuration, "midi_in::open_port: failed to get JNI environment"); + return std::errc{}; + } + + auto ctx = context::get_context(env); + if (!ctx) + { + libremidi_handle_error(configuration, "midi_in::open_port: failed to get Android context"); + return std::errc{}; + } + + context::refresh_midi_devices(env, ctx, false); + + if (port_number >= context::midi_devices.size()) + { + libremidi_handle_error(configuration, "midi_in::open_port: invalid port number"); + return std::errc{}; + } + + port_open = true; + running = true; + context::open_device(context::midi_devices[port_number], this, false); + return stdx::error{}; + } + + stdx::error close_port() override + { + if (!is_port_open()) + return std::errc{}; + + running = false; + + if (poll_thread.joinable()) + { + poll_thread.join(); + } + + if (midi_output_port) + { + AMidiOutputPort_close(midi_output_port); + midi_output_port = nullptr; + } + + if (receive_device) + { + AMidiDevice_release(receive_device); + receive_device = nullptr; + } + + port_open = false; + return stdx::error{}; + } + + bool is_port_open() const noexcept { return port_open; } + + void start_midi_thread() { poll_thread = std::thread(&midi_in::poll_midi, this); } + + timestamp absolute_timestamp() const noexcept override { return system_ns(); } + void poll_midi() + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = false, + .absolute_is_monotonic = false, + .has_samples = false, + }; + + uint8_t buffer[1024]; + int32_t op_code; + int64_t timestamp; + size_t num_bytes; + + while (running) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + ssize_t num_messages = AMidiOutputPort_receive( + midi_output_port, &op_code, buffer, sizeof(buffer), &num_bytes, ×tamp); + + if (num_messages < 0) + { + LOGE("Error receiving MIDI data: %d", (int)num_messages); + continue; + } + + if (num_messages > 0 && num_bytes > 0) + { + const auto to_ns = [=] { return timestamp; }; + + m_processing.on_bytes_multi( + {buffer, buffer + num_bytes}, + m_processing.timestamp(to_ns, timestamp)); + } + } + } + + static void open_callback(midi_in* self, AMidiDevice* device) + { + self->receive_device = device; + AMidiOutputPort_open(device, 0, &self->midi_output_port); + self->start_midi_thread(); + } + +private: + libremidi::input_configuration configuration; + AMidiDevice* receive_device = nullptr; + AMidiOutputPort* midi_output_port = nullptr; + std::thread poll_thread; + std::atomic port_open{false}; + std::atomic running{false}; + midi1::input_state_machine m_processing{this->configuration}; +}; +} +} diff --git a/include/libremidi/backends/android/midi_out.hpp b/include/libremidi/backends/android/midi_out.hpp new file mode 100644 index 0000000..fb12986 --- /dev/null +++ b/include/libremidi/backends/android/midi_out.hpp @@ -0,0 +1,126 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class midi_out final + : public midi1::out_api + , public error_handler +{ +public: + explicit midi_out( + libremidi::output_configuration&& conf, libremidi::android::output_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + libremidi_handle_error( + configuration, "Android backend only supports one client name per process"); + return; + } + context::client_name = std::string(aconf.client_name); + client_open_ = stdx::error{}; + } + + ~midi_out() override { close_port(); } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + stdx::error open_port(const output_port& port, std::string_view /*name*/) override + { + if (is_port_open()) + { + libremidi_handle_error(configuration, "midi_out::open_port: a port is already open"); + return std::errc{}; + } + + const unsigned int port_number = port.port; + + auto env = context::get_thread_env(); + if (!env) + { + libremidi_handle_error(configuration, "midi_out::open_port: failed to get JNI environment"); + return std::errc{}; + } + + auto ctx = context::get_context(env); + if (!ctx) + { + libremidi_handle_error(configuration, "midi_out::open_port: failed to get Android context"); + return std::errc{}; + } + + context::refresh_midi_devices(env, ctx, true); + + if (port_number >= context::midi_devices.size()) + { + libremidi_handle_error(configuration, "midi_out::open_port: invalid port number"); + return std::errc{}; + } + + port_open = true; + context::open_device(context::midi_devices[port_number], this, true); + return stdx::error{}; + } + + stdx::error close_port() override + { + if (!is_port_open()) + return std::errc{}; + + if (midi_input_port) + { + AMidiInputPort_close(midi_input_port); + midi_input_port = nullptr; + } + + if (send_device) + { + AMidiDevice_release(send_device); + send_device = nullptr; + } + + port_open = false; + return stdx::error{}; + } + + bool is_port_open() const noexcept { return port_open; } + + stdx::error send_message(const unsigned char* message, size_t size) override + { + if (!is_port_open() || !midi_input_port) + { + libremidi_handle_error(configuration, "midi_out::send_message: port is not open"); + return std::errc{}; + } + + ssize_t result = AMidiInputPort_send(midi_input_port, message, size); + if (result < 0) + { + libremidi_handle_error(configuration, "midi_out::send_message: failed to send MIDI data"); + return std::errc{}; + } + return stdx::error{}; + } + + static void open_callback(midi_out* self, AMidiDevice* device) + { + self->send_device = device; + AMidiInputPort_open(device, 0, &self->midi_input_port); + } + +private: + libremidi::output_configuration configuration; + AMidiDevice* send_device = nullptr; + AMidiInputPort* midi_input_port = nullptr; + bool port_open = false; +}; +} +} diff --git a/include/libremidi/backends/android/observer.hpp b/include/libremidi/backends/android/observer.hpp new file mode 100644 index 0000000..f320002 --- /dev/null +++ b/include/libremidi/backends/android/observer.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class observer final + : public libremidi::observer_api + , public error_handler +{ +public: + explicit observer( + libremidi::observer_configuration&& conf, libremidi::android::observer_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + LOGW("Android backend only supports one client name per process"); + } + context::client_name = std::string(aconf.client_name); + } + + ~observer() + { + auto env = context::get_thread_env(); + if (env) + { + context::cleanup_devices(env); + } + } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + std::vector get_input_ports() const noexcept override + { + auto env = context::get_thread_env(); + if (!env) + return {}; + + auto ctx = context::get_context(env); + if (!ctx) + return {}; + + context::refresh_midi_devices(env, ctx, false); + + std::vector ports; + for (size_t i = 0; i < context::midi_devices.size(); ++i) + { + libremidi::input_port port; + port.api = libremidi::API::ANDROID_AMIDI; + port.port_name = context::port_name(env, i); + port.port = i; + ports.push_back(std::move(port)); + } + + return ports; + } + + std::vector get_output_ports() const noexcept override + { + auto env = context::get_thread_env(); + if (!env) + return {}; + + auto ctx = context::get_context(env); + if (!ctx) + return {}; + + context::refresh_midi_devices(env, ctx, true); + + std::vector ports; + for (size_t i = 0; i < context::midi_devices.size(); ++i) + { + libremidi::output_port port; + port.port_name = context::port_name(env, i); + port.port = i; + ports.push_back(std::move(port)); + } + + return ports; + } + +private: + libremidi::observer_configuration configuration; +}; +} +} diff --git a/include/libremidi/backends/coremidi.hpp b/include/libremidi/backends/coremidi.hpp index e6f2ee4..eab24e8 100644 --- a/include/libremidi/backends/coremidi.hpp +++ b/include/libremidi/backends/coremidi.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { struct core_backend { @@ -14,8 +16,8 @@ struct core_backend using midi_out_configuration = coremidi_output_configuration; using midi_observer_configuration = coremidi_observer_configuration; static const constexpr auto API = libremidi::API::COREMIDI; - static const constexpr auto name = "core"; - static const constexpr auto display_name = "CoreMIDI"; + static const constexpr std::string_view name = "core"; + static const constexpr std::string_view display_name = "CoreMIDI"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/coremidi/config.hpp b/include/libremidi/backends/coremidi/config.hpp index 359dece..db31956 100644 --- a/include/libremidi/backends/coremidi/config.hpp +++ b/include/libremidi/backends/coremidi/config.hpp @@ -18,7 +18,7 @@ typedef MIDIObjectRef MIDIClientRef; using MIDIClientRef = uint32_t; using MIDIObjectRef = uint32_t; #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct coremidi_input_configuration diff --git a/include/libremidi/backends/coremidi/error_domain.hpp b/include/libremidi/backends/coremidi/error_domain.hpp index fb33d39..ff82ccc 100644 --- a/include/libremidi/backends/coremidi/error_domain.hpp +++ b/include/libremidi/backends/coremidi/error_domain.hpp @@ -3,7 +3,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct coremidi_error_domain : public stdx::error_domain { diff --git a/include/libremidi/backends/coremidi/helpers.hpp b/include/libremidi/backends/coremidi/helpers.hpp index 30f5eea..2d85134 100644 --- a/include/libremidi/backends/coremidi/helpers.hpp +++ b/include/libremidi/backends/coremidi/helpers.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include #include #include -#include #include #include @@ -21,13 +21,12 @@ #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime #endif -namespace libremidi +NAMESPACE_LIBREMIDI { using CFString_handle = unique_handle; using CFStringMutable_handle = unique_handle<__CFString, CFRelease>; -namespace -{ -static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept + +LIBREMIDI_STATIC std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept { CFStringRef res; if (MIDIObjectGetStringProperty(object, property, &res) || !res) @@ -39,14 +38,14 @@ static inline std::string get_string_property(MIDIObjectRef object, CFStringRef return name; } -static inline int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept +LIBREMIDI_STATIC int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept { SInt32 res; MIDIObjectGetIntegerProperty(object, property, &res); return res; } -static inline CFString_handle toCFString(std::string_view str) noexcept +LIBREMIDI_STATIC CFString_handle toCFString(std::string_view str) noexcept { return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)}; } @@ -71,20 +70,22 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime) return static_cast(res); } #endif + +LIBREMIDI_STATIC auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name) +{ + CFStringRef str = nullptr; + MIDIObjectGetStringProperty(prop, name, &str); + return CFString_handle{str}; +} + // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) { CFMutableStringRef result = CFStringCreateMutable(nullptr, 0); - static constexpr auto getProp = [](MIDIObjectRef prop) { - CFStringRef str = nullptr; - MIDIObjectGetStringProperty(prop, kMIDIPropertyName, &str); - return CFString_handle{str}; - }; - // Begin with the endpoint's name. - if (auto endpoint_name = getProp(endpoint)) + if (auto endpoint_name = get_cfstring_property(endpoint, kMIDIPropertyName)) { CFStringAppend(result, endpoint_name.get()); } @@ -101,7 +102,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) if (CFStringGetLength(result) == 0) { // endpoint name has zero length -- try the entity - if (auto entity_name = getProp(entity)) + if (auto entity_name = get_cfstring_property(entity, kMIDIPropertyName)) { CFStringAppend(result, entity_name.get()); } @@ -112,7 +113,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) if (device == 0) goto finish; - if (auto dev_name = getProp(device)) + if (auto dev_name = get_cfstring_property(device, kMIDIPropertyName)) { const auto dev_strlen = CFStringGetLength(dev_name.get()); @@ -218,22 +219,19 @@ locate_object(auto& self, const port_information& info, MIDIObjectType requested auto ret = MIDIObjectFindByUniqueID(uid, &object, &type); if (ret != noErr) { - self.libremidi_handle_error( - self.configuration, "cannot find port: " + info.port_name); + self.libremidi_handle_error(self.configuration, "cannot find port: " + info.port_name); return 0; } if (type != requested_type || object == 0) { self.libremidi_handle_error( - self.configuration, "invalid object: " + info.port_name + " : " - + std::to_string(object)); + self.configuration, "invalid object: " + info.port_name + " : " + std::to_string(object)); return 0; } return object; } -} // A structure to hold variables related to the CoreMIDI API // implementation. diff --git a/include/libremidi/backends/coremidi/midi_in.hpp b/include/libremidi/backends/coremidi/midi_in.hpp index deaaf82..0f84246 100644 --- a/include/libremidi/backends/coremidi/midi_in.hpp +++ b/include/libremidi/backends/coremidi/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_core final : public midi1::in_api @@ -25,8 +25,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -64,8 +63,7 @@ public: { close_client(*this); libremidi_handle_error( - this->configuration, "error creating macOS MIDI input port: " - + std::to_string(result)); + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); return from_osstatus(result); } @@ -74,8 +72,7 @@ public: { MIDIPortDispose(port); close_client(*this); - libremidi_handle_error( - this->configuration, "error connecting macOS MIDI input port."); + libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port."); return from_osstatus(result); } @@ -105,10 +102,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override { diff --git a/include/libremidi/backends/coremidi/midi_out.hpp b/include/libremidi/backends/coremidi/midi_out.hpp index e260475..39c76ac 100644 --- a/include/libremidi/backends/coremidi/midi_out.hpp +++ b/include/libremidi/backends/coremidi/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_core final : public midi1::out_api @@ -24,8 +24,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -60,8 +59,7 @@ public: if (result != noErr) { close_client(*this); - libremidi_handle_error( - this->configuration, "error creating macOS MIDI output port."); + libremidi_handle_error(this->configuration, "error creating macOS MIDI output port."); return from_osstatus(result); } @@ -80,9 +78,7 @@ public: if (result != noErr) { - libremidi_handle_error( - this->configuration, - "error creating macOS virtual MIDI source."); + libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source."); return from_osstatus(result); } @@ -92,10 +88,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } stdx::error send_message(const unsigned char* message, size_t size) override { @@ -118,8 +111,8 @@ public: const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME(); const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes; - Byte buffer[bufsize + 16]; // pad for other struct members - ByteCount listSize = sizeof(buffer); + Byte* buffer = (Byte*)alloca(bufsize + 16); // pad for other struct members + ByteCount listSize = bufsize + 16; MIDIPacketList* packetList = (MIDIPacketList*)buffer; ByteCount remainingBytes = nBytes; @@ -137,8 +130,7 @@ public: if (!packet) { - libremidi_handle_error( - this->configuration, "could not allocate packet list"); + libremidi_handle_error(this->configuration, "could not allocate packet list"); return std::errc::message_size; } @@ -163,9 +155,7 @@ public: auto result = MIDISend(this->port, this->destinationId, packetList); if (result != noErr) { - libremidi_handle_warning( - this->configuration, - "error sending MIDI message to port."); + libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return std::errc::io_error; } } diff --git a/include/libremidi/backends/coremidi/observer.hpp b/include/libremidi/backends/coremidi/observer.hpp index 833be51..1a3a93e 100644 --- a/include/libremidi/backends/coremidi/observer.hpp +++ b/include/libremidi/backends/coremidi/observer.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_core : public observer_api @@ -22,6 +22,10 @@ public: explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} + { + } + + void finish_init() { if (configuration.client_name.empty()) configuration.client_name = "libremidi observer"; @@ -32,15 +36,13 @@ public: auto result = MIDIClientCreate( toCFString(configuration.client_name).get(), +[](const MIDINotification* message, void* ctx) { - ((observer_core*)ctx)->notify(message); - }, - this, &client); + ((observer_core*)ctx)->notify(message); + }, this, &client); if (result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); return; } @@ -78,13 +80,56 @@ public: if (!ok) return {}; + // Get the MIDI device from the entity + libremidi::transport_type type{}; + libremidi::container_identifier usb_location_id{}; + libremidi::device_identifier usb_vendor_product{}; + { + MIDIDeviceRef device{}; + if (MIDIEntityGetDevice(e, &device) == noErr) + { + if (device) + { + using enum libremidi::transport_type; + SInt32 devid_res{}; + if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr) + { + type = (libremidi::transport_type)(hardware | usb); + usb_location_id = (uint64_t)devid_res; + } + + SInt32 vendor_res{}; + if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res) + == noErr) + { + type = (libremidi::transport_type)(hardware | usb); + usb_vendor_product = (uint64_t)vendor_res; + } + + const auto driver = get_string_property(device, kMIDIPropertyDriverOwner); + if (driver == "com.apple.AppleMIDIUSBDriver") + type = (libremidi::transport_type)(hardware | usb); + if (driver == "com.apple.AppleMIDIBluetoothDriver") + type = (libremidi::transport_type)(hardware | bluetooth); + if (driver == "com.apple.AppleMIDIIACDriver") + type = (libremidi::transport_type)(software); + if (driver == "com.apple.AppleMIDIRTPDriver") + type = (libremidi::transport_type)(network); + } + } + } + return std::conditional_t{ - {.client = (std::uintptr_t)this->client, + {.api = get_current_api(), + .client = (std::uintptr_t)this->client, + .container = usb_location_id, + .device = usb_vendor_product, .port = std::bit_cast(get_int_property(obj, kMIDIPropertyUniqueID)), .manufacturer = get_string_property(obj, kMIDIPropertyManufacturer), .device_name = get_string_property(obj, kMIDIPropertyModel), .port_name = get_string_property(obj, kMIDIPropertyName), - .display_name = get_string_property(obj, kMIDIPropertyDisplayName)}}; + .display_name = get_string_property(obj, kMIDIPropertyDisplayName), + .type = type}}; } std::vector get_input_ports() const noexcept override @@ -173,3 +218,17 @@ private: MIDIClientRef client{}; }; } + +NAMESPACE_LIBREMIDI::coremidi +{ +struct observer_impl : observer_core +{ + observer_impl(observer_configuration&& conf, coremidi_observer_configuration&& apiconf) + : observer_core{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; } +}; +} diff --git a/include/libremidi/backends/coremidi_ump.hpp b/include/libremidi/backends/coremidi_ump.hpp index 67f7728..3aa8457 100644 --- a/include/libremidi/backends/coremidi_ump.hpp +++ b/include/libremidi/backends/coremidi_ump.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi::coremidi_ump +#include + +NAMESPACE_LIBREMIDI::coremidi_ump { struct backend { @@ -14,8 +16,8 @@ struct backend using midi_out_configuration = coremidi_ump::output_configuration; using midi_observer_configuration = coremidi_ump::observer_configuration; static const constexpr auto API = libremidi::API::COREMIDI_UMP; - static const constexpr auto name = "core_ump"; - static const constexpr auto display_name = "CoreMIDI UMP"; + static const constexpr std::string_view name = "core_ump"; + static const constexpr std::string_view display_name = "CoreMIDI UMP"; static constexpr inline bool available() noexcept { return true; /* todo? */ } }; diff --git a/include/libremidi/backends/coremidi_ump/config.hpp b/include/libremidi/backends/coremidi_ump/config.hpp index dee80f9..7d5f927 100644 --- a/include/libremidi/backends/coremidi_ump/config.hpp +++ b/include/libremidi/backends/coremidi_ump/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { struct input_configuration { diff --git a/include/libremidi/backends/coremidi_ump/midi_in.hpp b/include/libremidi/backends/coremidi_ump/midi_in.hpp index fb94fb7..019612c 100644 --- a/include/libremidi/backends/coremidi_ump/midi_in.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class midi_in_impl final @@ -26,8 +26,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -69,8 +68,7 @@ public: { close_client(*this); libremidi_handle_error( - this->configuration, "error creating macOS MIDI input port: " - + std::to_string(result)); + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); return from_osstatus(result); } @@ -79,8 +77,7 @@ public: { MIDIPortDispose(port); close_client(*this); - libremidi_handle_error( - this->configuration, "error connecting macOS MIDI input port."); + libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port."); return from_osstatus(result); } @@ -113,10 +110,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override { diff --git a/include/libremidi/backends/coremidi_ump/midi_out.hpp b/include/libremidi/backends/coremidi_ump/midi_out.hpp index 4ef9a67..e71d956 100644 --- a/include/libremidi/backends/coremidi_ump/midi_out.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_out.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class midi_out_impl final : public midi2::out_api @@ -27,8 +27,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -63,8 +62,7 @@ public: if (result != noErr) { close_client(*this); - libremidi_handle_error( - this->configuration, "error creating macOS MIDI output port."); + libremidi_handle_error(this->configuration, "error creating macOS MIDI output port."); return from_osstatus(result); } @@ -84,9 +82,7 @@ public: if (result != noErr) { this->endpoint = 0; - libremidi_handle_error( - this->configuration, - "error creating macOS virtual MIDI source."); + libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source."); return from_osstatus(result); } @@ -94,10 +90,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override { @@ -142,9 +135,7 @@ public: auto result = MIDISendEventList(this->port, this->destinationId, eventList); if (result != noErr) { - libremidi_handle_warning( - this->configuration, - "error sending MIDI message to port."); + libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return std::errc::io_error; } } diff --git a/include/libremidi/backends/coremidi_ump/observer.hpp b/include/libremidi/backends/coremidi_ump/observer.hpp index 35a3f18..18dad5f 100644 --- a/include/libremidi/backends/coremidi_ump/observer.hpp +++ b/include/libremidi/backends/coremidi_ump/observer.hpp @@ -1,17 +1,20 @@ #pragma once -#include #include +#include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class observer_impl final : public libremidi::observer_core { public: - explicit observer_impl(libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) - : observer_core{std::move(conf), coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} + explicit observer_impl( + libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) + : observer_core{ + std::move(conf), + coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} { - + finish_init(); } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; } diff --git a/include/libremidi/backends/dummy.hpp b/include/libremidi/backends/dummy.hpp index b3fb029..0244997 100644 --- a/include/libremidi/backends/dummy.hpp +++ b/include/libremidi/backends/dummy.hpp @@ -5,7 +5,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { class observer_dummy : public observer_api { @@ -23,11 +25,12 @@ class midi_in_dummy final , public error_handler { public: - explicit midi_in_dummy(const input_configuration& configuration, std::any) + explicit midi_in_dummy(const input_configuration& configuration, const input_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } - explicit midi_in_dummy(const ump_input_configuration& configuration, std::any) + explicit midi_in_dummy( + const ump_input_configuration& configuration, const input_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } @@ -37,19 +40,9 @@ public: { return stdx::error{}; } - stdx::error open_virtual_port(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_client_name(std::string_view /*clientName*/) override - { - return stdx::error{}; - } - stdx::error set_port_name(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; } timestamp absolute_timestamp() const noexcept override { return 0; } }; @@ -58,7 +51,8 @@ class midi_out_dummy final , public error_handler { public: - explicit midi_out_dummy(const output_configuration& configuration, std::any) + explicit midi_out_dummy( + const output_configuration& configuration, const output_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } @@ -68,20 +62,10 @@ public: { return stdx::error{}; } - stdx::error open_virtual_port(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_client_name(std::string_view /*clientName*/) override - { - return stdx::error{}; - } - stdx::error set_port_name(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override { return stdx::error{}; @@ -97,8 +81,8 @@ struct dummy_backend using midi_out_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration; static const constexpr auto API = libremidi::API::DUMMY; - static const constexpr auto name = "dummy"; - static const constexpr auto display_name = "Dummy"; + static const constexpr std::string_view name = "dummy"; + static const constexpr std::string_view display_name = "Dummy"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/emscripten.hpp b/include/libremidi/backends/emscripten.hpp index 867e943..d948fef 100644 --- a/include/libremidi/backends/emscripten.hpp +++ b/include/libremidi/backends/emscripten.hpp @@ -7,7 +7,9 @@ #include #include -namespace libremidi + #include + +NAMESPACE_LIBREMIDI { struct emscripten_backend @@ -19,8 +21,8 @@ struct emscripten_backend using midi_out_configuration = emscripten_output_configuration; using midi_observer_configuration = emscripten_observer_configuration; static const constexpr auto API = libremidi::API::WEBMIDI; - static const constexpr auto name = "webmidi"; - static const constexpr auto display_name = "WebMIDI"; + static const constexpr std::string_view name = "webmidi"; + static const constexpr std::string_view display_name = "WebMIDI"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/emscripten/config.hpp b/include/libremidi/backends/emscripten/config.hpp index 6a1d735..869ea96 100644 --- a/include/libremidi/backends/emscripten/config.hpp +++ b/include/libremidi/backends/emscripten/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct emscripten_input_configuration diff --git a/include/libremidi/backends/emscripten/helpers.hpp b/include/libremidi/backends/emscripten/helpers.hpp index 3064ae1..9a30fa6 100644 --- a/include/libremidi/backends/emscripten/helpers.hpp +++ b/include/libremidi/backends/emscripten/helpers.hpp @@ -3,7 +3,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { namespace webmidi_helpers { diff --git a/include/libremidi/backends/emscripten/midi_access.hpp b/include/libremidi/backends/emscripten/midi_access.hpp index 3ca9f9e..fc5f171 100644 --- a/include/libremidi/backends/emscripten/midi_access.hpp +++ b/include/libremidi/backends/emscripten/midi_access.hpp @@ -16,7 +16,7 @@ EMSCRIPTEN_KEEPALIVE void libremidi_devices_input(int port, double timestamp, int len, char* bytes); } -namespace libremidi +NAMESPACE_LIBREMIDI { namespace webmidi_helpers { @@ -37,16 +37,25 @@ public: int input_count() const noexcept { + if (!available()) + return 0; + return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;); } int output_count() const noexcept { + if (!available()) + return 0; + return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;); } void load_current_infos() noexcept { + if (!available()) + return; + #define get_js_string(variable_to_read, ...) \ (char*)EM_ASM_INT( \ { \ @@ -206,6 +215,9 @@ public: stdx::error send_message(int port_index, const char* bytes, int len) { + if (!available()) + return std::errc::operation_not_supported; + const auto& id = m_current_outputs[port_index].id; EM_ASM( { @@ -216,6 +228,8 @@ public: output.send(Array.from(bytes)); }, bytes, len, id.c_str()); + + return stdx::error{}; } const std::vector& inputs() const noexcept { return m_current_inputs; } @@ -248,6 +262,9 @@ private: void start_stream(int port_index) { + if (!available()) + return; + // Isn't life great... // https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays const auto& id = m_current_inputs[port_index].id; @@ -282,6 +299,9 @@ private: void stop_stream(int port_index) { + if (!available()) + return; + const auto& id = m_current_inputs[port_index].id; EM_ASM(const id = UTF8ToString($1); diff --git a/include/libremidi/backends/emscripten/midi_in.cpp b/include/libremidi/backends/emscripten/midi_in.cpp index 3b919d3..3ca7476 100644 --- a/include/libremidi/backends/emscripten/midi_in.cpp +++ b/include/libremidi/backends/emscripten/midi_in.cpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten( input_configuration&& conf, emscripten_input_configuration&& apiconf) @@ -32,13 +32,12 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std:: if (portNumber < 0 || portNumber >= midi.input_count()) { - libremidi_handle_error( - this->configuration, "no MIDI output sources found."); + libremidi_handle_error(this->configuration, "no MIDI output sources found."); return std::errc::invalid_argument; } midi.open_input(portNumber, *this); - portNumber_ = portNumber; + m_portNumber = portNumber; return stdx::error{}; } @@ -52,7 +51,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port() { auto& midi = webmidi_helpers::midi_access_emscripten::instance(); - midi.close_input(portNumber_, *this); + midi.close_input(m_portNumber, *this); return stdx::error{}; } diff --git a/include/libremidi/backends/emscripten/midi_in.hpp b/include/libremidi/backends/emscripten/midi_in.hpp index 4833903..294c61f 100644 --- a/include/libremidi/backends/emscripten/midi_in.hpp +++ b/include/libremidi/backends/emscripten/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_emscripten final : public midi1::in_api @@ -31,7 +31,7 @@ public: void on_input(double ts, unsigned char* begin, unsigned char* end); private: - int portNumber_{}; + int m_portNumber{}; midi1::input_state_machine m_processing{this->configuration}; }; diff --git a/include/libremidi/backends/emscripten/midi_out.cpp b/include/libremidi/backends/emscripten/midi_out.cpp index e2bf024..e08d2ad 100644 --- a/include/libremidi/backends/emscripten/midi_out.cpp +++ b/include/libremidi/backends/emscripten/midi_out.cpp @@ -2,7 +2,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten( output_configuration&& conf, emscripten_output_configuration&& apiconf) @@ -29,34 +29,36 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std: if (portNumber >= midi.output_count()) { - libremidi_handle_error( - this->configuration, "no MIDI output sources found."); + libremidi_handle_error(this->configuration, "no MIDI output sources found."); return std::errc::invalid_argument; } - portNumber_ = portNumber; + m_portNumber = portNumber; return stdx::error{}; } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(const output_port& p, std::string_view nm) +LIBREMIDI_INLINE stdx::error +midi_out_emscripten::open_port(const output_port& p, std::string_view nm) { return open_port(p.port, nm); } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() { +LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() +{ return stdx::error{}; } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size) +LIBREMIDI_INLINE stdx::error +midi_out_emscripten::send_message(const unsigned char* message, size_t size) { - if (portNumber_ < 0) + if (m_portNumber < 0) libremidi_handle_error( this->configuration, "trying to send a message without an open " "port."); webmidi_helpers::midi_access_emscripten::instance().send_message( - portNumber_, reinterpret_cast(message), size); + m_portNumber, reinterpret_cast(message), size); return stdx::error{}; } } diff --git a/include/libremidi/backends/emscripten/midi_out.hpp b/include/libremidi/backends/emscripten/midi_out.hpp index f6f2137..62f3b55 100644 --- a/include/libremidi/backends/emscripten/midi_out.hpp +++ b/include/libremidi/backends/emscripten/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_emscripten final : public midi1::out_api @@ -28,6 +28,6 @@ public: stdx::error send_message(const unsigned char* message, size_t size) override; private: - int portNumber_{-1}; + int m_portNumber{-1}; }; } diff --git a/include/libremidi/backends/emscripten/observer.cpp b/include/libremidi/backends/emscripten/observer.cpp index 9cdda2d..b67a38b 100644 --- a/include/libremidi/backends/emscripten/observer.cpp +++ b/include/libremidi/backends/emscripten/observer.cpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE observer_emscripten::observer_emscripten( observer_configuration&& conf, emscripten_observer_configuration&& apiconf) @@ -42,7 +42,8 @@ static auto to_port_info(int index, const webmidi_helpers::device_information& d -> std::conditional_t { return { - {.client = 0, + {.api = libremidi::API::WEBMIDI, + .client = 0, .port = (uint64_t)index, .manufacturer = "", .device_name = "", diff --git a/include/libremidi/backends/emscripten/observer.hpp b/include/libremidi/backends/emscripten/observer.hpp index 017785d..81fb730 100644 --- a/include/libremidi/backends/emscripten/observer.hpp +++ b/include/libremidi/backends/emscripten/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_emscripten final : public observer_api { diff --git a/include/libremidi/backends/jack.hpp b/include/libremidi/backends/jack.hpp index 8367676..c703ecb 100644 --- a/include/libremidi/backends/jack.hpp +++ b/include/libremidi/backends/jack.hpp @@ -3,6 +3,8 @@ #include #include +#include + //*********************************************************************// // API: UNIX JACK // @@ -11,7 +13,7 @@ // // *********************************************************************// -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_backend { @@ -22,8 +24,8 @@ struct jack_backend using midi_out_configuration = jack_output_configuration; using midi_observer_configuration = jack_observer_configuration; static const constexpr auto API = libremidi::API::JACK_MIDI; - static const constexpr auto name = "jack"; - static const constexpr auto display_name = "JACK"; + static const constexpr std::string_view name = "jack"; + static const constexpr std::string_view display_name = "JACK"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/jack/config.hpp b/include/libremidi/backends/jack/config.hpp index d99ed92..6feabcb 100644 --- a/include/libremidi/backends/jack/config.hpp +++ b/include/libremidi/backends/jack/config.hpp @@ -11,7 +11,7 @@ typedef uint32_t jack_nframes_t; typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg); } -namespace libremidi +NAMESPACE_LIBREMIDI { using jack_callback_function = std::function; struct jack_callback diff --git a/include/libremidi/backends/jack/error_domain.hpp b/include/libremidi/backends/jack/error_domain.hpp index f63f27f..bc82ef8 100644 --- a/include/libremidi/backends/jack/error_domain.hpp +++ b/include/libremidi/backends/jack/error_domain.hpp @@ -11,7 +11,7 @@ #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_error_domain : public stdx::error_domain { @@ -34,36 +34,36 @@ public: stdx::string_ref message(const stdx::error& e) const noexcept override { const auto status = error_cast(e); - if(status == jack_status_t{}) + if (status == jack_status_t{}) return "Success"; - if(status & JackInvalidOption) + if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option"; - if(status & JackServerFailed) + if (status & JackServerFailed) return "Unable to connect to the JACK server"; - if(status & JackServerError) + if (status & JackServerError) return "Communication error with the JACK server"; - if(status & JackNoSuchClient) + if (status & JackNoSuchClient) return "Requested client does not exist"; - if(status & JackLoadFailure) + if (status & JackLoadFailure) return "Unable to load internal client"; - if(status & JackInitFailure) + if (status & JackInitFailure) return "Unable to initialize client"; - if(status & JackShmFailure) + if (status & JackShmFailure) return "Unable to access shared memory"; - if(status & JackVersionError) + if (status & JackVersionError) return "Client's protocol version does not match"; - if(status & JackBackendError) + if (status & JackBackendError) return "Backend error"; - if(status & JackClientZombie) + if (status & JackClientZombie) return "Client zombified failure"; - if(status & JackFailure) + if (status & JackFailure) return "Failure"; - if(status & JackNameNotUnique) + if (status & JackNameNotUnique) return "The desired client name was not unique"; // Can't happen in libremidi as we set JackNoStartServer - if(status & JackServerStarted) + if (status & JackServerStarted) return "Server was started"; return "Unknown JACK status code"; diff --git a/include/libremidi/backends/jack/helpers.hpp b/include/libremidi/backends/jack/helpers.hpp index 60eee90..1bf5f27 100644 --- a/include/libremidi/backends/jack/helpers.hpp +++ b/include/libremidi/backends/jack/helpers.hpp @@ -5,9 +5,11 @@ #include #include -#include +#include +#include +#include -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_client { @@ -39,31 +41,32 @@ struct jack_client } } - template + template static auto to_port_info(jack_client_t* client, jack_port_t* port) -> std::conditional_t { return {{ + .api = Api, .client = reinterpret_cast(client), .port = 0, .manufacturer = "", - .device_name = "", + .device_name = jack_get_client_name(client), .port_name = jack_port_name(port), .display_name = get_port_display_name(port), }}; } - template - static auto - get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept - -> std::vector> + template + static auto get_ports( + jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags, + bool midi2) noexcept -> std::vector> { std::vector> ret; if (!client) return {}; - const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags); + const char** ports = jack_get_ports(client, pattern, type, flags); if (ports == nullptr) return {}; @@ -73,7 +76,11 @@ struct jack_client { // FIXME this does not take into account filtering sw / hw ports auto port = jack_port_by_name(client, ports[i]); - ret.push_back(to_port_info(client, port)); + if (port) + { + if (bool(midi2) == bool(jack_port_flags(port) & 0x20)) + ret.push_back(to_port_info(client, port)); + } i++; } @@ -131,13 +138,13 @@ struct jack_helpers : jack_client configuration.set_process_func( {.token = this_instance, .callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int { - if (auto pt = p.lock()) - if (auto ppt = pt->load()) - self.process(nf); + if (auto pt = p.lock()) + if (pt->load()) + self.process(nf); - self.thread_lock.check_client_released(); - return 0; - }}); + self.thread_lock.check_client_released(); + return 0; + }}); this->client = configuration.context; return jack_status_t{}; @@ -149,26 +156,25 @@ struct jack_helpers : jack_client = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); if (this->client != nullptr) { - if(status & JackNameNotUnique) { - self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed."); + if (status & JackNameNotUnique) + { + self.libremidi_handle_warning( + self.configuration, "JACK client with the same name already exists, renamed."); } - jack_set_process_callback( - this->client, - +[](jack_nframes_t nf, void* ctx) -> int { - auto& self = *static_cast(ctx); - jack_port_t* port = self.port; + jack_set_process_callback(this->client, +[](jack_nframes_t nf, void* ctx) -> int { + auto& self = *static_cast(ctx); + jack_port_t* port = self.port; - // Is port created? - if (port == nullptr) - return 0; + // Is port created? + if (port == nullptr) + return 0; - self.process(nf); + self.process(nf); - self.thread_lock.check_client_released(); - return 0; - }, - &self); + self.thread_lock.check_client_released(); + return 0; + }, &self); jack_activate(this->client); } return status; @@ -192,8 +198,8 @@ struct jack_helpers : jack_client self.client_open_ = std::errc::not_connected; } - stdx::error - create_local_port(const auto& self, std::string_view portName, JackPortFlags flags) + stdx::error create_local_port( + const auto& self, std::string_view portName, const char* type, JackPortFlags flags) { // full name: "client_name:port_name\0" if (portName.empty()) @@ -202,15 +208,13 @@ struct jack_helpers : jack_client if (self.configuration.client_name.size() + portName.size() + 2u >= static_cast(jack_port_name_size())) { - self.libremidi_handle_error( - self.configuration, "port name length limit exceeded"); + self.libremidi_handle_error(self.configuration, "port name length limit exceeded"); return std::errc::invalid_argument; } if (!this->port) { - this->port - = jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0); + this->port = jack_port_register(this->client, portName.data(), type, flags, 0); } if (!this->port) @@ -238,4 +242,77 @@ struct jack_helpers : jack_client return from_errc(err); } }; + +struct jack_queue +{ +public: + static constexpr auto size_sz = sizeof(int32_t); + + jack_queue() = default; + jack_queue(const jack_queue&) = delete; + jack_queue(jack_queue&&) = delete; + jack_queue& operator=(const jack_queue&) = delete; + + jack_queue& operator=(jack_queue&& other) noexcept + { + ringbuffer = other.ringbuffer; + ringbuffer_space = other.ringbuffer_space; + other.ringbuffer = nullptr; + return *this; + } + + explicit jack_queue(int64_t sz) noexcept + { + ringbuffer = jack_ringbuffer_create(sz); + ringbuffer_space = jack_ringbuffer_write_space(ringbuffer); + } + + ~jack_queue() noexcept + { + if (ringbuffer) + jack_ringbuffer_free(ringbuffer); + } + + stdx::error write(const unsigned char* data, int64_t sz) const noexcept + { + if (static_cast(sz + size_sz) > ringbuffer_space) + return std::errc::no_buffer_space; + + while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) + sched_yield(); + + jack_ringbuffer_write(ringbuffer, reinterpret_cast(&sz), size_sz); + jack_ringbuffer_write(ringbuffer, reinterpret_cast(data), sz); + + return stdx::error{}; + } + + void read(void* jack_events) const noexcept + { + int32_t sz; + while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast(&sz), size_sz) == size_sz + && jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz) + { + jack_ringbuffer_read_advance(ringbuffer, size_sz); + + if (auto midi = jack_midi_event_reserve(jack_events, 0, sz)) + jack_ringbuffer_read(ringbuffer, reinterpret_cast(midi), sz); + else + jack_ringbuffer_read_advance(ringbuffer, sz); + } + } + + jack_ringbuffer_t* ringbuffer{}; + std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer +}; + +struct jack_midi1 +{ + static constexpr const char* port_type = "8 bit raw midi"; +}; + +struct jack_midi2 +{ + static constexpr const char* port_type = "32 bit raw UMP"; +}; } diff --git a/include/libremidi/backends/jack/midi_in.hpp b/include/libremidi/backends/jack/midi_in.hpp index c51b9b6..2f7467b 100644 --- a/include/libremidi/backends/jack/midi_in.hpp +++ b/include/libremidi/backends/jack/midi_in.hpp @@ -4,13 +4,12 @@ #include #include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_jack final : public midi1::in_api , public jack_helpers + , public jack_midi1 , public error_handler { public: @@ -46,15 +45,16 @@ public: stdx::error open_port(const input_port& port, std::string_view portName) override { - if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{}) + if (auto err = create_local_port(*this, portName, port_type, JackPortIsInput); + err != stdx::error{}) return err; if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); err != 0 && err != EEXIST) { libremidi_handle_error( - configuration, "could not connect to port: " + port.port_name + " -> " - + jack_port_name(this->port)); + configuration, + "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port)); return from_errc(err); } return stdx::error{}; @@ -62,7 +62,7 @@ public: stdx::error open_virtual_port(std::string_view portName) override { - return create_local_port(*this, portName, JackPortIsInput); + return create_local_port(*this, portName, port_type, JackPortIsInput); } stdx::error close_port() override { return do_close_port(); } @@ -96,8 +96,8 @@ public: this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs); // We have midi events in buffer - uint32_t evCount = jack_midi_get_event_count(buff); - for (uint32_t j = 0; j < evCount; j++) + uint32_t ev_count = jack_midi_get_event_count(buff); + for (uint32_t j = 0; j < ev_count; j++) { jack_midi_event_t event{}; jack_midi_event_get(&event, buff, j); diff --git a/include/libremidi/backends/jack/midi_out.hpp b/include/libremidi/backends/jack/midi_out.hpp index 7077541..fcac4f9 100644 --- a/include/libremidi/backends/jack/midi_out.hpp +++ b/include/libremidi/backends/jack/midi_out.hpp @@ -3,76 +3,12 @@ #include #include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { -struct jack_queue -{ -public: - static constexpr auto size_sz = sizeof(int32_t); - - jack_queue() = default; - jack_queue(const jack_queue&) = delete; - jack_queue(jack_queue&&) = delete; - jack_queue& operator=(const jack_queue&) = delete; - - jack_queue& operator=(jack_queue&& other) noexcept - { - ringbuffer = other.ringbuffer; - ringbuffer_space = other.ringbuffer_space; - other.ringbuffer = nullptr; - return *this; - } - - explicit jack_queue(int64_t sz) noexcept - { - ringbuffer = jack_ringbuffer_create(sz); - ringbuffer_space = jack_ringbuffer_write_space(ringbuffer); - } - - ~jack_queue() noexcept - { - if (ringbuffer) - jack_ringbuffer_free(ringbuffer); - } - - stdx::error write(const unsigned char* data, int64_t sz) const noexcept - { - if (static_cast(sz + size_sz) > ringbuffer_space) - return std::errc::no_buffer_space; - - while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) - sched_yield(); - - jack_ringbuffer_write(ringbuffer, reinterpret_cast(&sz), size_sz); - jack_ringbuffer_write(ringbuffer, reinterpret_cast(data), sz); - - return stdx::error{}; - } - - void read(void* jack_events) const noexcept - { - int32_t sz; - while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast(&sz), size_sz) == size_sz - && jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz) - { - jack_ringbuffer_read_advance(ringbuffer, size_sz); - - if (auto midi = jack_midi_event_reserve(jack_events, 0, sz)) - jack_ringbuffer_read(ringbuffer, reinterpret_cast(midi), sz); - else - jack_ringbuffer_read_advance(ringbuffer, sz); - } - } - - jack_ringbuffer_t* ringbuffer{}; - std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer -}; - class midi_out_jack : public midi1::out_api , public jack_helpers + , public jack_midi1 , public error_handler { public: @@ -94,15 +30,15 @@ public: stdx::error open_port(const output_port& port, std::string_view portName) override { - if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{}) + if (auto err = create_local_port(*this, portName, port_type, JackPortIsOutput); + err != stdx::error{}) return err; // Connecting to the output if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); err != 0 && err != EEXIST) { - libremidi_handle_error( - configuration, "could not connect to port" + port.port_name); + libremidi_handle_error(configuration, "could not connect to port" + port.port_name); return from_errc(err); } @@ -111,7 +47,7 @@ public: stdx::error open_virtual_port(std::string_view portName) override { - return create_local_port(*this, portName, JackPortIsOutput); + return create_local_port(*this, portName, port_type, JackPortIsOutput); } stdx::error close_port() override { return do_close_port(); } @@ -128,7 +64,7 @@ class midi_out_jack_queued final : public midi_out_jack public: midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf) : midi_out_jack{std::move(conf), std::move(apiconf)} - , queue{configuration.ringbuffer_size} + , m_queue{configuration.ringbuffer_size} { auto status = connect(*this); if (!this->client) @@ -150,7 +86,7 @@ public: stdx::error send_message(const unsigned char* message, std::size_t size) override { - return queue.write(message, size); + return m_queue.write(message, size); } int process(jack_nframes_t nframes) @@ -158,13 +94,13 @@ public: void* buff = jack_port_get_buffer(this->port, nframes); jack_midi_clear_buffer(buff); - this->queue.read(buff); + this->m_queue.read(buff); return 0; } private: - jack_queue queue; + jack_queue m_queue; }; class midi_out_jack_direct final : public midi_out_jack @@ -230,7 +166,7 @@ public: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/jack/observer.hpp b/include/libremidi/backends/jack/observer.hpp index 01226b1..87e6651 100644 --- a/include/libremidi/backends/jack/observer.hpp +++ b/include/libremidi/backends/jack/observer.hpp @@ -5,11 +5,12 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_jack final : public observer_api , private jack_client + , public jack_midi1 , private error_handler { public: @@ -48,8 +49,7 @@ public: void initial_callback() { { - const char** ports - = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput); + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput); if (ports != nullptr) { @@ -70,7 +70,8 @@ public: { seen_input_ports.insert(ports[i]); if (this->configuration.input_added && configuration.notify_in_constructor) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } i++; } @@ -80,8 +81,7 @@ public: } { - const char** ports - = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput); if (ports != nullptr) { @@ -102,7 +102,8 @@ public: { seen_output_ports.insert(ports[i]); if (this->configuration.output_added && configuration.notify_in_constructor) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } i++; } @@ -119,7 +120,7 @@ public: if (reg) { std::string_view type = jack_port_type(port); - if (type != JACK_DEFAULT_MIDI_TYPE) + if (type != port_type) return; bool physical = flags & JackPortIsPhysical; @@ -139,13 +140,15 @@ public: { seen_input_ports.insert(name); if (this->configuration.input_added) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } else if (flags & JackPortIsInput) { seen_output_ports.insert(name); if (this->configuration.output_added) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } } else @@ -153,13 +156,15 @@ public: if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) { if (this->configuration.input_removed) - this->configuration.input_removed(to_port_info(client, port)); + this->configuration.input_removed( + to_port_info(client, port)); seen_input_ports.erase(it); } if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) { if (this->configuration.output_removed) - this->configuration.output_removed(to_port_info(client, port)); + this->configuration.output_removed( + to_port_info(client, port)); seen_output_ports.erase(it); } } @@ -172,39 +177,37 @@ public: if (!configuration.has_callbacks()) return; - jack_set_port_registration_callback( - this->client, - +[](jack_port_id_t p, int r, void* arg) { - auto& self = *(observer_jack*)arg; - if (auto port = jack_port_by_id(self.client, p)) - { - self.on_port_callback(port, r != 0); - } - }, - this); + jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) { + auto& self = *(observer_jack*)arg; + if (auto port = jack_port_by_id(self.client, p)) + { + self.on_port_callback(port, r != 0); + } + }, this); jack_set_port_rename_callback( this->client, +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) { - const auto& self = *static_cast(arg); + const auto& self = *static_cast(arg); - auto port = jack_port_by_id(self.client, p); - if (!port) - return; - }, - this); + auto port = jack_port_by_id(self.client, p); + if (!port) + return; + }, this); } libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; } std::vector get_input_ports() const noexcept override { - return get_ports(this->client, nullptr, JackPortIsOutput); + return get_ports( + this->client, nullptr, port_type, JackPortIsOutput, false); } std::vector get_output_ports() const noexcept override { - return get_ports(this->client, nullptr, JackPortIsInput); + return get_ports( + this->client, nullptr, port_type, JackPortIsInput, false); } ~observer_jack() diff --git a/include/libremidi/backends/jack/shared_handler.hpp b/include/libremidi/backends/jack/shared_handler.hpp index 0ab18d0..b44f37d 100644 --- a/include/libremidi/backends/jack/shared_handler.hpp +++ b/include/libremidi/backends/jack/shared_handler.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi::jack +NAMESPACE_LIBREMIDI::jack { // Create a JACK client which will be shared across objects @@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context client = jack_client_open(v.data(), JackNoStartServer, &status); assert(client); assert(status == 0); - jack_set_process_callback( - client, - +[](jack_nframes_t cnt, void* ctx) -> int { - ((shared_handler*)ctx)->jack_callback(cnt); - return 0; - }, - this); + jack_set_process_callback(client, +[](jack_nframes_t cnt, void* ctx) -> int { + ((shared_handler*)ctx)->jack_callback(cnt); + return 0; + }, this); } virtual void start_processing() override { jack_activate(client); } @@ -73,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) @@ -93,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) @@ -141,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/jack_ump.hpp b/include/libremidi/backends/jack_ump.hpp new file mode 100644 index 0000000..89b6a9e --- /dev/null +++ b/include/libremidi/backends/jack_ump.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +struct backend +{ + using midi_in = midi_in_jack; + using midi_out = midi_out_jack; + using midi_observer = observer_jack; + using midi_in_configuration = jack_ump::input_configuration; + using midi_out_configuration = jack_ump::output_configuration; + using midi_observer_configuration = jack_ump::observer_configuration; + static const constexpr auto API = libremidi::API::JACK_UMP; + static const constexpr std::string_view name = "jack_ump"; + static const constexpr std::string_view display_name = "JACK (UMP)"; + + static inline bool available() noexcept + { +#if LIBREMIDI_WEAKJACK + if (WeakJack::instance().available() != 0) + return false; +#endif + + int major, minor, micro, patch; + jack_get_version(&major, &minor, µ, &patch); + return (major >= 3 && minor >= 1 && micro >= 4); + } +}; +} diff --git a/include/libremidi/backends/jack_ump/config.hpp b/include/libremidi/backends/jack_ump/config.hpp new file mode 100644 index 0000000..ab5fe8b --- /dev/null +++ b/include/libremidi/backends/jack_ump/config.hpp @@ -0,0 +1,33 @@ +#pragma once +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +struct input_configuration +{ + std::string client_name = "libremidi client"; + + jack_client_t* context{}; + std::function set_process_func{}; + std::function clear_process_func{}; +}; + +struct output_configuration +{ + std::string client_name = "libremidi client"; + + jack_client_t* context{}; + std::function set_process_func{}; + std::function clear_process_func{}; + + int32_t ringbuffer_size = 16384; + bool direct = false; +}; + +struct observer_configuration +{ + std::string client_name = "libremidi client"; + jack_client_t* context{}; +}; + +} diff --git a/include/libremidi/backends/jack_ump/midi_in.hpp b/include/libremidi/backends/jack_ump/midi_in.hpp new file mode 100644 index 0000000..6b185b3 --- /dev/null +++ b/include/libremidi/backends/jack_ump/midi_in.hpp @@ -0,0 +1,119 @@ +#pragma once +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +class midi_in_jack final + : public midi2::in_api + , public jack_helpers + , private jack_midi1 + , public error_handler +{ +public: + using midi_api::client_open_; + struct + : libremidi::ump_input_configuration + , jack_ump::input_configuration + { + } configuration; + + explicit midi_in_jack( + libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + client_open_ = stdx::error{}; + } + + ~midi_in_jack() override + { + midi_in_jack::close_port(); + + disconnect(*this); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + stdx::error open_port(const input_port& port, std::string_view portName) override + { + if (auto err + = create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20)); + err != stdx::error{}) + return err; + + if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); + err != 0 && err != EEXIST) + { + libremidi_handle_error( + configuration, + "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port)); + return from_errc(err); + } + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view portName) override + { + return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20)); + } + + stdx::error close_port() override { return do_close_port(); } + + stdx::error set_port_name(std::string_view portName) override + { + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); + } + + timestamp absolute_timestamp() const noexcept override + { + return 1000 * jack_frames_to_time(client, jack_frame_time(client)); + } + + int process(jack_nframes_t nframes) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = true, + .has_samples = true, + }; + void* buff = jack_port_get_buffer(this->port, nframes); + + // Timing + jack_nframes_t current_frames{}; + jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC + jack_time_t next_usecs{}; + float period_usecs{}; + jack_get_cycle_times( + this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs); + + // We have midi events in buffer + uint32_t ev_count = jack_midi_get_event_count(buff); + for (uint32_t j = 0; j < ev_count; j++) + { + jack_midi_event_t event{}; + jack_midi_event_get(&event, buff, j); + const auto to_ns + = [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); }; + + m_processing.on_bytes( + {(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)}, + m_processing.timestamp(to_ns, event.time)); + } + + return 0; + } + + midi2::input_state_machine m_processing{this->configuration}; +}; +} diff --git a/include/libremidi/backends/jack_ump/midi_out.hpp b/include/libremidi/backends/jack_ump/midi_out.hpp new file mode 100644 index 0000000..a6ebaa7 --- /dev/null +++ b/include/libremidi/backends/jack_ump/midi_out.hpp @@ -0,0 +1,184 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +class midi_out_jack + : public midi2::out_api + , public jack_helpers + , private jack_midi1 + , public error_handler +{ +public: + using midi_api::client_open_; + struct + : libremidi::output_configuration + , jack_ump::output_configuration + { + } configuration; + + midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + } + + ~midi_out_jack() override { } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + stdx::error open_port(const output_port& port, std::string_view portName) override + { + if (auto err + = create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20)); + err != stdx::error{}) + return err; + + // Connecting to the output + if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); + err != 0 && err != EEXIST) + { + libremidi_handle_error(configuration, "could not connect to port" + port.port_name); + return from_errc(err); + } + + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view portName) override + { + return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20)); + } + + stdx::error close_port() override { return do_close_port(); } + + stdx::error set_port_name(std::string_view portName) override + { + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); + } +}; + +class midi_out_jack_queued final : public midi_out_jack +{ +public: + midi_out_jack_queued( + libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : midi_out_jack{std::move(conf), std::move(apiconf)} + , m_queue{configuration.ringbuffer_size} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + client_open_ = stdx::error{}; + } + + ~midi_out_jack_queued() override + { + midi_out_jack::close_port(); + + disconnect(*this); + } + + stdx::error send_ump(const uint32_t* message, std::size_t size) override + { + return m_queue.write((unsigned char*)message, size * sizeof(uint32_t)); + } + + int process(jack_nframes_t nframes) + { + void* buff = jack_port_get_buffer(this->port, nframes); + jack_midi_clear_buffer(buff); + + this->m_queue.read(buff); + + return 0; + } + +private: + libremidi::jack_queue m_queue; +}; + +class midi_out_jack_direct final : public midi_out_jack +{ +public: + midi_out_jack_direct( + libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : midi_out_jack{std::move(conf), std::move(apiconf)} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + buffer_size = jack_get_buffer_size(this->client); + client_open_ = stdx::error{}; + } + + ~midi_out_jack_direct() override + { + midi_out_jack::close_port(); + + disconnect(*this); + } + + int process(jack_nframes_t nframes) + { + void* buff = jack_port_get_buffer(this->port, nframes); + jack_midi_clear_buffer(buff); + return 0; + } + + stdx::error send_ump(const uint32_t* message, std::size_t size) override + { + void* buff = jack_port_get_buffer(this->port, buffer_size); + int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t)); + return from_errc(ret); + } + + int convert_timestamp(int64_t user) const noexcept + { + switch (configuration.timestamps) + { + case timestamp_mode::AudioFrame: + return static_cast(user); + + default: + // TODO + return 0; + } + } + + stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override + { + void* buff = jack_port_get_buffer(this->port, buffer_size); + int ret = jack_midi_event_write( + buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t)); + return from_errc(ret); + } + + int buffer_size{}; +}; +} + +NAMESPACE_LIBREMIDI +{ +template <> +inline std::unique_ptr +make(output_configuration&& conf, jack_ump::output_configuration&& api) +{ + if (api.direct) + return std::make_unique(std::move(conf), std::move(api)); + else + return std::make_unique(std::move(conf), std::move(api)); +} +} diff --git a/include/libremidi/backends/jack_ump/observer.hpp b/include/libremidi/backends/jack_ump/observer.hpp new file mode 100644 index 0000000..0db4160 --- /dev/null +++ b/include/libremidi/backends/jack_ump/observer.hpp @@ -0,0 +1,243 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +class observer_jack final + : public observer_api + , private jack_client + , private jack_midi1 // seems like a bug in PipeWire? MIDI 2 flag is set but the MIDI 1 type string is used + , private error_handler +{ +public: + struct + : libremidi::observer_configuration + , jack_ump::observer_configuration + { + } configuration; + + explicit observer_jack( + libremidi::observer_configuration&& conf, jack_ump::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + // Initialize JACK client + if (configuration.context) + { + this->client = configuration.context; + set_callbacks(); + } + else + { + jack_status_t status{}; + this->client + = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); + if (status != jack_status_t{}) + libremidi_handle_error(configuration, std::to_string((int)status)); + + if (this->client != nullptr) + { + set_callbacks(); + + jack_activate(this->client); + } + } + } + + void initial_callback() + { + { + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput); + + if (ports != nullptr) + { + int i = 0; + while (ports[i] != nullptr) + { + const auto port = jack_port_by_name(client, ports[i]); + const auto flags = jack_port_flags(port); + + if (!(flags & 0x20)) // midi 2 check + { + i++; + continue; + } + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + + if (ok) + { + seen_input_ports.insert(ports[i]); + if (this->configuration.input_added && configuration.notify_in_constructor) + this->configuration.input_added( + to_port_info(client, port)); + } + i++; + } + } + + jack_free(ports); + } + + { + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput); + + if (ports != nullptr) + { + int i = 0; + while (ports[i] != nullptr) + { + const auto port = jack_port_by_name(client, ports[i]); + const auto flags = jack_port_flags(port); + + if (!(flags & 0x20)) // midi 2 check + { + i++; + continue; + } + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + + if (ok) + { + seen_output_ports.insert(ports[i]); + if (this->configuration.output_added && configuration.notify_in_constructor) + this->configuration.output_added( + to_port_info(client, port)); + } + i++; + } + } + + jack_free(ports); + } + } + + void on_port_callback(jack_port_t* port, bool reg) + { + auto flags = jack_port_flags(port); + std::string name = jack_port_name(port); + if (reg) + { + std::string_view type = jack_port_type(port); + if (type != port_type) + return; + + if (!(flags & 0x20)) // midi 2 check + return; + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + if (!ok) + return; + + // Note: we keep track of the ports as + // when disconnecting, jack_port_type and jack_port_flags aren't correctly + // set anymore. + + if (flags & JackPortIsOutput) + { + seen_input_ports.insert(name); + if (this->configuration.input_added) + this->configuration.input_added( + to_port_info(client, port)); + } + else if (flags & JackPortIsInput) + { + seen_output_ports.insert(name); + if (this->configuration.output_added) + this->configuration.output_added( + to_port_info(client, port)); + } + } + else + { + if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) + { + if (this->configuration.input_removed) + this->configuration.input_removed( + to_port_info(client, port)); + seen_input_ports.erase(it); + } + if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) + { + if (this->configuration.output_removed) + this->configuration.output_removed( + to_port_info(client, port)); + seen_output_ports.erase(it); + } + } + } + + void set_callbacks() + { + initial_callback(); + + if (!configuration.has_callbacks()) + return; + + jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) { + auto& self = *(observer_jack*)arg; + if (auto port = jack_port_by_id(self.client, p)) + { + self.on_port_callback(port, r != 0); + } + }, this); + + jack_set_port_rename_callback( + this->client, + +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) { + const auto& self = *static_cast(arg); + + auto port = jack_port_by_id(self.client, p); + if (!port) + return; + }, this); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + std::vector get_input_ports() const noexcept override + { + return get_ports( + this->client, nullptr, port_type, JackPortIsOutput, true); + } + + std::vector get_output_ports() const noexcept override + { + return get_ports( + this->client, nullptr, port_type, JackPortIsInput, true); + } + + ~observer_jack() + { + if (client && !configuration.context) + { + // If we own the client, deactivate it + jack_deactivate(this->client); + jack_client_close(this->client); + this->client = nullptr; + } + } + + std::unordered_set seen_input_ports; + std::unordered_set seen_output_ports; +}; +} diff --git a/include/libremidi/backends/jack_ump/shared_handler.hpp b/include/libremidi/backends/jack_ump/shared_handler.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/libremidi/backends/kdmapi.hpp b/include/libremidi/backends/kdmapi.hpp new file mode 100644 index 0000000..46846f9 --- /dev/null +++ b/include/libremidi/backends/kdmapi.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include + +#include + +//*********************************************************************// +// API: OmniMIDI KDMAPI (Keppy's Direct MIDI API) +//*********************************************************************// + +// OmniMIDI is a low-latency MIDI synthesizer driver for Windows. +// KDMAPI provides a direct interface to OmniMIDI, bypassing the +// Windows Multimedia API for lower latency. +// +// Note: KDMAPI is output-only. The midi_in class is a placeholder +// that always fails - use a different backend for MIDI input. +// +// https://github.com/KeppySoftware/OmniMIDI + +namespace libremidi +{ + +struct kdmapi_backend +{ + using midi_in = kdmapi::midi_in; + using midi_out = kdmapi::midi_out; + using midi_observer = kdmapi::observer; + using midi_in_configuration = kdmapi::input_configuration; + using midi_out_configuration = kdmapi::output_configuration; + using midi_observer_configuration = kdmapi::observer_configuration; + static const constexpr auto API = libremidi::API::KDMAPI; + static const constexpr std::string_view name = "kdmapi"; + static const constexpr std::string_view display_name = "OmniMIDI (KDMAPI)"; + + static inline bool available() noexcept + { + return kdmapi::kdmapi_loader::instance().is_available(); + } +}; + +} + diff --git a/include/libremidi/backends/kdmapi/config.hpp b/include/libremidi/backends/kdmapi/config.hpp new file mode 100644 index 0000000..df55793 --- /dev/null +++ b/include/libremidi/backends/kdmapi/config.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +namespace libremidi::kdmapi +{ +// KDMAPI does not support MIDI input +struct input_configuration +{ +}; + +struct output_configuration +{ + // Use the no-buffer variant of SendDirectData for lowest latency + // This bypasses OmniMIDI's internal buffer + bool use_no_buffer = false; +}; + +struct observer_configuration +{ +}; + +} + diff --git a/include/libremidi/backends/kdmapi/helpers.hpp b/include/libremidi/backends/kdmapi/helpers.hpp new file mode 100644 index 0000000..affcf1b --- /dev/null +++ b/include/libremidi/backends/kdmapi/helpers.hpp @@ -0,0 +1,178 @@ +#pragma once +// clang-format off +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN 1 + +#include +#include +// clang-format on + +#include + +#include +#include + +namespace libremidi::kdmapi +{ + +using IsKDMAPIAvailable_t = BOOL(WINAPI*)(); +using InitializeKDMAPIStream_t = BOOL(WINAPI*)(); +using TerminateKDMAPIStream_t = BOOL(WINAPI*)(); +using ResetKDMAPIStream_t = VOID(WINAPI*)(); +using SendDirectData_t = VOID(WINAPI*)(DWORD); +using SendDirectDataNoBuf_t = VOID(WINAPI*)(DWORD); +using SendDirectLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using SendDirectLongDataNoBuf_t = UINT(WINAPI*)(LPSTR, DWORD); +using PrepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using UnprepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using ReturnKDMAPIVer_t = BOOL(WINAPI*)(LPDWORD, LPDWORD, LPDWORD, LPDWORD); + +class kdmapi_loader +{ +public: + static kdmapi_loader& instance() + { + static kdmapi_loader loader; + return loader; + } + + bool is_available() const noexcept { return m_available; } + + HMODULE handle() const noexcept { return m_handle; } + + // KDMAPI function pointers + IsKDMAPIAvailable_t IsKDMAPIAvailable{}; + InitializeKDMAPIStream_t InitializeKDMAPIStream{}; + TerminateKDMAPIStream_t TerminateKDMAPIStream{}; + ResetKDMAPIStream_t ResetKDMAPIStream{}; + SendDirectData_t SendDirectData{}; + SendDirectDataNoBuf_t SendDirectDataNoBuf{}; + SendDirectLongData_t SendDirectLongData{}; + SendDirectLongDataNoBuf_t SendDirectLongDataNoBuf{}; + PrepareLongData_t PrepareLongData{}; + UnprepareLongData_t UnprepareLongData{}; + ReturnKDMAPIVer_t ReturnKDMAPIVer{}; + +private: + kdmapi_loader() + { + // Try to get OmniMIDI if it's already loaded + m_handle = GetModuleHandleW(L"OmniMIDI"); + if (!m_handle) + { + // Try to load it explicitly + m_handle = LoadLibraryW(L"OmniMIDI.dll"); + } + + if (!m_handle) + { + m_available = false; + return; + } + +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + + // Load all function pointers + IsKDMAPIAvailable + = reinterpret_cast(GetProcAddress(m_handle, "IsKDMAPIAvailable")); + InitializeKDMAPIStream = reinterpret_cast( + GetProcAddress(m_handle, "InitializeKDMAPIStream")); + TerminateKDMAPIStream = reinterpret_cast( + GetProcAddress(m_handle, "TerminateKDMAPIStream")); + ResetKDMAPIStream + = reinterpret_cast(GetProcAddress(m_handle, "ResetKDMAPIStream")); + SendDirectData + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectData")); + SendDirectDataNoBuf + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectDataNoBuf")); + SendDirectLongData + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectLongData")); + SendDirectLongDataNoBuf = reinterpret_cast( + GetProcAddress(m_handle, "SendDirectLongDataNoBuf")); + PrepareLongData + = reinterpret_cast(GetProcAddress(m_handle, "PrepareLongData")); + UnprepareLongData + = reinterpret_cast(GetProcAddress(m_handle, "UnprepareLongData")); + ReturnKDMAPIVer + = reinterpret_cast(GetProcAddress(m_handle, "ReturnKDMAPIVer")); + +#if !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif + + // Check if the minimum required functions are available + if (!IsKDMAPIAvailable || !InitializeKDMAPIStream || !TerminateKDMAPIStream || !SendDirectData) + { + m_available = false; + return; + } + + // Call IsKDMAPIAvailable to activate KDMAPI mode in OmniMIDI + m_available = IsKDMAPIAvailable() != FALSE; + } + + ~kdmapi_loader() + { + } + + kdmapi_loader(const kdmapi_loader&) = delete; + kdmapi_loader& operator=(const kdmapi_loader&) = delete; + + HMODULE m_handle{}; + bool m_available{false}; +}; + +class kdmapi_stream_manager +{ +public: + static kdmapi_stream_manager& instance() + { + static kdmapi_stream_manager mgr; + return mgr; + } + + bool acquire() + { + std::lock_guard lock{m_mutex}; + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return false; + + if (m_rc == 0) + { + if (!loader.InitializeKDMAPIStream()) + return false; + } + ++m_rc; + return true; + } + + void release() + { + std::lock_guard lock{m_mutex}; + if (m_rc == 0) + return; + + --m_rc; + if (m_rc == 0) + { + auto& loader = kdmapi_loader::instance(); + if (loader.TerminateKDMAPIStream) + loader.TerminateKDMAPIStream(); + } + } + +private: + kdmapi_stream_manager() = default; + ~kdmapi_stream_manager() = default; + kdmapi_stream_manager(const kdmapi_stream_manager&) = delete; + kdmapi_stream_manager& operator=(const kdmapi_stream_manager&) = delete; + + std::mutex m_mutex; + std::size_t m_rc{0}; +}; + +} diff --git a/include/libremidi/backends/kdmapi/midi_in.hpp b/include/libremidi/backends/kdmapi/midi_in.hpp new file mode 100644 index 0000000..fa8eeaf --- /dev/null +++ b/include/libremidi/backends/kdmapi/midi_in.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include + +namespace libremidi::kdmapi +{ + +using midi_in = libremidi::midi_in_dummy; + +} diff --git a/include/libremidi/backends/kdmapi/midi_out.hpp b/include/libremidi/backends/kdmapi/midi_out.hpp new file mode 100644 index 0000000..5ade417 --- /dev/null +++ b/include/libremidi/backends/kdmapi/midi_out.hpp @@ -0,0 +1,173 @@ +#pragma once +#include +#include +#include +#include + +namespace libremidi::kdmapi +{ + +class midi_out final + : public midi1::out_api + , public error_handler +{ +public: + struct + : libremidi::output_configuration + , kdmapi::output_configuration + { + } configuration; + + midi_out(libremidi::output_configuration&& conf, kdmapi::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + { + libremidi_handle_error( + configuration, "KDMAPI is not available. Is OmniMIDI installed?"); + this->client_open_ = std::errc::no_such_device; + return; + } + + this->client_open_ = stdx::error{}; + } + + ~midi_out() override + { + midi_out::close_port(); + this->client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; } + + stdx::error open_port(const output_port& port, std::string_view) override + { + if (port.api != libremidi::API::KDMAPI) + { + libremidi_handle_error(configuration, "port is not a KDMAPI port"); + return std::errc::invalid_argument; + } + + return do_open(); + } + + stdx::error do_open() + { + auto& mgr = kdmapi_stream_manager::instance(); + if (!mgr.acquire()) + { + libremidi_handle_error(configuration, "failed to initialize KDMAPI stream"); + return std::errc::io_error; + } + + connected_ = true; + return stdx::error{}; + } + + stdx::error close_port() override + { + if (connected_) + { + auto& mgr = kdmapi_stream_manager::instance(); + mgr.release(); + connected_ = false; + } + return stdx::error{}; + } + + stdx::error send_message(const unsigned char* message, size_t size) override + { + if (!connected_) + return std::errc::not_connected; + + if (size == 0) + { + libremidi_handle_warning(configuration, "message argument is empty!"); + return std::errc::invalid_argument; + } + + auto& loader = kdmapi_loader::instance(); + + if (message[0] == 0xF0) + { + // SysEx message + return send_sysex(message, size); + } + else + { + // Channel or system message + if (size > 3) + { + libremidi_handle_warning( + configuration, "message size is greater than 3 bytes (and not sysex)!"); + return std::errc::message_size; + } + + // Pack MIDI bytes into DWORD + DWORD packet = 0; + std::copy_n(message, size, reinterpret_cast(&packet)); + + // Send the message + if (configuration.use_no_buffer && loader.SendDirectDataNoBuf) + loader.SendDirectDataNoBuf(packet); + else + loader.SendDirectData(packet); + } + + return stdx::error{}; + } + +private: + stdx::error send_sysex(const unsigned char* message, size_t size) + { + auto& loader = kdmapi_loader::instance(); + + if (configuration.use_no_buffer && loader.SendDirectLongDataNoBuf) + { + // Use the no-buffer variant - it takes raw data directly + loader.SendDirectLongDataNoBuf( + const_cast(reinterpret_cast(message)), static_cast(size)); + return stdx::error{}; + } + + // Standard path with MIDIHDR + buffer_.assign(message, message + size); + + MIDIHDR sysex{}; + sysex.lpData = reinterpret_cast(buffer_.data()); + sysex.dwBufferLength = static_cast(size); + sysex.dwFlags = 0; + + if (loader.PrepareLongData) + { + UINT result = loader.PrepareLongData(&sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) + { + libremidi_handle_error(configuration, "error preparing sysex header"); + return std::errc::io_error; + } + } + + if (loader.SendDirectLongData) + { + UINT result = loader.SendDirectLongData(&sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) + { + libremidi_handle_error(configuration, "error sending sysex message"); + // Still try to unprepare + } + } + + if (loader.UnprepareLongData) + { + loader.UnprepareLongData(&sysex, sizeof(MIDIHDR)); + } + + return stdx::error{}; + } + + std::vector buffer_; +}; + +} diff --git a/include/libremidi/backends/kdmapi/observer.hpp b/include/libremidi/backends/kdmapi/observer.hpp new file mode 100644 index 0000000..19b5df2 --- /dev/null +++ b/include/libremidi/backends/kdmapi/observer.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include + +namespace libremidi::kdmapi +{ + +class observer final : public observer_api +{ +public: + struct + : libremidi::observer_configuration + , kdmapi::observer_configuration + { + } configuration; + + explicit observer( + libremidi::observer_configuration&& conf, kdmapi::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (!configuration.has_callbacks()) + return; + + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return; + + // KDMAPI provides a single virtual output port + if (configuration.notify_in_constructor) + { + if (configuration.output_added) + { + auto ports = get_output_ports(); + for (const auto& port : ports) + configuration.output_added(port); + } + } + } + + ~observer() = default; + + libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; } + + std::vector get_input_ports() const noexcept override + { + // KDMAPI does not provide input ports + return {}; + } + + std::vector get_output_ports() const noexcept override + { + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return {}; + + // Get KDMAPI version for display name + std::string display_name = "OmniMIDI"; + if (loader.ReturnKDMAPIVer) + { + DWORD major{}, minor{}, build{}, rev{}; + if (loader.ReturnKDMAPIVer(&major, &minor, &build, &rev)) + { + display_name += " (KDMAPI "; + display_name += std::to_string(major); + display_name += "."; + display_name += std::to_string(minor); + display_name += "."; + display_name += std::to_string(build); + display_name += ")"; + } + } + + return {libremidi::output_port{ + {.api = libremidi::API::KDMAPI, + .client = 0, + .port = 0, + .manufacturer = "KeppySoftware", + .device_name = "OmniMIDI", + .port_name = "OmniMIDI", + .display_name = std::move(display_name)}, + }}; + } +}; + +} diff --git a/include/libremidi/backends/keyboard.hpp b/include/libremidi/backends/keyboard.hpp index 4674341..650b510 100644 --- a/include/libremidi/backends/keyboard.hpp +++ b/include/libremidi/backends/keyboard.hpp @@ -2,7 +2,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { struct kbd_backend { @@ -13,8 +15,8 @@ struct kbd_backend using midi_out_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration; static const constexpr auto API = libremidi::API::KEYBOARD; - static const constexpr auto name = "keyboard"; - static const constexpr auto display_name = "Computer keyboard"; + static const constexpr std::string_view name = "keyboard"; + static const constexpr std::string_view display_name = "Computer keyboard"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/keyboard/config.hpp b/include/libremidi/backends/keyboard/config.hpp index 109122e..c469abf 100644 --- a/include/libremidi/backends/keyboard/config.hpp +++ b/include/libremidi/backends/keyboard/config.hpp @@ -4,8 +4,130 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { +enum kbd_event +{ + NOTE_0 = 0x0, // C + VEL_0 = NOTE_0 + 128, // Set velocity to 0 + OCT_0 = VEL_0 + 128, // Set octave to 0 + OCTAVE_PLUS = OCT_0 + 128, + OCTAVE_MINUS, + VELOCITY_PLUS, + VELOCITY_MINUS, +}; + +// Default map +// +// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------, +// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- | +// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----| +// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| | +// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| | +// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | | +// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----| +// | -^ | | O-| O+| V-| V+| | | | | | | ----^ | +// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------| +// | ctrl | | alt | |altgr | | ctrl | +// '------' '-----'--------------------------'------' '------' + +// clang-format off + +// actually based on macOS virtual codes as it seems +// impossible to get raw scan codes. +// https://eastmanreference.com/complete-list-of-applescript-key-codes +LIBREMIDI_STATIC const std::map& scancode_map_macos(){ + static const std::map ret{ + { 0, NOTE_0}, // C0 + { 13, NOTE_0 + 1}, + { 1, NOTE_0 + 2}, + { 14, NOTE_0 + 3}, + { 2, NOTE_0 + 4}, + { 3, NOTE_0 + 5}, + { 17, NOTE_0 + 6}, + { 5, NOTE_0 + 7}, + { 16, NOTE_0 + 8}, + { 4, NOTE_0 + 9}, + { 32, NOTE_0 + 10}, + { 38, NOTE_0 + 11}, + { 40, NOTE_0 + 12}, // C1 + { 31, NOTE_0 + 13}, + { 37, NOTE_0 + 14}, + { 35, NOTE_0 + 15}, + { 41, NOTE_0 + 16}, + { 39, NOTE_0 + 17}, + { 30, NOTE_0 + 18}, + { 42, NOTE_0 + 19}, + + { 50, VEL_0 + int(0)}, + { 18, VEL_0 + int(1 * 127 / 12)}, + { 19, VEL_0 + int(2 * 127 / 12)}, + { 20, VEL_0 + int(3 * 127 / 12)}, + { 21, VEL_0 + int(4 * 127 / 12)}, + { 23, VEL_0 + int(5 * 127 / 12)}, + { 22, VEL_0 + int(6 * 127 / 12)}, + { 26, VEL_0 + int(7 * 127 / 12)}, + { 28, VEL_0 + int(8 * 127 / 12)}, + { 25, VEL_0 + int(9 * 127 / 12)}, + { 29, VEL_0 + int(10 * 127 / 12)}, + { 27, VEL_0 + int(11 * 127 / 12)}, + { 24, VEL_0 + int(127)}, + + { 6, OCTAVE_MINUS }, + { 7, OCTAVE_PLUS }, + { 8, VELOCITY_MINUS }, + { 9, VELOCITY_PLUS}, + }; + return ret; +} + +// Use: https://kbdlayout.info/KBDUSX/scancodes +// Also valid for windows +LIBREMIDI_STATIC const std::map& scancode_map_linux() { + static const std::map ret{ + { 0x1E, NOTE_0}, // C0 + { 0x11, NOTE_0 + 1}, + { 0x1F, NOTE_0 + 2}, + { 0x12, NOTE_0 + 3}, + { 0x20, NOTE_0 + 4}, + { 0x21, NOTE_0 + 5}, + { 0x14, NOTE_0 + 6}, + { 0x22, NOTE_0 + 7}, + { 0x15, NOTE_0 + 8}, + { 0x23, NOTE_0 + 9}, + { 0x16, NOTE_0 + 10}, + { 0x24, NOTE_0 + 11}, + { 0x25, NOTE_0 + 12}, // C1 + { 0x18, NOTE_0 + 13}, + { 0x26, NOTE_0 + 14}, + { 0x19, NOTE_0 + 15}, + { 0x27, NOTE_0 + 16}, + { 0x28, NOTE_0 + 17}, + { 0x1B, NOTE_0 + 18}, + { 0x2B, NOTE_0 + 19}, + + { 0x29, VEL_0 + int(0)}, + { 0x02, VEL_0 + int(1 * 127 / 12)}, + { 0x03, VEL_0 + int(2 * 127 / 12)}, + { 0x04, VEL_0 + int(3 * 127 / 12)}, + { 0x05, VEL_0 + int(4 * 127 / 12)}, + { 0x06, VEL_0 + int(5 * 127 / 12)}, + { 0x07, VEL_0 + int(6 * 127 / 12)}, + { 0x08, VEL_0 + int(7 * 127 / 12)}, + { 0x09, VEL_0 + int(8 * 127 / 12)}, + { 0x0A, VEL_0 + int(9 * 127 / 12)}, + { 0x0B, VEL_0 + int(10 * 127 / 12)}, + { 0x0C, VEL_0 + int(11 * 127 / 12)}, + { 0x0D, VEL_0 + int(127)}, + + { 0x2C, OCTAVE_MINUS }, + { 0x2D, OCTAVE_PLUS }, + { 0x2E, VELOCITY_MINUS }, + { 0x2F, VELOCITY_PLUS}, + }; + return ret; +} +// clang-format on /** * Used to set up keyboard input. * Your app should pass a function that will give you a callback @@ -13,80 +135,20 @@ namespace libremidi */ struct kbd_input_configuration { + // Note that on Linux with X11 scancodes should be offset by 8 (an X11 legacy quirk) + // ex.: pass QKeyEvent::nativeScanCode() - 8 to this API using scancode_callback = std::function; + // First argument is on key press, second on key release std::function set_input_scancode_callbacks - = [](scancode_callback, scancode_callback) {}; + = [](const scancode_callback&, const scancode_callback&) { }; - enum kbd_event - { - NOTE_0 = 0x0, // C - VEL_0 = NOTE_0 + 128, // Set velocity to 0 - OCT_0 = VEL_0 + 128, // Set octave to 0 - OCTAVE_PLUS = OCT_0 + 128, - OCTAVE_MINUS, - VELOCITY_PLUS, - VELOCITY_MINUS, - }; - - // Default map - // - // ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------, - // | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- | - // |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----| - // | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| | - // |-----',--',--',--',--',--',--',--',--',--',--',--',--'| | - // | Caps | C | D | E | F | G | A | B | C | D | E | F | G | | - // |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----| - // | -^ | | O-| O+| V-| V+| | | | | | | ----^ | - // |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------| - // | ctrl | | alt | |altgr | | ctrl | - // '------' '-----'--------------------------'------' '------' - - // clang-format off - // Use: https://kbdlayout.info/KBDUSX/scancodes - std::map scancode_map{ - { 0x1E, NOTE_0}, // C0 - { 0x11, NOTE_0 + 1}, - { 0x1F, NOTE_0 + 2}, - { 0x12, NOTE_0 + 3}, - { 0x20, NOTE_0 + 4}, - { 0x21, NOTE_0 + 5}, - { 0x14, NOTE_0 + 6}, - { 0x22, NOTE_0 + 7}, - { 0x15, NOTE_0 + 8}, - { 0x23, NOTE_0 + 9}, - { 0x16, NOTE_0 + 10}, - { 0x24, NOTE_0 + 11}, - { 0x25, NOTE_0 + 12}, // C1 - { 0x18, NOTE_0 + 13}, - { 0x26, NOTE_0 + 14}, - { 0x19, NOTE_0 + 15}, - { 0x27, NOTE_0 + 16}, - { 0x28, NOTE_0 + 17}, - { 0x1B, NOTE_0 + 18}, - { 0x2B, NOTE_0 + 19}, - - { 0x29, VEL_0 + int(0)}, - { 0x02, VEL_0 + int(1 * 127 / 12)}, - { 0x03, VEL_0 + int(2 * 127 / 12)}, - { 0x04, VEL_0 + int(3 * 127 / 12)}, - { 0x05, VEL_0 + int(4 * 127 / 12)}, - { 0x06, VEL_0 + int(5 * 127 / 12)}, - { 0x07, VEL_0 + int(6 * 127 / 12)}, - { 0x08, VEL_0 + int(7 * 127 / 12)}, - { 0x09, VEL_0 + int(8 * 127 / 12)}, - { 0x0A, VEL_0 + int(9 * 127 / 12)}, - { 0x0B, VEL_0 + int(10 * 127 / 12)}, - { 0x0C, VEL_0 + int(11 * 127 / 12)}, - { 0x0D, VEL_0 + int(127)}, - - { 0x2C, OCTAVE_MINUS }, - { 0x2D, OCTAVE_PLUS }, - { 0x2E, VELOCITY_MINUS }, - { 0x2F, VELOCITY_PLUS}, - }; - // clang-format on + std::map scancode_map +#if defined(__APPLE__) + = scancode_map_macos() +#else + = scancode_map_linux() +#endif + ; }; - } diff --git a/include/libremidi/backends/keyboard/midi_in.hpp b/include/libremidi/backends/keyboard/midi_in.hpp index 5b11124..674ff38 100644 --- a/include/libremidi/backends/keyboard/midi_in.hpp +++ b/include/libremidi/backends/keyboard/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_kbd final : public midi1::in_api @@ -22,22 +22,22 @@ public: explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { - configuration.set_input_scancode_callbacks( - [this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); }); } ~midi_in_kbd() override { } libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; } - stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; } + stdx::error open_port(const input_port&, std::string_view) override + { + configuration.set_input_scancode_callbacks( + [this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); }); - stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; } + return stdx::error{}; + } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); @@ -45,7 +45,7 @@ public: void on_keypress(int scancode) { - using kevent = kbd_input_configuration::kbd_event; + using kevent = libremidi::kbd_event; auto it = configuration.scancode_map.find(scancode); if (it == configuration.scancode_map.end()) @@ -88,7 +88,7 @@ public: void on_keyrelease(int scancode) { - using kevent = kbd_input_configuration::kbd_event; + using kevent = libremidi::kbd_event; auto it = configuration.scancode_map.find(scancode); if (it == configuration.scancode_map.end()) diff --git a/include/libremidi/backends/linux/alsa.hpp b/include/libremidi/backends/linux/alsa.hpp index 2820d5e..2846ea8 100644 --- a/include/libremidi/backends/linux/alsa.hpp +++ b/include/libremidi/backends/linux/alsa.hpp @@ -2,7 +2,7 @@ #include -#include +#include // IWYU pragma: export #if defined(SND_LIB_VERSION) #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6) @@ -13,9 +13,13 @@ #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10) #define LIBREMIDI_ALSA_HAS_UMP 1 #endif + + #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 14) + #define LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS 1 + #endif #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct libasound @@ -241,6 +245,7 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, client_id) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name) + LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_card) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, close) @@ -277,6 +282,7 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc) + LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real) @@ -291,6 +297,12 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo) LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port) + +#if LIBREMIDI_ALSA_HAS_UMP + LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version) + LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_endpoint_info) + LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_block_info) +#endif } bool available{true}; @@ -298,6 +310,7 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, client_id) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name) + LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_card) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, close) @@ -334,6 +347,7 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc) + LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real) @@ -350,6 +364,12 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port) +#if LIBREMIDI_ALSA_HAS_UMP + LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version) + LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_endpoint_info) + LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_block_info) +#endif + #if LIBREMIDI_ALSA_HAS_UMP struct ump_t { @@ -360,7 +380,6 @@ struct libasound available = false; return; } - LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct) @@ -368,10 +387,10 @@ struct libasound bool available{true}; - LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct) + } ump; #endif } seq{library}; @@ -387,39 +406,98 @@ struct libasound return; } - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name) - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof) - LIBREMIDI_SYMBOL_INIT(snd_ump, close) - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name) - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof) + // Core UMP functions LIBREMIDI_SYMBOL_INIT(snd_ump, open) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents) + LIBREMIDI_SYMBOL_INIT(snd_ump, close) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_INIT(snd_ump, read) LIBREMIDI_SYMBOL_INIT(snd_ump, tread) LIBREMIDI_SYMBOL_INIT(snd_ump, write) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents) + + // Endpoint info functions + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_card) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_device) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_flags) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol_caps) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_num_blocks) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_version) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_manufacturer_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_family_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_model_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_sw_revision) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_product_id) + + // Block info functions + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_set_block_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_block_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_active) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_flags) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_direction) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_first_group) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_num_groups) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_midi_ci_version) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_sysex8_streams) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_ui_hint) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name) } bool available{true}; - LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name) - LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof) - LIBREMIDI_SYMBOL_DEF(snd_ump, close) - LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name) - LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof) + + // Core UMP functions LIBREMIDI_SYMBOL_DEF(snd_ump, open) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents) + LIBREMIDI_SYMBOL_DEF(snd_ump, close) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_DEF(snd_ump, read) LIBREMIDI_SYMBOL_DEF(snd_ump, tread) LIBREMIDI_SYMBOL_DEF(snd_ump, write) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents) + + // Endpoint info functions + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_card) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_device) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_flags) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol_caps) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_num_blocks) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_version) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_manufacturer_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_family_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_model_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_sw_revision) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_product_id) + + // Block info functions + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_set_block_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_block_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_active) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_flags) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_direction) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_first_group) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_num_groups) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_midi_ci_version) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_sysex8_streams) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_ui_hint) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name) } ump{library}; #endif }; @@ -447,6 +525,8 @@ struct libasound #define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info) #undef snd_seq_port_info_alloca #define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info) +#undef snd_seq_port_subscribe_alloca +#define snd_seq_port_subscribe_alloca(ptr) snd_dylib_alloca(ptr, seq, port_subscribe) #undef snd_seq_queue_tempo_alloca #define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo) diff --git a/include/libremidi/backends/linux/dylib_loader.hpp b/include/libremidi/backends/linux/dylib_loader.hpp index 808e197..80c2490 100644 --- a/include/libremidi/backends/linux/dylib_loader.hpp +++ b/include/libremidi/backends/linux/dylib_loader.hpp @@ -1,10 +1,11 @@ #pragma once +#include #if __has_include() #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class dylib_loader { diff --git a/include/libremidi/backends/linux/helpers.hpp b/include/libremidi/backends/linux/helpers.hpp index b86b461..ccce4c9 100644 --- a/include/libremidi/backends/linux/helpers.hpp +++ b/include/libremidi/backends/linux/helpers.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct eventfd_notifier { diff --git a/include/libremidi/backends/linux/pipewire.hpp b/include/libremidi/backends/linux/pipewire.hpp index c14bd39..171e20c 100644 --- a/include/libremidi/backends/linux/pipewire.hpp +++ b/include/libremidi/backends/linux/pipewire.hpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { #pragma GCC diagnostic push @@ -12,6 +12,8 @@ namespace libremidi class libpipewire { public: + decltype(&::pw_get_library_version) get_library_version{}; + decltype(&::pw_init) init{}; decltype(&::pw_deinit) deinit{}; @@ -67,12 +69,12 @@ public: bool available{true}; private: - dylib_loader library; + dylib_loader m_library; libpipewire() - : library("libpipewire-0.3.so.0") + : m_library("libpipewire-0.3.so.0") { - if (!library) + if (!m_library) { available = false; return; @@ -81,52 +83,59 @@ private: // in terms of regex: // decltype\(&::([a-z_]+)\) [a-z_]+{}; // \1 = library.symbol("\1"); - init = library.symbol("pw_init"); - deinit = library.symbol("pw_deinit"); + get_library_version + = m_library.symbol("pw_get_library_version"); - context_new = library.symbol("pw_context_new"); - context_connect = library.symbol("pw_context_connect"); - context_destroy = library.symbol("pw_context_destroy"); + init = m_library.symbol("pw_init"); + deinit = m_library.symbol("pw_deinit"); - core_disconnect = library.symbol("pw_core_disconnect"); + context_new = m_library.symbol("pw_context_new"); + context_connect = m_library.symbol("pw_context_connect"); + context_destroy = m_library.symbol("pw_context_destroy"); + + core_disconnect = m_library.symbol("pw_core_disconnect"); proxy_add_listener - = library.symbol("pw_proxy_add_listener"); - proxy_destroy = library.symbol("pw_proxy_destroy"); + = m_library.symbol("pw_proxy_add_listener"); + proxy_destroy = m_library.symbol("pw_proxy_destroy"); - main_loop_new = library.symbol("pw_main_loop_new"); - main_loop_destroy = library.symbol("pw_main_loop_destroy"); - main_loop_quit = library.symbol("pw_main_loop_quit"); - main_loop_run = library.symbol("pw_main_loop_run"); + main_loop_new = m_library.symbol("pw_main_loop_new"); + main_loop_destroy + = m_library.symbol("pw_main_loop_destroy"); + main_loop_quit = m_library.symbol("pw_main_loop_quit"); + main_loop_run = m_library.symbol("pw_main_loop_run"); main_loop_get_loop - = library.symbol("pw_main_loop_get_loop"); + = m_library.symbol("pw_main_loop_get_loop"); - properties_new = library.symbol("pw_properties_new"); - properties_free = library.symbol("pw_properties_free"); - properties_get = library.symbol("pw_properties_get"); + properties_new = m_library.symbol("pw_properties_new"); + properties_free = m_library.symbol("pw_properties_free"); + properties_get = m_library.symbol("pw_properties_get"); - filter_new_simple = library.symbol("pw_filter_new_simple"); + filter_new_simple + = m_library.symbol("pw_filter_new_simple"); filter_get_node_id - = library.symbol("pw_filter_get_node_id"); + = m_library.symbol("pw_filter_get_node_id"); filter_get_properties - = library.symbol("pw_filter_get_properties"); - filter_add_port = library.symbol("pw_filter_add_port"); + = m_library.symbol("pw_filter_get_properties"); + filter_add_port = m_library.symbol("pw_filter_add_port"); filter_remove_port - = library.symbol("pw_filter_remove_port"); - filter_update_properties - = library.symbol("pw_filter_update_properties"); + = m_library.symbol("pw_filter_remove_port"); + filter_update_properties = m_library.symbol( + "pw_filter_update_properties"); filter_update_params - = library.symbol("pw_filter_update_params"); - filter_get_time = library.symbol("pw_filter_get_time"); - filter_destroy = library.symbol("pw_filter_destroy"); - filter_connect = library.symbol("pw_filter_connect"); + = m_library.symbol("pw_filter_update_params"); + filter_get_time = m_library.symbol("pw_filter_get_time"); + filter_destroy = m_library.symbol("pw_filter_destroy"); + filter_connect = m_library.symbol("pw_filter_connect"); filter_get_dsp_buffer - = library.symbol("pw_filter_get_dsp_buffer"); + = m_library.symbol("pw_filter_get_dsp_buffer"); filter_dequeue_buffer - = library.symbol("pw_filter_dequeue_buffer"); + = m_library.symbol("pw_filter_dequeue_buffer"); filter_queue_buffer - = library.symbol("pw_filter_queue_buffer"); - filter_flush = library.symbol("pw_filter_flush"); + = m_library.symbol("pw_filter_queue_buffer"); + filter_flush = m_library.symbol("pw_filter_flush"); + + assert(get_library_version); assert(init); assert(deinit); diff --git a/include/libremidi/backends/linux/udev.hpp b/include/libremidi/backends/linux/udev.hpp index 48e26cb..ff33e8d 100644 --- a/include/libremidi/backends/linux/udev.hpp +++ b/include/libremidi/backends/linux/udev.hpp @@ -1,12 +1,14 @@ #pragma once #include #include +#include #include #include +#include -namespace libremidi +NAMESPACE_LIBREMIDI { struct libudev @@ -25,12 +27,28 @@ struct libudev LIBREMIDI_SYMBOL_INIT(udev, device_get_action) LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem) + LIBREMIDI_SYMBOL_INIT(udev, device_get_property_value) + LIBREMIDI_SYMBOL_INIT(udev, device_new_from_syspath) + LIBREMIDI_SYMBOL_INIT(udev, device_get_parent_with_subsystem_devtype) LIBREMIDI_SYMBOL_INIT(udev, device_unref) + LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd) LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device) LIBREMIDI_SYMBOL_INIT(udev, monitor_unref) + + LIBREMIDI_SYMBOL_INIT(udev, enumerate_new) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_unref) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_is_initialized) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_subsystem) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_sysname) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_scan_devices) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_get_list_entry) + + LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_name) + LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_next) + LIBREMIDI_SYMBOL_INIT2(udev, new, create) LIBREMIDI_SYMBOL_INIT(udev, unref) } @@ -46,12 +64,28 @@ struct libudev LIBREMIDI_SYMBOL_DEF(udev, device_get_action) LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem) + LIBREMIDI_SYMBOL_DEF(udev, device_new_from_syspath) + LIBREMIDI_SYMBOL_DEF(udev, device_get_property_value) + LIBREMIDI_SYMBOL_DEF(udev, device_get_parent_with_subsystem_devtype) LIBREMIDI_SYMBOL_DEF(udev, device_unref) + LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd) LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device) LIBREMIDI_SYMBOL_DEF(udev, monitor_unref) + + LIBREMIDI_SYMBOL_DEF(udev, enumerate_new) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_unref) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_is_initialized) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_subsystem) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_sysname) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_scan_devices) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_get_list_entry) + + LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_name) + LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_next) + LIBREMIDI_SYMBOL_DEF2(udev, new, create) LIBREMIDI_SYMBOL_DEF(udev, unref) }; @@ -89,4 +123,107 @@ struct udev_helper udev_monitor* monitor{}; }; +struct udev_enumerator +{ + explicit udev_enumerator(const udev_helper& h) + : helper{h} + { + auto& udev = helper.udev; + auto& instance = helper.instance; + enumerator = udev.enumerate_new(instance); + assert(enumerator); + } + + ~udev_enumerator() + { + auto& udev = helper.udev; + udev.enumerate_unref(enumerator); + } + + const udev_helper& helper; + udev_enumerate* enumerator{}; + operator udev_enumerate*() const noexcept { return enumerator; } +}; + +struct udev_soundcard_info +{ + container_identifier container; + device_identifier path; + transport_type type{}; + std::string vendor; + std::string product; + std::string serial; +}; + +inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card) +{ + auto& udev = helper.udev; + udev_enumerator e{helper}; + udev.enumerate_add_match_subsystem(e, "sound"); + udev.enumerate_add_match_sysname(e, ("controlC" + std::to_string(card)).c_str()); + if (udev.enumerate_scan_devices(e) != 0) + return {}; + + for (auto entry = udev.enumerate_get_list_entry(e); entry; + entry = udev.list_entry_get_next(entry)) + { + auto path = udev.list_entry_get_name(entry); + if (!path) + continue; + + auto dev = udev.device_new_from_syspath(helper.instance, path); + if (!dev) + continue; + + const auto usb_device + = udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"); + const auto pci_device = udev.device_get_parent_with_subsystem_devtype(dev, "pci", nullptr); + + transport_type type = transport_type::hardware; + if (usb_device) + type = (transport_type)(type | transport_type::usb); + else if (pci_device) + type = (transport_type)(type | transport_type::pci); + + auto id_path = udev.device_get_property_value(dev, "ID_PATH"); + if (!id_path) + id_path = ""; + + auto read_property = [&](const char* property) { + auto res = udev.device_get_property_value(dev, property); + if (!res) + res = ""; + if (strlen(res) == 0) + { + if (usb_device) + { + res = udev.device_get_property_value(usb_device, property); + if (!res) + res = ""; + } + else if (pci_device) + { + res = udev.device_get_property_value(pci_device, property); + if (!res) + res = ""; + } + } + return res; + }; + + auto id_vendor = read_property("ID_VENDOR_FROM_DATABASE"); + auto id_product = read_property("ID_MODEL_FROM_DATABASE"); + auto id_serial = read_property("ID_SERIAL"); + auto ret = udev_soundcard_info{ + .container = id_path, + .path = path, + .type = type, + .vendor = id_vendor, + .product = id_product, + .serial = id_serial}; + udev.device_unref(dev); + return ret; + } + return {}; +} } diff --git a/include/libremidi/backends/net/config.hpp b/include/libremidi/backends/net/config.hpp index 8365b50..f441557 100644 --- a/include/libremidi/backends/net/config.hpp +++ b/include/libremidi/backends/net/config.hpp @@ -2,12 +2,15 @@ #include #include + +#if !defined(BOOST_ASIO_IO_CONTEXT_HPP) namespace boost::asio { struct io_context; } +#endif -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { enum class protocol { @@ -46,7 +49,7 @@ struct dgram_observer_configuration } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { enum class protocol { diff --git a/include/libremidi/backends/net/helpers.hpp b/include/libremidi/backends/net/helpers.hpp index 54d4879..ca1251b 100644 --- a/include/libremidi/backends/net/helpers.hpp +++ b/include/libremidi/backends/net/helpers.hpp @@ -23,7 +23,7 @@ public: string_ref message(const error& e) const noexcept override { auto err = e.m_value; - + } [[noreturn]] void throw_exception(const error& e) const override @@ -35,38 +35,41 @@ public: */ } -namespace libremidi +NAMESPACE_LIBREMIDI { template struct optionally_owned { public: explicit optionally_owned(T* maybe_existing) - : storage{.ref = maybe_existing} + : m_storage{.ref = maybe_existing} { - if (storage.ref) + if (m_storage.ref) { - ownership = unowned; + m_ownership = unowned; return; } else { - std::destroy_at(&storage.ref); - std::construct_at(reinterpret_cast(storage.object)); - ownership = owned; + std::destroy_at(&m_storage.ref); + std::construct_at(reinterpret_cast(m_storage.object)); + m_ownership = owned; } } ~optionally_owned() { if (is_owned()) - std::destroy_at(reinterpret_cast(storage.object)); + std::destroy_at(reinterpret_cast(m_storage.object)); } - T& get() noexcept { return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); } + T& get() noexcept + { + return *(is_owned() ? reinterpret_cast(&m_storage.object) : m_storage.ref); + } const T& get() const noexcept { - return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); + return *(is_owned() ? reinterpret_cast(&m_storage.object) : m_storage.ref); } optionally_owned(const optionally_owned&) = delete; @@ -74,18 +77,18 @@ public: optionally_owned& operator=(const optionally_owned&) = delete; optionally_owned& operator=(optionally_owned&&) noexcept = delete; - bool is_owned() const noexcept { return ownership == owned; } + bool is_owned() const noexcept { return m_ownership == owned; } private: union { alignas(T) unsigned char object[sizeof(T)]; T* ref; - } storage; + } m_storage; enum { owned, unowned - } ownership{}; + } m_ownership{}; }; } diff --git a/include/libremidi/backends/net/midi_in.hpp b/include/libremidi/backends/net/midi_in.hpp index abf5c67..fb1b03a 100644 --- a/include/libremidi/backends/net/midi_in.hpp +++ b/include/libremidi/backends/net/midi_in.hpp @@ -4,7 +4,8 @@ #include #include -namespace libremidi +#include +NAMESPACE_LIBREMIDI { template struct osc_parser @@ -114,7 +115,7 @@ struct osc_parser } -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { struct osc_parser_midi1 @@ -219,8 +220,6 @@ public: return {}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); @@ -265,7 +264,7 @@ public: }; } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { struct osc_parser_midi2 { @@ -394,8 +393,6 @@ public: return {}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); diff --git a/include/libremidi/backends/net/midi_out.hpp b/include/libremidi/backends/net/midi_out.hpp index c77f2f1..ccb4e1e 100644 --- a/include/libremidi/backends/net/midi_out.hpp +++ b/include/libremidi/backends/net/midi_out.hpp @@ -8,7 +8,7 @@ #include #include -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { struct osc_midi1_packet @@ -146,7 +146,7 @@ public: } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { struct osc_midi2_packet { diff --git a/include/libremidi/backends/net/observer.hpp b/include/libremidi/backends/net/observer.hpp index d2b5516..30320b5 100644 --- a/include/libremidi/backends/net/observer.hpp +++ b/include/libremidi/backends/net/observer.hpp @@ -1,13 +1,13 @@ #pragma once #include -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { using net_observer_configuration = libremidi::dummy_configuration; using observer = libremidi::observer_dummy; } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { using net_observer_configuration = libremidi::dummy_configuration; using observer = libremidi::observer_dummy; diff --git a/include/libremidi/backends/network.hpp b/include/libremidi/backends/network.hpp index 42930e5..f393a99 100644 --- a/include/libremidi/backends/network.hpp +++ b/include/libremidi/backends/network.hpp @@ -5,7 +5,9 @@ #include #include -namespace libremidi::net +#include + +NAMESPACE_LIBREMIDI::net { struct backend { @@ -16,8 +18,8 @@ struct backend using midi_out_configuration = libremidi::net::dgram_output_configuration; using midi_observer_configuration = libremidi::net::net_observer_configuration; static const constexpr auto API = libremidi::API::NETWORK; - static const constexpr auto name = "network"; - static const constexpr auto display_name = "Network"; + static const constexpr std::string_view name = "network"; + static const constexpr std::string_view display_name = "Network"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/network_ump.hpp b/include/libremidi/backends/network_ump.hpp index 7b12562..90842ba 100644 --- a/include/libremidi/backends/network_ump.hpp +++ b/include/libremidi/backends/network_ump.hpp @@ -4,7 +4,9 @@ #include #include -namespace libremidi::net_ump +#include + +NAMESPACE_LIBREMIDI::net_ump { struct backend { @@ -15,8 +17,8 @@ struct backend using midi_out_configuration = libremidi::net_ump::dgram_output_configuration; using midi_observer_configuration = libremidi::net_ump::net_observer_configuration; static const constexpr auto API = libremidi::API::NETWORK_UMP; - static const constexpr auto name = "network (UMP)"; - static const constexpr auto display_name = "Network (UMP)"; + static const constexpr std::string_view name = "network (UMP)"; + static const constexpr std::string_view display_name = "Network (UMP)"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/pipewire.hpp b/include/libremidi/backends/pipewire.hpp index f3a9a96..914a44d 100644 --- a/include/libremidi/backends/pipewire.hpp +++ b/include/libremidi/backends/pipewire.hpp @@ -7,7 +7,9 @@ #include #include -namespace libremidi::pipewire +#include + +NAMESPACE_LIBREMIDI::pipewire { struct backend { @@ -18,8 +20,8 @@ struct backend using midi_out_configuration = pipewire_output_configuration; using midi_observer_configuration = pipewire_observer_configuration; static const constexpr auto API = libremidi::API::PIPEWIRE; - static const constexpr auto name = "pipewire"; - static const constexpr auto display_name = "PipeWire"; + static const constexpr std::string_view name = "pipewire"; + static const constexpr std::string_view display_name = "PipeWire"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/pipewire/config.hpp b/include/libremidi/backends/pipewire/config.hpp index efb5c25..69efb05 100644 --- a/include/libremidi/backends/pipewire/config.hpp +++ b/include/libremidi/backends/pipewire/config.hpp @@ -12,7 +12,7 @@ struct pw_filter; struct spa_io_position; } -namespace libremidi +NAMESPACE_LIBREMIDI { using pipewire_callback_function = std::function; struct pipewire_callback diff --git a/include/libremidi/backends/pipewire/context.hpp b/include/libremidi/backends/pipewire/context.hpp index b9a8635..d13b7af 100644 --- a/include/libremidi/backends/pipewire/context.hpp +++ b/include/libremidi/backends/pipewire/context.hpp @@ -18,13 +18,14 @@ #include #include #include +#include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" -namespace libremidi +NAMESPACE_LIBREMIDI { template using hash_map = std::unordered_map; @@ -79,11 +80,11 @@ struct pipewire_context struct graph { mutable std::mutex mtx; - libremidi::hash_map physical_audio; - libremidi::hash_map physical_midi; - libremidi::hash_map software_audio; - libremidi::hash_map software_midi; - libremidi::hash_map port_cache; + libremidi::hash_map physical_audio; + libremidi::hash_map physical_midi; + libremidi::hash_map software_audio; + libremidi::hash_map software_midi; + libremidi::hash_map port_cache; void for_each_port(auto func) { @@ -123,7 +124,7 @@ struct pipewire_context } explicit pipewire_context(std::shared_ptr inst) - : global_instance{inst} + : global_instance{std::move(inst)} , owns_main_loop{true} { this->main_loop = pw.main_loop_new(nullptr); @@ -187,13 +188,13 @@ struct pipewire_context pipewire_context& self = *(pipewire_context*)object; if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) self.register_port(id, type); - }, + }, .global_remove = [](void* object, uint32_t id) { pipewire_context& self = *(pipewire_context*)object; self.unregister_port(id); - }, - }; + }, + }; // Start listening pw_registry_add_listener(this->registry, &this->registry_listener, ®istry_events, this); @@ -244,8 +245,8 @@ struct pipewire_context // Remove from the listeners auto it = std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) { - return l.id == id; - }); + return l.id == id; + }); if (it != port_listener.end()) { pw.proxy_destroy((pw_proxy*)it->port); @@ -266,14 +267,14 @@ struct pipewire_context static constexpr struct pw_core_events core_events = { .version = PW_VERSION_CORE_EVENTS, .done = - [](void* object, uint32_t id, int seq) { + [](void* object, uint32_t id, int seq) { auto& self = *(pipewire_context*)object; - if(id == PW_ID_CORE && seq == self.pending) + if (id == PW_ID_CORE && seq == self.pending) { self.done = 1; libpipewire::instance().main_loop_quit(self.main_loop); } - }, + }, }; spa_zero(core_listener); @@ -287,7 +288,7 @@ struct pipewire_context spa_hook_remove(&core_listener); } - [[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_t in_port) + [[nodiscard]] pw_proxy* link_ports(uint64_t out_port, uint64_t in_port) { auto props = pw.properties_new( PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT, @@ -361,6 +362,8 @@ struct pipewire_context return &this->current_graph.physical_audio[nid]; else if (p.format.find("midi") != p.format.npos) return &this->current_graph.physical_midi[nid]; + else if (p.format.find("UMP") != p.format.npos) + return &this->current_graph.physical_midi[nid]; } else { @@ -368,6 +371,8 @@ struct pipewire_context return &this->current_graph.software_audio[nid]; else if (p.format.find("midi") != p.format.npos) return &this->current_graph.software_midi[nid]; + else if (p.format.find("UMP") != p.format.npos) + return &this->current_graph.software_midi[nid]; } return nullptr; }; @@ -453,12 +458,12 @@ struct pipewire_filter }* port{}; explicit pipewire_filter(std::shared_ptr loop) - : loop{loop} + : loop{std::move(loop)} { } explicit pipewire_filter(std::shared_ptr loop, pw_filter* filter) - : loop{loop} + : loop{std::move(loop)} , filter{filter} { } @@ -498,7 +503,8 @@ struct pipewire_filter pw.filter_destroy(this->filter); } - stdx::error create_local_port(std::string_view port_name, spa_direction direction) + stdx::error + create_local_port(std::string_view port_name, spa_direction direction, const char* format) { // clang-format off this->port = (struct port*)pw.filter_add_port( @@ -507,7 +513,7 @@ struct pipewire_filter PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw.properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, format, PW_KEY_PORT_NAME, port_name.data(), nullptr), nullptr, 0); @@ -517,7 +523,7 @@ struct pipewire_filter return stdx::error{}; } - void set_port_buffer(int bytes) + void set_port_buffer(int64_t bytes) { uint8_t buffer[1024]; struct spa_pod_builder builder; diff --git a/include/libremidi/backends/pipewire/helpers.hpp b/include/libremidi/backends/pipewire/helpers.hpp index 8c2970c..a8dc2be 100644 --- a/include/libremidi/backends/pipewire/helpers.hpp +++ b/include/libremidi/backends/pipewire/helpers.hpp @@ -9,11 +9,14 @@ #include #include -#include -#include #include -namespace libremidi +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wsign-compare" +#endif + +NAMESPACE_LIBREMIDI { struct pipewire_helpers { @@ -66,14 +69,14 @@ struct pipewire_helpers { this->filter = std::make_unique(this->global_context, configuration.filter); - pipewire_callback cbs{ + libremidi::pipewire_callback cbs{ .token = this_instance, .callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void { - if (auto pt = p.lock()) - self.process(nf); + if (auto pt = p.lock()) + self.process(nf); - self.thread_lock.check_client_released(); - }}; + self.thread_lock.check_client_released(); + }}; configuration.set_process_func(cbs); } else @@ -84,10 +87,10 @@ struct pipewire_helpers static constexpr struct pw_filter_events filter_events = {.version = PW_VERSION_FILTER_EVENTS, .process = +[](void* _data, struct spa_io_position* position) -> void { - // FIXME likely we need the thread_lock check here too - Self& self = *static_cast(_data); - self.process(position); - }}; + // FIXME likely we need the thread_lock check here too + Self& self = *static_cast(_data); + self.process(position); + }}; #pragma GCC diagnostic pop this->filter->create_filter(self.configuration.client_name, filter_events, &self); @@ -197,7 +200,8 @@ struct pipewire_helpers } template - stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction) + stdx::error create_local_port( + Self& self, std::string_view portName, spa_direction direction, const char* format) { assert(this->global_context); assert(this->filter); @@ -207,7 +211,7 @@ struct pipewire_helpers if (!this->filter->port) { - auto ret = this->filter->create_local_port(portName.data(), direction); + auto ret = this->filter->create_local_port(portName.data(), direction, format); if (ret != stdx::error{}) { self.libremidi_handle_error(self.configuration, "error creating port"); @@ -218,11 +222,12 @@ struct pipewire_helpers return stdx::error{}; } - void add_callbacks(const observer_configuration& conf) + template + void add_callbacks(std::string format, const observer_configuration& conf) { assert(global_context); - global_context->on_port_added = [&conf](const pipewire_context::port_info& port) { - if (port.format.find("midi") == std::string::npos) + global_context->on_port_added = [format, &conf](const pipewire_context::port_info& port) { + if (port.format.find(format) == std::string::npos) return; bool unfiltered = conf.track_any; @@ -233,18 +238,18 @@ struct pipewire_helpers if (port.direction == SPA_DIRECTION_INPUT) { if (conf.output_added) - conf.output_added(to_port_info(port)); + conf.output_added(to_port_info(port)); } else { if (conf.input_added) - conf.input_added(to_port_info(port)); + conf.input_added(to_port_info(port)); } } }; - global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) { - if (port.format.find("midi") == std::string::npos) + global_context->on_port_removed = [format, &conf](const pipewire_context::port_info& port) { + if (port.format.find(format) == std::string::npos) return; bool unfiltered = conf.track_any; @@ -255,12 +260,12 @@ struct pipewire_helpers if (port.direction == SPA_DIRECTION_INPUT) { if (conf.output_removed) - conf.output_removed(to_port_info(port)); + conf.output_removed(to_port_info(port)); } else { if (conf.input_removed) - conf.input_removed(to_port_info(port)); + conf.input_removed(to_port_info(port)); } } }; @@ -408,7 +413,7 @@ struct pipewire_helpers return stdx::error{}; } - template + template static auto to_port_info(const pipewire_context::port_info& port) -> std::conditional_t { @@ -425,6 +430,7 @@ struct pipewire_helpers } return {{ + .api = Api, .client = 0, .port = port.id, .manufacturer = "", @@ -436,8 +442,10 @@ struct pipewire_helpers // Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer) // is an "output" port from the point of view of pipewire as data will come out of it - template - static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept + template + static auto get_ports( + std::string_view format, const observer_configuration& conf, + const pipewire_context& ctx) noexcept -> std::vector< std::conditional_t> { @@ -452,7 +460,8 @@ struct pipewire_helpers for (auto& port : (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { - ret.push_back(to_port_info(port)); + if (port.format.find(format) != std::string::npos) + ret.push_back(to_port_info(port)); } } @@ -462,7 +471,8 @@ struct pipewire_helpers for (auto& port : (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { - ret.push_back(to_port_info(port)); + if (port.format.find(format) != std::string::npos) + ret.push_back(to_port_info(port)); } } } @@ -471,3 +481,7 @@ struct pipewire_helpers } }; } + +#if defined(__clang__) + #pragma clang diagnostic pop +#endif diff --git a/include/libremidi/backends/pipewire/midi_in.hpp b/include/libremidi/backends/pipewire/midi_in.hpp index a584b2d..6d0ae97 100644 --- a/include/libremidi/backends/pipewire/midi_in.hpp +++ b/include/libremidi/backends/pipewire/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_pipewire final : public midi1::in_api @@ -47,7 +47,8 @@ public: stdx::error open_port(const input_port& in_port, std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi"); + err != stdx::error{}) return err; if (auto err = link_ports(*this, in_port); err != stdx::error{}) @@ -59,7 +60,8 @@ public: stdx::error open_virtual_port(std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi"); + err != stdx::error{}) return err; start_thread(); @@ -72,10 +74,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } timestamp absolute_timestamp() const noexcept override { return system_ns(); } diff --git a/include/libremidi/backends/pipewire/midi_out.hpp b/include/libremidi/backends/pipewire/midi_out.hpp index e8ae5c5..38bf1c2 100644 --- a/include/libremidi/backends/pipewire/midi_out.hpp +++ b/include/libremidi/backends/pipewire/midi_out.hpp @@ -5,9 +5,7 @@ #include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_pipewire : public midi1::out_api @@ -50,7 +48,8 @@ public: stdx::error open_port(const output_port& out_port, std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi"); + err != stdx::error{}) return err; this->filter->set_port_buffer(configuration.output_buffer_size); @@ -64,7 +63,8 @@ public: stdx::error open_virtual_port(std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi"); + err != stdx::error{}) return err; this->filter->set_port_buffer(configuration.output_buffer_size); @@ -79,10 +79,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } int process(spa_io_position* pos) { @@ -121,8 +118,9 @@ public: continue; } - spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi); - int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size()); + spa_pod_builder_control(&build, static_cast(m.timestamp), SPA_CONTROL_Midi); + int res + = spa_pod_builder_bytes(&build, m.bytes.data(), static_cast(m.bytes.size())); // Try again next buffer if (res == -ENOSPC) diff --git a/include/libremidi/backends/pipewire/observer.hpp b/include/libremidi/backends/pipewire/observer.hpp index 66eba74..acc87a3 100644 --- a/include/libremidi/backends/pipewire/observer.hpp +++ b/include/libremidi/backends/pipewire/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_pipewire final : public observer_api @@ -19,24 +19,15 @@ public: { } configuration; - explicit observer_pipewire(observer_configuration&& conf, pipewire_observer_configuration&& apiconf) + explicit observer_pipewire( + observer_configuration&& conf, pipewire_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { create_context(*this); - // FIXME notify_in_constructor // FIXME port rename callback -#if 0 - // Initialize PipeWire client - if (configuration.context) { - this->client = configuration.context; - set_callbacks(); - } - else -#endif - { - this->add_callbacks(configuration); + this->add_callbacks("midi", configuration); this->start_thread(); } @@ -56,27 +47,20 @@ public: std::vector get_input_ports() const noexcept override { - return get_ports(this->configuration, *this->global_context); + return get_ports( + "midi", this->configuration, *this->global_context); } std::vector get_output_ports() const noexcept override { - return get_ports(this->configuration, *this->global_context); + return get_ports( + "midi", this->configuration, *this->global_context); } ~observer_pipewire() { stop_thread(); destroy_context(); -#if 0 - if (client && !configuration.context) - { - // If we own the client, deactivate it - pipewire_deactivate(this->client); - pipewire_client_close(this->client); - this->client = nullptr; - } -#endif } std::unordered_set seen_input_ports; diff --git a/include/libremidi/backends/pipewire/shared_handler.hpp b/include/libremidi/backends/pipewire/shared_handler.hpp index 5dab246..a6e531a 100644 --- a/include/libremidi/backends/pipewire/shared_handler.hpp +++ b/include/libremidi/backends/pipewire/shared_handler.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi::pipewire +NAMESPACE_LIBREMIDI::pipewire { // Create a PipeWire client which will be shared across objects @@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context client = pipewire_client_open(v.data(), PipewireNoStartServer, &status); assert(client); assert(status == 0); - pipewire_set_process_callback( - client, - +[](pipewire_nframes_t cnt, void* ctx) -> int { - ((shared_handler*)ctx)->pipewire_callback(cnt); - return 0; - }, - this); + pipewire_set_process_callback(client, +[](pipewire_nframes_t cnt, void* ctx) -> int { + ((shared_handler*)ctx)->pipewire_callback(cnt); + return 0; + }, this); } virtual void start_processing() override { pipewire_activate(client); } @@ -74,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) @@ -94,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) @@ -141,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/pipewire_ump.hpp b/include/libremidi/backends/pipewire_ump.hpp new file mode 100644 index 0000000..eed0a7e --- /dev/null +++ b/include/libremidi/backends/pipewire_ump.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +struct backend +{ + using midi_in = libremidi::pipewire_ump::midi_in_pipewire; + using midi_out = libremidi::pipewire_ump::midi_out_pipewire; + using midi_observer = libremidi::pipewire_ump::observer_pipewire; + using midi_in_configuration = libremidi::pipewire_ump::input_configuration; + using midi_out_configuration = libremidi::pipewire_ump::output_configuration; + using midi_observer_configuration = libremidi::pipewire_ump::observer_configuration; + static const constexpr auto API = libremidi::API::PIPEWIRE_UMP; + static const constexpr std::string_view name = "pipewire_ump"; + static const constexpr std::string_view display_name = "PipeWire (UMP)"; + + static inline bool available() noexcept + { + static const libpipewire& pw = libpipewire::instance(); + if (!pw.available) + return false; + const std::string_view version = pw.get_library_version(); + if (version.size() >= 3) + { + if (version[0] > '1' || version[2] >= '4') + return true; + } + return false; + } +}; +} diff --git a/include/libremidi/backends/pipewire_ump/config.hpp b/include/libremidi/backends/pipewire_ump/config.hpp new file mode 100644 index 0000000..ab59684 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/config.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +struct input_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; + pw_filter* filter{}; + std::function set_process_func{}; + std::function clear_process_func{}; +}; + +struct output_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; + pw_filter* filter{}; + std::function set_process_func{}; + std::function clear_process_func{}; + + int64_t output_buffer_size{65536}; +}; + +struct observer_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; +}; + +} diff --git a/include/libremidi/backends/pipewire_ump/midi_in.hpp b/include/libremidi/backends/pipewire_ump/midi_in.hpp new file mode 100644 index 0000000..05b112b --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/midi_in.hpp @@ -0,0 +1,132 @@ +#pragma once +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +class midi_in_pipewire final + : public midi2::in_api + , public pipewire_helpers + , public error_handler +{ +public: + struct + : ump_input_configuration + , libremidi::pipewire_ump::input_configuration + { + } configuration; + + explicit midi_in_pipewire( + ump_input_configuration&& conf, libremidi::pipewire_ump::input_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (auto ret = create_context(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + if (auto ret = create_filter(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + client_open_ = stdx::error{}; + } + + ~midi_in_pipewire() override + { + stop_thread(); + do_close_port(); + destroy_filter(*this); + destroy_context(); + client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + stdx::error open_port(const input_port& in_port, std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + if (auto err = link_ports(*this, in_port); err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error close_port() override + { + stop_thread(); + return do_close_port(); + } + + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } + + timestamp absolute_timestamp() const noexcept override { return system_ns(); } + + void process(struct spa_io_position* position) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = true, + .has_samples = true, + }; + + assert(this->filter); + assert(this->filter->port); + const auto b = pw.filter_dequeue_buffer(this->filter->port); + if (!b) + return; + + const auto buf = b->buffer; + const auto d = &buf->datas[0]; + + if (d->data == nullptr) + return; + + const auto pod + = (spa_pod*)spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size); + if (!pod) + return; + if (!spa_pod_is_sequence(pod)) + return; + + struct spa_pod_control* c{}; + SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) + { + if (c->type != SPA_CONTROL_UMP) + continue; + + auto data = (uint8_t*)SPA_POD_BODY(&c->value); + auto size = SPA_POD_BODY_SIZE(&c->value); + + const auto to_ns = [=, clk = position->clock] { + return 1e9 * ((clk.position + c->offset) / (double)clk.rate.denom); + }; + + m_processing.on_bytes( + {(uint32_t*)data, (uint32_t*)(data + size)}, + m_processing.timestamp(to_ns, c->offset)); + } + + pw.filter_queue_buffer(this->filter->port, b); + } + + midi2::input_state_machine m_processing{this->configuration}; +}; +} diff --git a/include/libremidi/backends/pipewire_ump/midi_out.hpp b/include/libremidi/backends/pipewire_ump/midi_out.hpp new file mode 100644 index 0000000..1c2bbff --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/midi_out.hpp @@ -0,0 +1,182 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +class midi_out_pipewire + : public midi2::out_api + , public pipewire_helpers + , public error_handler +{ +public: + struct + : libremidi::output_configuration + , libremidi::pipewire_ump::output_configuration + { + } configuration; + + midi_out_pipewire( + libremidi::output_configuration&& conf, + libremidi::pipewire_ump::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (auto ret = create_context(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + if (auto ret = create_filter(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + client_open_ = stdx::error{}; + } + + ~midi_out_pipewire() override + { + stop_thread(); + do_close_port(); + destroy_filter(*this); + destroy_context(); + client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + stdx::error open_port(const output_port& out_port, std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + this->filter->set_port_buffer(configuration.output_buffer_size); + + if (auto err = link_ports(*this, out_port); err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + this->filter->set_port_buffer(configuration.output_buffer_size); + + start_thread(); + return stdx::error{}; + } + + stdx::error close_port() override + { + stop_thread(); + return do_close_port(); + } + + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } + + int process(spa_io_position* pos) + { + m_process_clock.store(pos->clock.nsec, std::memory_order_relaxed); + const auto b = pw.filter_dequeue_buffer(this->filter->port); + if (!b) + return 1; + + const auto buf = b->buffer; + const auto d = &buf->datas[0]; + + if (d->data == nullptr) + return 1; + + spa_pod_builder build; + spa_zero(build); + spa_pod_builder_init(&build, d->data, d->maxsize); + + spa_pod_frame f; + spa_pod_builder_push_sequence(&build, &f, 0); + + // for all events + while (auto m_ptr = m_queue.peek()) + { + auto& m = *m_ptr; + + spa_pod_builder_control(&build, static_cast(m.timestamp), SPA_CONTROL_UMP); + int res = spa_pod_builder_bytes(&build, m.data, cmidi2_ump_get_message_size_bytes(m.data)); + + // Try again next buffer + if (res == -ENOSPC) + break; + + // Recycle the memory + m_queue.pop(); + } + spa_pod_builder_pop(&build, &f); + + int n_fill_frames = build.state.offset; + if (n_fill_frames > 0) + { + d->chunk->offset = 0; + d->chunk->stride = 1; + d->chunk->size = n_fill_frames; + b->size = n_fill_frames; + + pw.filter_queue_buffer(this->filter->port, b); + return 0; + } + + pw.filter_flush(this->filter->filter, true); + + return 0; + } + + stdx::error send_ump(const uint32_t* message, size_t size) override + { + // FIXME + if (size > 4) + return std::errc::message_size; + + libremidi::ump m; + std::copy_n(message, size, m.data); + m.timestamp = 0; + m_queue.enqueue(std::move(m)); + return stdx::error{}; + } + + int convert_timestamp(int64_t user) const noexcept + { + switch (configuration.timestamps) + { + case timestamp_mode::AudioFrame: + return static_cast(user); + + default: + // TODO + return 0; + } + } + + stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override + { + // FIXME + if (size > 4) + return std::errc::message_size; + + libremidi::ump m; + std::copy_n(message, size, m.data); + m.timestamp = convert_timestamp(ts); + m_queue.enqueue(std::move(m)); // FIXME actually send them scheudled + return stdx::error{}; + } + + moodycamel::ReaderWriterQueue m_queue; + std::atomic_int64_t m_process_clock = 0; +}; +} diff --git a/include/libremidi/backends/pipewire_ump/observer.hpp b/include/libremidi/backends/pipewire_ump/observer.hpp new file mode 100644 index 0000000..e3b1bd8 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/observer.hpp @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +class observer_pipewire final + : public observer_api + , private pipewire_helpers + , private error_handler +{ +public: + struct + : libremidi::observer_configuration + , libremidi::pipewire_ump::observer_configuration + { + } configuration; + + explicit observer_pipewire( + libremidi::observer_configuration&& conf, + libremidi::pipewire_ump::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + create_context(*this); + + // FIXME port rename callback + { + this->add_callbacks("UMP", configuration); + this->start_thread(); + } + + if (configuration.notify_in_constructor) + { + if (configuration.input_added) + for (const auto& p : get_input_ports()) + configuration.input_added(p); + + if (configuration.output_added) + for (const auto& p : get_output_ports()) + configuration.output_added(p); + } + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + std::vector get_input_ports() const noexcept override + { + return get_ports( + "UMP", this->configuration, *this->global_context); + } + + std::vector get_output_ports() const noexcept override + { + return get_ports( + "UMP", this->configuration, *this->global_context); + } + + ~observer_pipewire() + { + stop_thread(); + destroy_context(); + } + + std::unordered_set seen_input_ports; + std::unordered_set seen_output_ports; +}; +} diff --git a/include/libremidi/backends/winmidi.hpp b/include/libremidi/backends/winmidi.hpp index be7788c..a641ab2 100644 --- a/include/libremidi/backends/winmidi.hpp +++ b/include/libremidi/backends/winmidi.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi::winmidi +#include + +NAMESPACE_LIBREMIDI::winmidi { struct backend { @@ -14,9 +16,11 @@ struct backend using midi_out_configuration = winmidi::output_configuration; using midi_observer_configuration = winmidi::observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES; - static const constexpr auto name = "winmidi"; - static const constexpr auto display_name = "Windows MIDI Services"; + static const constexpr std::string_view name = "winmidi"; + static const constexpr std::string_view display_name = "Windows MIDI Services"; - static constexpr inline bool available() noexcept { return true; } + static inline bool available() noexcept { + return libremidi::instance()->ready; + } }; -} // namespace libremidi +} // NAMESPACE_LIBREMIDI diff --git a/include/libremidi/backends/winmidi/config.hpp b/include/libremidi/backends/winmidi/config.hpp index 276b616..c1ef02f 100644 --- a/include/libremidi/backends/winmidi/config.hpp +++ b/include/libremidi/backends/winmidi/config.hpp @@ -1,19 +1,36 @@ #pragma once #include -namespace libremidi::winmidi +#include + +#if !defined(LIBREMIDI_MODULE_BUILD) || !defined(_WIN32) +namespace winrt::Microsoft::Windows::Devices::Midi2 +{ +struct MidiSession; +struct MidiEndpointDeviceInformation; +struct MidiEndpointConnection; +} +#endif + +// TODO allow to share midi session and endpoints +NAMESPACE_LIBREMIDI::winmidi { struct input_configuration { + std::string client_name = "libremidi input"; + winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{}; }; struct output_configuration { + std::string client_name = "libremidi output"; + winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{}; }; struct observer_configuration { + std::string client_name = "libremidi observer"; }; } diff --git a/include/libremidi/backends/winmidi/helpers.hpp b/include/libremidi/backends/winmidi/helpers.hpp index f8218a5..0a586bd 100644 --- a/include/libremidi/backends/winmidi/helpers.hpp +++ b/include/libremidi/backends/winmidi/helpers.hpp @@ -3,58 +3,216 @@ #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN 1 #include +#include -#include #include #include -#include -#include #include +#include #include #include #include -#include +#include +#include +#if __has_include() +#include + #define LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS 1 -namespace midi2 = winrt::Windows::Devices::Midi2; -namespace foundation = winrt::Windows::Foundation; -namespace collections = winrt::Windows::Foundation::Collections; +#define LIBREMIDI_DEFINE_GUID_CONSTEXPR(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + LIBREMIDI_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); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiClientInitializer, 0x8087b303, 0xd551, 0xbce2, 0x1e, 0xad, 0xa2, 0x50, 0x0d, 0x50, 0xc5, 0x80); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_MidiClientInitializerUuid, 0xc3263827, 0xc3b0, 0xbdbd, 0x25, 0x00, 0xce, 0x63, 0xa3, 0xf3, 0xf2, 0xc3); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_MidiSrvTransportUuid, 0x2ba15e4e, 0x5417, 0x4a66, 0x85, 0xb8, 0x2b, 0x22, 0x60, 0xef, 0xbc, 0x84); +} +#endif // clang-format on -namespace libremidi::winmidi +namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2; +namespace foundation = winrt::Windows::Foundation; + +NAMESPACE_LIBREMIDI::winmidi { using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Storage::Streams; -using namespace winrt::Windows::Devices::Midi2; -using namespace Windows::Devices::Enumeration; +using namespace winrt::Microsoft::Windows::Devices::Midi2; inline bool ichar_equals(char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); + return std::tolower(static_cast(a)) + == std::tolower(static_cast(b)); } + inline bool iequals(std::string_view lhs, std::string_view rhs) { return std::ranges::equal(lhs, rhs, ichar_equals); } -inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation -get_port_by_name(const std::string& port) { +inline std::pair< + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointDeviceInformation, + winrt::Microsoft::Windows::Devices::Midi2::MidiGroupTerminalBlock> +get_port(const std::string& device_name, int group_terminal_block) +{ auto eps = MidiEndpointDeviceInformation::FindAll(); for (const auto& ep : eps) { - auto str = to_string(ep.Id()); + auto str = to_string(ep.EndpointDeviceId()); if (str.empty()) continue; - if (iequals(str, port)) - return ep; + if (iequals(str, device_name)) + { + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Number() == group_terminal_block) + { + return std::make_pair(ep, gp); + } + } + } } - return {nullptr}; + return {nullptr, nullptr}; } + +// From Microsoft.Windows.Devices.Midi2.Initialization.hpp +typedef enum +{ + Platform_x64 = 1, + // Platform_Arm64 = 2, + // Platform_Arm64EC = 3, + Platform_Arm64X = 4, +} MidiAppSDKPlatform; + +struct IMidiClientInitializer : ::IUnknown +{ + // returns the SDK version info. Supply nullptr for arguments you don't care about + STDMETHOD(GetInstalledWindowsMidiServicesSdkVersion)( + MidiAppSDKPlatform* buildPlatform, + USHORT* versionMajor, + USHORT* versionMinor, + USHORT* versionPatch, + + LPWSTR* buildSource, + LPWSTR* versionName, + LPWSTR* versionFullString + ) = 0; + + // demand-starts the service if present + STDMETHOD(EnsureServiceAvailable)() = 0; +}; +struct winmidi_shared_data_instance +{ + IMidiClientInitializer* initializer{nullptr}; + bool ready{false}; + winmidi_shared_data_instance() + { + // 1. Check if MIDI Services are available + { + ::IUnknown* servicePointer{ nullptr }; + auto hr = CoCreateInstance( + libremidi::IID_MidiSrvTransportUuid, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&servicePointer) + ); + + if (SUCCEEDED(hr)) + { + if (servicePointer == nullptr) + { + ready = false; + return; + } + + // Here is the good case: + servicePointer->Release(); + servicePointer = nullptr; + ready = true; + } + else if (hr == REGDB_E_CLASSNOTREG) + { + servicePointer = nullptr; + ready = false; + return; + } + else + { + servicePointer = nullptr; + ready = false; + return; + } + } + + // 2. Check if MIDI services can be created + { + if (SUCCEEDED(CoCreateInstance( + libremidi::IID_MidiClientInitializerUuid, + NULL, + CLSCTX::CLSCTX_INPROC_SERVER | CLSCTX::CLSCTX_FROM_DEFAULT_CONTEXT, + libremidi::IID_IMidiClientInitializer, + reinterpret_cast(&initializer) + ))) + { + if (initializer != nullptr) + { + ready = true; + } + else + { + ready = false; + return; + } + } + else + { + ready = false; + return; + } + } + + // 3. Check if MIDI services can be used + if (SUCCEEDED(initializer->EnsureServiceAvailable())) + { + ready = true; + } + else + { + ready = false; + initializer->Release(); + initializer = nullptr; + return; + } + } + + ~winmidi_shared_data_instance() + { + if (initializer != nullptr) + { + initializer->Release(); + initializer = nullptr; + } + } +}; + +struct winmidi_shared_data +{ + std::shared_ptr self = libremidi::instance(); + + winmidi_shared_data() + { + } + + ~winmidi_shared_data() + { + } +}; + } diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index 041060e..b8d0fc4 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -5,14 +5,12 @@ #include #include -#include - -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { - class midi_in_impl final : public midi2::in_api , public error_handler + , public winmidi_shared_data { public: struct @@ -21,10 +19,62 @@ public: { } 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 == libremidi::IID_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)} - , m_session{MidiSession::CreateSession(L"libremidi session")} + , m_session{ + configuration.context ? *configuration.context + : MidiSession::Create(to_hstring(configuration.client_name))} { this->client_open_ = stdx::error{}; } @@ -42,24 +92,41 @@ public: stdx::error open_port(const input_port& port, std::string_view) override { - auto ep = get_port_by_name(port.port_name); - if (!ep) + auto device_id = get_if(&port.device); + if (!device_id) + return std::errc::invalid_argument; + + auto [ep, gp] = get_port(*device_id, port.port); + if (!ep || !gp) return std::errc::address_not_available; - m_endpoint = m_session.CreateEndpointConnection(ep.Id()); + m_group_filter = port.port - 1; + // 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( - [&](const foundation::IInspectable& sender, - const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { + [this]( + const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&, + const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { process_message(args); }); +#else + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + + m_raw_endpoint->SetMessagesReceivedCallback( + &raw_callback + ); +#endif m_endpoint.Open(); return stdx::error{}; } - void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) + void process_message( + const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) { static constexpr timestamp_backend_info timestamp_info{ .has_absolute_timestamps = true, @@ -68,6 +135,14 @@ public: }; const auto& ump = msg.GetMessagePacket(); + auto pk = msg.PeekFirstWord(); + if (m_group_filter >= 0) + { + int group = cmidi2_ump_get_group(&pk); + if (group != m_group_filter) + return; + } + const auto& b = ump.GetAllWords(); uint32_t ump_space[64]; @@ -79,9 +154,49 @@ public: {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(!m_endpoint) + return std::errc::not_connected; + +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS m_endpoint.MessageReceived(m_revoke_token); +#else + if(m_raw_endpoint) { + m_raw_endpoint->RemoveMessagesReceivedCallback(); + m_raw_endpoint = nullptr; + } +#endif m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); return stdx::error{}; } @@ -91,7 +206,11 @@ public: private: MidiSession m_session; winrt::event_token m_revoke_token{}; - winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; + 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 3a95546..29b334c 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -1,16 +1,17 @@ #pragma once -#include #include #include #include #include +#include -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { class midi_out_impl final : public midi2::out_api , public error_handler + , public winmidi_shared_data { public: struct @@ -21,7 +22,9 @@ public: midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , m_session{MidiSession::CreateSession(L"libremidi session")} + , m_session{ + configuration.context ? *configuration.context + : MidiSession::Create(to_hstring(configuration.client_name))} { this->client_open_ = stdx::error{}; } @@ -35,11 +38,18 @@ public: stdx::error open_port(const output_port& port, std::string_view) override { - auto ep = get_port_by_name(port.port_name); - if (!ep) + auto device_id = get_if(&port.device); + if (!device_id) + return std::errc::invalid_argument; + + auto [ep, gp] = get_port(*device_id, port.port); + if (!ep || !gp) return std::errc::address_not_available; - m_endpoint = m_session.CreateEndpointConnection(ep.Id()); + m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + #endif m_endpoint.Open(); return stdx::error{}; @@ -47,6 +57,9 @@ public: stdx::error close_port() override { + if(!m_endpoint) + return std::errc::not_connected; + m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); return stdx::error{}; } @@ -54,8 +67,10 @@ public: 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) + switch (bytes / 4) { case 1: ret = m_endpoint.SendSingleMessagePacket(MidiMessage32(0, ump[0])); @@ -67,23 +82,52 @@ public: ret = m_endpoint.SendSingleMessagePacket(MidiMessage96(0, ump[0], ump[1], ump[2])); break; case 4: - ret = m_endpoint.SendSingleMessagePacket(MidiMessage128(0, ump[0], ump[1], ump[2], ump[3])); + ret = m_endpoint.SendSingleMessagePacket( + MidiMessage128(0, ump[0], ump[1], ump[2], ump[3])); break; default: return std::errc::bad_message; } - - if(ret != MidiSendMessageResults::Succeeded) + 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}; }; - return segment_ump_stream(message, size, write_func, []() {}); + return segment_ump_stream(message, size, write_func, []() { }); } private: MidiSession m_session; - winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + winrt::impl::com_ref m_raw_endpoint{}; +#endif }; } diff --git a/include/libremidi/backends/winmidi/observer.hpp b/include/libremidi/backends/winmidi/observer.hpp index 3642324..b26f180 100644 --- a/include/libremidi/backends/winmidi/observer.hpp +++ b/include/libremidi/backends/winmidi/observer.hpp @@ -2,16 +2,18 @@ #include #include #include -; -namespace libremidi::winmidi + +NAMESPACE_LIBREMIDI::winmidi { struct port_info { - hstring id; - hstring name; + winrt::hstring id; + winrt::hstring name; }; -class observer_impl final : public observer_api +class observer_impl final + : public observer_api + , public winmidi_shared_data { public: struct @@ -20,46 +22,55 @@ public: { } configuration; + MidiEndpointDeviceWatcher watcher = nullptr; + MidiEndpointDeviceWatcher::Added_revoker m_addHandler; + MidiEndpointDeviceWatcher::Updated_revoker m_updHandler; + MidiEndpointDeviceWatcher::Removed_revoker m_delHandler; + std::map> m_known_input_devices; + std::map> m_known_output_devices; + std::mutex m_devices_mtx; + std::atomic_bool m_in_constructor{true}; explicit observer_impl( libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , session{MidiSession::CreateSession(L"libremidi session")} { + struct on_delete { observer_impl& self; ~on_delete() { self.m_in_constructor = false; } } on_delete{*this}; + if (!configuration.has_callbacks()) return; - if (configuration.notify_in_constructor) + // Note: winmidi also notifies for existing devices, so the "notify in constructor" + // is handled in the callbacks + if ((watcher = MidiEndpointDeviceWatcher::Create())) { - if (configuration.input_added) - for (const auto& p : get_input_ports()) - configuration.input_added(p); + namespace enumeration = winrt::Windows::Devices::Enumeration; + auto addHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationAddedEventArgs>( + this, &observer_impl::on_device_added); + auto updHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationUpdatedEventArgs>( + this, &observer_impl::on_device_updated); + auto delHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationRemovedEventArgs>( + this, &observer_impl::on_device_removed); - if (configuration.output_added) - for (const auto& p : get_output_ports()) - configuration.output_added(p); + m_addHandler = watcher.Added(winrt::auto_revoke, addHandler); + m_updHandler = watcher.Updated(winrt::auto_revoke, updHandler); + m_delHandler = watcher.Removed(winrt::auto_revoke, delHandler); + + watcher.Start(); } - - /* - evTokenOnInputAdded_ - = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); }); - evTokenOnInputRemoved_ - = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); }); - evTokenOnOutputAdded_ - = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); }); - evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved( - [this](const port_info& p) { on_output_removed(p); }); -*/ } ~observer_impl() { if (!configuration.has_callbacks()) return; - // internalInPortObserver_.PortAdded(evTokenOnInputAdded_); - // internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_); - // internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_); - // internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_); + + m_addHandler.revoke(); + m_updHandler.revoke(); + m_delHandler.revoke(); } libremidi::API get_current_api() const noexcept override @@ -67,33 +78,60 @@ public: return libremidi::API::WINDOWS_MIDI_SERVICES; } - template - auto to_port_info(const DeviceInformation& p) const noexcept - -> std::conditional_t + static transport_type code_to_type(std::string_view str) noexcept { + using enum transport_type; + + if (str.starts_with("KS")) + return hardware; + if (str == "BLE") + return transport_type(hardware | bluetooth); + if (str == "VPB" || str == "APP") + return software; + if (str == "LOOP") + return transport_type(software | loopback); + if (str.starts_with("NET")) + return network; + return unknown; + } + + template + auto to_port_info(const MidiEndpointDeviceInformation& p, const MidiGroupTerminalBlock& gp) + const noexcept -> std::conditional_t + { + const auto& tinfo = p.GetTransportSuppliedInfo(); + return { - {.client = 0, - .port = 0, - .manufacturer = "", - .device_name = "", - .port_name = to_string(p.Id()), - .display_name = to_string(p.Name())}}; + {.api = libremidi::API::WINDOWS_MIDI_SERVICES, + .client = 0, + .container = std::bit_cast(p.ContainerId()), + .device = to_string(p.EndpointDeviceId()), + .port = gp.Number(), + .manufacturer = to_string(tinfo.ManufacturerName), + .product = to_string(tinfo.Name), + .serial = to_string(tinfo.SerialNumber), + .device_name = to_string(p.Name()), + .port_name = to_string(gp.Name()), + .display_name = to_string(gp.Name()) + " " + std::to_string(gp.Number()), + .type = code_to_type(to_string(p.GetTransportSuppliedInfo().TransportCode))}}; } std::vector get_input_ports() const noexcept override { std::vector ret; - auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); - auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& ep : endpointDevices) + for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { - if(ep.Name().starts_with(L"Diagnostics")) { + if (ep.Name().starts_with(L"Diagnostics")) + { continue; } - // FIXME if(has input...) - ret.emplace_back(to_port_info(ep)); + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockOutput) + ret.emplace_back(to_port_info(ep, gp)); + } } return ret; @@ -103,46 +141,142 @@ public: { std::vector ret; - auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); - auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& ep : endpointDevices) + for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { - if(ep.Name().starts_with(L"Diagnostics")) { + if (ep.Name().starts_with(L"Diagnostics")) + { continue; } - // FIXME if(has output...) - ret.emplace_back(to_port_info(ep)); + + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockInput) + ret.emplace_back(to_port_info(ep, gp)); + } } return ret; } - void on_input_added(const DeviceInformation& name) + // Note: these callbacks are called from some random thread! + void on_device_added( + const MidiEndpointDeviceWatcher&, const MidiEndpointDeviceInformationAddedEventArgs& result) { - if (configuration.input_added) - configuration.input_added(to_port_info(name)); + add_device(result.AddedDevice()); } - void on_input_removed(const DeviceInformation& name) + void on_device_updated( + const MidiEndpointDeviceWatcher&, + const MidiEndpointDeviceInformationUpdatedEventArgs& result) { + // OPTIMIZEME + remove_device(result.EndpointDeviceId()); + + add_device( + MidiEndpointDeviceInformation::CreateFromEndpointDeviceId(result.EndpointDeviceId())); + } + + void on_device_removed( + const MidiEndpointDeviceWatcher&, + const MidiEndpointDeviceInformationRemovedEventArgs& result) + { + remove_device(result.EndpointDeviceId()); + } + + void add_device(const MidiEndpointDeviceInformation& ep) + { + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + MidiGroupTerminalBlockDirection direction = gp.Direction(); + switch (direction) + { + case MidiGroupTerminalBlockDirection::Bidirectional: { + if (configuration.input_added) + { + auto ip = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.input_added(std::move(ip)); + } + if (configuration.output_added) + { + auto op = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_output_devices[ep.EndpointDeviceId()].push_back(op); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.output_added(std::move(op)); + } + break; + } + case MidiGroupTerminalBlockDirection::BlockInput: + if (configuration.input_added) + { + auto ip = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.input_added(std::move(ip)); + } + break; + case MidiGroupTerminalBlockDirection::BlockOutput: + if (configuration.output_added) + { + auto op = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_output_devices[ep.EndpointDeviceId()].push_back(op); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.output_added(std::move(op)); + } + break; + } + } + } + + void remove_device(winrt::hstring eid) + { + std::vector to_remove_in; + std::vector to_remove_out; + + { + std::lock_guard _{m_devices_mtx}; + if (auto it = m_known_input_devices.find(eid); it != m_known_input_devices.end()) + { + for (auto& ip : it->second) + { + to_remove_in.push_back(ip); + } + m_known_input_devices.erase(it); + } + if (auto it = m_known_output_devices.find(eid); it != m_known_output_devices.end()) + { + for (auto& op : it->second) + { + to_remove_out.push_back(op); + } + m_known_output_devices.erase(it); + } + } + if (configuration.input_removed) - configuration.input_removed(to_port_info(name)); - } - - void on_output_added(const DeviceInformation& name) - { - if (configuration.output_added) - configuration.output_added(to_port_info(name)); - } - - void on_output_removed(const DeviceInformation& name) - { + for (auto& port : to_remove_in) + configuration.input_removed(port); if (configuration.output_removed) - configuration.output_removed(to_port_info(name)); + for (auto& port : to_remove_out) + configuration.output_removed(port); } - -private: - MidiSession session; }; } diff --git a/include/libremidi/backends/winmm.hpp b/include/libremidi/backends/winmm.hpp index a45692f..04b5baf 100644 --- a/include/libremidi/backends/winmm.hpp +++ b/include/libremidi/backends/winmm.hpp @@ -3,6 +3,8 @@ #include #include +#include + // Default for Windows is to add an identifier to the port names; this // flag can be defined (e.g. in your project file) to disable this behaviour. // #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES @@ -16,7 +18,7 @@ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp // Thanks to Jean-Baptiste Berruchon for the sysex code. -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_backend @@ -28,8 +30,8 @@ struct winmm_backend using midi_out_configuration = winmm_output_configuration; using midi_observer_configuration = winmm_observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_MM; - static const constexpr auto name = "winmm"; - static const constexpr auto display_name = "Windows Multimedia"; + static const constexpr std::string_view name = "winmm"; + static const constexpr std::string_view display_name = "Windows Multimedia"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/winmm/config.hpp b/include/libremidi/backends/winmm/config.hpp index 61863e0..f681e45 100644 --- a/include/libremidi/backends/winmm/config.hpp +++ b/include/libremidi/backends/winmm/config.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_input_configuration diff --git a/include/libremidi/backends/winmm/error_domain.hpp b/include/libremidi/backends/winmm/error_domain.hpp index f1bf49c..fb4d04c 100644 --- a/include/libremidi/backends/winmm/error_domain.hpp +++ b/include/libremidi/backends/winmm/error_domain.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_error_domain : public stdx::error_domain @@ -34,28 +34,50 @@ public: { switch (error_cast(e)) { - case MMSYSERR_NOERROR: return "No error"; - case MMSYSERR_ERROR: return "Error"; - case MMSYSERR_BADDEVICEID: return "Bad device ID"; - case MMSYSERR_NOTENABLED: return "Not enabled"; - case MMSYSERR_ALLOCATED: return "Allocated"; - case MMSYSERR_INVALHANDLE: return "Invalid handle"; - case MMSYSERR_NODRIVER: return "No driver"; - case MMSYSERR_NOMEM: return "No memory"; - case MMSYSERR_NOTSUPPORTED: return "Not supported"; - case MMSYSERR_BADERRNUM: return "Bad errnum"; - case MMSYSERR_INVALFLAG: return "Invalid flag"; - case MMSYSERR_INVALPARAM: return "Invalid parameter"; - case MMSYSERR_HANDLEBUSY: return "Handle busy"; - case MMSYSERR_INVALIDALIAS: return "Invalid alias"; - case MMSYSERR_BADDB: return "Bad database"; - case MMSYSERR_KEYNOTFOUND: return "Key not found"; - case MMSYSERR_READERROR: return "Read error"; - case MMSYSERR_WRITEERROR: return "Write error"; - case MMSYSERR_DELETEERROR: return "Delete error"; - case MMSYSERR_VALNOTFOUND: return "Value not found"; - case MMSYSERR_NODRIVERCB: return "No driver callback"; - case MMSYSERR_MOREDATA: return "More data"; + case MMSYSERR_NOERROR: + return "No error"; + case MMSYSERR_ERROR: + return "Error"; + case MMSYSERR_BADDEVICEID: + return "Bad device ID"; + case MMSYSERR_NOTENABLED: + return "Not enabled"; + case MMSYSERR_ALLOCATED: + return "Allocated"; + case MMSYSERR_INVALHANDLE: + return "Invalid handle"; + case MMSYSERR_NODRIVER: + return "No driver"; + case MMSYSERR_NOMEM: + return "No memory"; + case MMSYSERR_NOTSUPPORTED: + return "Not supported"; + case MMSYSERR_BADERRNUM: + return "Bad errnum"; + case MMSYSERR_INVALFLAG: + return "Invalid flag"; + case MMSYSERR_INVALPARAM: + return "Invalid parameter"; + case MMSYSERR_HANDLEBUSY: + return "Handle busy"; + case MMSYSERR_INVALIDALIAS: + return "Invalid alias"; + case MMSYSERR_BADDB: + return "Bad database"; + case MMSYSERR_KEYNOTFOUND: + return "Key not found"; + case MMSYSERR_READERROR: + return "Read error"; + case MMSYSERR_WRITEERROR: + return "Write error"; + case MMSYSERR_DELETEERROR: + return "Delete error"; + case MMSYSERR_VALNOTFOUND: + return "Value not found"; + case MMSYSERR_NODRIVERCB: + return "No driver callback"; + case MMSYSERR_MOREDATA: + return "More data"; } return "Unknown error code"; } diff --git a/include/libremidi/backends/winmm/helpers.hpp b/include/libremidi/backends/winmm/helpers.hpp index ee75550..c116c75 100644 --- a/include/libremidi/backends/winmm/helpers.hpp +++ b/include/libremidi/backends/winmm/helpers.hpp @@ -1,9 +1,10 @@ #pragma once #include #include -#include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { // Convert a nullptr-terminated wide string or ANSI-encoded string to UTF-8. @@ -70,5 +71,4 @@ inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumbe deviceName += " "; deviceName += std::to_string(x); } - } diff --git a/include/libremidi/backends/winmm/midi_in.hpp b/include/libremidi/backends/winmm/midi_in.hpp index f04d57e..2ada660 100644 --- a/include/libremidi/backends/winmm/midi_in.hpp +++ b/include/libremidi/backends/winmm/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_winmm final @@ -24,9 +24,7 @@ public: { if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400)) { - libremidi_handle_error( - configuration, - "InitializeCriticalSectionAndSpinCount failed."); + libremidi_handle_error(configuration, "InitializeCriticalSectionAndSpinCount failed."); this->client_open_ = std::errc::too_many_files_open; return; @@ -40,7 +38,7 @@ public: // Close a connection if it exists. midi_in_winmm::close_port(); - if(this->client_open_ == stdx::error{}) + if (this->client_open_ == stdx::error{}) DeleteCriticalSection(&(this->_mutex)); } @@ -53,8 +51,7 @@ public: std::bit_cast(this), CALLBACK_FUNCTION); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error creating Windows MM MIDI input port."); + libremidi_handle_error(configuration, "error creating Windows MM MIDI input port."); return from_mmerr(result); } @@ -101,8 +98,7 @@ public: { midiInClose(this->inHandle); this->inHandle = nullptr; - libremidi_handle_error( - configuration, "error starting Windows MM MIDI input port."); + libremidi_handle_error(configuration, "error starting Windows MM MIDI input port."); return from_mmerr(result); } @@ -126,8 +122,7 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - libremidi_handle_error( - configuration, "port not found: " + p.port_name); + libremidi_handle_error(configuration, "port not found: " + p.port_name); return std::errc::invalid_argument; } @@ -238,13 +233,13 @@ private: { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) const auto* sysex = reinterpret_cast(midiMessage); - if(inputStatus == MIM_LONGERROR) + if (inputStatus == MIM_LONGERROR) { self.m_processing.reset(); } else if (!self.configuration.ignore_sysex) { - if(sysex->dwBytesRecorded > 0) + if (sysex->dwBytesRecorded > 0) { const auto sysex_bytes = reinterpret_cast(sysex->lpData); diff --git a/include/libremidi/backends/winmm/midi_out.hpp b/include/libremidi/backends/winmm/midi_out.hpp index 3f8b596..3a854f7 100644 --- a/include/libremidi/backends/winmm/midi_out.hpp +++ b/include/libremidi/backends/winmm/midi_out.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_winmm final @@ -65,8 +65,7 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - libremidi_handle_error( - configuration, "port not found: " + p.port_name); + libremidi_handle_error(configuration, "port not found: " + p.port_name); return std::errc::invalid_argument; } @@ -107,8 +106,7 @@ public: auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error preparing sysex header."); + libremidi_handle_error(configuration, "error preparing sysex header."); return from_mmerr(result); } @@ -116,8 +114,7 @@ public: result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error sending sysex message."); + libremidi_handle_error(configuration, "error sending sysex message."); return from_mmerr(result); } @@ -148,8 +145,7 @@ public: auto result = midiOutShortMsg(this->outHandle, packet); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error sending MIDI message."); + libremidi_handle_error(configuration, "error sending MIDI message."); return from_mmerr(result); } } diff --git a/include/libremidi/backends/winmm/observer.hpp b/include/libremidi/backends/winmm/observer.hpp index c243bd3..57948ed 100644 --- a/include/libremidi/backends/winmm/observer.hpp +++ b/include/libremidi/backends/winmm/observer.hpp @@ -3,12 +3,9 @@ #include #include -#include -#include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_winmm : public observer_api @@ -106,8 +103,12 @@ protected: auto rawName = ConvertToUTF8(deviceCaps.szPname); auto portName = rawName; MakeUniqueInPortName(portName, index); + return { - {.client = 0, + {.api = libremidi::API::WINDOWS_MM, + .client = 0, + .device + = usb_device_identifier{.vendor_id = deviceCaps.wMid, .product_id = deviceCaps.wPid}, .port = index, .manufacturer = "", .device_name = "", @@ -123,8 +124,12 @@ protected: auto rawName = ConvertToUTF8(deviceCaps.szPname); auto portName = rawName; MakeUniqueOutPortName(portName, index); + return { - {.client = 0, + {.api = libremidi::API::WINDOWS_MM, + .client = 0, + .device + = usb_device_identifier{.vendor_id = deviceCaps.wMid, .product_id = deviceCaps.wPid}, .port = index, .manufacturer = "", .device_name = "", @@ -167,7 +172,7 @@ protected: #if __has_include() && __cpp_lib_jthread >= 201911L #include -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_threaded final : public observer_winmm { @@ -197,7 +202,7 @@ private: #else #include #include -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_threaded final : public observer_winmm { @@ -237,7 +242,7 @@ private: } #endif -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_manual final : public observer_winmm { @@ -257,7 +262,7 @@ public: ~observer_manual() { } }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/winuwp.hpp b/include/libremidi/backends/winuwp.hpp index 48e91b3..898344d 100644 --- a/include/libremidi/backends/winuwp.hpp +++ b/include/libremidi/backends/winuwp.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { struct winuwp_backend { @@ -14,9 +16,9 @@ struct winuwp_backend using midi_out_configuration = winuwp_output_configuration; using midi_observer_configuration = winuwp_observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_UWP; - static const constexpr auto name = "winuwp"; - static const constexpr auto display_name = "Windows UWP"; + static const constexpr std::string_view name = "winuwp"; + static const constexpr std::string_view display_name = "Windows UWP"; static constexpr inline bool available() noexcept { return true; } }; -} // namespace libremidi +} // NAMESPACE_LIBREMIDI diff --git a/include/libremidi/backends/winuwp/config.hpp b/include/libremidi/backends/winuwp/config.hpp index 27bb1a1..a123b57 100644 --- a/include/libremidi/backends/winuwp/config.hpp +++ b/include/libremidi/backends/winuwp/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winuwp_input_configuration diff --git a/include/libremidi/backends/winuwp/helpers.hpp b/include/libremidi/backends/winuwp/helpers.hpp index 380e458..7b876f4 100644 --- a/include/libremidi/backends/winuwp/helpers.hpp +++ b/include/libremidi/backends/winuwp/helpers.hpp @@ -7,13 +7,14 @@ #include #include #include +#include #include #include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { inline void winrt_init() { @@ -23,8 +24,6 @@ inline void winrt_init() // winrt::init_apartment(); } -namespace -{ using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Midi; @@ -36,7 +35,7 @@ using namespace winrt::Windows::Storage::Streams; // get() function from winrt is that we avoid an assertion if waiting // from the STA thread. template -auto get(T const& async) +LIBREMIDI_STATIC auto get(T const& async) { if (async.Status() != AsyncStatus::Completed) { @@ -60,4 +59,3 @@ auto get(T const& async) return async.GetResults(); } } -} diff --git a/include/libremidi/backends/winuwp/midi_in.hpp b/include/libremidi/backends/winuwp/midi_in.hpp index 0ccfbc5..3d70613 100644 --- a/include/libremidi/backends/winuwp/midi_in.hpp +++ b/include/libremidi/backends/winuwp/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_winuwp final @@ -47,7 +47,8 @@ public: midi_start_timestamp = std::chrono::steady_clock::now(); port_.MessageReceived( - [=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort, + [this]( + const winrt::Windows::Devices::Midi::IMidiInPort&, const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) { this->process_message(args.Message()); }); diff --git a/include/libremidi/backends/winuwp/midi_out.hpp b/include/libremidi/backends/winuwp/midi_out.hpp index af7d2e7..48893f0 100644 --- a/include/libremidi/backends/winuwp/midi_out.hpp +++ b/include/libremidi/backends/winuwp/midi_out.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_winuwp final diff --git a/include/libremidi/backends/winuwp/observer.hpp b/include/libremidi/backends/winuwp/observer.hpp index 86eeb59..1921720 100644 --- a/include/libremidi/backends/winuwp/observer.hpp +++ b/include/libremidi/backends/winuwp/observer.hpp @@ -1,16 +1,17 @@ #pragma once #include +#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_winuwp_internal { public: struct port_info { - hstring id; - hstring name; + winrt::hstring id; + winrt::hstring name; }; struct callback { @@ -33,16 +34,16 @@ public: cbs.emplace_back(tk, f); return tk; } + void remove(int tk) { auto it = std::remove_if( cbs.begin(), cbs.end(), [tk](const callback& c) { return c.token == tk; }); - auto r = std::distance(it, cbs.end()); cbs.erase(it, cbs.end()); } }; - explicit observer_winuwp_internal(hstring deviceSelector) { initialize(deviceSelector); } + explicit observer_winuwp_internal(winrt::hstring deviceSelector) { initialize(deviceSelector); } ~observer_winuwp_internal() { terminate(); } std::vector get_ports() const @@ -66,10 +67,10 @@ public: return true; } - hstring get_port_id(unsigned int portNumber) const + winrt::hstring get_port_id(unsigned int portNumber) const { std::lock_guard lock(portListMutex_); - return portNumber < portList_.size() ? portList_[portNumber].id : hstring{}; + return portNumber < portList_.size() ? portList_[portNumber].id : winrt::hstring{}; } std::string get_port_name(unsigned int portNumber) const @@ -97,7 +98,7 @@ private: observer_winuwp_internal& operator=(const observer_winuwp_internal&) = delete; private: - void initialize(hstring deviceSelector) + void initialize(winrt::hstring deviceSelector) { deviceWatcher_ = DeviceInformation::CreateWatcher(deviceSelector); @@ -122,7 +123,7 @@ private: deviceWatcher_.Added(evTokenOnDeviceAdded_); } - void on_device_added(DeviceWatcher sender, DeviceInformation deviceInfo) + void on_device_added(const DeviceWatcher&, const DeviceInformation& deviceInfo) { port_info p; { @@ -133,12 +134,12 @@ private: portAddedEvent_(p); } - void on_device_removed(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) + void on_device_removed(const DeviceWatcher&, const DeviceInformationUpdate& deviceUpdate) { const auto id = deviceUpdate.Id(); auto pred = [&id](const port_info& portInfo) { return portInfo.id == id; }; std::optional p; - hstring name; + winrt::hstring name; { std::lock_guard lock(portListMutex_); auto iter = std::find_if(portList_.begin(), portList_.end(), pred); @@ -152,9 +153,9 @@ private: portRemovedEvent_(*p); } - void on_device_updated(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) { } + void on_device_updated(const DeviceWatcher&, const DeviceInformationUpdate&) { } - void on_device_enumeration_completed(DeviceWatcher sender, IInspectable const&) { } + void on_device_enumeration_completed(const DeviceWatcher&, const IInspectable&) { } private: std::vector portList_; @@ -197,13 +198,13 @@ public: configuration.output_added(p); } - evTokenOnInputAdded_ - = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); }); - evTokenOnInputRemoved_ - = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); }); - evTokenOnOutputAdded_ - = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); }); - evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved( + evTokenOnInputAdded_ = get_internal_in_port_observer().PortAdded( + [this](const port_info& p) { on_input_added(p); }); + evTokenOnInputRemoved_ = get_internal_in_port_observer().PortRemoved( + [this](const port_info& p) { on_input_removed(p); }); + evTokenOnOutputAdded_ = get_internal_out_port_observer().PortAdded( + [this](const port_info& p) { on_output_added(p); }); + evTokenOnOutputRemoved_ = get_internal_out_port_observer().PortRemoved( [this](const port_info& p) { on_output_removed(p); }); } @@ -211,10 +212,10 @@ public: { if (!configuration.has_callbacks()) return; - internalInPortObserver_.PortAdded(evTokenOnInputAdded_); - internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_); - internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_); - internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_); + get_internal_in_port_observer().PortAdded(evTokenOnInputAdded_); + get_internal_in_port_observer().PortRemoved(evTokenOnInputRemoved_); + get_internal_out_port_observer().PortAdded(evTokenOnOutputAdded_); + get_internal_out_port_observer().PortRemoved(evTokenOnOutputRemoved_); } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; } @@ -224,7 +225,8 @@ public: -> std::conditional_t { return { - {.client = 0, + {.api = libremidi::API::WINDOWS_UWP, + .client = 0, .port = 0, .manufacturer = "", .device_name = "", @@ -235,7 +237,7 @@ public: std::vector get_input_ports() const noexcept override { std::vector ret; - for (auto& port : internalInPortObserver_.get_ports()) + for (auto& port : get_internal_in_port_observer().get_ports()) ret.push_back(to_port_info(port)); return ret; } @@ -243,18 +245,20 @@ public: std::vector get_output_ports() const noexcept override { std::vector ret; - for (auto& port : internalOutPortObserver_.get_ports()) + for (auto& port : get_internal_out_port_observer().get_ports()) ret.push_back(to_port_info(port)); return ret; } static observer_winuwp_internal& get_internal_in_port_observer() { + static observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()}; return internalInPortObserver_; } static observer_winuwp_internal& get_internal_out_port_observer() { + static observer_winuwp_internal internalOutPortObserver_{MidiOutPort::GetDeviceSelector()}; return internalOutPortObserver_; } @@ -283,10 +287,6 @@ public: } private: - static inline observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()}; - static inline observer_winuwp_internal internalOutPortObserver_{ - MidiOutPort::GetDeviceSelector()}; - int evTokenOnInputAdded_{-1}; int evTokenOnInputRemoved_{-1}; int evTokenOnOutputAdded_{-1}; diff --git a/include/libremidi/client.cpp b/include/libremidi/client.cpp index 4d2c2d2..1e22509 100644 --- a/include/libremidi/client.cpp +++ b/include/libremidi/client.cpp @@ -10,7 +10,7 @@ #ifdef LIBREMIDI_JACK #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE shared_configurations diff --git a/include/libremidi/client.hpp b/include/libremidi/client.hpp index 068c496..1a913c2 100644 --- a/include/libremidi/client.hpp +++ b/include/libremidi/client.hpp @@ -4,8 +4,9 @@ #include #include +#include -namespace libremidi::midi1 +NAMESPACE_LIBREMIDI::midi1 { struct client_configuration { @@ -17,7 +18,7 @@ struct client_configuration //! Set a callback function to be invoked for incoming MIDI messages. //! Mandatory! std::function on_message - = [](const libremidi::input_port& /*port*/, libremidi::message&&) {}; + = [](const libremidi::input_port& /*port*/, libremidi::message&&) { }; //! Observation callbacks for when ports are added or removed input_port_callback input_added; @@ -66,8 +67,8 @@ public: } explicit client(const client_configuration& conf, shared_configurations ctx) - : configuration{conf} - , context{ctx} + : m_configuration{conf} + , m_context{std::move(ctx)} , m_observer{ observer_configuration{ .on_error = conf.on_error, @@ -81,10 +82,10 @@ public: .track_hardware = conf.track_hardware, .track_virtual = conf.track_virtual, .notify_in_constructor = false}, - context.observer} + m_context.observer} { - if (context.context) - context.context->start_processing(); + if (m_context.context) + m_context.context->start_processing(); } ~client() @@ -92,8 +93,8 @@ public: m_inputs.clear(); m_outputs.clear(); - if (context.context) - context.context->stop_processing(); + if (m_context.context) + m_context.context->stop_processing(); } std::vector get_input_ports() const noexcept @@ -116,19 +117,19 @@ public: input_configuration{ .on_message = [this, - port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); }, + port](libremidi::message&& m) { m_configuration.on_message(port, std::move(m)); }, .on_raw_data = {}, .get_timestamp = {}, - .on_error = configuration.on_error, - .on_warning = configuration.on_warning, + .on_error = m_configuration.on_error, + .on_warning = m_configuration.on_warning, - .ignore_sysex = configuration.ignore_sysex, - .ignore_timing = configuration.ignore_timing, - .ignore_sensing = configuration.ignore_sensing, + .ignore_sysex = m_configuration.ignore_sysex, + .ignore_timing = m_configuration.ignore_timing, + .ignore_sensing = m_configuration.ignore_sensing, - .timestamps = configuration.timestamps}, - context.in); + .timestamps = m_configuration.timestamps}, + m_context.in); res.first->second.open_port(port, name); } @@ -141,11 +142,11 @@ public: auto res = m_outputs.try_emplace( port, output_configuration{ - .on_error = configuration.on_error, - .on_warning = configuration.on_warning, + .on_error = m_configuration.on_error, + .on_warning = m_configuration.on_warning, - .timestamps = configuration.timestamps}, - context.out); + .timestamps = m_configuration.timestamps}, + m_context.out); res.first->second.open_port(port, name); } @@ -190,8 +191,8 @@ public: } private: - client_configuration configuration; - shared_configurations context; + client_configuration m_configuration; + shared_configurations m_context; std::map m_inputs; std::map m_outputs; diff --git a/include/libremidi/cmidi2.hpp b/include/libremidi/cmidi2.hpp index 534e2b6..57c80c7 100644 --- a/include/libremidi/cmidi2.hpp +++ b/include/libremidi/cmidi2.hpp @@ -1,15 +1,25 @@ + #ifndef CMIDI2_H_INCLUDED #define CMIDI2_H_INCLUDED +#include + #include #include #include -#define MIDI_2_0_RESERVED 0 -#define JR_TIMESTAMP_TICKS_PER_SECOND 31250 +#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 +// FIXME: remove those global defs that are quite harmful (conflicts with other libraries) +#define MIDI_2_0_RESERVED 0 +#define JR_TIMESTAMP_TICKS_PER_SECOND 31250 #ifdef __cplusplus extern "C" { @@ -235,13 +245,6 @@ enum cmidi2_system_message_status CMIDI2_SYSTEM_STATUS_RESET = 0xFF, }; -// TODO: remove this. Use cmidi2_utility_message_status instead -enum cmidi2_jr_timestamp_status -{ - CMIDI2_JR_CLOCK = 0x10, - CMIDI2_JR_TIMESTAMP = 0x20, -}; - enum cmidi2_utility_message_status { CMIDI2_UTILITY_STATUS_NOOP = 0, @@ -277,7 +280,7 @@ enum cmidi2_flex_data_metadata_text_status CMIDI2_FLEX_DATA_STATUS_ARRANGER_NAME = 7, CMIDI2_FLEX_DATA_STATUS_PUBLISHER_NAME = 8, CMIDI2_FLEX_DATA_STATUS_PRIMARY_PERFORMER_NAME = 9, - CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMAER_NAME = 10, + CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMER_NAME = 10, CMIDI2_FLEX_DATA_STATUS_RECORDING_DATE = 11, CMIDI2_FLEX_DATA_STATUS_RECORDING_LOCATION = 12 }; @@ -311,7 +314,7 @@ enum cmidi2_ump_chord_name_tonic_note CMIDI2_UMP_CHORD_NAME_G = 7, }; -enum cmidi2_ump_chor_name_chord_type +enum cmidi2_ump_chord_name_chord_type { CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0, CMIDI2_UMP_CHORD_TYPE_MAJOR = 1, @@ -344,7 +347,7 @@ enum cmidi2_ump_chor_name_chord_type CMIDI2_UMP_CHORD_TYPE_7_SUSPENDED_4TH = 27, }; -static inline uint8_t cmidi2_ump_get_num_bytes(uint32_t data) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_num_bytes(uint32_t data) { switch (((data & 0xF0000000) >> 28) & 0xF) { @@ -411,7 +414,7 @@ enum cmidi2_ump_function_block_discovery_flags // 7.1 UMP Stream Messages -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap) { cmidi2_ump128_t ret @@ -422,7 +425,7 @@ cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap return ret; } -static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( cmidi2_ump_version_t version, bool isStaticFunctionBlock, uint8_t numFunctionBlocks, bool midi2Capable, bool midi1Capable, bool rxJR, bool txJR) { @@ -436,7 +439,7 @@ static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( return ret; } -static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_device_identity_notification( uint32_t manufacturerIdIn7bitArray, uint8_t deviceFamilyLSB, uint8_t deviceFamilyMSB, uint8_t deviceFamilyModelLSB, uint8_t deviceFamilyModelMSB, uint32_t softwareRevisionIn7bitArray) @@ -452,7 +455,7 @@ static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification( return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14]) { cmidi2_ump128_t ret @@ -464,17 +467,17 @@ cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14]) return ret; } -static inline cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME, name); } -static inline cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID, id); } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR) { cmidi2_ump128_t ret @@ -485,7 +488,7 @@ cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR) return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool txJR) { cmidi2_ump128_t ret @@ -496,7 +499,7 @@ cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool t return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter) { cmidi2_ump128_t ret @@ -507,7 +510,7 @@ cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter) return ret; } -static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_info_notification( bool active, uint8_t numFunctionBlocks, uint8_t uiHint, uint8_t midi1, uint8_t direction, uint8_t firstGroup, uint8_t numSpannedGroup, uint8_t midiCIVersionFormat, uint8_t maxNumSysEx8Streams) @@ -522,12 +525,12 @@ static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification( return ret; } -static inline cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME, name); } -static inline cmidi2_ump128_t cmidi2_ump_start_of_clip() +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_start_of_clip() { cmidi2_ump128_t ret = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) @@ -536,7 +539,7 @@ static inline cmidi2_ump128_t cmidi2_ump_start_of_clip() return ret; } -static inline cmidi2_ump128_t cmidi2_ump_end_of_clip() +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_end_of_clip() { cmidi2_ump128_t ret = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) @@ -546,48 +549,46 @@ static inline cmidi2_ump128_t cmidi2_ump_end_of_clip() } // 7.2 Utility Messages -static inline uint32_t cmidi2_ump_noop(uint8_t group) +LIBREMIDI_STATIC uint32_t cmidi2_ump_noop() { - return (group & 0xF) << 24; + return 0; } -static inline uint32_t cmidi2_ump_jr_clock_direct(uint8_t group, uint32_t senderClockTime) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_clock_direct(uint16_t senderClockTime) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime; + return (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime; } -static inline uint32_t cmidi2_ump_jr_clock(uint8_t group, double senderClockTime) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_clock(double senderClockTime) { uint16_t value = (uint16_t)(senderClockTime * JR_TIMESTAMP_TICKS_PER_SECOND); - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value; + return (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value; } -static inline uint32_t cmidi2_ump_jr_timestamp_direct(uint8_t group, uint16_t senderClockTimestamp) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_timestamp_direct(uint16_t senderClockTimestamp) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) - + senderClockTimestamp; + return (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + senderClockTimestamp; } -static inline uint32_t cmidi2_ump_jr_timestamp(uint8_t group, double senderClockTimestamp) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_timestamp(double senderClockTimestamp) { uint16_t value = (uint16_t)(senderClockTimestamp * JR_TIMESTAMP_TICKS_PER_SECOND); - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value; + return (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value; } -static inline uint32_t cmidi2_ump_dctpq(uint8_t group, uint32_t dctpq) +LIBREMIDI_STATIC uint32_t cmidi2_ump_dctpq(uint32_t dctpq) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq; + return (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq; } -static inline uint32_t cmidi2_ump_dcs(uint8_t group, uint32_t ticks) +LIBREMIDI_STATIC uint32_t cmidi2_ump_dcs(uint32_t ticks) { // Note that unlike JR timestamp delta clockstamps accepts ticks up to 20 bits. - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16) - + (ticks & 0xFFFFF); + return (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16) + (ticks & 0xFFFFF); } // 7.6 System Common and System Real Time Messages -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uint8_t midi1Byte3) { return (CMIDI2_MESSAGE_TYPE_SYSTEM << 28) + ((group & 0xF) << 24) + (status << 16) @@ -595,66 +596,66 @@ cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uin } // 7.3 MIDI 1.0 Channel Voice Messages -static inline int32_t cmidi2_ump_midi1_message( +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_message( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4) { return (CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL << 28) + ((group & 0xF) << 24) + (((code & 0xF0) + (channel & 0xF)) << 16) + ((byte3 & 0x7F) << 8) + (byte4 & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_note_off(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, velocity & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_note_on(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, velocity & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_paf(uint8_t group, uint8_t channel, uint8_t note, uint8_t data) { return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_PAF, channel, note & 0x7F, data & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_cc(uint8_t group, uint8_t channel, uint8_t index, uint8_t data) { return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_CC, channel, index & 0x7F, data & 0x7F); } -static inline int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PROGRAM, channel, program & 0x7F, MIDI_2_0_RESERVED); } -static inline int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_CAF, channel, data & 0x7F, MIDI_2_0_RESERVED); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend_direct(uint8_t group, uint8_t channel, uint16_t data) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend_split(uint8_t group, uint8_t channel, uint8_t dataLSB, uint8_t dataMSB) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PITCH_BEND, channel, dataLSB & 0x7F, dataMSB & 0x7F); } -static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data) { data += 8192; return cmidi2_ump_midi1_message( @@ -662,7 +663,7 @@ static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel } // 7.4 MIDI 2.0 Channel Voice Messages -static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint16_t short1, uint16_t short2) { @@ -672,7 +673,7 @@ static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( + ((uint64_t)short1 << 16) + short2; } -static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_channel_message_8_8_32( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint32_t rest) { return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24) @@ -681,7 +682,7 @@ static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32( + rest; } -static inline uint16_t cmidi2_ump_pitch_7_9(double semitone) +LIBREMIDI_STATIC uint16_t cmidi2_ump_pitch_7_9(double semitone) { double actual = semitone < 0.0 ? 0.0 : semitone >= 128.0 ? 128.0 : semitone; uint16_t dec = (uint16_t)actual; @@ -689,7 +690,7 @@ static inline uint16_t cmidi2_ump_pitch_7_9(double semitone) return (dec << 9) + (int)(microtone * 512.0); } -static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone) +LIBREMIDI_STATIC uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone) { uint16_t ret = (uint16_t)(semitone & 0x7F) << 9; double actual = microtone < 0.0 ? 0.0 : microtone > 1.0 ? 1.0 : microtone; @@ -697,7 +698,7 @@ static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double micro return ret; } -static inline int64_t cmidi2_ump_midi2_note_off( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_note_off( uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity, uint16_t attributeData) { @@ -705,7 +706,7 @@ static inline int64_t cmidi2_ump_midi2_note_off( group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, attributeType, velocity, attributeData); } -static inline int64_t cmidi2_ump_midi2_note_on( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_note_on( uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity, uint16_t attributeData) { @@ -713,56 +714,56 @@ static inline int64_t cmidi2_ump_midi2_note_on( group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, attributeType, velocity, attributeData); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_paf(uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PAF, channel, note & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t cmidi2_ump_midi2_per_note_rcc( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_rcc( uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_RCC, channel, note & 0x7F, index, data); } -static inline int64_t cmidi2_ump_midi2_per_note_acc( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_acc( uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_ACC, channel, note & 0x7F, index, data); } -static inline int64_t cmidi2_ump_midi2_per_note_management( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_management( uint8_t group, uint8_t channel, uint8_t note, uint8_t optionFlags) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_MANAGEMENT, channel, note & 0x7F, optionFlags & 3, 0); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_cc(uint8_t group, uint8_t channel, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_CC, channel, index & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t cmidi2_ump_midi2_rpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_rpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_nrpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_nrpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_relative_rpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_relative_rpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -770,7 +771,7 @@ static inline int64_t cmidi2_ump_midi2_relative_rpn( dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_relative_nrpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_relative_nrpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -778,7 +779,7 @@ static inline int64_t cmidi2_ump_midi2_relative_nrpn( dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_program( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_program( uint8_t group, uint8_t channel, uint8_t optionFlags, uint8_t program, uint8_t bankMSB, uint8_t bankLSB) { @@ -787,13 +788,13 @@ static inline int64_t cmidi2_ump_midi2_program( ((program & 0x7F) << 24) + (bankMSB << 8) + bankLSB); } -static inline int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data) +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_CAF, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED, data); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsignedData) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -801,19 +802,19 @@ cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsi unsignedData); } -static inline int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data) +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data) { return cmidi2_ump_midi2_pitch_bend_direct(group, channel, 0x80000000 + data); } -static inline int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct( uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_PITCH_BEND, channel, note & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_per_note_pitch_bend_direct(group, channel, note, 0x80000000 + data); @@ -821,22 +822,22 @@ cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t not // Common utility functions for sysex support -static inline uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index) { return (uint8_t)(src >> ((7 - index) * 8) & 0xFF); } -static inline uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index) { return (uint8_t)(src >> ((7 - index) * 8) & 0xFF); } -static inline uint8_t cmidi2_ump_sysex_get_num_packets(uint8_t numBytes, uint8_t radix) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex_get_num_packets(size_t numBytes, uint8_t radix) { - return numBytes <= radix ? 1 : numBytes / radix + (numBytes % radix ? 1 : 0); + return numBytes <= radix ? 1 : (numBytes / radix + (numBytes % radix ? 1 : 0)); } -static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) { const uint8_t* bytes = (const uint8_t*)sequence; uint32_t ret = 0; @@ -845,7 +846,7 @@ static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) return ret; } -static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) { const uint8_t* bytes = (const uint8_t*)sequence; uint32_t ret = 0; @@ -854,38 +855,38 @@ static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) return ret; } -static inline bool cmidi2_util_is_platform_little_endian() +LIBREMIDI_STATIC bool cmidi2_util_is_platform_little_endian() { int i = 1; return *(char*)&i; } -static inline uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence) { return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint32_bytes_le(sequence) : cmidi2_ump_read_uint32_bytes_be(sequence); } -static inline uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence) { return ((uint64_t)cmidi2_ump_read_uint32_bytes_le(sequence) << 32) + cmidi2_ump_read_uint32_bytes_le((const uint8_t*)sequence + 4); } -static inline uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence) { return ((uint64_t)cmidi2_ump_read_uint32_bytes_be(sequence) << 32) + cmidi2_ump_read_uint32_bytes_be((const uint8_t*)sequence + 4); } -static inline uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence) { return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint64_bytes_le(sequence) : cmidi2_ump_read_uint64_bytes_be(sequence); } -static inline void cmidi2_ump_sysex_get_packet_of( - uint64_t* result1, uint64_t* result2, uint8_t group, uint8_t numBytes, const void* srcData, +LIBREMIDI_STATIC void cmidi2_ump_sysex_get_packet_of( + uint64_t* result1, uint64_t* result2, uint8_t group, size_t numBytes, const void* srcData, int32_t index, enum cmidi2_message_type messageType, int radix, bool hasStreamId, uint8_t streamId) { @@ -897,7 +898,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( enum cmidi2_sysex_status status; uint8_t size; - if (numBytes <= radix) + if (numBytes <= (size_t)radix) { status = CMIDI2_SYSEX_IN_ONE_UMP; size = numBytes; // single packet message @@ -909,7 +910,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( } else { - uint8_t isEnd = index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; + uint8_t isEnd = (size_t)index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; if (isEnd) { size = numBytes % radix ? numBytes % radix : radix; @@ -927,7 +928,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( dst8[2] = streamId; uint8_t dstOffset = hasStreamId ? 3 : 2; - for (uint8_t i = 0, j = index * radix; i < size; i++, j++) + for (size_t i = 0, j = index * radix; i < size; i++, j++) dst8[i + dstOffset] = src8[j]; *result1 = cmidi2_ump_read_uint64_bytes_be(dst8); @@ -936,7 +937,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( } // 7.7 System Exclusive 7-Bit Messages -static inline uint64_t cmidi2_ump_sysex7_direct( +LIBREMIDI_STATIC uint64_t cmidi2_ump_sysex7_direct( uint8_t group, uint8_t status, uint8_t numBytes, uint8_t data1, uint8_t data2, uint8_t data3, uint8_t data4, uint8_t data5, uint8_t data6) { @@ -947,7 +948,7 @@ static inline uint64_t cmidi2_ump_sysex7_direct( + (data5 << 8) + data6; } -static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) +LIBREMIDI_STATIC uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) { int i = 0; const uint8_t* csrc = (const uint8_t*)srcData; @@ -957,13 +958,13 @@ static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) return i - (csrc[0] == 0xF0 ? 1 : 0); } -static inline uint8_t cmidi2_ump_sysex7_get_num_packets(uint8_t numSysex7Bytes) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex7_get_num_packets(size_t numSysex7Bytes) { return cmidi2_ump_sysex_get_num_packets(numSysex7Bytes, 6); } -static inline uint64_t cmidi2_ump_sysex7_get_packet_of( - uint8_t group, uint8_t numBytes, const void* srcData, int32_t index) +LIBREMIDI_STATIC uint64_t +cmidi2_ump_sysex7_get_packet_of(uint8_t group, size_t numBytes, const void* srcData, int32_t index) { uint64_t result; int srcOffset = numBytes > 0 && ((const uint8_t*)srcData)[0] == 0xF0 ? 1 : 0; @@ -978,11 +979,11 @@ static inline uint64_t cmidi2_ump_sysex7_get_packet_of( // This returns NULL for success, or anything else for failure. typedef void* (*cmidi2_ump_handler_u64)(uint64_t data, void* context); +// Processes sysex7 inputs where we do not always end at F7 and thus takes length as the argument. // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_sysex7_process( - uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context) +LIBREMIDI_STATIC void* cmidi2_ump_sysex7_process_n( + uint8_t group, void* sysex, uint32_t length, cmidi2_ump_handler_u64 sendUMP, void* context) { - int32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex); int32_t numPackets = cmidi2_ump_sysex7_get_num_packets(length); for (int p = 0; p < numPackets; p++) { @@ -994,15 +995,23 @@ static inline void* cmidi2_ump_sysex7_process( return NULL; } +// This returns NULL for success, or anything else that `sendUMP` returns for failure. +LIBREMIDI_STATIC void* cmidi2_ump_sysex7_process( + uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context) +{ + uint32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex); + return cmidi2_ump_sysex7_process_n(group, sysex, length, sendUMP, context); +} + // 7.8 System Exclusive 8-Bit Messages -static inline int8_t cmidi2_ump_sysex8_get_num_packets(uint8_t numBytes) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex8_get_num_packets(size_t numBytes) { return cmidi2_ump_sysex_get_num_packets(numBytes, 13); } -static inline void cmidi2_ump_sysex8_get_packet_of( - uint8_t group, uint8_t streamId, uint8_t numBytes, const void* srcData, int32_t index, +LIBREMIDI_STATIC void cmidi2_ump_sysex8_get_packet_of( + uint8_t group, uint8_t streamId, size_t numBytes, const void* srcData, size_t index, uint64_t* result1, uint64_t* result2) { cmidi2_ump_sysex_get_packet_of( @@ -1017,7 +1026,7 @@ typedef void* (*cmidi2_ump_handler_u128)( uint64_t data1, uint64_t data2, size_t index, void* context); // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_sysex8_process( +LIBREMIDI_STATIC void* cmidi2_ump_sysex8_process( uint8_t group, void* sysex, uint32_t length, uint8_t streamId, cmidi2_ump_handler_u128 sendUMP, void* context) { @@ -1035,21 +1044,21 @@ static inline void* cmidi2_ump_sysex8_process( // 7.9 Mixed Data Set Message -static inline uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS) +LIBREMIDI_STATIC uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS) { uint32_t radix = 14 * 0x10000; return numTotalBytesInMDS / radix + (numTotalBytesInMDS % radix ? 1 : 0); } // Returns -1 if input is out of range -static inline int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk) +LIBREMIDI_STATIC int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk) { if (numTotalBytesinChunk > 14 * 65535) return -1; return numTotalBytesinChunk / 14 + (numTotalBytesinChunk % 14 ? 1 : 0); } -static inline void cmidi2_ump_mds_get_header( +LIBREMIDI_STATIC void cmidi2_ump_mds_get_header( uint8_t group, uint8_t mdsId, uint16_t numBytesInChunk, uint16_t numChunks, uint16_t chunkIndex, uint16_t manufacturerId, uint16_t deviceId, uint16_t subId, uint16_t subId2, uint64_t* result1, uint64_t* result2) @@ -1073,7 +1082,7 @@ static inline void cmidi2_ump_mds_get_header( } // srcData points to exact start of the source data. -static inline void cmidi2_ump_mds_get_payload_of( +LIBREMIDI_STATIC void cmidi2_ump_mds_get_payload_of( uint8_t group, uint8_t mdsId, uint16_t numBytes, const void* srcData, uint64_t* result1, uint64_t* result2) { @@ -1101,7 +1110,7 @@ typedef void* (*cmidi2_mds_handler)( uint64_t data1, uint64_t data2, size_t chunkId, size_t payloadId, void* context); // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_mds_process( +LIBREMIDI_STATIC void* cmidi2_ump_mds_process( uint8_t group, uint8_t mdsId, void* data, uint32_t length, cmidi2_mds_handler sendUMP, void* context) { @@ -1127,12 +1136,12 @@ static inline void* cmidi2_ump_mds_process( // 7.5 Flex Data Message -static inline uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData) +LIBREMIDI_STATIC uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData) { return numTotalBytesInFlexData / 12 + (numTotalBytesInFlexData % 12 ? 1 : 0); } -static inline void cmidi2_ump_flex_data_complete_packet( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_complete_packet( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, uint32_t data1, uint32_t data2, uint32_t data3, uint64_t* result1, uint64_t* result2) { @@ -1147,7 +1156,7 @@ static inline void cmidi2_ump_flex_data_complete_packet( *result2 = ((uint64_t)data2 << 32) + data3; } -static inline void cmidi2_ump_flex_data_get_packet_of( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_get_packet_of( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, uint16_t numBytes, const void* srcData, int32_t currentPacket, uint64_t* result1, uint64_t* result2) @@ -1173,7 +1182,7 @@ static inline void cmidi2_ump_flex_data_get_packet_of( } else { - uint8_t isEnd = currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; + uint8_t isEnd = (size_t)currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; if (isEnd) { size = numBytes % radix ? numBytes % radix : radix; @@ -1202,7 +1211,7 @@ typedef void* (*cmidi2_flex_data_handler)(uint64_t data1, uint64_t data2, void* // This returns NULL for success, or anything else that `sendUMP` returns for failure. // `text` is usually a null-terminated text string, but it may contain `\0` as Melisma in lyricText. Hence we still need `length`. -static inline void* cmidi2_ump_flex_data_process( +LIBREMIDI_STATIC void* cmidi2_ump_flex_data_process( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, const char* text, uint32_t length, cmidi2_flex_data_handler sendUMP, void* context) { @@ -1221,7 +1230,7 @@ static inline void* cmidi2_ump_flex_data_process( // individual flex data message generators -static inline void cmidi2_ump_flex_data_set_tempo_direct( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_tempo_direct( uint8_t group, uint8_t channel, uint32_t tempoIn10NanosecondsPerQN, uint64_t* result1, uint64_t* result2) { @@ -1230,7 +1239,7 @@ static inline void cmidi2_ump_flex_data_set_tempo_direct( CMIDI2_FLEX_DATA_STATUS_SET_TEMPO, tempoIn10NanosecondsPerQN, 0, 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_time_signature( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_time_signature( uint8_t group, uint8_t channel, uint8_t numerator, uint8_t denominator, uint8_t numberOf32thNotes, uint64_t* result1, uint64_t* result2) { @@ -1240,7 +1249,7 @@ static inline void cmidi2_ump_flex_data_set_time_signature( (numerator << 24) + (denominator << 16) + (numberOf32thNotes << 8), 0, 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_metronome( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_metronome( uint8_t group, uint8_t channel, uint8_t clocksPerPrimaryClick, uint8_t barAccent1, uint8_t barAccent2, uint8_t barAccent3, uint8_t subDivisionClicks1, uint8_t subDivisionClicks2, uint64_t* result1, uint64_t* result2) @@ -1252,7 +1261,7 @@ static inline void cmidi2_ump_flex_data_set_metronome( (subDivisionClicks1 << 24) + (subDivisionClicks2 << 16), 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_key_signature( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_key_signature( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t tonicNote, uint64_t* result1, uint64_t* result2) { @@ -1262,7 +1271,7 @@ static inline void cmidi2_ump_flex_data_set_key_signature( result1, result2); } -static inline void cmidi2_ump_flex_data_set_chord_name( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_chord_name( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t chordTonic, uint8_t chordType, uint8_t alter1Type, uint8_t alter1Degree, uint8_t alter2Type, uint8_t alter2Degree, uint8_t alter3Type, uint8_t alter3Degree, uint8_t alter4Type, @@ -1288,18 +1297,18 @@ static inline void cmidi2_ump_flex_data_set_chord_name( typedef uint32_t cmidi2_ump; -static inline void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value) +LIBREMIDI_STATIC void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value) { dst[0] = value; } -static inline void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value) +LIBREMIDI_STATIC void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value) { dst[0] = value >> 32; dst[1] = value & 0xFFFFFFFF; } -static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2) +LIBREMIDI_STATIC void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2) { dst[0] = value1 >> 32; dst[1] = value1 & 0xFFFFFFFF; @@ -1307,7 +1316,7 @@ static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_ dst[3] = value2 & 0xFFFFFFFF; } -static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) { ump += at / 4; switch (at % 4) @@ -1324,12 +1333,12 @@ static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) return 0; // This is unexpected. } -static inline uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump) { return *ump >> 28; } -static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) { switch (cmidi2_ump_get_message_type(ump)) { @@ -1346,267 +1355,272 @@ static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) return 0; // invalid } -static inline uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump) { return (*ump >> 24) & 0xF; } -static inline uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_status_byte(const cmidi2_ump* ump) +{ + return (*ump >> 16) & 0xFF; +} + +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump) { return (*ump >> 16) & 0xF0; } -static inline uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump) { return (*ump >> 16) & 0xF; } -static inline uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump) { return *(ump + 1); } -static inline uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump) { return ((cmidi2_ump_get_byte_at(ump, 1) & 0xF) << 16) + (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2) + cmidi2_ump_get_byte_at(ump, 3) * 0x80; } -static inline uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 4) << 8) + (cmidi2_ump_get_byte_at(ump, 5)); } -static inline uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6) << 8) + (cmidi2_ump_get_byte_at(ump, 7)); } -static inline uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint32_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // absolute RPN or relative RPN -static inline uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } // absolute RPN or relative RPN -static inline uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } // absolute RPN or relative RPN -static inline uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // absolute NRPN or relative NRPN -static inline uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } // absolute NRPN or relative NRPN -static inline uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } // absolute NRPN or relative NRPN -static inline uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 4); } -static inline uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6)); } -static inline uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 7)); } -static inline uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // either per-note or channel -static inline uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 4) << 8) + cmidi2_ump_get_byte_at(ump, 5); } -static inline uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6) << 8) + cmidi2_ump_get_byte_at(ump, 7); } -static inline uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 8) << 8) + cmidi2_ump_get_byte_at(ump, 9); } -static inline uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 10) << 8) + cmidi2_ump_get_byte_at(ump, 11); } -static inline uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 12) << 8) + cmidi2_ump_get_byte_at(ump, 13); } -static inline uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 14) << 8) + cmidi2_ump_get_byte_at(ump, 15); } @@ -1662,13 +1676,13 @@ typedef struct cmidi2_ump_binary_read_state enum cmidi2_ump_binary_reader_result_code resultCode; } cmidi2_ump_binary_read_state; -static inline void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state) +LIBREMIDI_STATIC void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state) { state->dataSize = 0; state->resultCode = CMIDI2_BINARY_READER_RESULT_INCOMPLETE; } -static inline void cmidi2_ump_binary_read_state_init( +LIBREMIDI_STATIC void cmidi2_ump_binary_read_state_init( cmidi2_ump_binary_read_state* state, void* context, uint8_t* dataBuffer, size_t dataCapacity, bool continueOnCompletion) { @@ -1699,7 +1713,7 @@ typedef bool (*cmidi2_ump_binary_read_continuity_checker_func)( cmidi2_ump_binary_read_state* stream, cmidi2_ump* ump); // it is a special copy function that only works with sysex8 memory state at `src` that can access beyond `sizeInBytes`. -static inline void cmidi2_internal_sysex8_copy_data_byte_swapping( +LIBREMIDI_STATIC void cmidi2_internal_sysex8_copy_data_byte_swapping( uint8_t* dst, uint8_t srcHead, uint32_t* srcTail, size_t sizeInBytes) { if (sizeInBytes == 0) @@ -1728,7 +1742,7 @@ static inline void cmidi2_internal_sysex8_copy_data_byte_swapping( /// Parse and store sysex8 binary, using some fine-tuned behavioral functions. /// Return the number of 32-bit ints (number of `cmidi2_ump`s) -static inline size_t cmidi2_ump_get_sysex8_data( +LIBREMIDI_STATIC size_t cmidi2_ump_get_sysex8_data( cmidi2_ump_stream_selector_func streamSelector, void* streamSelectorContext, cmidi2_ump_binary_read_continuity_checker_func continuityChecker, const cmidi2_ump* ump, const size_t umpCapacityInInt) @@ -1798,32 +1812,32 @@ static inline size_t cmidi2_ump_get_sysex8_data( /* byte stream splitter */ -static inline void* cmidi2_ump_sequence_next(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes(ptr)); } // similar to LV2_ATOM Utilities API... -#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next(iter)) -static inline void* cmidi2_ump_sequence_next_le(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next_le(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_le(ptr)); } -#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next_le(iter)) -static inline void* cmidi2_ump_sequence_next_be(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next_be(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_be(ptr)); } -#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next_be(iter)) // -------- @@ -1890,14 +1904,14 @@ typedef struct } cmidi2_profile_id; // Assumes the input value is already 7-bit encoded if required. -static inline void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v) +LIBREMIDI_STATIC void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v) { buf[0] = v & 0xFF; buf[1] = (v >> 8) & 0xFF; } // Assumes the input value is already 7-bit encoded if required. -static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0xFF; buf[1] = (v >> 8) & 0xFF; @@ -1905,20 +1919,20 @@ static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) buf[3] = (v >> 24) & 0xFF; } -static inline void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; } -static inline void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; buf[2] = (v >> 14) & 0x7F; } -static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; @@ -1926,7 +1940,7 @@ static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) buf[3] = (v >> 21) & 0x7F; } -static inline void cmidi2_ci_message_common( +LIBREMIDI_STATIC void cmidi2_ci_message_common( uint8_t* buf, uint8_t destination, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -1941,7 +1955,7 @@ static inline void cmidi2_ci_message_common( // Discovery -static inline void cmidi2_ci_discovery_common( +LIBREMIDI_STATIC void cmidi2_ci_discovery_common( uint8_t* buf, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint32_t deviceManufacturer3Bytes, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, @@ -1962,7 +1976,7 @@ static inline void cmidi2_ci_discovery_common( buf[29] = initiatorOutputPathId; } -static inline void cmidi2_ci_discovery( +LIBREMIDI_STATIC void cmidi2_ci_discovery( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId) @@ -1973,7 +1987,7 @@ static inline void cmidi2_ci_discovery( ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId); } -static inline void cmidi2_ci_discovery_reply( +LIBREMIDI_STATIC void cmidi2_ci_discovery_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, @@ -1986,7 +2000,7 @@ static inline void cmidi2_ci_discovery_reply( buf[30] = functionBlockOr7Fh; } -static inline void cmidi2_ci_discovery_invalidate_muid( +LIBREMIDI_STATIC void cmidi2_ci_discovery_invalidate_muid( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t targetMUID) { cmidi2_ci_message_common( @@ -1994,7 +2008,7 @@ static inline void cmidi2_ci_discovery_invalidate_muid( cmidi2_ci_direct_uint32_at(buf + 13, targetMUID); } -static inline void cmidi2_ci_ack_nak_common( +LIBREMIDI_STATIC void cmidi2_ci_ack_nak_common( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t statusCode, uint8_t statusData, uint8_t* details5Bytes, uint16_t messageLength, const char* messageText) @@ -2009,7 +2023,7 @@ static inline void cmidi2_ci_ack_nak_common( memcpy(buf + 23, messageText, messageLength); } -static inline void cmidi2_ci_ack( +LIBREMIDI_STATIC void cmidi2_ci_ack( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t ackStatusCode, uint8_t ackStatusData, uint8_t* ackDetails5Bytes, uint16_t messageLength, @@ -2020,7 +2034,7 @@ static inline void cmidi2_ci_ack( ackStatusCode, ackStatusData, ackDetails5Bytes, messageLength, messageText); } -static inline void cmidi2_ci_nak( +LIBREMIDI_STATIC void cmidi2_ci_nak( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t nakStatusCode, uint8_t nakStatusData, uint8_t* nakDetails5Bytes, uint16_t messageLength, @@ -2034,7 +2048,7 @@ static inline void cmidi2_ci_nak( // Protocol Negotiation // Note that it was removed in MIDI 2.0 specification June 2023 Updates (MIDI-CI 1.2). -static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info) +LIBREMIDI_STATIC void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info) { buf[0] = info.type; buf[1] = info.version; @@ -2043,7 +2057,7 @@ static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type buf[4] = info.reserved2; } -static inline void cmidi2_ci_protocols( +LIBREMIDI_STATIC void cmidi2_ci_protocols( uint8_t* buf, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes) { buf[0] = numSupportedProtocols; @@ -2051,7 +2065,7 @@ static inline void cmidi2_ci_protocols( cmidi2_ci_protocol_info(buf + 1 + i * 5, protocolTypes[i]); } -static inline void cmidi2_ci_protocol_negotiation( +LIBREMIDI_STATIC void cmidi2_ci_protocol_negotiation( uint8_t* buf, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes) @@ -2065,7 +2079,7 @@ static inline void cmidi2_ci_protocol_negotiation( cmidi2_ci_protocols(buf + 14, numSupportedProtocols, protocolTypes); } -static inline void cmidi2_ci_protocol_set( +LIBREMIDI_STATIC void cmidi2_ci_protocol_set( uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, cmidi2_ci_protocol_type_info newProtocolType) { @@ -2075,7 +2089,7 @@ static inline void cmidi2_ci_protocol_set( cmidi2_ci_protocol_info(buf + 14, newProtocolType); } -static inline void cmidi2_ci_protocol_test( +LIBREMIDI_STATIC void cmidi2_ci_protocol_test( uint8_t* buf, bool isInitiatorToResponder, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, uint8_t* testData48Bytes) { @@ -2088,7 +2102,7 @@ static inline void cmidi2_ci_protocol_test( memcpy(buf + 14, testData48Bytes, 48); } -static inline void cmidi2_ci_protocol_confirm_established( +LIBREMIDI_STATIC void cmidi2_ci_protocol_confirm_established( uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel) { cmidi2_ci_message_common( @@ -2097,7 +2111,7 @@ static inline void cmidi2_ci_protocol_confirm_established( buf[13] = authorityLevel; } -static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length) +LIBREMIDI_STATIC int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length) { return (length != 19 || buf[0] != 0x7E || buf[1] != 0x7F || buf[2] != CMIDI2_CI_SUB_ID || buf[3] != CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL || buf[4] != 1) @@ -2107,7 +2121,7 @@ static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t leng // Profile Configuration -static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) +LIBREMIDI_STATIC void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) { buf[0] = info.fixed_7e; buf[1] = info.bank; @@ -2116,14 +2130,14 @@ static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) buf[4] = info.level; } -static inline void cmidi2_ci_profile_inquiry( +LIBREMIDI_STATIC void cmidi2_ci_profile_inquiry( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID) { cmidi2_ci_message_common( buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY, 1, sourceMUID, destinationMUID); } -static inline void cmidi2_ci_profile_inquiry_reply( +LIBREMIDI_STATIC void cmidi2_ci_profile_inquiry_reply( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t numEnabledProfiles, cmidi2_profile_id* enabledProfiles, uint8_t numDisabledProfiles, cmidi2_profile_id* disabledProfiles) @@ -2139,7 +2153,7 @@ static inline void cmidi2_ci_profile_inquiry_reply( cmidi2_ci_profile(buf + pos + i * 5, disabledProfiles[i]); } -static inline void cmidi2_ci_profile_set( +LIBREMIDI_STATIC void cmidi2_ci_profile_set( uint8_t* buf, uint8_t destination, bool turnOn, uint32_t sourceMUID, uint32_t destinationMUID, cmidi2_profile_id profile) { @@ -2150,7 +2164,7 @@ static inline void cmidi2_ci_profile_set( cmidi2_ci_profile(buf + 13, profile); } -static inline void cmidi2_ci_profile_report( +LIBREMIDI_STATIC void cmidi2_ci_profile_report( uint8_t* buf, uint8_t source, bool isEnabledReport, uint32_t sourceMUID, cmidi2_profile_id profile) { @@ -2162,7 +2176,7 @@ static inline void cmidi2_ci_profile_report( cmidi2_ci_profile(buf + 13, profile); } -static inline void cmidi2_ci_profile_specific_data( +LIBREMIDI_STATIC void cmidi2_ci_profile_specific_data( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID, cmidi2_profile_id profile, uint32_t dataSize, void* data) { @@ -2175,7 +2189,7 @@ static inline void cmidi2_ci_profile_specific_data( // Property Exchange -static inline void cmidi2_ci_property_get_capabilities( +LIBREMIDI_STATIC void cmidi2_ci_property_get_capabilities( uint8_t* buf, uint8_t destination, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t maxSupportedRequests) { @@ -2188,7 +2202,7 @@ static inline void cmidi2_ci_property_get_capabilities( } // common to all of: has data & reply, get data & reply, set data & reply, subscribe & reply, notify -static inline void cmidi2_ci_property_common( +LIBREMIDI_STATIC void cmidi2_ci_property_common( uint8_t* buf, uint8_t destination, uint8_t messageTypeSubId2, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, uint16_t headerSize, void* header, uint16_t numChunks, uint16_t chunkIndex, uint16_t dataSize, void* data) @@ -2203,7 +2217,7 @@ static inline void cmidi2_ci_property_common( memcpy(buf + 22 + headerSize, data, dataSize); } -static inline void cmidi2_ci_property_get_capabilities_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_get_capabilities_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t maxSupportedRequests, uint8_t peMajorVersion, uint8_t peMinorVersion) { @@ -2216,7 +2230,7 @@ static inline void cmidi2_ci_property_get_capabilities_reply( buf[15] = peMinorVersion; } -static inline void cmidi2_ci_property_data_common( +LIBREMIDI_STATIC void cmidi2_ci_property_data_common( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, @@ -2236,7 +2250,7 @@ static inline void cmidi2_ci_property_data_common( memcpy(buf + headerSize + 22, propertyData, propertyDataLength); } -static inline void cmidi2_ci_property_get_data( +LIBREMIDI_STATIC void cmidi2_ci_property_get_data( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData) { @@ -2245,7 +2259,7 @@ static inline void cmidi2_ci_property_get_data( requestId, headerSize, headerData, 1, 1, 0, NULL); } -static inline void cmidi2_ci_property_get_data_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_get_data_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2256,7 +2270,7 @@ static inline void cmidi2_ci_property_get_data_reply( propertyDataLength, propertyData); } -static inline void cmidi2_ci_property_set_data( +LIBREMIDI_STATIC void cmidi2_ci_property_set_data( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2267,7 +2281,7 @@ static inline void cmidi2_ci_property_set_data( propertyData); } -static inline void cmidi2_ci_property_set_data_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_set_data_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData) { @@ -2276,7 +2290,7 @@ static inline void cmidi2_ci_property_set_data_reply( destinationMUID, requestId, headerSize, headerData, 1, 1, 0, NULL); } -static inline void cmidi2_ci_property_subscribe( +LIBREMIDI_STATIC void cmidi2_ci_property_subscribe( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2287,7 +2301,7 @@ static inline void cmidi2_ci_property_subscribe( propertyData); } -static inline void cmidi2_ci_property_subscribe_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_subscribe_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2298,7 +2312,7 @@ static inline void cmidi2_ci_property_subscribe_reply( propertyDataLength, propertyData); } -static inline void cmidi2_ci_property_notify( +LIBREMIDI_STATIC void cmidi2_ci_property_notify( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2311,7 +2325,7 @@ static inline void cmidi2_ci_property_notify( // Process Inquiry -static inline void cmidi2_ci_process_get_capabilities( +LIBREMIDI_STATIC void cmidi2_ci_process_get_capabilities( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -2321,7 +2335,7 @@ static inline void cmidi2_ci_process_get_capabilities( versionAndFormat, sourceMUID, destinationMUID); } -static inline void cmidi2_ci_process_get_capabilities_reply( +LIBREMIDI_STATIC void cmidi2_ci_process_get_capabilities_reply( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t processInquirySupportedFeatures) { @@ -2333,7 +2347,7 @@ static inline void cmidi2_ci_process_get_capabilities_reply( buf[13] = processInquirySupportedFeatures; } -static inline void cmidi2_ci_process_midi_report_common( +LIBREMIDI_STATIC void cmidi2_ci_process_midi_report_common( uint8_t* buf, uint8_t deviceId, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2349,7 +2363,7 @@ static inline void cmidi2_ci_process_midi_report_common( buf[17] = requestedNoteDataMessages; } -static inline void cmidi2_ci_process_get_midi_report( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2360,7 +2374,7 @@ static inline void cmidi2_ci_process_get_midi_report( requestedChannelControllerMessages, requestedNoteDataMessages); } -static inline void cmidi2_ci_process_get_midi_report_reply( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report_reply( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2371,7 +2385,7 @@ static inline void cmidi2_ci_process_get_midi_report_reply( requestedChannelControllerMessages, requestedNoteDataMessages); } -static inline void cmidi2_ci_process_get_midi_report_end( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report_end( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -2383,7 +2397,7 @@ static inline void cmidi2_ci_process_get_midi_report_end( // Miscellaneous MIDI Utilities /** Encodes `value` into `bytes` stream. Returns the length of the encoded value in bytes. */ -static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value) +LIBREMIDI_STATIC uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value) { uint8_t pos = 0; for (;; pos++) @@ -2400,7 +2414,7 @@ static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32 } /** Returns the length of `value` when it would be encoded into a byte stream. */ -static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) +LIBREMIDI_STATIC uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) { for (uint8_t ret = 1;; ret++) { @@ -2413,7 +2427,7 @@ static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) } /** Returns the 7-bit encoded value in `bytes` stream of `length` bytes. */ -static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length) +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length) { uint8_t* start = bytes; uint32_t value = 0; @@ -2430,38 +2444,71 @@ static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_ return value; } -static inline uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length) +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length) { - uint32_t metaLength; - uint8_t* start = bytes; - uint8_t* end = bytes + length; switch (bytes[0]) { - case 0xF0: + case 0xF0: { + uint8_t* start = bytes; + uint8_t* end = bytes + length; for (bytes++; bytes < end; bytes++) if (*bytes == 0xF7) break; bytes++; - break; - case 0xFF: - bytes++; - metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes); - bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength); - break; + return bytes - start; + } + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: + return 2; + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + return 3; + case CMIDI2_SYSTEM_STATUS_TUNE_REQUEST: + case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK: + case CMIDI2_SYSTEM_STATUS_START: + case CMIDI2_SYSTEM_STATUS_CONTINUE: + case CMIDI2_SYSTEM_STATUS_STOP: + case CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING: + case CMIDI2_SYSTEM_STATUS_RESET: // Use cmidi2_midi1_get_message_size_smf to handle meta-events + return 1; default: switch (bytes[0] & 0xF0) { case 0xC0: case 0xD0: - bytes += 2; - break; + return 2; default: - bytes += 3; - break; + return 3; } break; } - return bytes - start; + return 0; +} + +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size_live(uint8_t* bytes, uint32_t length) +{ + // 0xFF in live MIDI stream indicates reset. We handle it there. + return cmidi2_midi1_get_message_size(bytes, length); +} + +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size_smf(uint8_t* bytes, uint32_t length) +{ + switch (bytes[0]) + { + case 0xFF: // 0xFF in standard MIDI file indicates Meta-events, not RESET + { + uint32_t metaLength; + uint8_t* start = bytes; + uint8_t* end = bytes + length; + + bytes++; + metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes); + bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength); + + return bytes - start; + } + default: + return cmidi2_midi1_get_message_size(bytes, length); + } } // MIDI1 to UMP Translator @@ -2531,7 +2578,7 @@ typedef struct cmidi2_midi_conversion_context int32_t context_rpn; int32_t context_nrpn; int32_t context_dte; - // MIDI 2.0 Defalult Translation (UMP specification Appendix D.3) accepts only DTE LSB + // MIDI 2.0 Default Translation (UMP specification Appendix D.3) accepts only DTE LSB // as the conversion terminator, but cmidi2 allows DTE LSB to come first, // if this flag is enabled. bool allow_reordered_dte; @@ -2562,7 +2609,7 @@ enum cmidi2_midi_conversion_result CMIDI2_CONVERSION_RESULT_INVALID_INPUT = 0x40, }; -static inline void +LIBREMIDI_STATIC void cmidi2_midi_conversion_context_initialize(cmidi2_midi_conversion_context* context) { context->is_midi1_smf = false; @@ -2591,17 +2638,18 @@ typedef struct cmidi2_convert_sysex_context size_t dst_offset; } cmidi2_convert_sysex_context; -static inline void* +LIBREMIDI_STATIC void* cmidi2_internal_convert_add_midi1_sysex7_ump_to_list(uint64_t data, void* context) { cmidi2_convert_sysex_context* s7ctx = (cmidi2_convert_sysex_context*)context; s7ctx->conversion_context->ump[s7ctx->dst_offset] = data >> 32; s7ctx->conversion_context->ump[s7ctx->dst_offset + 1] = data & 0xFFFFFFFF; - s7ctx->conversion_context->ump_proceeded_bytes += 2; + s7ctx->conversion_context->ump_proceeded_bytes += 2 * sizeof(cmidi2_ump); + s7ctx->dst_offset += 2; return NULL; } -static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( +LIBREMIDI_STATIC void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( uint64_t data1, uint64_t data2, size_t index, void* context) { (void)index; @@ -2610,11 +2658,12 @@ static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( s8ctx->conversion_context->ump[s8ctx->dst_offset + 1] = data1 & 0xFFFFFFFF; s8ctx->conversion_context->ump[s8ctx->dst_offset + 2] = data2 >> 32; s8ctx->conversion_context->ump[s8ctx->dst_offset + 3] = data2 & 0xFFFFFFFF; - s8ctx->conversion_context->ump_proceeded_bytes += 4; + s8ctx->conversion_context->ump_proceeded_bytes += 4 * sizeof(cmidi2_ump); + s8ctx->dst_offset += 4; return NULL; } -static inline uint64_t +LIBREMIDI_STATIC uint64_t cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context, uint8_t channel) { bool isRpn = (context->context_rpn & 0x8080) == 0; @@ -2629,7 +2678,7 @@ cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context : cmidi2_ump_midi2_nrpn(context->group, channel, msb, lsb, data); } -static inline uint32_t cmidi2_internal_swap_endian(uint32_t v) +LIBREMIDI_STATIC uint32_t cmidi2_internal_swap_endian(uint32_t v) { return ((v & 0xFF) << 24) + (((v >> 8) & 0xFF) << 16) + (((v >> 16) & 0xFF) << 8) + ((v >> 24) & 0xFF); @@ -2651,7 +2700,8 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) { // FIXME: implement deltaTime to JR Timestamp conversion. - if (context->midi1[*sIdx] == 0xF0) + uint8_t status = context->midi1[*sIdx]; + if (status == 0xF0) { // sysex uint8_t* f7 @@ -2682,26 +2732,38 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) context->group, context->midi1 + *sIdx, cmidi2_internal_convert_add_midi1_sysex7_ump_to_list, &sysExCtx); } - *sIdx += sysexSize + 1; // +1 for 0xF7 + *sIdx += sysexSize + 2; // +1 for 0xF0 and 0xF7 } else { // fixed sized message size_t remaining = sLen - *sIdx; - size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, remaining); - if (len > remaining) + size_t inputMidi1Len + = context->is_midi1_smf + ? cmidi2_midi1_get_message_size_smf(context->midi1 + *sIdx, remaining) + : cmidi2_midi1_get_message_size_live(context->midi1 + *sIdx, remaining); + if (inputMidi1Len > remaining) return CMIDI2_CONVERSION_RESULT_INVALID_INPUT; - uint8_t byte2 = context->midi1[*sIdx + 1]; - uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0; - uint8_t channel = context->midi1[*sIdx] & 0xF; + uint8_t byte2 = inputMidi1Len > 1 ? context->midi1[*sIdx + 1] : 0; + uint8_t byte3 = inputMidi1Len > 2 ? context->midi1[*sIdx + 2] : 0; + uint8_t channel = status & 0xF; if (context->midi_protocol == CMIDI2_PROTOCOL_TYPE_MIDI1) { // generate MIDI1 UMPs - dst[*dIdx] = cmidi2_ump_midi1_message( - context->group, context->midi1[*sIdx] & 0xF0, channel, byte2, byte3); - *sIdx += len; - *dIdx += 4; + if (status > 0xF0) + { + dst[*dIdx] = cmidi2_ump_system_message(context->group, status, byte2, byte3); + *sIdx += inputMidi1Len; + *dIdx += 4; + } + else + { + dst[*dIdx] + = cmidi2_ump_midi1_message(context->group, status & 0xF0, channel, byte2, byte3); + *sIdx += inputMidi1Len; + *dIdx += 4; + } } else { @@ -2711,18 +2773,22 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) const int16_t NO_ATTRIBUTE_DATA = 0; bool bankValid, bankMsbValid, bankLsbValid; bool skipEmitUmp = false; - switch (context->midi1[*sIdx] & 0xF0) + int outputUmpLen = 0; + switch (status & 0xF0) { case CMIDI2_STATUS_NOTE_OFF: m2 = cmidi2_ump_midi2_note_off( context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA); + outputUmpLen = 2; break; case CMIDI2_STATUS_NOTE_ON: m2 = cmidi2_ump_midi2_note_on( context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA); + outputUmpLen = 2; break; case CMIDI2_STATUS_PAF: m2 = cmidi2_ump_midi2_paf(context->group, channel, byte2, byte3 << 25); + outputUmpLen = 2; break; case CMIDI2_STATUS_CC: switch (byte2) @@ -2747,7 +2813,10 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) context->context_dte = (context->context_dte & 0xFF) | (byte3 << 8); if (context->allow_reordered_dte && (context->context_dte & 0x8080) == 0) + { m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel); + outputUmpLen = 2; + } else skipEmitUmp = true; @@ -2760,6 +2829,7 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) if ((context->context_rpn & 0x8080) && (context->context_nrpn & 0x8080)) return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE; m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel); + outputUmpLen = 2; break; case CMIDI2_CC_BANK_SELECT: @@ -2772,6 +2842,7 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) break; default: m2 = cmidi2_ump_midi2_cc(context->group, channel, byte2, byte3 << 25); + outputUmpLen = 2; break; } break; @@ -2785,37 +2856,77 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) : CMIDI2_PROGRAM_CHANGE_OPTION_NONE, byte2, bankMsbValid ? context->context_bank >> 8 : 0, bankLsbValid ? context->context_bank & 0x7F : 0); + outputUmpLen = 2; context->context_bank = 0x8080; break; case CMIDI2_STATUS_CAF: m2 = cmidi2_ump_midi2_caf(context->group, channel, byte2 << 25); + outputUmpLen = 2; break; case CMIDI2_STATUS_PITCH_BEND: // Note: Pitch Bend values in the MIDI 1.0 Protocol are presented as Little Endian. m2 = cmidi2_ump_midi2_pitch_bend_direct( context->group, channel, ((byte3 << 7) + byte2) << 18); + outputUmpLen = 2; break; default: - return CMIDI2_CONVERSION_RESULT_INVALID_STATUS; + switch (status) + { + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: + inputMidi1Len = 2; + m2 = cmidi2_ump_system_message(context->group, status, byte2, byte3); + outputUmpLen = 1; + break; + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + inputMidi1Len = 3; + m2 = cmidi2_ump_system_message(context->group, status, byte2, byte3); + outputUmpLen = 1; + break; + case CMIDI2_SYSTEM_STATUS_TUNE_REQUEST: + case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK: + case CMIDI2_SYSTEM_STATUS_START: + case CMIDI2_SYSTEM_STATUS_CONTINUE: + case CMIDI2_SYSTEM_STATUS_STOP: + case CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING: + case CMIDI2_SYSTEM_STATUS_RESET: + inputMidi1Len = 1; + m2 = cmidi2_ump_system_message(context->group, status, 0, 0); + outputUmpLen = 1; + break; + default: + return CMIDI2_CONVERSION_RESULT_INVALID_STATUS; + } + break; } if (!skipEmitUmp) { - int platEndian = cmidi2_util_is_platform_little_endian() - ? CMIDI2_TRANSLATOR_LITTLE_ENDIAN - : CMIDI2_TRANSLATOR_BIG_ENDIAN; - int actualEndian - = context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN - ? context->ump_serialization_endianness - : platEndian; - if (platEndian != actualEndian) - m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32) - | cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF); - *(uint32_t*)(dst + *dIdx) = m2 >> 32; - *dIdx += 4; - *(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF; - *dIdx += 4; + switch (outputUmpLen) + { + case 1: + *(uint32_t*)(dst + *dIdx) = m2; + *dIdx += 4; + break; + case 2: { + int platEndian = cmidi2_util_is_platform_little_endian() + ? CMIDI2_TRANSLATOR_LITTLE_ENDIAN + : CMIDI2_TRANSLATOR_BIG_ENDIAN; + int actualEndian + = context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN + ? context->ump_serialization_endianness + : platEndian; + if (platEndian != actualEndian) + m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32) + | cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF); + *(uint32_t*)(dst + *dIdx) = m2 >> 32; + *dIdx += 4; + *(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF; + *dIdx += 4; + break; + } + } } - *sIdx += len; + *sIdx += inputMidi1Len; } } } @@ -2852,11 +2963,13 @@ static size_t cmidi2_internal_add_midi1_delta_time( /// Convert one single UMP (without JR Timestamp) to MIDI 1.0 Message (without delta time) /// It is a lengthy function, so it is recommended to wrap it in another non-inline function. -static inline size_t cmidi2_convert_single_ump_to_timed_midi1( +LIBREMIDI_STATIC size_t cmidi2_convert_single_ump_to_timed_midi1( uint8_t* dst, size_t maxBytes, cmidi2_ump* ump, int32_t deltaTime, cmidi2_midi_conversion_context* context, uint8_t* sysex7Buffer, size_t* sysex7BufferIndex) { - (void)maxBytes; + if (maxBytes < 1) + return 0; + size_t midiEventSize = 0; uint64_t sysex7U64; uint8_t sysex7NumBytesInUmp; @@ -2871,29 +2984,47 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( switch (messageType) { case CMIDI2_MESSAGE_TYPE_SYSTEM: + statusCode = cmidi2_ump_get_status_byte(ump); CMIDI2_INTERNAL_ADD_DELTA_TIME - midiEventSize = 1; + switch (statusCode) { - case 0xF1: - case 0xF3: - case 0xF9: - dst[1] = cmidi2_ump_get_system_message_byte2(ump); + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + midiEventSize = 3; + break; + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: midiEventSize = 2; break; + default: + midiEventSize = 1; + break; } + if (maxBytes < midiEventSize) + return 0; + dst[0] = statusCode; // no channel filtering + if (midiEventSize >= 2) + dst[1] = cmidi2_ump_get_midi1_byte2(ump); + if (midiEventSize >= 3) + dst[2] = cmidi2_ump_get_midi1_byte3(ump); break; case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: CMIDI2_INTERNAL_ADD_DELTA_TIME - midiEventSize = 3; - dst[1] = cmidi2_ump_get_midi1_byte2(ump); + switch (statusCode) { case 0xC0: case 0xD0: midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; + dst[1] = cmidi2_ump_get_midi1_byte2(ump); break; default: + midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; + dst[1] = cmidi2_ump_get_midi1_byte2(ump); dst[2] = cmidi2_ump_get_midi1_byte3(ump); break; } @@ -2905,6 +3036,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_RPN: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 12; + if (maxBytes < midiEventSize) + return 0; dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC; dst[1] = CMIDI2_CC_RPN_MSB; dst[2] = cmidi2_ump_get_midi2_rpn_msb(ump); @@ -2921,6 +3054,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_NRPN: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 12; + if (maxBytes < midiEventSize) + return 0; dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC; dst[1] = CMIDI2_CC_NRPN_MSB; dst[2] = cmidi2_ump_get_midi2_nrpn_msb(ump); @@ -2938,18 +3073,24 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_NOTE_ON: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_note_note(ump); dst[2] = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; break; case CMIDI2_STATUS_PAF: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_paf_note(ump); dst[2] = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000; break; case CMIDI2_STATUS_CC: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[0] = statusCode | cmidi2_ump_get_channel(ump); dst[1] = cmidi2_ump_get_midi2_cc_index(ump); dst[2] = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000; @@ -2960,6 +3101,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( if (cmidi2_ump_get_midi2_program_options(ump) & CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID) { midiEventSize = 8; + if (maxBytes < midiEventSize) + return 0; dst[6] = dst[0]; // copy dst[7] = cmidi2_ump_get_midi2_program_program(ump); dst[0] = (dst[6] & 0xF) + CMIDI2_STATUS_CC; @@ -2972,17 +3115,23 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( else { midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_program_program(ump); } break; case CMIDI2_STATUS_CAF: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000; break; case CMIDI2_STATUS_PITCH_BEND: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; uint32_t pitchBendV1 = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000; dst[1] = pitchBendV1 % 0x80; dst[2] = pitchBendV1 / 0x80; @@ -3006,6 +3155,9 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( // minimal implementation for single-byte sysex7 CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 1 + cmidi2_ump_get_sysex7_num_bytes(ump); + if (maxBytes < midiEventSize) + return 0; + sysex7U64 = cmidi2_ump_read_uint64_bytes(ump); for (size_t i = 0; i < midiEventSize - 1; i++) dst[i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i); @@ -3020,7 +3172,7 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( } // Left for API backward compatibility. -static inline size_t +LIBREMIDI_STATIC size_t cmidi2_convert_single_ump_to_midi1(uint8_t* dst, size_t maxBytes, cmidi2_ump* ump) { return cmidi2_convert_single_ump_to_timed_midi1(dst, maxBytes, ump, 0, NULL, NULL, 0); @@ -3103,7 +3255,7 @@ typedef struct cmidi2_ump_forge size_t offset; } cmidi2_ump_forge; -static inline void +LIBREMIDI_STATIC void cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capacityInBytes) { forge->ump = buffer; @@ -3111,7 +3263,7 @@ cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capaci forge->offset = 0; } -static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump) { int size = 4; if (forge->offset + size > forge->capacity) @@ -3122,7 +3274,7 @@ static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint3 return true; } -static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump) { int size = 8; if (forge->offset + size > forge->capacity) @@ -3133,7 +3285,7 @@ static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint6 return true; } -static inline bool +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t ump2) { int size = 16; @@ -3145,7 +3297,7 @@ cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t return true; } -static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump) { int size = cmidi2_ump_get_message_size_bytes(ump); if (forge->offset + size > forge->capacity) @@ -3155,7 +3307,7 @@ static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, c return true; } -static inline bool +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t size) { if (forge->offset + size > forge->capacity) @@ -3167,7 +3319,7 @@ cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t s // UMP sequence merger -static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time( +LIBREMIDI_STATIC bool cmidi2_internal_ump_merge_sequence_write_delta_time( int32_t timestamp1, int32_t timestamp2, int32_t* lastTimestamp, void* dst, int32_t* dIdx, size_t dstSize) { @@ -3179,14 +3331,14 @@ static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time( { cmidi2_ump_write32( (cmidi2_ump*)((uint8_t*)dst + *dIdx), - cmidi2_ump_jr_timestamp_direct(0, deltaTime > 0xFFFF ? 0xFFFF : deltaTime)); + cmidi2_ump_jr_timestamp_direct(deltaTime > 0xFFFF ? 0xFFFF : deltaTime)); *dIdx += 4; } *lastTimestamp += deltaTime; return true; } -static inline size_t cmidi2_ump_merge_sequences( +LIBREMIDI_STATIC size_t cmidi2_ump_merge_sequences( cmidi2_ump* dst, size_t dstCapacity, cmidi2_ump* seq1, size_t seq1Size, cmidi2_ump* seq2, size_t seq2Size) { diff --git a/include/libremidi/config.hpp b/include/libremidi/config.hpp index e845d4b..94367d8 100644 --- a/include/libremidi/config.hpp +++ b/include/libremidi/config.hpp @@ -1,11 +1,51 @@ #pragma once -#if defined(MSC_VER) +#define LIBREMIDI_VERSION "5.4.3" + +// clang-format off +#if !defined(LIBREMIDI_BASE_NAMESPACE) + #define LIBREMIDI_BASE_NAMESPACE libremidi +#endif +#if defined(LIBREMIDI_MODULE_BUILD) + #define LIBREMIDI_MODULE_EXPORT export + #define LIBREMIDI_MODULE_BEGIN_EXPORT export { + #define LIBREMIDI_MODULE_END_EXPORT } + #define NAMESPACE_LIBREMIDI export namespace LIBREMIDI_BASE_NAMESPACE + #define NAMESPACE_STDX export namespace stdx + #define LIBREMIDI_STATIC inline + #define LIBREMIDI_STATIC_IMPLEMENTATION inline + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION inline + #define LIBREMIDI_INLINE_IMPLEMENTATION + #define LIBREMIDI_ANONYMOUS_NAMESPACE detail +#else + #define LIBREMIDI_MODULE_EXPORT + #define LIBREMIDI_MODULE_BEGIN_EXPORT + #define LIBREMIDI_MODULE_END_EXPORT + #define NAMESPACE_LIBREMIDI namespace LIBREMIDI_BASE_NAMESPACE + #define NAMESPACE_STDX namespace stdx + #define LIBREMIDI_STATIC static inline + +#if defined(LIBREMIDI_HEADER_ONLY) + #define LIBREMIDI_STATIC_IMPLEMENTATION static + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION static inline + #define LIBREMIDI_INLINE_IMPLEMENTATION inline +#else + #define LIBREMIDI_STATIC_IMPLEMENTATION static + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION static + #define LIBREMIDI_INLINE_IMPLEMENTATION +#endif + + #define LIBREMIDI_ANONYMOUS_NAMESPACE +#endif +// clang-format on + +#if defined(_MSC_VER) #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN #endif -#if defined(LIBREMIDI_EXPORTS) +// Dynamic exports +#if defined(LIBREMIDI_EXPORTS) || defined(LIBREMIDI_MODULE_BUILD) #if defined(_MSC_VER) #define LIBREMIDI_EXPORT __declspec(dllexport) #elif defined(__GNUC__) || defined(__clang__) @@ -15,8 +55,7 @@ #define LIBREMIDI_EXPORT #endif -#define LIBREMIDI_VERSION "4.5.0" - +// Boost check to prevent ABI issues #if defined(LIBREMIDI_USE_BOOST) #if !__has_include() #error \ @@ -28,31 +67,77 @@ #endif #endif +// Use boost::small_vector if available #if __has_include() && !defined(LIBREMIDI_NO_BOOST) - #if LIBREMIDI_SLIM_MESSAGE > 0 #include -namespace libremidi +NAMESPACE_LIBREMIDI { using midi_bytes = boost::container::static_vector; } #else #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static constexpr int small_vector_minimum_size +LIBREMIDI_STATIC constexpr int small_vector_minimum_size = sizeof(boost::container::small_vector); using midi_bytes = boost::container::small_vector; } #endif #else #include -namespace libremidi +NAMESPACE_LIBREMIDI { 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) + #include + #define LIBREMIDI_VARIANT_IS_BOOST_VARIANT2 +namespace libremidi_variant_alias = boost::variant2; +#endif + +#if !defined(LIBREMIDI_VARIANT_IS_BOOST_VARIANT2) + #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; +} + #if __has_include() && defined(LIBREMIDI_USE_NI_MIDI2) #define LIBREMIDI_NI_MIDI2_COMPAT 1 #endif @@ -62,3 +147,9 @@ using midi_bytes = std::vector; #else #define LIBREMIDI_INLINE #endif + +#if __cpp_contracts >= 202502L + #define LIBREMIDI_PRECONDITION(...) pre(__VA_ARGS__) +#else + #define LIBREMIDI_PRECONDITION(...) +#endif diff --git a/include/libremidi/configurations.hpp b/include/libremidi/configurations.hpp index b872f41..9af9fba 100644 --- a/include/libremidi/configurations.hpp +++ b/include/libremidi/configurations.hpp @@ -1,24 +1,90 @@ #pragma once +#include #include #include #include #include +#include #include #include #include #include +#include +#include #include #include #include +#include #include #include #include +#include -namespace libremidi +#include +NAMESPACE_LIBREMIDI { - +struct unspecified_configuration +{ +}; struct dummy_configuration { }; + +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>; + +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, + coremidi_ump::output_configuration, emscripten_output_configuration, jack_output_configuration, + kdmapi::output_configuration, libremidi::net::dgram_output_configuration, + libremidi::net_ump::dgram_output_configuration, pipewire_output_configuration, + winmidi::output_configuration, winmm_output_configuration, winuwp_output_configuration, + jack_ump::output_configuration, pipewire_ump::output_configuration, + android::output_configuration, libremidi::API>; + +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, + coremidi_ump::observer_configuration, emscripten_observer_configuration, + jack_observer_configuration, kdmapi::observer_configuration, + libremidi::net::dgram_observer_configuration, libremidi::net_ump::dgram_observer_configuration, + pipewire_observer_configuration, winmidi::observer_configuration, winmm_observer_configuration, + winuwp_observer_configuration, jack_ump::observer_configuration, + pipewire_ump::observer_configuration, android::observer_configuration, libremidi::API>; + +LIBREMIDI_EXPORT +libremidi::API midi_api(const input_api_configuration& conf); +LIBREMIDI_EXPORT +libremidi::API midi_api(const output_api_configuration& conf); +LIBREMIDI_EXPORT +libremidi::API midi_api(const observer_api_configuration& conf); + +void set_client_name(auto&& conf, std::string name) +{ + std::visit([&](auto& impl) { + if constexpr (requires { impl.client_name; }) + impl.client_name = std::move(name); + }, conf); +} + +std::optional client_name(const auto& conf) noexcept +{ + return std::visit([&](auto& impl) -> std::optional { + if constexpr (requires { impl.client_name; }) + return impl.client_name; + return std::nullopt; + }, conf); +} } diff --git a/include/libremidi/defaults.hpp b/include/libremidi/defaults.hpp index 6d380dc..f50e267 100644 --- a/include/libremidi/defaults.hpp +++ b/include/libremidi/defaults.hpp @@ -1,14 +1,14 @@ #pragma once #include #include +#include #include #include #include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in; class midi_out; @@ -16,22 +16,22 @@ class observer; //! Get the default midi in configuration object for a given API LIBREMIDI_EXPORT -std::any midi_in_configuration_for(libremidi::API); +input_api_configuration midi_in_configuration_for(libremidi::API); //! Get the default midi out configuration object for a given API LIBREMIDI_EXPORT -std::any midi_out_configuration_for(libremidi::API); +output_api_configuration midi_out_configuration_for(libremidi::API); //! Get the default observer configuration object for a given API LIBREMIDI_EXPORT -std::any observer_configuration_for(libremidi::API); +observer_api_configuration observer_configuration_for(libremidi::API); //! Get a matching midi in configuration object for a given observer instance. LIBREMIDI_EXPORT -std::any midi_in_configuration_for(const libremidi::observer&); +input_api_configuration midi_in_configuration_for(const libremidi::observer&); //! Get a matching midi out configuration object for a given observer instance. LIBREMIDI_EXPORT -std::any midi_out_configuration_for(const libremidi::observer&); +output_api_configuration midi_out_configuration_for(const libremidi::observer&); //! Get the default port for midi input (if any) in a given API. //! Note that this will only return a hardware / physical port. @@ -47,15 +47,15 @@ namespace midi1 { //! Get a default configuration for creating a MIDI 1 input LIBREMIDI_EXPORT -std::any in_default_configuration(); +input_api_configuration in_default_configuration(); //! Get a default configuration for creating a MIDI 1 output LIBREMIDI_EXPORT -std::any out_default_configuration(); +output_api_configuration out_default_configuration(); //! Get a default configuration for creating a MIDI 1 observer LIBREMIDI_EXPORT -std::any observer_default_configuration(); +observer_api_configuration observer_default_configuration(); //! Get a default MIDI 1 input port for the default API of the system inline std::optional in_default_port() noexcept @@ -74,15 +74,15 @@ namespace midi2 { //! Get a default configuration for creating a MIDI 2 input LIBREMIDI_EXPORT -std::any in_default_configuration(); +input_api_configuration in_default_configuration(); //! Get a default configuration for creating a MIDI 2 output LIBREMIDI_EXPORT -std::any out_default_configuration(); +output_api_configuration out_default_configuration(); //! Get a default configuration for creating a MIDI 2 observer LIBREMIDI_EXPORT -std::any observer_default_configuration(); +observer_api_configuration observer_default_configuration(); //! Get a default MIDI 2 input port for the default API of the system inline std::optional in_default_port() noexcept diff --git a/include/libremidi/detail/conversion.hpp b/include/libremidi/detail/conversion.hpp index d627c30..eab7f13 100644 --- a/include/libremidi/detail/conversion.hpp +++ b/include/libremidi/detail/conversion.hpp @@ -7,8 +7,251 @@ #include #include #include -namespace libremidi + +#include + +NAMESPACE_LIBREMIDI { +struct cmidi2_error_domain : public stdx::error_domain +{ +public: + constexpr cmidi2_error_domain() noexcept + : error_domain{{0x636d696469325f5fULL, 0x636f6e7665727400ULL}} + { + } + + stdx::string_ref name() const noexcept override { return "cmidi2_conversion"; } + + bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override + { + if (lhs.domain() == rhs.domain()) + return error_cast(lhs) + == error_cast(rhs); + + return false; + } + + stdx::string_ref message(const stdx::error& e) const noexcept override + { + const auto status = error_cast(e); + switch (status) + { + case CMIDI2_CONVERSION_RESULT_OK: + return "Success"; + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + return "Destination buffer is too small"; + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + return "Invalid System Exclusive message format"; + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + return "Invalid Data Entry, RPN, or NRPN sequence"; + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + return "Invalid or unsupported MIDI status byte"; + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + return "Incomplete 7-bit System Exclusive message"; + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return "Invalid input data length or content"; + default: + return "Unknown cmidi2 conversion error"; + } + } +}; + +inline stdx::error from_cmidi2_result(cmidi2_midi_conversion_result ret) noexcept +{ + static constexpr cmidi2_error_domain domain{}; + // We explicitly map OK to success (0 value error) just in case, + // though usually error{} is default constructed for success. + if (ret == CMIDI2_CONVERSION_RESULT_OK) + return stdx::error{}; + + return {ret, domain}; +} +} + +LIBREMIDI_STATIC void cmidi2_reverse(int64_t v, cmidi2_ump* output) +{ + union + { + uint32_t u[2]; + int64_t i; + } e0, e1; + e0.i = v; + e1.u[0] = e0.u[1]; + e1.u[1] = e0.u[0]; + memcpy(output, &e1, 8); +} + +LIBREMIDI_STATIC auto u7_to_u16(uint8_t in) -> uint16_t +{ + static constexpr auto ratio = float(std::numeric_limits::max()) / 127.f; + return std::clamp(std::round(in * ratio), 0.f, float(std::numeric_limits::max())); +}; + +LIBREMIDI_STATIC auto u7_to_u32(uint8_t in) -> uint32_t +{ + static constexpr auto ratio = double(std::numeric_limits::max()) / 127.; + return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); +}; + +LIBREMIDI_STATIC auto u16_to_u32(uint16_t in) -> uint32_t +{ + static constexpr auto ratio = double(std::numeric_limits::max()) + / double(std::numeric_limits::max()); + return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); +}; + +LIBREMIDI_STATIC bool +cmidi2_midi1_channel_voice_to_midi2(const uint8_t* bytes, std::size_t sz, cmidi2_ump* output) +{ + if (sz < 2) + return false; + if (sz > 3) + return false; + const auto status = libremidi::message_type(bytes[0] & 0xF0); + const int channel = bytes[0] & 0x0F; + const int group = 0; + switch (status) + { + case libremidi::message_type::NOTE_OFF: { + const int64_t msg + = cmidi2_ump_midi2_note_off(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::NOTE_ON: { + const int64_t msg + = cmidi2_ump_midi2_note_on(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::POLY_PRESSURE: { + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, bytes[1], u7_to_u32(bytes[2])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::CONTROL_CHANGE: { + const int64_t msg = cmidi2_ump_midi2_cc(group, channel, bytes[1], u7_to_u32(bytes[2])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::PROGRAM_CHANGE: { + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, bytes[1], 0, 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::AFTERTOUCH: { + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(bytes[1])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::PITCH_BEND: { + const auto pb = bytes[1] + bytes[2] * 0x80; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb)); + cmidi2_reverse(msg, output); + break; + } + default: + return false; + } + + return true; +} + +LIBREMIDI_STATIC bool +cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(const cmidi2_ump* input, cmidi2_ump* output) +{ + if (cmidi2_ump_get_message_type(input) != CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL) + return false; + + const auto group = cmidi2_ump_get_group(input); + const auto channel = cmidi2_ump_get_channel(input); + const auto status = cmidi2_ump_get_status_code(input); + const auto b1 = cmidi2_ump_get_midi1_byte2(input); + const auto b2 = cmidi2_ump_get_midi1_byte3(input); + + switch (status) + { + // Not in midi 1: + case CMIDI2_STATUS_PER_NOTE_RCC: + case CMIDI2_STATUS_PER_NOTE_ACC: + case CMIDI2_STATUS_RPN: + case CMIDI2_STATUS_NRPN: + case CMIDI2_STATUS_RELATIVE_RPN: + case CMIDI2_STATUS_RELATIVE_NRPN: + case CMIDI2_STATUS_PER_NOTE_PITCH_BEND: + case CMIDI2_STATUS_PER_NOTE_MANAGEMENT: + return false; + + case CMIDI2_STATUS_NOTE_OFF: { + const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, b1, 0, u7_to_u16(b2), 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_NOTE_ON: { + const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, b1, 0, u7_to_u16(b2), 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PAF: { + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, b1, u7_to_u32(b2)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_CC: { + int64_t msg = cmidi2_ump_midi2_cc(group, channel, b1, u7_to_u32(b2)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PROGRAM: { + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, b1, 0, 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_CAF: { + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(b1)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PITCH_BEND: { + const auto pb = b1 + b2 * 0x80; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb)); + cmidi2_reverse(msg, output); + break; + } + } + + return true; +} + +NAMESPACE_LIBREMIDI +{ +LIBREMIDI_STATIC libremidi::message midi1_from_ump(libremidi::ump u) +{ + libremidi::message ret; + ret.bytes.resize(4); + if (auto n = cmidi2_convert_single_ump_to_midi1((uint8_t*)ret.bytes.data(), 4, u.data)) + { + ret.timestamp = u.timestamp; + ret.bytes.resize(n); + } + else + { + ret.bytes.clear(); + } + return ret; +} + +LIBREMIDI_STATIC libremidi::ump ump_from_midi1(libremidi::message u) +{ + libremidi::ump ret; + if (!u.bytes.empty()) + { + ret.timestamp = u.timestamp; + cmidi2_midi1_channel_voice_to_midi2(u.bytes.data(), u.bytes.size(), ret.data); + } + return ret; +} + struct midi1_to_midi2 { stdx::error @@ -20,11 +263,27 @@ struct midi1_to_midi2 context.ump = ump; context.ump_num_bytes = sizeof(ump); context.ump_proceeded_bytes = 0; + context.skip_delta_time = true; - if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK) - return std::errc::invalid_argument; - - return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp); + switch (auto err = cmidi2_convert_midi1_to_ump(&context)) + { + case CMIDI2_CONVERSION_RESULT_OK: { + // FIXME handle sysex here + if (auto n = context.ump_proceeded_bytes; n > 0) + return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp); + else + return std::errc::no_message; + } + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return from_cmidi2_result(err); + default: + return std::errc::operation_not_supported; + } } cmidi2_midi_conversion_context context = [] { @@ -32,20 +291,40 @@ struct midi1_to_midi2 cmidi2_midi_conversion_context_initialize(&tmp); return tmp; }(); + uint32_t ump[65536 / 4]; }; struct midi2_to_midi1 { - stdx::error - convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi) + stdx::error convert(const uint32_t* message, std::size_t size, int64_t timestamp, auto on_midi) { - auto n - = cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast(message)); - if (n > 0) - return on_midi(midi, n, timestamp); - else - return std::errc::no_buffer_space; + context.midi1 = static_cast(midi); + context.midi1_num_bytes = sizeof(midi); + context.midi1_proceeded_bytes = 0; + context.ump = const_cast(message); + context.ump_num_bytes = size * sizeof(uint32_t); + context.ump_proceeded_bytes = 0; + context.skip_delta_time = true; + + switch (auto err = cmidi2_convert_ump_to_midi1(&context)) + { + case CMIDI2_CONVERSION_RESULT_OK: { + if (auto n = context.midi1_proceeded_bytes; n > 0) + return on_midi(midi, n, timestamp); + else + return std::errc::no_message; + } + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return from_cmidi2_result(err); + default: + return std::errc::operation_not_supported; + } } cmidi2_midi_conversion_context context = [] { @@ -53,6 +332,7 @@ struct midi2_to_midi1 cmidi2_midi_conversion_context_initialize(&tmp); return tmp; }(); + uint8_t midi[65536]; }; diff --git a/include/libremidi/detail/memory.hpp b/include/libremidi/detail/memory.hpp index d398c42..02ddc27 100644 --- a/include/libremidi/detail/memory.hpp +++ b/include/libremidi/detail/memory.hpp @@ -1,9 +1,11 @@ #pragma once +#include + #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { template struct deleter diff --git a/include/libremidi/detail/midi_api.hpp b/include/libremidi/detail/midi_api.hpp index 2e5ccab..3225da4 100644 --- a/include/libremidi/detail/midi_api.hpp +++ b/include/libremidi/detail/midi_api.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_api { @@ -23,14 +23,7 @@ public: { return std::errc::function_not_supported; } - virtual stdx::error set_client_name(std::string_view) - { - return std::errc::function_not_supported; - } - virtual stdx::error set_port_name(std::string_view) - { - return std::errc::function_not_supported; - } + virtual stdx::error set_port_name(std::string_view) { return std::errc::function_not_supported; } virtual stdx::error close_port() = 0; diff --git a/include/libremidi/detail/midi_in.hpp b/include/libremidi/detail/midi_in.hpp index 29f7307..7f88b87 100644 --- a/include/libremidi/detail/midi_in.hpp +++ b/include/libremidi/detail/midi_in.hpp @@ -1,10 +1,10 @@ #pragma once #include -#include -#include #include +#include +#include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_api : public midi_api { diff --git a/include/libremidi/detail/midi_out.hpp b/include/libremidi/detail/midi_out.hpp index 7966a14..74a5b3d 100644 --- a/include/libremidi/detail/midi_out.hpp +++ b/include/libremidi/detail/midi_out.hpp @@ -6,7 +6,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_api : public midi_api diff --git a/include/libremidi/detail/midi_stream_decoder.hpp b/include/libremidi/detail/midi_stream_decoder.hpp index b69fc6b..cea4644 100644 --- a/include/libremidi/detail/midi_stream_decoder.hpp +++ b/include/libremidi/detail/midi_stream_decoder.hpp @@ -1,16 +1,18 @@ #pragma once +#include +#include #include -#include +#include + #include #include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static inline int64_t system_ns() noexcept +LIBREMIDI_STATIC int64_t system_ns() noexcept { namespace clk = std::chrono; return clk::duration_cast(clk::steady_clock::now().time_since_epoch()).count(); @@ -29,7 +31,7 @@ struct timestamp_backend_info bool has_samples{}; }; -template +template struct input_state_machine_base { const Configuration& configuration; @@ -107,12 +109,12 @@ struct input_state_machine : input_state_machine_base { message.bytes.clear(); message.timestamp = {}; - state = main; + m_state = main; } bool has_finished_sysex(std::span bytes) const noexcept { - return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7)); + return (((bytes.front() == 0xF0) || (m_state == in_sysex)) && (bytes.back() == 0xF7)); } // Function to process a byte stream which may contain multiple successive @@ -139,22 +141,22 @@ private: void on_bytes_multi_segmented( const message_callback& cb, std::span bytes, int64_t timestamp) { - int64_t nBytes = bytes.size(); - int64_t iByte = 0; + int64_t n_bytes = bytes.size(); + int64_t i_byte = 0; const bool finished_sysex = has_finished_sysex(bytes); - switch (state) + switch (m_state) { case in_sysex: { return on_continue_sysex(cb, bytes, finished_sysex); } case main: { - while (iByte < nBytes) + while (i_byte < n_bytes) { int64_t size = 1; // We are expecting that the next byte in the packet is a status // byte. - const auto status = bytes[iByte]; + const auto status = bytes[i_byte]; if (!(status & 0x80)) break; @@ -170,17 +172,17 @@ private: if (configuration.ignore_sysex) { size = 0; - iByte = nBytes; + i_byte = n_bytes; } else { - size = nBytes - iByte; + size = n_bytes - i_byte; } - if (bytes[nBytes - 1] != 0xF7) + if (bytes[n_bytes - 1] != 0xF7) { // We know per CoreMIDI API there can't be anything else in this packet - state = in_sysex; + m_state = in_sysex; message.assign(bytes.begin(), bytes.begin() + size); message.timestamp = timestamp; return; @@ -192,7 +194,7 @@ private: if (configuration.ignore_timing) { size = 0; - iByte += 2; + i_byte += 2; } else { @@ -209,7 +211,7 @@ private: if (configuration.ignore_timing) { size = 0; - iByte += 1; + i_byte += 1; } else { @@ -222,7 +224,7 @@ private: if (configuration.ignore_sensing) { size = 0; - iByte += 1; + i_byte += 1; } else { @@ -238,14 +240,14 @@ private: // Now process the actual bytes of the message if (size > 0) { - auto begin = bytes.begin() + iByte; + auto begin = bytes.begin() + i_byte; message.assign(begin, begin + size); message.timestamp = timestamp; cb(std::move(message)); message.clear(); - iByte += size; + i_byte += size; } } } @@ -256,7 +258,7 @@ private: const message_callback& cb, std::span bytes, bool finished_sysex) { if (finished_sysex) - state = main; + m_state = main; if (configuration.ignore_sysex) { @@ -283,7 +285,7 @@ private: // SYSEX start case 0xF0: { if (!finished_sysex) - state = in_sysex; + m_state = in_sysex; if (!this->configuration.ignore_sysex) { @@ -328,7 +330,7 @@ private: return; const bool finished_sysex = has_finished_sysex(bytes); - switch (state) + switch (m_state) { case in_sysex: return on_continue_sysex(cb, bytes, finished_sysex); @@ -346,7 +348,7 @@ private: { main, in_sysex - } state{main}; + } m_state{main}; }; } @@ -413,22 +415,20 @@ private: on_bytes_segmented(const ump_callback& cb, std::span bytes, int64_t timestamp) { // Filter according to message type - switch(cmidi2_ump_get_message_type(bytes.data())) + switch (cmidi2_ump_get_message_type(bytes.data())) { - case CMIDI2_MESSAGE_TYPE_UTILITY: - { + case CMIDI2_MESSAGE_TYPE_UTILITY: { // All the utility messages are about timing if (this->configuration.ignore_timing) return; break; } - case CMIDI2_MESSAGE_TYPE_SYSTEM: - { + case CMIDI2_MESSAGE_TYPE_SYSTEM: { if (this->configuration.ignore_timing) { auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); - switch(status) + switch (status) { case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: case CMIDI2_SYSTEM_STATUS_SONG_POSITION: @@ -437,22 +437,33 @@ private: } } - if (this->configuration.ignore_sensing) - { - auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); - if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING) - return; - } - break; + if (this->configuration.ignore_sensing) + { + auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); + if (status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING) + return; + } + break; } case CMIDI2_MESSAGE_TYPE_SYSEX7: - case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS: - { + case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS: { if (this->configuration.ignore_sysex) return; break; } + + case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: { + if (this->configuration.midi1_channel_events_to_midi2) + { + libremidi::ump msg; + cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(bytes.data(), msg.data); + msg.timestamp = timestamp; + cb(std::move(msg)); + return; + } + break; + } } libremidi::ump msg; diff --git a/include/libremidi/detail/observer.hpp b/include/libremidi/detail/observer.hpp index 3a7f138..f891e0d 100644 --- a/include/libremidi/detail/observer.hpp +++ b/include/libremidi/detail/observer.hpp @@ -1,13 +1,13 @@ #pragma once #include #include -#include #include +#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_api { diff --git a/include/libremidi/detail/semaphore.hpp b/include/libremidi/detail/semaphore.hpp index 255c45f..8cc0c56 100644 --- a/include/libremidi/detail/semaphore.hpp +++ b/include/libremidi/detail/semaphore.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include struct semaphore_pair_lock diff --git a/include/libremidi/detail/ump_stream.hpp b/include/libremidi/detail/ump_stream.hpp index 3dd3f2a..fe58af2 100644 --- a/include/libremidi/detail/ump_stream.hpp +++ b/include/libremidi/detail/ump_stream.hpp @@ -2,10 +2,9 @@ #include #include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { enum class segmentation_error { diff --git a/include/libremidi/error.hpp b/include/libremidi/error.hpp index c038d3f..fbc1061 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 @@ -13,10 +17,15 @@ #if __has_include() && (__cpp_lib_source_location >= 201907L) #include -namespace libremidi { using source_location = std::source_location; } +NAMESPACE_LIBREMIDI +{ +using source_location = std::source_location; +} #else -namespace libremidi { -struct source_location { +NAMESPACE_LIBREMIDI +{ +struct source_location +{ static source_location current() noexcept { return {}; } int line() const noexcept { return -1; } int offset() const noexcept { return -1; } @@ -26,9 +35,9 @@ struct source_location { } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { -inline auto from_errc(int ret) noexcept +inline auto from_errc(int64_t ret) noexcept { return static_cast(-ret); } @@ -40,8 +49,10 @@ inline auto from_errc(int ret) noexcept Note that class behaviour is undefined after a critical error (not a warning) is reported. */ -using midi_error_callback = std::function; -using midi_warning_callback = std::function; +using midi_error_callback + = std::function; +using midi_warning_callback + = std::function; } #if !defined(LIBREMIDI_LOG) diff --git a/include/libremidi/error_handler.hpp b/include/libremidi/error_handler.hpp index 9fb8c4e..d3fc2a4 100644 --- a/include/libremidi/error_handler.hpp +++ b/include/libremidi/error_handler.hpp @@ -2,13 +2,13 @@ #include #include #include -#include #include +#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct error_handler { @@ -58,6 +58,8 @@ struct error_handler }; // Needed as apple still doesn't support source_location in xcode 15.3 -#define libremidi_handle_error(config, str) error_impl(config.on_error, str, libremidi::source_location::current()) -#define libremidi_handle_warning(config, str) warning_impl(config.on_warning, str, libremidi::source_location::current()) +#define libremidi_handle_error(config, str) \ + error_impl(config.on_error, str, libremidi::source_location::current()) +#define libremidi_handle_warning(config, str) \ + warning_impl(config.on_warning, str, libremidi::source_location::current()) } diff --git a/include/libremidi/input_configuration.hpp b/include/libremidi/input_configuration.hpp index 574f02f..64666e2 100644 --- a/include/libremidi/input_configuration.hpp +++ b/include/libremidi/input_configuration.hpp @@ -6,7 +6,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { //! Specify how timestamps are handled in the system enum timestamp_mode @@ -123,5 +123,10 @@ struct ump_input_configuration uint32_t ignore_sensing : 1 = true; uint32_t timestamps : 3 = timestamp_mode::Absolute; + + //! Upscale MIDI 1 channel events to MIDI 2 channel events. + //! Note that this only has an effect on Windows with MIDI Services + //! as other platforms already do this by default. + uint32_t midi1_channel_events_to_midi2 : 1 = true; }; } diff --git a/include/libremidi/libremidi-c.cpp b/include/libremidi/libremidi-c.cpp index 1413f70..3062b4e 100644 --- a/include/libremidi/libremidi-c.cpp +++ b/include/libremidi/libremidi-c.cpp @@ -24,10 +24,10 @@ struct libremidi_midi_out_handle libremidi::midi_out self; }; -namespace libremidi +NAMESPACE_LIBREMIDI { -static void assign_error_callback(const auto& src, auto& dst) +LIBREMIDI_STATIC void assign_error_callback(const auto& src, auto& dst) { if (src.callback) { @@ -44,14 +44,14 @@ const char* libremidi_get_version(void) return LIBREMIDI_VERSION; } -void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +void libremidi_midi1_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) { if (!cb) return; libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); }); } -void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +void libremidi_midi2_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) { if (!cb) return; @@ -123,6 +123,16 @@ int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** return 0; } +int libremidi_midi_in_port_handle(const libremidi_midi_in_port* port, uint64_t* handle) +{ + if (!port || !handle) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *handle = static_cast(p.port); + return 0; +} + int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst) { @@ -152,6 +162,16 @@ int libremidi_midi_out_port_name( return 0; } +int libremidi_midi_out_port_handle(const libremidi_midi_out_port* port, uint64_t* handle) +{ + if (!port || !handle) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *handle = static_cast(p.port); + return 0; +} + int libremidi_midi_observer_new( const libremidi_observer_configuration* c, libremidi_api_configuration* api, libremidi_midi_observer_handle** out) @@ -276,11 +296,12 @@ int libremidi_midi_in_new( = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; } - if (c->on_midi1_message.callback) + if (c->version == libremidi_midi_configuration::MIDI1 && c->on_midi1_message.callback) conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) { cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size()); }; - else if (c->on_midi1_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -319,11 +340,12 @@ int libremidi_midi_in_new( = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; } - if (c->on_midi2_message.callback) + if (c->version == libremidi_midi_configuration::MIDI2 && c->on_midi2_message.callback) conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) { cb.callback(cb.context, msg.timestamp, msg.data, msg.size()); }; - else if (c->on_midi2_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -470,7 +492,8 @@ int libremidi_midi_out_send_message( return res != stdx::error{} ? -EIO : 0; } -int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) +int libremidi_midi_out_send_ump( + libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) { if (!out || !msg || sz > std::numeric_limits::max()) return -EINVAL; diff --git a/include/libremidi/libremidi-c.h b/include/libremidi/libremidi-c.h index c43d7e1..bb9e60b 100644 --- a/include/libremidi/libremidi-c.h +++ b/include/libremidi/libremidi-c.h @@ -198,6 +198,10 @@ LIBREMIDI_EXPORT int libremidi_midi_in_port_name( const libremidi_midi_in_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_in_port_handle( + const libremidi_midi_in_port* port, uint64_t* handle); + LIBREMIDI_EXPORT int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst); @@ -209,6 +213,10 @@ LIBREMIDI_EXPORT int libremidi_midi_out_port_name( const libremidi_midi_out_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_out_port_handle( + const libremidi_midi_out_port* port, uint64_t* handle); + /// Observer API LIBREMIDI_EXPORT int libremidi_midi_observer_new( diff --git a/include/libremidi/libremidi.cpp b/include/libremidi/libremidi.cpp index 3cb79ae..fd1427e 100644 --- a/include/libremidi/libremidi.cpp +++ b/include/libremidi/libremidi.cpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE @@ -53,14 +53,13 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name) midi2::for_all_backends([&](auto b) { apis.push_back(b.API); }); return apis; } + LIBREMIDI_INLINE -libremidi::API midi_api(const std::any& conf) +libremidi::API midi_api(const input_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::any_cast(&conf) - || std::any_cast(&conf) - || std::any_cast(&conf)) + if (get_if(&conf)) { ret = T::API; } @@ -68,9 +67,34 @@ libremidi::API midi_api(const std::any& conf) return ret; } LIBREMIDI_INLINE -std::any midi_in_configuration_for(libremidi::API api) +libremidi::API midi_api(const output_api_configuration& conf) { - std::any ret; + libremidi::API ret = libremidi::API::UNSPECIFIED; + midi_any::for_all_backends([&](T) { + if (get_if(&conf)) + { + ret = T::API; + } + }); + return ret; +} +LIBREMIDI_INLINE +libremidi::API midi_api(const observer_api_configuration& conf) +{ + libremidi::API ret = libremidi::API::UNSPECIFIED; + midi_any::for_all_backends([&](T) { + if (get_if(&conf)) + { + ret = T::API; + } + }); + return ret; +} + +LIBREMIDI_INLINE +input_api_configuration midi_in_configuration_for(libremidi::API api) +{ + input_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_in_configuration; ret = conf_type{}; @@ -79,9 +103,9 @@ std::any midi_in_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any midi_out_configuration_for(libremidi::API api) +output_api_configuration midi_out_configuration_for(libremidi::API api) { - std::any ret; + output_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_out_configuration; ret = conf_type{}; @@ -90,9 +114,9 @@ std::any midi_out_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any observer_configuration_for(libremidi::API api) +observer_api_configuration observer_configuration_for(libremidi::API api) { - std::any ret; + observer_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_observer_configuration; ret = conf_type{}; @@ -101,13 +125,13 @@ std::any observer_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any midi_in_configuration_for(const libremidi::observer& obs) +input_api_configuration midi_in_configuration_for(const libremidi::observer& obs) { return midi_in_configuration_for(obs.get_current_api()); } LIBREMIDI_INLINE -std::any midi_out_configuration_for(const libremidi::observer& obs) +output_api_configuration midi_out_configuration_for(const libremidi::observer& obs) { // FIXME reuse context when meaningful, e.g. ALSA, JACK... return midi_out_configuration_for(obs.get_current_api()); @@ -144,19 +168,19 @@ catch (const std::exception& e) namespace midi1 { LIBREMIDI_INLINE -std::any in_default_configuration() +input_api_configuration in_default_configuration() { return midi_in_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any out_default_configuration() +output_api_configuration out_default_configuration() { return midi_out_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any observer_default_configuration() +observer_api_configuration observer_default_configuration() { return observer_configuration_for(default_api()); } @@ -165,19 +189,19 @@ std::any observer_default_configuration() namespace midi2 { LIBREMIDI_INLINE -std::any in_default_configuration() +input_api_configuration in_default_configuration() { return midi_in_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any out_default_configuration() +output_api_configuration out_default_configuration() { return midi_out_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any observer_default_configuration() +observer_api_configuration observer_default_configuration() { return observer_configuration_for(default_api()); } diff --git a/include/libremidi/libremidi.hpp b/include/libremidi/libremidi.hpp index 4085980..c354efa 100644 --- a/include/libremidi/libremidi.hpp +++ b/include/libremidi/libremidi.hpp @@ -58,6 +58,7 @@ */ #include +#include #include #include #include @@ -68,9 +69,8 @@ #include #include #endif -#include -namespace libremidi +NAMESPACE_LIBREMIDI { //! Main class for observing hotplug of MIDI 1.0 and 2.0 devices. //! The callbacks will be called whenever a device is added or removed @@ -84,7 +84,7 @@ public: //! such as jack_observer_configuration, winmm_observer_configuration, etc... //! * if no callbacks are passed, no secondary thread will be created unless absolutely necessary explicit observer(const observer_configuration& conf = {}) noexcept; - explicit observer(observer_configuration conf, std::any api_conf); + explicit observer(const observer_configuration& conf, observer_api_configuration api_conf); observer(const observer&) = delete; observer(observer&& other) noexcept; observer& operator=(const observer&) = delete; @@ -98,7 +98,7 @@ public: [[nodiscard]] std::vector get_output_ports() const noexcept; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; //! Main class for receiving MIDI 1.0 and 2.0 messages. @@ -111,15 +111,15 @@ public: //! Construct a midi_in object with a configuration object for a specific MIDI 1 back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_in(input_configuration conf, std::any api_conf); + explicit midi_in(const input_configuration& conf, const input_api_configuration& api_conf); //! Construct a midi_in object with the default MIDI 2 back-end for the platform - explicit midi_in(ump_input_configuration conf) noexcept; + explicit midi_in(const ump_input_configuration& conf) noexcept; //! Construct a midi_in object with a configuration object for a specific MIDI 2 back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_in(ump_input_configuration conf, std::any api_conf); + explicit midi_in(const ump_input_configuration& conf, const input_api_configuration& api_conf); midi_in(const midi_in&) = delete; midi_in(midi_in&& other) noexcept; @@ -157,7 +157,7 @@ public: timestamp absolute_timestamp() const noexcept; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; //! Main class for sending MIDI 1.0 and 2.0 messages. @@ -170,7 +170,7 @@ public: //! Construct a midi_out object with a configuration object for a specific back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_out(output_configuration conf, std::any api_conf); + explicit midi_out(const output_configuration& conf, const output_api_configuration& api_conf); midi_out(const midi_out&) = delete; midi_out(midi_out&& other) noexcept; @@ -260,7 +260,7 @@ public: stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; } diff --git a/include/libremidi/message.hpp b/include/libremidi/message.hpp index da8366e..182935d 100644 --- a/include/libremidi/message.hpp +++ b/include/libremidi/message.hpp @@ -2,13 +2,14 @@ #include #include +#include #include #include #if defined(__cpp_exceptions) #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { enum class message_type : uint8_t { @@ -121,12 +122,8 @@ struct message auto rbegin() noexcept { return bytes.rbegin(); } auto rend() noexcept { return bytes.rend(); } - bool uses_channel(int channel) const + bool uses_channel(int channel) const LIBREMIDI_PRECONDITION(channel > 0 && channel <= 16) { -#if defined(__cpp_exceptions) - if (channel <= 0 || channel > 16) - throw std::range_error("out of range"); -#endif return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0); } @@ -250,12 +247,12 @@ struct meta_events int notated_32nd_notes_per_beat = 96) { int n = 1; - int powTwo = 0; + int pow_two = 0; while (n < denominator) { n <<= 1; - ++powTwo; + ++pow_two; } return { @@ -263,7 +260,7 @@ struct meta_events 0x58, 0x04, static_cast(numerator), - static_cast(powTwo), + static_cast(pow_two), static_cast(clocks_per_click), static_cast(notated_32nd_notes_per_beat)}; } diff --git a/include/libremidi/midi_in.cpp b/include/libremidi/midi_in.cpp index 29ba76a..1d4d9a3 100644 --- a/include/libremidi/midi_in.cpp +++ b/include/libremidi/midi_in.cpp @@ -7,10 +7,10 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static libremidi::ump_input_configuration +LIBREMIDI_STATIC_IMPLEMENTATION libremidi::ump_input_configuration convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) noexcept { libremidi::ump_input_configuration c2; @@ -32,7 +32,7 @@ convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) return c2; } -static libremidi::input_configuration +LIBREMIDI_STATIC_IMPLEMENTATION libremidi::input_configuration convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_conf) noexcept { libremidi::input_configuration c2; @@ -41,12 +41,11 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c converter.convert( msg.bytes.data(), msg.bytes.size(), msg.timestamp, [cb](const uint32_t* ump, std::size_t n, int64_t ts) { - if(n >= 4) - { - libremidi::ump u{ump[0],ump[1],ump[2],ump[3]}; - u.timestamp = ts; - cb(std::move(u)); - } + libremidi::ump u{ump[0]}; + for (std::size_t i = 1; i < n && i < 4; i++) + u.data[i] = ump[i]; + u.timestamp = ts; + cb(std::move(u)); return stdx::error{}; }); }; @@ -60,15 +59,15 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c return c2; } -static LIBREMIDI_INLINE std::unique_ptr -make_midi_in(auto base_conf, std::any api_conf, auto backends) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends) { std::unique_ptr ptr; assert(base_conf.on_message || base_conf.on_raw_data); auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -80,7 +79,7 @@ make_midi_in(auto base_conf, std::any api_conf, auto backends) } /// MIDI 1 helpers -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in(const input_configuration& base_conf) { for (const auto& api : available_apis()) @@ -117,8 +116,9 @@ make_midi1_in(const input_configuration& base_conf) return std::make_unique(input_configuration{}, dummy_configuration{}); } -static LIBREMIDI_INLINE std::unique_ptr -make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in( + const input_configuration& base_conf, const input_api_configuration& api_conf, + libremidi::API api) { if (libremidi::is_midi1(api)) { @@ -132,14 +132,14 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, li return {}; } -static LIBREMIDI_INLINE std::unique_ptr -make_midi1_in(const input_configuration& base_conf, const std::any& api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi1_in(const input_configuration& base_conf, const input_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_midi1_in(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -161,24 +161,24 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf) /// MIDI 1 constructors LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept - : impl_{make_midi1_in(base_conf)} + : m_impl{make_midi1_in(base_conf)} { } LIBREMIDI_INLINE -midi_in::midi_in(input_configuration base_conf, std::any api_conf) - : impl_{make_midi1_in(base_conf, api_conf)} +midi_in::midi_in(const input_configuration& base_conf, const input_api_configuration& api_conf) + : m_impl{make_midi1_in(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } } /// MIDI 2 helpers -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in(const ump_input_configuration& base_conf) { for (const auto& api : available_ump_apis()) @@ -213,8 +213,9 @@ make_midi2_in(const ump_input_configuration& base_conf) return {}; } -static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( - const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in( + const ump_input_configuration& base_conf, const input_api_configuration& api_conf, + libremidi::API api) { if (is_midi2(api)) { @@ -229,14 +230,14 @@ static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( return {}; } -static LIBREMIDI_INLINE std::unique_ptr -make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi2_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_midi2_in(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -257,45 +258,45 @@ make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf } /// MIDI 2 constructors -LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept - : impl_{make_midi2_in(base_conf)} +LIBREMIDI_INLINE midi_in::midi_in(const ump_input_configuration& base_conf) noexcept + : m_impl{make_midi2_in(base_conf)} { } LIBREMIDI_INLINE -midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf) - : impl_{make_midi2_in(base_conf, api_conf)} +midi_in::midi_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) + : m_impl{make_midi2_in(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE midi_in::~midi_in() = default; LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ + other.m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE stdx::error midi_in::set_port_name(std::string_view portName) { - if(impl_->is_port_open()) - return impl_->set_port_name(portName); + if (m_impl->is_port_open()) + return m_impl->set_port_name(portName); return std::errc::not_connected; } LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); return *this; } @@ -303,23 +304,26 @@ LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept LIBREMIDI_INLINE libremidi::API midi_in::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE stdx::error midi_in::open_port(const input_port& port, std::string_view portName) { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (port.api != get_current_api()) + return std::errc::invalid_argument; + + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_port(port, portName); + auto ret = m_impl->open_port(port, portName); if (ret == stdx::error{}) { - impl_->connected_ = true; - impl_->port_open_ = true; + m_impl->connected_ = true; + m_impl->port_open_ = true; } return ret; } @@ -327,28 +331,28 @@ stdx::error midi_in::open_port(const input_port& port, std::string_view portName LIBREMIDI_INLINE stdx::error midi_in::open_virtual_port(std::string_view portName) { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_virtual_port(portName); + auto ret = m_impl->open_virtual_port(portName); if (ret == stdx::error{}) - impl_->port_open_ = true; + m_impl->port_open_ = true; return ret; } LIBREMIDI_INLINE stdx::error midi_in::close_port() { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - auto ret = impl_->close_port(); + auto ret = m_impl->close_port(); - impl_->connected_ = false; - impl_->port_open_ = false; + m_impl->connected_ = false; + m_impl->port_open_ = false; return ret; } @@ -356,18 +360,18 @@ stdx::error midi_in::close_port() LIBREMIDI_INLINE bool midi_in::is_port_open() const noexcept { - return impl_->is_port_open(); + return m_impl->is_port_open(); } LIBREMIDI_INLINE bool midi_in::is_port_connected() const noexcept { - return impl_->is_port_connected(); + return m_impl->is_port_connected(); } LIBREMIDI_INLINE int64_t midi_in::absolute_timestamp() const noexcept { - return impl_->absolute_timestamp(); + return m_impl->absolute_timestamp(); } } diff --git a/include/libremidi/midi_out.cpp b/include/libremidi/midi_out.cpp index 82b3b7a..205f369 100644 --- a/include/libremidi/midi_out.cpp +++ b/include/libremidi/midi_out.cpp @@ -8,14 +8,14 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static LIBREMIDI_INLINE std::unique_ptr -make_midi_out_impl(auto base_conf, std::any api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +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::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -23,11 +23,12 @@ make_midi_out_impl(auto base_conf, std::any api_conf) return false; }; std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends); - std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); + if (!ptr) + std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); return ptr; } -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi_out(const output_configuration& base_conf) { for (const auto& api : available_apis()) @@ -57,14 +58,14 @@ make_midi_out(const output_configuration& base_conf) return std::make_unique(output_configuration{}, dummy_configuration{}); } -static LIBREMIDI_INLINE std::unique_ptr -make_midi_out(const output_configuration& base_conf, const std::any& api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_midi_out(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -85,35 +86,35 @@ make_midi_out(const output_configuration& base_conf, const std::any& api_conf) } LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept - : impl_{make_midi_out(base_conf)} + : m_impl{make_midi_out(base_conf)} { } LIBREMIDI_INLINE -midi_out::midi_out(output_configuration base_conf, std::any api_conf) - : impl_{make_midi_out(base_conf, api_conf)} +midi_out::midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) + : m_impl{make_midi_out(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi out for the given api"); - impl_ = std::make_unique(output_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE midi_out::~midi_out() = default; LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ + other.m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); return *this; } @@ -121,8 +122,8 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept LIBREMIDI_INLINE stdx::error midi_out::set_port_name(std::string_view portName) const { - if(impl_->is_port_open()) - return impl_->set_port_name(portName); + if (m_impl->is_port_open()) + return m_impl->set_port_name(portName); return std::errc::not_connected; } @@ -130,23 +131,26 @@ stdx::error midi_out::set_port_name(std::string_view portName) const LIBREMIDI_INLINE libremidi::API midi_out::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (port.api != get_current_api()) + return std::errc::invalid_argument; + + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_port(port, portName); + auto ret = m_impl->open_port(port, portName); if (ret == stdx::error{}) { - impl_->connected_ = true; - impl_->port_open_ = true; + m_impl->connected_ = true; + m_impl->port_open_ = true; } return ret; } @@ -154,40 +158,40 @@ stdx::error midi_out::open_port(const output_port& port, std::string_view portNa LIBREMIDI_INLINE stdx::error midi_out::open_virtual_port(std::string_view portName) const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_virtual_port(portName); + auto ret = m_impl->open_virtual_port(portName); if (ret == stdx::error{}) - impl_->port_open_ = true; + m_impl->port_open_ = true; return ret; } LIBREMIDI_INLINE stdx::error midi_out::close_port() const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - auto ret = impl_->close_port(); - impl_->connected_ = false; - impl_->port_open_ = false; + auto ret = m_impl->close_port(); + m_impl->connected_ = false; + m_impl->port_open_ = false; return ret; } LIBREMIDI_INLINE bool midi_out::is_port_open() const noexcept { - return impl_->is_port_open(); + return m_impl->is_port_open(); } LIBREMIDI_INLINE bool midi_out::is_port_connected() const noexcept { - return impl_->is_port_connected(); + return m_impl->is_port_connected(); } LIBREMIDI_INLINE @@ -227,13 +231,13 @@ stdx::error midi_out::send_message(const unsigned char* message, size_t size) co assert(size > 0); #endif - return impl_->send_message(message, size); + return m_impl->send_message(message, size); } LIBREMIDI_INLINE int64_t midi_out::current_time() { - return impl_->current_time(); + return m_impl->current_time(); } LIBREMIDI_INLINE @@ -243,7 +247,7 @@ stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message, assert(size > 0); #endif - return impl_->schedule_message(ts, message, size); + return m_impl->schedule_message(ts, message, size); } LIBREMIDI_INLINE @@ -254,7 +258,7 @@ stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const assert(size <= 4); #endif - return impl_->send_ump(message, size); + return m_impl->send_ump(message, size); } LIBREMIDI_INLINE stdx::error midi_out::send_ump(const libremidi::ump& message) const @@ -321,6 +325,6 @@ stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t s assert(size > 0); #endif - return impl_->schedule_ump(ts, message, size); + return m_impl->schedule_ump(ts, message, size); } } diff --git a/include/libremidi/observer.cpp b/include/libremidi/observer.cpp index 442cffb..156cba9 100644 --- a/include/libremidi/observer.cpp +++ b/include/libremidi/observer.cpp @@ -5,17 +5,19 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { -static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_observer(const auto& base_conf) { for (const auto& api : available_apis()) { try { - if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) - return impl_; + if (auto impl = make_observer(base_conf, observer_configuration_for(api))) + return impl; } catch (const std::exception& e) { @@ -26,8 +28,8 @@ static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_co { try { - if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) - return impl_; + if (auto impl = make_observer(base_conf, observer_configuration_for(api))) + return impl; } catch (const std::exception& e) { @@ -37,11 +39,11 @@ static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_co return std::make_unique(observer_configuration{}, dummy_configuration{}); } -LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf) +LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configuration api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -49,17 +51,19 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf) return false; }; std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends); - std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); + if (!ptr) + std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); return ptr; } -LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf, std::any api_conf) +LIBREMIDI_INLINE std::unique_ptr +make_observer(const auto& base_conf, observer_api_configuration api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_observer(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -80,32 +84,33 @@ LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf, std } LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept - : impl_{make_observer(base_conf)} + : m_impl{make_observer(base_conf)} { } -LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf) - : impl_{make_observer(base_conf, api_conf)} +LIBREMIDI_INLINE +observer::observer(const observer_configuration& base_conf, observer_api_configuration api_conf) + : m_impl{make_observer(base_conf, std::move(api_conf))} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open observer for the given api"); - impl_ = std::make_unique(observer_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(observer_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE observer::observer(observer&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ = std::make_unique( + other.m_impl = std::make_unique( observer_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE observer& observer::operator=(observer&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ = std::make_unique( + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique( observer_configuration{}, dummy_configuration{}); return *this; } @@ -116,18 +121,18 @@ observer::~observer() = default; LIBREMIDI_INLINE libremidi::API observer::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE std::vector observer::get_input_ports() const noexcept { - return impl_->get_input_ports(); + return m_impl->get_input_ports(); } LIBREMIDI_INLINE std::vector observer::get_output_ports() const noexcept { - return impl_->get_output_ports(); + return m_impl->get_output_ports(); } } diff --git a/include/libremidi/observer_configuration.hpp b/include/libremidi/observer_configuration.hpp index 0e427a3..1572272 100644 --- a/include/libremidi/observer_configuration.hpp +++ b/include/libremidi/observer_configuration.hpp @@ -1,59 +1,8 @@ #pragma once -#include -#include +#include -#include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { -using client_handle = std::uint64_t; -using port_handle = std::uint64_t; - -struct LIBREMIDI_EXPORT port_information -{ - // Handle to the client object: - - // ALSA Raw: unused - // ALSA Seq: snd_seq_t* - // CoreMIDI: MidiClientRef - // WebMIDI: unused - // JACK: jack_client_t* - // PipeWire: unused - // WinMM: unused - // WinUWP: unused - client_handle client = static_cast(-1); - - // ALSA Raw: { uint16_t card, device, sub, padding; } - // ALSA Seq: { uint32_t client, uint32_t port; } - // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t) - // WebMIDI: unused - // JACK: jack_port_id_t - // PipeWire: port.id - // WinMM: unset, identified by port_name - // WinUWP: unused - port_handle port = static_cast(-1); - - std::string manufacturer{}; - std::string device_name{}; - std::string port_name{}; - std::string display_name{}; - - bool operator==(const port_information& other) const noexcept = default; - std::strong_ordering operator<=>(const port_information& other) const noexcept = default; -}; - -struct input_port : port_information -{ - bool operator==(const input_port& other) const noexcept = default; - std::strong_ordering operator<=>(const input_port& other) const noexcept = default; -}; -struct output_port : port_information -{ - bool operator==(const output_port& other) const noexcept = default; - std::strong_ordering operator<=>(const output_port& other) const noexcept = default; -}; - using input_port_callback = std::function; using output_port_callback = std::function; struct observer_configuration @@ -72,6 +21,9 @@ struct observer_configuration // Observe software (virtual) ports if the API provides it uint32_t track_virtual : 1 = false; + // Observe network ports if the API provides it + uint32_t track_network : 1 = false; + // Observe any port - some systems have other weird port types than hw / sw, this covers them uint32_t track_any : 1 = false; diff --git a/include/libremidi/output_configuration.hpp b/include/libremidi/output_configuration.hpp index 44e3373..10e776c 100644 --- a/include/libremidi/output_configuration.hpp +++ b/include/libremidi/output_configuration.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct output_configuration { diff --git a/include/libremidi/port_comparison.hpp b/include/libremidi/port_comparison.hpp new file mode 100644 index 0000000..4d7ef25 --- /dev/null +++ b/include/libremidi/port_comparison.hpp @@ -0,0 +1,565 @@ +#pragma once +#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 +{ +// Simple compare-the-name equality. Useful for saving / reloading +struct port_name_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.port_name == rhs.port_name; + } +}; +struct port_name_less +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return std::tie(lhs.api, lhs.port_name) < std::tie(rhs.api, rhs.port_name); + } +}; + +// Compare an existing port with others. +// Note that on multiple APIs, this comparison method is only valid as long +// as no device gets connected / disconnected, as the port identifier / handle +// is sadly just the index in the list of devices returned by the OS, which will +// change as soon as a device changes. +// Thus, it should only ever be used to compare devices between a group obtained +// from a single call to get_input_ports / get_output_ports or in situations where we can +// be sure that there is no hot-plugging. +struct port_identity_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.port == rhs.port; + } +}; +struct port_identity_less +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return std::tie(lhs.api, lhs.port) < std::tie(rhs.api, rhs.port); + } +}; + +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 + static constexpr int W_HARDWARE_ID = 1000; // Unique HW IDs (Container, Device) + static constexpr int W_SERIAL = 800; // Serial Number + static constexpr int W_NAME_EXACT = 400; // Display/Port/Device Names + static constexpr int W_METADATA = 100; // Manufacturer/Product + static constexpr int W_HANDLE = 50; // Port Index/Handle + + // Penalties for mismatches when data is present in both but differs + static constexpr int P_HARDWARE_MISMATCH = -2000; + static constexpr int P_SERIAL_MISMATCH = -1000; + static constexpr int P_NAME_MISMATCH = -100; + static constexpr int P_PRODUCT_MISMATCH = -10; + + struct match_score + { + int score = 0; + bool api_mismatch = false; + + bool is_match() const { return !api_mismatch && score > 0; } + constexpr auto operator<=>(const match_score& other) const noexcept = default; + }; + + static inline constexpr bool chars_equal_ignore_case(char lhs, char rhs) { + if(lhs >= 'A' && lhs <= 'Z') + lhs -= 'A' - 'a'; + if(rhs >= 'A' && rhs <= 'Z') + rhs -= 'A' - 'a'; + + return lhs == rhs; + } + + static inline double fuzzy_match_name(std::string_view s1, std::string_view s2) + { + const size_t len1 = s1.size(); + const size_t len2 = s2.size(); + if (len1 == 0 && len2 == 0) + return 1.0; + if (len1 == 0 || len2 == 0) + return 0; + if (len1 > 1024 || len2 > 1024) + return 0; + if (len1 > len2) + return fuzzy_match_name(s2, s1); + + auto col = (size_t*)alloca(sizeof(size_t) * (len1 + 1)); + std::fill_n(col, len1 + 1, 0); + + // Initialize first column (0, 1, 2... len1) + for (size_t i = 0; i <= len1; ++i) col[i] = i; + + // Compute Levenshtein distance + for (size_t j = 1; j <= len2; ++j) { + size_t prev_diag = col[0]; + col[0] = j; + + for (size_t i = 1; i <= len1; ++i) { + size_t prev_col = col[i]; + size_t cost = chars_equal_ignore_case(s1[i - 1], s2[j - 1]) ? 0 : 1; + + col[i] = std::min({ + col[i] + 1, // Deletion + col[i - 1] + 1, // Insertion + prev_diag + cost // Substitution + }); + + prev_diag = prev_col; + } + } + + const size_t distance = col[len1]; + const size_t max_len = std::max(len1, len2); + + if (max_len == 0) + return 1.0; + + return 1.0 - (static_cast(distance) / static_cast(max_len)); + } + + match_score calculate(const port_information& target, const port_information& candidate) const + { + match_score result; + + // 1. API Mismatch + // It is impossible for a port to be the same if the API is different. + if (target.api != libremidi::API::UNSPECIFIED) + { + if (target.api != candidate.api) + { + result.api_mismatch = true; + result.score = std::numeric_limits::min(); + return result; + } + } + + // 2. Hardware Identifiers & Serial Number + // High value, but unreliable presence. + switch(target.api) + { + case libremidi::API::COREMIDI: + case libremidi::API::COREMIDI_UMP: + case libremidi::API::WINDOWS_MM: + { + score_variant(result.score, target.device, candidate.device, W_HARDWARE_ID, P_HARDWARE_MISMATCH); + break; + } + default: + break; + } + + score_string(result.score, target.manufacturer, candidate.manufacturer, W_METADATA, P_HARDWARE_MISMATCH); + score_string(result.score, target.product, candidate.product, W_METADATA, P_HARDWARE_MISMATCH); + score_string(result.score, target.serial, candidate.serial, W_SERIAL, P_SERIAL_MISMATCH); + + // 3. Names + // We accumulate score for every name that matches. + score_string(result.score, target.display_name, candidate.display_name, W_NAME_EXACT, P_NAME_MISMATCH); + score_string(result.score, target.port_name, candidate.port_name, W_NAME_EXACT, P_NAME_MISMATCH); + score_string(result.score, target.device_name, candidate.device_name, W_NAME_EXACT, P_NAME_MISMATCH); + + // 4. Port Handle (Index) + // Only check if it's not the default -1. + // We rely on this primarily as a tie-breaker if names/hardware IDs are identical + // (e.g. two identical controllers plugged in). + if (target.port != static_cast(-1)) + { + if (target.port == candidate.port) + { + result.score += W_HANDLE; + } + } + + return result; + } + +private: + void score_string(int& score, std::string_view target_s, std::string_view cand_s, int reward, int penalty) const + { + // If the target doesn't know this info, we can't judge. Skip. + if (target_s.empty()) + return; + + const double res = fuzzy_match_name(cand_s, target_s); + if (res >= 0.5) + { + score += res * reward; + } + else if (!cand_s.empty()) + { + // If candidate value is empty, it's just missing info, not necessarily a mismatch. + score += penalty; + } + } + + // Helper for std::variant fields (device / container identifiers) + template + void score_variant(int& score, const T& target_v, const T& cand_v, int reward, int penalty) const + { + if (holds_alternative(target_v)) + return; + + // For those we want an exact search + if (target_v == cand_v) + { + score += reward; + } + else if (!holds_alternative(cand_v)) + { + // Candidate has a specific ID, and it differs from Target's specific ID. + score += penalty; + } + } +}; + +template +struct port_search_result +{ + const T* port = nullptr; + int score = 0; + bool found = false; +}; + +template +inline port_search_result find_closest_port(const T& target, std::span candidates) +{ + port_heuristic_matcher matcher{}; + + const T* best_match = nullptr; + port_heuristic_matcher::match_score best_score; + best_score.score = -1; + + for (const auto& candidate : candidates) + { + port_heuristic_matcher::match_score current = matcher.calculate(target, candidate); + + if (current.is_match() && current > best_score) + { + best_score = current; + best_match = &candidate; + } + } + + if (best_match) + return { best_match, best_score.score, true }; + + return { nullptr, 0, false }; +} + +template +inline std::vector +optimistic_serialized_port_lookup(const T& target, std::span ports) +{ + if (ports.empty()) + return {}; + + // 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; + } + } + + // 2. Heuristics + + // Look for candidates in the same API + candidates.clear(); + for (auto& candidate : ports) + { + if (target.api != libremidi::API::UNSPECIFIED) + { + 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); + } + } + } + } + + 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; + } + + candidates.clear(); + candidates.reserve(ranked_candidates.size()); + for (auto [score, candidate] : ranked_candidates) + candidates.insert(candidates.begin(), candidate); + return candidates; +} +} diff --git a/include/libremidi/port_information.hpp b/include/libremidi/port_information.hpp new file mode 100644 index 0000000..9edeffb --- /dev/null +++ b/include/libremidi/port_information.hpp @@ -0,0 +1,164 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +namespace libremidi +{ + +struct LIBREMIDI_EXPORT port_information +{ + // Compat + using port_type = libremidi::transport_type; + + /// Which API is this port for. port_information objects are in general + /// not useable for different APIs than the API of the observer that created them. + libremidi::API api{}; + + /// Handle to the API client object if the API provides one + // Android: unused + // ALSA Raw: unused + // ALSA Seq: snd_seq_t* + // CoreMIDI: MidiClientRef + // JACK: jack_client_t* + // PipeWire: unused // FIXME: pw_context? pw_main_loop? + // WebMIDI: unused + // WinMIDI: TODO + // WinMM: unused + // WinUWP: unused + client_handle client = static_cast(-1); + + /// Container identifier if the API provides one + // Android: unavailable + // ALSA: device id (std::string), e.g. ID_PATH as returned by udev: "pci-0000:00:14.0-usb-0:12:1.0" + // CoreMIDI: USBLocationID (int32_t) + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: ContainerID GUID (bit_cast to a winapi or winrt::GUID ; + // this is not the string but the binary representation). + // WinMM: unavailable + // WinUWP: unavailable + container_identifier container = libremidi_variant_alias::monostate{}; + + /// Device identifier if the API provides one + // Android: unavailable + // ALSA: sysfs path (std::string), e.g. "/sys/devices/pci0000:00/0000:00:02.2/0000:02:00.0/sound/card0/controlC0" + // CoreMIDI: USBVendorProduct (int32_t) + // JACK: unavailable + // PipeWire: unavailable + // 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 = 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. + // ALSA Raw: bit_cast to struct { uint16_t card, device, sub, padding; }. + // ALSA Seq: bit_cast to struct { uint32_t client, port; } + // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t) + // JACK: jack_port_id_t + // PipeWire: "port.id" (uint32_t) + // WebMIDI: index of the MIDI device in the list provided by the browser. + // WinMIDI: uint64_t terminal_block_number; (MidiGroupTerminalBlock::Number(), index is 1-based) + // WinMM: port index between 0 and midi{In,Out}GetNumDevs() + // WinUWP: index of the MIDI device in the list provided by the OS. + port_handle port = static_cast(-1); + + /// User-readable information + // Android: unavailable + // ALSA Raw: ID_VENDOR_FROM_DATABASE if provided by udev + // ALSA Seq: ID_VENDOR_FROM_DATABASE if provided by udev + // CoreMIDI: kMIDIPropertyManufacturer + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName + // WinMM: unavailable + // WinUWP: unavailable + std::string manufacturer{}; + + // Android: unavailable + // ALSA Raw: ID_MODEL_FROM_DATABASE if provided by udev + // ALSA Seq: ID_MODEL_FROM_DATABASE if provided by udev + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().Name + // WinUWP: unavailable + std::string product{}; + + /// "Unique" serial number. Note that this is super unreliable - pretty + /// much no MIDI device manufacturer bothers with unique per-device serial number + /// unlike most USB devices. + // Android: unavailable + // ALSA Raw: ID_USB_SERIAL if provided by udev. + // ALSA Seq: ID_USB_SERIAL if provided by udev. + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().SerialNumber + // WinUWP: unavailable + std::string serial{}; + + // Android: unavailable + // ALSA Raw: Name returned by snd_rawmidi_info_get_name + // ALSA Seq: Name returned by snd_seq_client_info_get_name + // CoreMIDI: kMIDIPropertyModel + // JACK: jack_get_client_name + // PipeWire: first part of port_alias, e.g. "A" in A:B + // WinMIDI: MidiEndpointDeviceInformation::Name + // WinMM: unavailable + // WinUWP: unavailable + std::string device_name{}; + + // Android: MidiDeviceInfo.getProperties().getString("name") + // ALSA Raw: Name returned by snd_rawmidi_info_get_subdevice_name + // ALSA Seq: Name returned by snd_seq_port_info_get_name + // CoreMIDI: kMIDIPropertyName + // JACK: jack_port_name + // PipeWire: "port.name" + // WinMIDI: MidiGroupTerminalBlock::Name + // WinMM: szPname + // WinUWP: DeviceInformation.Id() + std::string port_name{}; + + // CoreMIDI: kMIDIPropertyDisplayName + // WinUWP: DeviceInformation.Name() + // Otherwise: the closest to a unique name we can get + std::string display_name{}; + + /// Port type + // Android: unavailable + // ALSA Raw / Seq: available if udev is avaialble + // CoreMIDI: available + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: available + // WinMM: unavailable + // WinUWP: unavailable + port_type type = port_type::unknown; + + // Equality and comparison operators are deleted as there is not one + // single correct way to compare two port_information: + // in some cases it may be useful to only compare the names, while in other cases + // it is necessary to check whether this is the exact same low-level identifier. + // Thus, the end-user must define their own custom equality operators + // if using std:: containers or algorithms + bool operator==(const port_information& other) const noexcept = delete; + std::strong_ordering operator<=>(const port_information& other) const noexcept = delete; +}; + +struct input_port : port_information +{ + bool operator==(const input_port& other) const noexcept = delete; + std::strong_ordering operator<=>(const input_port& other) const noexcept = delete; +}; +struct output_port : port_information +{ + bool operator==(const output_port& other) const noexcept = delete; + std::strong_ordering operator<=>(const output_port& other) const noexcept = delete; +}; + +} diff --git a/include/libremidi/protocols/remote_control.hpp b/include/libremidi/protocols/remote_control.hpp new file mode 100644 index 0000000..8281509 --- /dev/null +++ b/include/libremidi/protocols/remote_control.hpp @@ -0,0 +1,858 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +static constexpr auto to_underlying(auto e) +{ + return static_cast>(e); +} + +// A clean-room reverse-engineered remote control protocol compatible with many hardware devices. +// Thanks https://github.com/NicoG60/TouchMCU ! +struct remote_control_protocol +{ + enum class device_type : uint8_t + { + logic_control = 0x10, + logic_control_xt = 0x11, + mackie_control = 0x14 + }; + + enum class command_to_device : uint8_t + { + device_query = 0x00, + host_connection_reply = 0x02, + + transport_click = 0x0A, + lcd_backlight_save = 0x0B, + touchless_movable_fader = 0x0C, + + faders_touch_sensitivity = 0x0E, + go_offline = 0x0F, + update_tc_display = 0x10, + update_assignment_display = 0x11, + update_lcd = 0x12, + firmware_version_request = 0x13, + version_reply = 0x14, + + firmware_update = 0x18, + + channel_meter_mode = 0x20, + global_lcd_meter_mode = 0x21, + + faders_to_minimum = 0x61, + all_leds_off = 0x62, + reset = 0x63, + }; + + enum class command_from_device : uint8_t + { + host_connection_query = 0x01, + host_connection_confirmation = 0x03, + host_connection_error = 0x04, + version_reply = 0x14, + }; + + enum class lcd_meter_mode : uint8_t + { + horizontal = 0x00, + vertical = 0x01, + }; + + enum class fader_sensitivity : uint8_t + { + sensitivity_0 = 0x00, + sensitivity_1 = 0x01, + sensitivity_2 = 0x02, + sensitivity_3 = 0x03, + sensitivity_4 = 0x04, + + sensitivity_default = sensitivity_3, + }; + + // CC Message: + // 0b0LMMVVVV + // L: toggle underneath LED + // MM: mode as led_ring_mode + // VVVV: value + enum class led_ring_mode : uint8_t + { + mode_0 = 0b00, // one led only + mode_1 = 0b01, // pan pot + mode_2 = 0b10, // fill leds from left + mode_3 = 0b11, // fill leds from middle + }; + + enum class pot : uint8_t + { + pot_0 = 0x00, + pot_1 = 0x01, + pot_2 = 0x02, + pot_3 = 0x03, + pot_4 = 0x04, + pot_5 = 0x05, + pot_6 = 0x06, + pot_7 = 0x07, + }; + + enum class fader : uint8_t + { + fader_0 = 0x00, + fader_1 = 0x01, + fader_2 = 0x02, + fader_3 = 0x03, + fader_4 = 0x04, + fader_5 = 0x05, + fader_6 = 0x06, + fader_7 = 0x07, + fader_master = 0x08, + }; + + // control changes + enum class mixer_control : uint8_t + { + // rotation: CC + // 0b00000001 : clockwise + // 0b01000001 : counter-clockwise + vpot_rotation_0 = 0x10 + 0x00, + vpot_rotation_1 = 0x10 + 0x01, + vpot_rotation_2 = 0x10 + 0x02, + vpot_rotation_3 = 0x10 + 0x03, + vpot_rotation_4 = 0x10 + 0x04, + vpot_rotation_5 = 0x10 + 0x05, + vpot_rotation_6 = 0x10 + 0x06, + vpot_rotation_7 = 0x10 + 0x07, + + external_control = 0x2E, + + // led ring: CC + vpot_led_0 = 0x30 + 0x00, + vpot_led_1 = 0x30 + 0x01, + vpot_led_2 = 0x30 + 0x02, + vpot_led_3 = 0x30 + 0x03, + vpot_led_4 = 0x30 + 0x04, + vpot_led_5 = 0x30 + 0x05, + vpot_led_6 = 0x30 + 0x06, + vpot_led_7 = 0x30 + 0x07, + + jog_wheel = 0x3C, + + timecode_digit_0 = 0x40 + 0x00, + timecode_digit_1 = 0x40 + 0x01, + timecode_digit_2 = 0x40 + 0x02, + timecode_digit_3 = 0x40 + 0x03, + timecode_digit_4 = 0x40 + 0x04, + timecode_digit_5 = 0x40 + 0x05, + timecode_digit_6 = 0x40 + 0x06, + timecode_digit_7 = 0x40 + 0x07, + timecode_digit_8 = 0x40 + 0x08, + timecode_digit_9 = 0x40 + 0x09, + + assignment_digit_0 = 0x4A, + assignment_digit_1 = 0x4B, + }; + + // note events + enum class mixer_command : uint8_t + { + vpot_click_0 = 0x20 + 0x00, + vpot_click_1 = 0x20 + 0x01, + vpot_click_2 = 0x20 + 0x02, + vpot_click_3 = 0x20 + 0x03, + vpot_click_4 = 0x20 + 0x04, + vpot_click_5 = 0x20 + 0x05, + vpot_click_6 = 0x20 + 0x06, + vpot_click_7 = 0x20 + 0x07, + + rec_0 = 0x00 + 0x00, + rec_1 = 0x00 + 0x01, + rec_2 = 0x00 + 0x02, + rec_3 = 0x00 + 0x03, + rec_4 = 0x00 + 0x04, + rec_5 = 0x00 + 0x05, + rec_6 = 0x00 + 0x06, + rec_7 = 0x00 + 0x07, + + solo_0 = 0x08 + 0x00, + solo_1 = 0x08 + 0x01, + solo_2 = 0x08 + 0x02, + solo_3 = 0x08 + 0x03, + solo_4 = 0x08 + 0x04, + solo_5 = 0x08 + 0x05, + solo_6 = 0x08 + 0x06, + solo_7 = 0x08 + 0x07, + + mute_0 = 0x10 + 0x00, + mute_1 = 0x10 + 0x01, + mute_2 = 0x10 + 0x02, + mute_3 = 0x10 + 0x03, + mute_4 = 0x10 + 0x04, + mute_5 = 0x10 + 0x05, + mute_6 = 0x10 + 0x06, + mute_7 = 0x10 + 0x07, + + sel_0 = 0x18 + 0x00, + sel_1 = 0x18 + 0x01, + sel_2 = 0x18 + 0x02, + sel_3 = 0x18 + 0x03, + sel_4 = 0x18 + 0x04, + sel_5 = 0x18 + 0x05, + sel_6 = 0x18 + 0x06, + sel_7 = 0x18 + 0x07, + + // TODO metering + assign_track = 0x28, + assign_send = 0x29, + assign_pan = 0x2A, + assign_plugin = 0x2B, + assign_eq = 0x2C, + assign_instrument = 0x2D, + + bank_left = 0x2E, + bank_right = 0x2F, + channel_left = 0x30, + channel_right = 0x31, + flip = 0x32, + global = 0x33, + + name_value_button = 0x34, + smpte_beats_button = 0x35, + + f1 = 0x36 + 0x00, + f2 = 0x36 + 0x01, + f3 = 0x36 + 0x02, + f4 = 0x36 + 0x03, + f5 = 0x36 + 0x04, + f6 = 0x36 + 0x05, + f7 = 0x36 + 0x06, + f8 = 0x36 + 0x07, + + midi_tracks = 0x3E, + inputs = 0x3F, + audio_tracks = 0x40, + audio_instruments = 0x41, + aux = 0x42, + busses = 0x43, + outputs = 0x44, + user = 0x45, + + shift = 0x46, + option = 0x47, + control = 0x48, + alt = 0x49, + + save = 0x50, + undo = 0x51, + cancel = 0x52, + enter = 0x53, + + markers = 0x54, + nudge = 0x55, + cycle = 0x56, + drop = 0x57, + replace = 0x58, + click = 0x59, + solo = 0x5a, + + rewind = 0x5b, + forward = 0x5c, + stop = 0x5d, + play = 0x5e, + record = 0x5f, + + up = 0x60, + down = 0x61, + left = 0x62, + right = 0x63, + zoom = 0x64, + scrub = 0x65, + + user_switch_1 = 0x66, + user_switch_2 = 0x67, + + fader_touched_0 = 0x68, + fader_touched_1 = 0x69, + fader_touched_2 = 0x6a, + fader_touched_3 = 0x6b, + fader_touched_4 = 0x6c, + fader_touched_5 = 0x6d, + fader_touched_6 = 0x6e, + fader_touched_7 = 0x6f, + fader_touched_master = 0x70, + + smpte_led = 0x71, + beats_led = 0x72, + rude_solo_led = 0x73, + + relay_click = 0x76, + }; + + template + using arr = std::array; + + device_type type = device_type::mackie_control; + + static libremidi::message make_command_impl(auto&&... data) + { + using namespace std; + libremidi::message m; + m.bytes.reserve((std::ssize(data) + ...)); + (m.bytes.insert(m.bytes.end(), begin(data), end(data)), ...); + return m; + } + + libremidi::message make_command(command_to_device c, auto&&... data) + { + using namespace std; + const auto type = to_underlying(this->type); + const auto cmd = to_underlying(c); + const uint8_t header[6]{0xF0, 0x00, 0x00, 0x66, type, cmd}; + const uint8_t footer[1]{0xF7}; + return make_command_impl(header, data..., footer); + } + + auto device_query() { return make_command(command_to_device::device_query); } + + auto response_to_challenge(arr<4> c) + { + arr<4> r; + + r[0] = 0x7F & (c[0] + (c[1] ^ 0x0A) - c[3]); + r[1] = 0x7F & ((c[2] >> 4) ^ (c[0] + c[3])); + r[2] = 0x7F & (c[3] - (c[2] << 2) ^ (c[0] | c[1])); + r[3] = 0x7F & (c[1] - c[2] + (0xF0 ^ (c[3] << 4))); + + return r; + } + + auto host_connection_reply(arr<7> serial, arr<4> challenge_code) + { + const auto res = response_to_challenge(challenge_code); + return make_command(command_to_device::host_connection_reply, serial, res); + } + + auto transport_click(bool enabled) + { + return make_command( + command_to_device::transport_click, arr<1>{uint8_t(enabled ? 0x01 : 0x00)}); + } + + auto lcd_backlight_save(uint8_t timeout) + { + // 0: instant off otherwise timeout in minutes + return make_command(command_to_device::lcd_backlight_save, arr<1>{timeout}); + } + + auto touchless_movable_fader(bool enabled) + { + return make_command( + command_to_device::touchless_movable_fader, arr<1>{uint8_t(enabled ? 0x01 : 0x00)}); + } + + auto faders_touch_sensitivity(uint8_t fader_id, fader_sensitivity sens) + { + return make_command( + command_to_device::faders_touch_sensitivity, arr<2>{fader_id, to_underlying(sens)}); + } + + auto go_offline() { return make_command(command_to_device::go_offline, arr<1>{0x7F}); } + + auto update_tc_display() + { + // FIXME 1 .. 10 + return make_command(command_to_device::update_tc_display, arr<10>{}); + } + + auto update_assignment_display() + { + // FIXME 1 .. 2 + return make_command(command_to_device::update_assignment_display, arr<2>{}); + } + + auto update_lcd(std::string_view txt, int pos) + { + // FIXME + if (pos < 0 || pos >= 112) + return libremidi::message{}; + + int len = int(std::ssize(txt)); + + if (len > (112 - pos)) + { + txt = txt.substr(0, 112 - pos); + len = 112 - pos; + } + + uint8_t buf[128]; + const int N = std::min(len, 112 - pos); + for (int i = 0; i < N; i++) + { + buf[i + pos] = charmap_lcd(txt[i]); + } + buf[55] = '\n'; + buf[111] = '\n'; + + uint8_t cmd_pos = pos; + + return make_command(command_to_device::update_lcd, arr<1>{cmd_pos}, std::span(buf + pos, len)); + } + + auto update_lcd(std::string_view txt) + { + uint8_t buf[112] = {}; + for (int i = 0; i < std::min(int(std::ssize(txt)), 112); i++) + { + buf[i] = charmap_lcd(txt[i]); + } + buf[55] = '\n'; + buf[111] = '\n'; + return make_command(command_to_device::update_lcd, arr<1>{0}, std::span(buf, 112)); + } + + auto firmware_version_request() + { + return make_command(command_to_device::firmware_version_request, arr<1>{0}); + } + + auto firmware_update(std::span firmware) + { + return make_command(command_to_device::firmware_update, firmware); + } + + auto channel_meter_mode(uint8_t fader_id, bool level_meter, bool peak_hold, bool signal_led) + { + uint8_t mode = 0; + + if (signal_led) + mode |= 0b1; + if (peak_hold) + mode |= 0b10; + if (level_meter) + mode |= 0b100; + + return make_command(command_to_device::channel_meter_mode, arr<2>{fader_id, mode}); + } + + auto global_lcd_meter_mode(lcd_meter_mode mode) + { + return make_command(command_to_device::global_lcd_meter_mode, arr<1>{to_underlying(mode)}); + } + + auto faders_to_minimum() { return make_command(command_to_device::faders_to_minimum); } + + auto all_leds_off() { return make_command(command_to_device::all_leds_off); } + + auto reset() { return make_command(command_to_device::reset); } + + static auto timecode(int hi, int mi, int si, int framei) + { + std::vector msg; + auto h = std::to_string(hi); + while (h.size() < 3) + h.insert(h.begin(), '0'); + auto m = std::to_string(mi); + while (m.size() < 2) + m.insert(m.begin(), '0'); + auto s = std::to_string(si); + while (s.size() < 2) + s.insert(s.begin(), '0'); + auto f = std::to_string(framei); + while (f.size() < 3) + f.insert(f.begin(), '0'); + + using ce = libremidi::channel_events; + msg.push_back(ce::control_change(1, 0x49, charmap_7segment(h[0]))); + msg.push_back(ce::control_change(1, 0x48, charmap_7segment(h[1]))); + msg.push_back(ce::control_change(1, 0x47, charmap_7segment(h[2]))); + + msg.push_back(ce::control_change(1, 0x46, charmap_7segment(m[0]))); + msg.push_back(ce::control_change(1, 0x45, charmap_7segment(m[1]))); + + msg.push_back(ce::control_change(1, 0x44, charmap_7segment(s[0]))); + msg.push_back(ce::control_change(1, 0x43, charmap_7segment(s[1]))); + + msg.push_back(ce::control_change(1, 0x42, charmap_7segment(f[0]))); + msg.push_back(ce::control_change(1, 0x41, charmap_7segment(f[1]))); + msg.push_back(ce::control_change(1, 0x40, charmap_7segment(f[2]))); + + return msg; + } + + static uint8_t charmap_7segment(char c, bool dot) + { + uint8_t res = charmap_7segment(c); + if (dot) + res |= 0b00100000; + return res; + }; + + static uint8_t charmap_7segment(char c) + { + // FIXME there are some more characters but what to map them to ? :) + if (c >= 'a' && c <= 'z') + return c - 'a' + 1; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 1; + else if (c >= '0' && c <= '9') + return c - '0' + 0x30; + else + switch (c) + { + case '[': + case '{': + return 0x1B; + case '\\': + case '~': // yen too ? + return 0x1C; + case ']': + case '}': + return 0x1D; + case '^': + return 0x1E; + case '_': + return 0x1F; + + case '!': + return 0x21; + case '"': + return 0x22; + case '#': + return 0x23; + case '$': + return 0x24; + case '%': + return 0x25; + case '&': + return 0x26; + case '\'': + return 0x27; + case '(': + return 0x28; + case ')': + return 0x29; + case '*': + return 0x2A; + case '+': + return 0x2B; + case ',': + return 0x2C; + case '-': + return 0x2D; + case '.': + return 0x2E; + case '/': + return 0x2F; + + case ':': + return 0x3A; + case ';': + return 0x3B; + case '<': + return 0x3C; + case '=': + return 0x3D; + case '>': + return 0x3E; + case '?': + return 0x3F; + + default: + return 0x00; + } + } + static uint8_t charmap_lcd(char c) + { + // FIXME there are some more characters but what to map them to ? :) + if (c >= 'a' && c <= 'z') + return c - 'a' + 0x61; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 0x41; + else if (c >= '0' && c <= '9') + return c - '0' + 0x30; + else + switch (c) + { + case '!': + return 0x21; + case '"': + return 0x22; + case '#': + return 0x23; + case '$': + return 0x24; + case '%': + return 0x25; + case '&': + return 0x26; + case '\'': + return 0x27; + case '(': + return 0x28; + case ')': + return 0x29; + case '*': + return 0x2A; + case '+': + return 0x2B; + case ',': + return 0x2C; + case '-': + return 0x2D; + case '.': + return 0x2E; + case '/': + return 0x2F; + + case ':': + return 0x3A; + case ';': + return 0x3B; + case '<': + return 0x3C; + case '=': + return 0x3D; + case '>': + return 0x3E; + case '?': + return 0x3F; + + case '@': + return 0x40; + case '[': + return 0x5B; + case '~': // Yen symbol... builtin mojibake? + return 0x5C; + case ']': + return 0x5D; + case '^': + return 0x5E; + case '_': + return 0x5F; + case '`': + return 0x60; + case '{': + return 0x7B; + case '|': + return 0x7C; + case '}': + return 0x7D; + case '\u000E': + return 0x7E; + case '\u000F': + return 0x7F; + default: + return c; // gives access to the bubble first row 0x00 > 0x0F + } + } +}; + +struct rcp_configuration +{ + //! How to send MIDI messages to the device. + //! Note: this function *will* be called from different thread, + //! thus it has to be thread-safe, for instance + //! by storing the message in an event queue. + std::function midi_out; + + std::function on_connected; + std::function on_command; + std::function on_control; + std::function on_fader; + + libremidi::midi_error_callback on_error{}; +}; + +struct remote_control_processor : libremidi::error_handler +{ + using rcp = libremidi::remote_control_protocol; + rcp_configuration configuration; + rcp impl; + + explicit remote_control_processor(rcp_configuration conf) + : configuration{std::move(conf)} + { + assert(configuration.midi_out); + + if (!configuration.on_error) + configuration.on_error = [](std::string_view s, auto&&...) { + std::fprintf(stderr, "libremidi: rcp error: %s\n", s.data()); + }; + + if (!configuration.on_connected) + configuration.on_connected + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_connected"); }; + + if (!configuration.on_command) + configuration.on_command + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_command"); }; + + if (!configuration.on_control) + configuration.on_control + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_control"); }; + + if (!configuration.on_fader) + configuration.on_fader + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_fader"); }; + } + + void start() + { + current_state = waiting_for_query; + configuration.midi_out(impl.device_query()); + } + + void on_midi(const libremidi::message& message) + { + switch (message.get_message_type()) + { + case libremidi::message_type::SYSTEM_EXCLUSIVE: + if (auto N = message.size(); N >= 7) + { + const uint8_t* bytes = message.bytes.data(); + + // strip 0xF0 & 0xF7 + bytes += 1; + N -= 2; + + // Mackie manufacturer ID check + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x66) + { + impl.type = static_cast(bytes[3]); + + // strip header + bytes += 4; + N -= 4; + on_rcp_command(std::span(bytes, N)); + } + } + else + { + libremidi_handle_error(configuration, "Invalid sysex"); + } + break; + case libremidi::message_type::NOTE_ON: + configuration.on_command(static_cast(message[1]), message[2] > 0); + break; + case libremidi::message_type::NOTE_OFF: + break; + case libremidi::message_type::CONTROL_CHANGE: + configuration.on_control(static_cast(message[1]), message[2]); + break; + case libremidi::message_type::PITCH_BEND: { + uint16_t value = message.bytes[2] * 128 + message.bytes[1]; + configuration.on_fader(static_cast(uint8_t(message.get_channel() - 1)), value); + break; + } + default: + break; + } + } + + void on_rcp_command(std::span cmd) + { + if (cmd.empty()) + { + libremidi_handle_error(configuration, "on_rcp_command: empty command"); + return; + } + + auto command = static_cast(cmd[0]); + cmd = cmd.subspan(1); + switch (command) + { + case rcp::command_from_device::host_connection_query: { + if (cmd.size() == 11) + { + current_state = got_query; + + std::array serial; + std::array challenge; + std::copy_n(cmd.data(), 7, serial.begin()); + std::copy_n(cmd.data() + 7, 4, challenge.begin()); + + configuration.midi_out(impl.host_connection_reply(serial, challenge)); + } + else + libremidi_handle_error(configuration, "host_connection_query: invalid size"); + break; + } + case rcp::command_from_device::host_connection_confirmation: + current_state = connected; + configuration.on_connected(impl.type); + break; + case rcp::command_from_device::host_connection_error: + current_state = errored; + libremidi_handle_error(configuration, "host_connection_error"); + break; + case rcp::command_from_device::version_reply: { + // TODO + break; + } + default: + break; + } + } + + void update_timecode(int h, int m, int s, int f) + { + for (auto&& m : rcp::timecode(h, m, s, f)) + configuration.midi_out(std::move(m)); + } + + void update_lcd(std::string_view v) + { + auto res = impl.update_lcd(v); + if (!res.empty()) + configuration.midi_out(std::move(res)); + } + + void update_lcd(std::string_view v, int pos) + { + auto res = impl.update_lcd(v, pos); + if (!res.empty()) + configuration.midi_out(std::move(res)); + } + + void command(remote_control_protocol::mixer_command c, bool press) + { + using ce = libremidi::channel_events; + configuration.midi_out(ce::note_on(1, to_underlying(c), press ? 127 : 0)); + configuration.midi_out(ce::note_off(1, to_underlying(c), press ? 127 : 0)); + } + + void control(remote_control_protocol::mixer_control c, int value) + { + using ce = libremidi::channel_events; + configuration.midi_out(ce::control_change(1, to_underlying(c), value)); + } + + void fader(remote_control_protocol::fader c, uint16_t value) + { + int idx = to_underlying(c); + + using ce = libremidi::channel_events; + configuration.midi_out(ce::pitch_bend(idx + 1, value)); + } + + // State machine + enum + { + waiting_for_query, + got_query, + connected, + errored + } current_state{waiting_for_query}; +}; +} diff --git a/include/libremidi/reader.cpp b/include/libremidi/reader.cpp index 5beb5b1..cf59923 100644 --- a/include/libremidi/reader.cpp +++ b/include/libremidi/reader.cpp @@ -55,18 +55,18 @@ std::ostream& operator<<(std::ostream& s, const libremidi::message& m) } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { namespace util { struct no_validator { - static inline bool validate_track([[maybe_unused]] const midi_track& track) { return true; } + static bool validate_track([[maybe_unused]] const midi_track& track) { return true; } }; struct validator { - static inline bool validate_track(const midi_track& track) + static bool validate_track(const midi_track& track) { if (track.empty()) { @@ -108,13 +108,13 @@ struct read_unchecked { // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits, // with top bit set to signify that another byte follows). - static inline void ensure_size( + static void ensure_size( [[maybe_unused]] const uint8_t* begin, [[maybe_unused]] const uint8_t* end, [[maybe_unused]] int64_t needed) { } - static inline uint32_t + static uint32_t read_variable_length(uint8_t const*& data, [[maybe_unused]] uint8_t const* end) { uint32_t result = 0; @@ -133,7 +133,7 @@ struct read_unchecked } } - static inline void read_bytes( + static void read_bytes( midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end, const std::size_t num) { @@ -142,20 +142,20 @@ struct read_unchecked buffer.push_back(*data++); } - static inline void read_bytes( + static void read_bytes( midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end, const int num) { read_bytes(buffer, data, end, static_cast(num)); } - static inline uint16_t read_uint16_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint16_t read_uint16_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint16_t result = *data++ << 8u; result += *data++; return result; } - static inline uint32_t read_uint24_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint32_t read_uint24_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint32_t result = *data++ << 16u; result += static_cast(*data++ << 8u); @@ -163,7 +163,7 @@ struct read_unchecked return result; } - static inline uint32_t read_uint32_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint32_t read_uint32_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint32_t result = *data++ << 24u; result += static_cast(*data++ << 16u); @@ -178,14 +178,14 @@ struct read_checked { // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits, // with top bit set to signify that another byte follows). - static inline void + static void ensure_size(const uint8_t* begin, const uint8_t* end, const std::size_t needed) { if (const auto available = static_cast(end - begin); available < needed) throw std::runtime_error("MIDI reader: not enough data to process"); } - static inline std::size_t read_variable_length(uint8_t const*& data, uint8_t const* end) + static std::size_t read_variable_length(uint8_t const*& data, uint8_t const* end) { std::size_t result = 0; while (true) @@ -204,26 +204,26 @@ struct read_checked } } - static inline void + static void read_bytes(midi_bytes& buffer, uint8_t const*& data, uint8_t const* end, const std::size_t num) { ensure_size(data, end, num); read_unchecked::read_bytes(buffer, data, end, num); } - static inline uint16_t read_uint16_be(uint8_t const*& data, uint8_t const* end) + static uint16_t read_uint16_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 2); return read_unchecked::read_uint16_be(data, end); } - static inline uint32_t read_uint24_be(uint8_t const*& data, uint8_t const* end) + static uint32_t read_uint24_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 3); return read_unchecked::read_uint24_be(data, end); } - static inline uint32_t read_uint32_be(uint8_t const*& data, uint8_t const* end) + static uint32_t read_uint32_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 4); return read_unchecked::read_uint32_be(data, end); @@ -521,7 +521,7 @@ LIBREMIDI_INLINE reader::reader(bool useAbsolute) : ticksPerBeat(480) , startingTempo(120) - , useAbsoluteTicks(useAbsolute) + , m_useAbsoluteTicks(useAbsolute) { } @@ -537,7 +537,7 @@ LIBREMIDI_INLINE auto reader::parse(const uint8_t* dataPtr, std::size_t size) noexcept -> parse_result try { - using namespace libremidi::util; + using namespace util; tracks.clear(); @@ -549,12 +549,12 @@ try return parse_result::invalid; } - const uint8_t* const dataEnd = dataPtr + size; + const uint8_t* const data_end = dataPtr + size; - uint32_t headerId = read_checked::read_uint32_be(dataPtr, dataEnd); - uint32_t headerLength = read_checked::read_uint32_be(dataPtr, dataEnd); + uint32_t header_id = read_checked::read_uint32_be(dataPtr, data_end); + uint32_t header_length = read_checked::read_uint32_be(dataPtr, data_end); - if (static_cast(headerId) != str_to_headerid("MThd") || headerLength != 6) + if (static_cast(header_id) != str_to_headerid("MThd") || header_length != 6) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "couldn't parse header" << std::endl; @@ -563,7 +563,7 @@ try } format = read_checked::read_uint16_be( - dataPtr, dataEnd); //@tofix format type -> save for later eventually + dataPtr, data_end); //@tofix format type -> save for later eventually if (format > 2) { #if defined(__LIBREMIDI_DEBUG__) @@ -571,12 +571,12 @@ try #endif return parse_result::invalid; } - int trackCount = read_checked::read_uint16_be(dataPtr, dataEnd); - uint16_t timeDivision = read_checked::read_uint16_be(dataPtr, dataEnd); + int track_count = read_checked::read_uint16_be(dataPtr, data_end); + uint16_t time_division = read_checked::read_uint16_be(dataPtr, data_end); // CBB: deal with the SMPTE style time coding // timeDivision is described here http://www.sonicspot.com/guide/midifiles.html - if (timeDivision & 0x8000) + if (time_division & 0x8000) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "found SMPTE time frames (unsupported)" << std::endl; @@ -589,19 +589,19 @@ try return parse_result::invalid; } - startingTempo = 120.0f; // midi default - ticksPerBeat = float(timeDivision); // ticks per beat (a beat is defined as a quarter note) + startingTempo = 120.0f; // midi default + ticksPerBeat = float(time_division); // ticks per beat (a beat is defined as a quarter note) parse_result result = parse_result::validated; - for (int i = 0; i < trackCount; ++i) + for (int i = 0; i < track_count; ++i) { midi_track track; - headerId = read_checked::read_uint32_be(dataPtr, dataEnd); - headerLength = read_checked::read_uint32_be(dataPtr, dataEnd); + header_id = read_checked::read_uint32_be(dataPtr, data_end); + header_length = read_checked::read_uint32_be(dataPtr, data_end); - if (headerId != str_to_headerid("MTrk")) + if (header_id != str_to_headerid("MTrk")) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "couldn't find track header" << std::endl; @@ -609,8 +609,8 @@ try return parse_result::incomplete; } - int64_t available = dataEnd - dataPtr; - if (available < headerLength) + int64_t available = data_end - dataPtr; + if (available < header_length) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "not enough data available" << std::endl; @@ -618,35 +618,35 @@ try return parse_result::incomplete; } - track.reserve(headerLength / 3); + track.reserve(header_length / 3); - const uint8_t* const trackEnd = dataPtr + headerLength; + const uint8_t* const track_end = dataPtr + header_length; - auto runningEvent = message_type::INVALID; + auto running_event = message_type::INVALID; - std::size_t tickCount = 0; + std::size_t tick_count = 0; - while (dataPtr < trackEnd) + while (dataPtr < track_end) { - const auto tick = read_checked::read_variable_length(dataPtr, trackEnd); - if (useAbsoluteTicks) + const auto tick = read_checked::read_variable_length(dataPtr, track_end); + if (m_useAbsoluteTicks) { - tickCount += tick; + tick_count += tick; } else { - tickCount = tick; + tick_count = tick; } try { track_event ev - = parse_event(static_cast(tickCount), i, dataPtr, trackEnd, runningEvent); + = parse_event(static_cast(tick_count), i, dataPtr, track_end, running_event); if (!ev.m.empty()) { if (!ev.m.is_meta_event()) { - runningEvent = static_cast(ev.m.bytes[0]); + running_event = static_cast(ev.m.bytes[0]); } } else @@ -654,7 +654,7 @@ try #if defined(__LIBREMIDI_DEBUG__) std::cerr << "could not read event" << std::endl; #endif - dataPtr = trackEnd; + dataPtr = track_end; result = parse_result::incomplete; continue; } @@ -666,7 +666,7 @@ try #if defined(__LIBREMIDI_DEBUG__) std::cerr << "" << e.what() << std::endl; #endif - dataPtr = trackEnd; + dataPtr = track_end; result = parse_result::incomplete; continue; } @@ -684,7 +684,7 @@ try if (result == parse_result::validated) { - if (dataPtr != dataEnd) + if (dataPtr != data_end) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "midifile has junk at end: " << std::intptr_t(dataEnd - dataPtr) << std::endl; @@ -706,33 +706,33 @@ catch (const std::exception& e) LIBREMIDI_INLINE double reader::get_end_time() const noexcept { - if (useAbsoluteTicks) + if (m_useAbsoluteTicks) { - double totalLength = 0.; + double total_length = 0.; for (const auto& t : tracks) { if (!t.empty()) { const auto& last_event = t.back(); - if (last_event.tick > totalLength) - totalLength = last_event.tick; + if (last_event.tick > total_length) + total_length = last_event.tick; } } - return totalLength; + return total_length; } else { - double totalLength = 0.; + double total_length = 0.; for (const auto& t : tracks) { - double trackLength = 0.; + double track_length = 0.; for (const auto& e : t) - trackLength += e.tick; + track_length += e.tick; - if (trackLength > totalLength) - totalLength = trackLength; + if (track_length > total_length) + total_length = track_length; } - return totalLength; + return total_length; } } diff --git a/include/libremidi/reader.hpp b/include/libremidi/reader.hpp index 1d99b46..2cb8d34 100644 --- a/include/libremidi/reader.hpp +++ b/include/libremidi/reader.hpp @@ -27,7 +27,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -namespace libremidi +NAMESPACE_LIBREMIDI { /** * @brief reads Standard MIDI files (SMF). @@ -66,7 +66,7 @@ public: std::vector tracks; private: - bool useAbsoluteTicks{}; + bool m_useAbsoluteTicks{}; }; } diff --git a/include/libremidi/shared_context.hpp b/include/libremidi/shared_context.hpp index f794aa2..5d9e68e 100644 --- a/include/libremidi/shared_context.hpp +++ b/include/libremidi/shared_context.hpp @@ -1,10 +1,8 @@ #pragma once #include -#include +#include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { class shared_context @@ -24,7 +22,9 @@ public: struct shared_configurations { std::shared_ptr context; - std::any observer, in, out; + observer_api_configuration observer; + input_api_configuration in; + output_api_configuration out; }; LIBREMIDI_EXPORT diff --git a/include/libremidi/system_error2.hpp b/include/libremidi/system_error2.hpp index b5dc1cd..cee4fe2 100644 --- a/include/libremidi/system_error2.hpp +++ b/include/libremidi/system_error2.hpp @@ -1,3 +1,4 @@ +#pragma once #ifndef STDX_COMPILER_HPP #define STDX_COMPILER_HPP @@ -63,6 +64,12 @@ #define STDX_TRIVIALLY_RELOCATABLE #endif +#if defined(__clang__) && defined(__has_warning) + #pragma clang diagnostic push + #if __has_warning("-Wdeprecated-declarations") + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif +#endif #endif // STDX_COMPILER_HPP @@ -71,7 +78,7 @@ #include -namespace stdx { +NAMESPACE_STDX { // Implementation of std::void_t for use with pre-C++17 compilers. // @@ -180,7 +187,11 @@ using is_trivially_move_constructible = is_trivially_copyable; #if defined(__cpp_lib_trivially_relocatable) using std::is_trivially_relocatable; #elif defined(__has_builtin) - #if __has_builtin(__is_trivially_relocatable) + #if __has_builtin(__builtin_is_cpp_trivially_relocatable) +template +struct is_trivially_relocatable : std::bool_constant<__builtin_is_cpp_trivially_relocatable(T)> { }; + #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE + #elif __has_builtin(__is_trivially_relocatable) template struct is_trivially_relocatable : std::bool_constant<__is_trivially_relocatable(T)> { }; #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE @@ -214,7 +225,7 @@ struct is_trivially_relocatable : is_trivially_copyable { }; #include -namespace stdx { +NAMESPACE_STDX { namespace detail { @@ -374,7 +385,7 @@ struct is_bit_castable #include #include -namespace stdx { +NAMESPACE_STDX { struct default_intrusive_reference_count; class default_intrusive_reference_control; @@ -1151,7 +1162,7 @@ struct is_trivially_relocatable> : std::true_type #include #include -namespace stdx { +NAMESPACE_STDX { class string_ref; @@ -1596,7 +1607,7 @@ public: #include -namespace stdx { +NAMESPACE_STDX { #if __cplusplus >= 201703L #if defined(__cpp_lib_launder) @@ -1638,7 +1649,7 @@ constexpr T* launder(T* p) noexcept #include -namespace stdx { +NAMESPACE_STDX { class error; @@ -1685,7 +1696,7 @@ constexpr auto construct_error_from_adl(Args&&... args) noexcept( } // end namespace stdx_adl -namespace stdx { +NAMESPACE_STDX { enum class dynamic_exception_errc { @@ -2844,9 +2855,9 @@ struct is_error_code_enum : std::true_type #include -namespace stdx { +NAMESPACE_STDX { -namespace { +// namespace { inline const char* dynamic_exception_errc_str(unsigned ev) noexcept { @@ -2924,7 +2935,7 @@ inline const std::error_category& dynamic_exception_category() noexcept return dynamic_exception_error_category_instance; } -} // end anonymous namespace +//} // end anonymous namespace inline std::error_code make_error_code(dynamic_exception_errc code) noexcept { @@ -3143,9 +3154,9 @@ inline bool generic_error_domain::equivalent(const error& lhs, const error& rhs) return false; } -namespace { +// namespace { -string_ref generic_error_code_message(std::errc code) noexcept +inline string_ref generic_error_code_message(std::errc code) noexcept { switch (code) { @@ -3312,7 +3323,7 @@ string_ref generic_error_code_message(std::errc code) noexcept } } -} // end anonymous namespace +// } // end anonymous namespace inline string_ref generic_error_domain::message(const error& e) const noexcept { @@ -3406,9 +3417,9 @@ inline string_ref dynamic_exception_error_domain::message(const error& e) const return string_ref{"Unknown dynamic exception"}; } -namespace { +// namespace { -std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept +inline std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept { switch (code) { @@ -3432,7 +3443,7 @@ std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) no return std::errc{}; } -} // end anonymous namespace +// } // end anonymous namespace inline bool dynamic_exception_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept { @@ -3497,5 +3508,6 @@ inline string_ref dynamic_exception_code_error_domain::message(const error& e) c } // end namespace stdx - - +#if defined(__clang__) && defined(__has_warning) +#pragma clang diagnostic pop +#endif diff --git a/include/libremidi/types.hpp b/include/libremidi/types.hpp new file mode 100644 index 0000000..dfd0e2e --- /dev/null +++ b/include/libremidi/types.hpp @@ -0,0 +1,70 @@ +#pragma once +#include + +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +/// MIDI protocol supported by an endpoint or function block +enum class midi_protocol : uint8_t +{ + midi1 = (1 << 1), + midi2 = (1 << 2), + both = midi1 | midi2 +}; + +enum transport_type : uint8_t +{ + unknown = 0, + + software = (1 << 1), + loopback = (1 << 2), + + hardware = (1 << 3), + usb = (1 << 4), + bluetooth = (1 << 5), + pci = (1 << 6), + + network = (1 << 7), +}; + +/// UMP version information +struct ump_version +{ + uint8_t major{1}; + uint8_t minor{1}; + + bool operator==(const ump_version&) const noexcept = default; + std::strong_ordering operator<=>(const ump_version&) const noexcept = default; +}; + +using client_handle = std::uint64_t; +using port_handle = std::uint64_t; + +struct uuid +{ + std::array bytes; + + bool operator==(const uuid& other) const noexcept = default; + std::strong_ordering operator<=>(const uuid& other) const noexcept = default; +}; + +struct usb_device_identifier +{ + uint16_t vendor_id; + uint16_t product_id; + + bool operator==(const usb_device_identifier& other) const noexcept = default; + std::strong_ordering operator<=>(const usb_device_identifier& other) const noexcept = default; +}; + +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>; +} diff --git a/include/libremidi/ump.hpp b/include/libremidi/ump.hpp index 59d3091..2efd89f 100644 --- a/include/libremidi/ump.hpp +++ b/include/libremidi/ump.hpp @@ -1,14 +1,27 @@ #pragma once #include -#include - #if LIBREMIDI_NI_MIDI2_COMPAT #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { +namespace midi2 +{ +// Imported from cmidi2 +enum class message_type +{ + // MIDI 2.0 UMP Section 3. + UTILITY = 0, + SYSTEM = 1, + MIDI_1_CHANNEL = 2, + SYSEX7 = 3, + MIDI_2_CHANNEL = 4, + SYSEX8_MDS = 5, +}; +} + struct ump { alignas(4) uint32_t data[4] = {}; @@ -62,28 +75,16 @@ struct ump constexpr std::size_t size() const noexcept { - // Imported from cmidi2 - enum midi2_message_type + switch (midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF)) { - // MIDI 2.0 UMP Section 3. - UTILITY = 0, - SYSTEM = 1, - MIDI_1_CHANNEL = 2, - SYSEX7 = 3, - MIDI_2_CHANNEL = 4, - SYSEX8_MDS = 5, - }; - - switch (((data[0] & 0xF0000000) >> 28) & 0xF) - { - case UTILITY: - case SYSTEM: - case MIDI_1_CHANNEL: + case midi2::message_type::UTILITY: + case midi2::message_type::SYSTEM: + case midi2::message_type::MIDI_1_CHANNEL: return 1; - case MIDI_2_CHANNEL: - case SYSEX7: + case midi2::message_type::MIDI_2_CHANNEL: + case midi2::message_type::SYSEX7: return 2; - case SYSEX8_MDS: + case midi2::message_type::SYSEX8_MDS: return 4; default: return 0; @@ -109,5 +110,14 @@ struct ump constexpr auto cend() const noexcept { return data + size(); } constexpr auto cbegin() noexcept { return data; } constexpr auto cend() noexcept { return data + size(); } + + constexpr midi2::message_type get_type() const noexcept + { + return midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF); + } + constexpr uint8_t get_group() const noexcept { return (data[0] >> 24) & 0x0F; } + constexpr uint8_t get_channel_0_15() const noexcept { return (data[0] >> 16) & 0x0F; } + constexpr uint8_t get_channel() const noexcept { return get_channel_0_15() + 1; } + constexpr uint8_t get_status_code() const noexcept { return (data[0] >> 16) & 0xF0; } }; } diff --git a/include/libremidi/ump_events.hpp b/include/libremidi/ump_events.hpp new file mode 100644 index 0000000..24e201e --- /dev/null +++ b/include/libremidi/ump_events.hpp @@ -0,0 +1,374 @@ +#pragma once +#include +#include +#include + +// FIXME! we need to review them all to check the channels +NAMESPACE_LIBREMIDI::ump_events +{ +inline libremidi::ump note_on(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, pitch, 0, vel, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, pitch, 0, vel, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, pitch, vel); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t group, uint8_t channel, uint8_t index, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc(group, channel, index, value); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump program_change(uint8_t group, uint8_t channel, uint8_t index) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, index, 0, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t group, uint8_t channel, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, value); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t group, uint8_t channel, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, value); + cmidi2_reverse(msg, u.data); + return u; +} +} + +NAMESPACE_LIBREMIDI::from_midi1 +{ +inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, uint8_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on(0, channel, pitch, 0, u7_to_u16(vel), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, uint8_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off(0, channel, pitch, 0, u7_to_u16(vel), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf(0, channel, pitch, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t channel, uint8_t index, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc(0, channel, index, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump program_change(uint8_t channel, uint8_t index) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_program(0, channel, 0, index, 0, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t channel, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf(0, channel, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t channel, uint16_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(0, channel, u16_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} +} + +NAMESPACE_LIBREMIDI::from_01 +{ +inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, double vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on( + 0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits::max(), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, double vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off( + 0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits::max(), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf( + 0, channel, pitch, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t channel, uint8_t index, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc( + 0, channel, index, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t channel, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf( + 0, channel, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t channel, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct( + 0, channel, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} +} + +NAMESPACE_LIBREMIDI::as_midi1 +{ +inline auto note_off(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; + return res; +} + +inline auto note_on(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; + return res; +} + +inline auto poly_pressure(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_paf_note(ump); + res.value = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000; + return res; +} + +inline auto control_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t control; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.control = cmidi2_ump_get_midi2_cc_index(ump); + res.value = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000; + return res; +} + +inline auto program_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t program; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.program = cmidi2_ump_get_midi2_program_program(ump); + return res; +} + +inline auto aftertouch(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000; + return res; +} + +inline auto pitch_bend(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint16_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000; + return res; +} +} + +NAMESPACE_LIBREMIDI::as_01 +{ +inline auto note_off(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity + = double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto note_on(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity + = double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto poly_pressure(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_paf_note(ump); + res.value = double(cmidi2_ump_get_midi2_paf_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto control_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t control; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.control = cmidi2_ump_get_midi2_cc_index(ump); + res.value = double(cmidi2_ump_get_midi2_cc_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto aftertouch(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = double(cmidi2_ump_get_midi2_caf_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto pitch_bend(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value + = double(cmidi2_ump_get_midi2_pitch_bend_data(ump)) / std::numeric_limits::max(); + return res; +} +} diff --git a/include/libremidi/writer.cpp b/include/libremidi/writer.cpp index 984865c..1d65b78 100644 --- a/include/libremidi/writer.cpp +++ b/include/libremidi/writer.cpp @@ -47,12 +47,12 @@ constexpr R bit_cast(const T& v) noexcept } } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { namespace util { template -static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::ostream& write_be(std::ostream& out, T value) { static_assert( std::endian::native == std::endian::big || std::endian::native == std::endian::little); @@ -62,14 +62,14 @@ static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) } else { - static constexpr auto N = sizeof(value); + static constexpr auto n = sizeof(value); struct storage { - uint8_t bytes[N]; + uint8_t bytes[n]; }; auto data = std::bit_cast(value); - std::reverse(data.bytes, data.bytes + N); - out.write(reinterpret_cast(data.bytes), static_cast(N)); + std::reverse(data.bytes, data.bytes + n); + out.write(reinterpret_cast(data.bytes), static_cast(n)); } return out; } @@ -77,7 +77,7 @@ static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) // Write a number to the midifile // as a variable length value which segments a file into 7-bit // values. Maximum size of aValue is 0x7fffffff -static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector& outdata) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION void write_variable_length(uint32_t aValue, std::vector& outdata) { uint8_t bytes[5] = {0}; @@ -99,7 +99,7 @@ static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector< outdata.push_back(bytes[4]); } -static LIBREMIDI_INLINE void +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION void add_event_track_count_check(std::vector& tracks, int track) { if (track < 0) @@ -146,12 +146,12 @@ void writer::write(std::ostream& out) const util::write_be(out, static_cast(tracks.size())); util::write_be(out, ticksPerQuarterNote); - std::vector trackRawData; + std::vector track_raw_data; for (const auto& event_list : tracks) { - trackRawData.clear(); + track_raw_data.clear(); // Rough estimation of the memory to allocate - trackRawData.reserve(event_list.size() * 3); + track_raw_data.reserve(event_list.size() * 3); for (const auto& event : event_list) { @@ -164,7 +164,7 @@ void writer::write(std::ostream& out) const if (msg.get_meta_event_type() == meta_event_type::END_OF_TRACK) continue; - util::write_variable_length(static_cast(event.tick), trackRawData); + util::write_variable_length(static_cast(event.tick), track_raw_data); if ((msg.get_message_type() == message_type::SYSTEM_EXCLUSIVE) || (event.m.get_message_type() == message_type::EOX)) @@ -176,38 +176,38 @@ void writer::write(std::ostream& out) const // In other words, when creating a 0xf0 or 0xf7 MIDI message, // do not insert the VLV byte length yourself, as this code will // do it for you automatically. - trackRawData.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7; + track_raw_data.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7; - util::write_variable_length(static_cast(msg.size()) - 1, trackRawData); + util::write_variable_length(static_cast(msg.size()) - 1, track_raw_data); - trackRawData.insert( - trackRawData.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size()); + track_raw_data.insert( + track_raw_data.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size()); } else { // Non-sysex type of message, so just output the bytes of the message: - trackRawData.insert( - trackRawData.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size()); + track_raw_data.insert( + track_raw_data.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size()); } } - auto size = trackRawData.size(); + auto size = track_raw_data.size(); const auto eot = meta_events::end_of_track(); - if ((size < 3) || !((trackRawData[size - 3] == 0xFF) && (trackRawData[size - 2] == 0x2F))) + if ((size < 3) || !((track_raw_data[size - 3] == 0xFF) && (track_raw_data[size - 2] == 0x2F))) { - trackRawData.emplace_back(0x0); // tick - trackRawData.emplace_back(eot[0]); - trackRawData.emplace_back(eot[1]); - trackRawData.emplace_back(eot[2]); + track_raw_data.emplace_back(0x0); // tick + track_raw_data.emplace_back(eot[0]); + track_raw_data.emplace_back(eot[1]); + track_raw_data.emplace_back(eot[2]); } // Write the track ID marker "MTrk": out.write("MTrk", 4); - util::write_be(out, static_cast(trackRawData.size())); + util::write_be(out, static_cast(track_raw_data.size())); out.write( - reinterpret_cast(trackRawData.data()), - static_cast(trackRawData.size())); + reinterpret_cast(track_raw_data.data()), + static_cast(track_raw_data.size())); } } } diff --git a/include/libremidi/writer.hpp b/include/libremidi/writer.hpp index 489df86..348ce86 100644 --- a/include/libremidi/writer.hpp +++ b/include/libremidi/writer.hpp @@ -30,7 +30,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct LIBREMIDI_EXPORT writer { diff --git a/src/midi.cpp b/src/midi.cpp index 52a9b09..ccccba0 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -11,12 +11,20 @@ namespace VISEQ::MIDI { void Midi::enumerate(){ std::cout << (int)obs.get_current_api() << std::endl; + + int i = 0; midi_out_ports.clear(); for(const libremidi::output_port& port : obs.get_output_ports()){ midi_out_ports.push_back({port, i, false}); i++; } + + auto out_conf = libremidi::midi1::out_default_configuration(); + virtual_midi_out = libremidi::midi_out(libremidi::output_configuration(), out_conf); + virtual_midi_out.open_virtual_port("viseq out"); + + //midi_out_ports.push_back({&virtual_midi_out, 99, false}); } void Midi::open_midi_out(MidiOutPort *out_port){ @@ -41,6 +49,8 @@ namespace VISEQ::MIDI { notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate); } } + virtual_midi_out.send_message(libremidi::channel_events::note_on(channel, note, velocity)); + notes.emplace_back(99, channel, note, velocity, GetTime(), gate); } void Midi::cc(int midi_out, int channel, int cc, int value){ @@ -64,6 +74,7 @@ namespace VISEQ::MIDI { for(auto &n : notes){ if(time - n.start >= (float)n.gate / 1000.0f){ if(midi_outs.contains(n.midi_out) && midi_outs[n.midi_out]->is_port_open()) midi_outs[n.midi_out]->send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); + virtual_midi_out.send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); n.erase = true; } } diff --git a/src/midi.h b/src/midi.h index 768d20e..b2442fc 100644 --- a/src/midi.h +++ b/src/midi.h @@ -3,6 +3,9 @@ #include "libremidi/libremidi.hpp" #include "libremidi/observer_configuration.hpp" +#include +#include +#include #include #include #include "datatypes.h" @@ -31,6 +34,8 @@ namespace VISEQ::MIDI { std::map> midi_outs; std::vector midi_in_ports; std::vector midi_out_ports; + + libremidi::midi_out virtual_midi_out; std::vector notes; diff --git a/src/signal-manager.cpp b/src/signal-manager.cpp index e1fe7ac..ff5c9a3 100644 --- a/src/signal-manager.cpp +++ b/src/signal-manager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace VISEQ::SIGNAL { @@ -104,6 +105,7 @@ namespace VISEQ::SIGNAL { if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); } } + if(midinode->send_note) midi.note(99, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); s->last = s->next; s->next = next_node; } diff --git a/src/signal-manager.h b/src/signal-manager.h index 005a53a..91f85c0 100644 --- a/src/signal-manager.h +++ b/src/signal-manager.h @@ -6,6 +6,7 @@ #include "midi.h" #include +#include using namespace VISEQ::GRAPH;