packages feed

libremidi 0.3.0 → 0.4.0

raw patch · 126 files changed

+24259/−1 lines, 126 files

Files

libremidi.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           libremidi-version:        0.3.0+version:        0.4.0 synopsis:       libremidi bindings for haskell description:    Please see the README on GitHub at <https://github.com/ejconlon/libremidi-haskell#readme> homepage:       https://github.com/ejconlon/libremidi-haskell#readme@@ -67,8 +67,133 @@   include-dirs:       vendor   install-includes:+      vendor/atomicops.h       vendor/libremidi/api-c.h       vendor/libremidi/libremidi-c.h+      vendor/readerwriterqueue.h+      vendor/libremidi/api.hpp+      vendor/libremidi/backends.hpp+      vendor/libremidi/backends/alsa_raw.hpp+      vendor/libremidi/backends/alsa_raw/config.hpp+      vendor/libremidi/backends/alsa_raw/error_domain.hpp+      vendor/libremidi/backends/alsa_raw/helpers.hpp+      vendor/libremidi/backends/alsa_raw/midi_in.hpp+      vendor/libremidi/backends/alsa_raw/midi_out.hpp+      vendor/libremidi/backends/alsa_raw/observer.hpp+      vendor/libremidi/backends/alsa_raw_ump.hpp+      vendor/libremidi/backends/alsa_raw_ump/config.hpp+      vendor/libremidi/backends/alsa_raw_ump/helpers.hpp+      vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp+      vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp+      vendor/libremidi/backends/alsa_raw_ump/observer.hpp+      vendor/libremidi/backends/alsa_seq.hpp+      vendor/libremidi/backends/alsa_seq/config.hpp+      vendor/libremidi/backends/alsa_seq/helpers.hpp+      vendor/libremidi/backends/alsa_seq/midi_in.hpp+      vendor/libremidi/backends/alsa_seq/midi_out.hpp+      vendor/libremidi/backends/alsa_seq/observer.hpp+      vendor/libremidi/backends/alsa_seq/shared_handler.hpp+      vendor/libremidi/backends/alsa_seq_ump.hpp+      vendor/libremidi/backends/alsa_seq_ump/config.hpp+      vendor/libremidi/backends/alsa_seq_ump/helpers.hpp+      vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp+      vendor/libremidi/backends/coremidi.hpp+      vendor/libremidi/backends/coremidi/config.hpp+      vendor/libremidi/backends/coremidi/error_domain.hpp+      vendor/libremidi/backends/coremidi/helpers.hpp+      vendor/libremidi/backends/coremidi/midi_in.hpp+      vendor/libremidi/backends/coremidi/midi_out.hpp+      vendor/libremidi/backends/coremidi/observer.hpp+      vendor/libremidi/backends/coremidi_ump.hpp+      vendor/libremidi/backends/coremidi_ump/config.hpp+      vendor/libremidi/backends/coremidi_ump/helpers.hpp+      vendor/libremidi/backends/coremidi_ump/midi_in.hpp+      vendor/libremidi/backends/coremidi_ump/midi_out.hpp+      vendor/libremidi/backends/coremidi_ump/observer.hpp+      vendor/libremidi/backends/dummy.hpp+      vendor/libremidi/backends/emscripten.hpp+      vendor/libremidi/backends/emscripten/config.hpp+      vendor/libremidi/backends/emscripten/helpers.hpp+      vendor/libremidi/backends/emscripten/midi_access.hpp+      vendor/libremidi/backends/emscripten/midi_in.hpp+      vendor/libremidi/backends/emscripten/midi_out.hpp+      vendor/libremidi/backends/emscripten/observer.hpp+      vendor/libremidi/backends/jack.hpp+      vendor/libremidi/backends/jack/config.hpp+      vendor/libremidi/backends/jack/error_domain.hpp+      vendor/libremidi/backends/jack/helpers.hpp+      vendor/libremidi/backends/jack/midi_in.hpp+      vendor/libremidi/backends/jack/midi_out.hpp+      vendor/libremidi/backends/jack/observer.hpp+      vendor/libremidi/backends/jack/shared_handler.hpp+      vendor/libremidi/backends/keyboard.hpp+      vendor/libremidi/backends/keyboard/config.hpp+      vendor/libremidi/backends/keyboard/midi_in.hpp+      vendor/libremidi/backends/linux/alsa.hpp+      vendor/libremidi/backends/linux/dylib_loader.hpp+      vendor/libremidi/backends/linux/helpers.hpp+      vendor/libremidi/backends/linux/pipewire.hpp+      vendor/libremidi/backends/linux/udev.hpp+      vendor/libremidi/backends/net/config.hpp+      vendor/libremidi/backends/net/helpers.hpp+      vendor/libremidi/backends/net/midi_in.hpp+      vendor/libremidi/backends/net/midi_out.hpp+      vendor/libremidi/backends/net/observer.hpp+      vendor/libremidi/backends/network.hpp+      vendor/libremidi/backends/network_ump.hpp+      vendor/libremidi/backends/pipewire.hpp+      vendor/libremidi/backends/pipewire/config.hpp+      vendor/libremidi/backends/pipewire/context.hpp+      vendor/libremidi/backends/pipewire/helpers.hpp+      vendor/libremidi/backends/pipewire/midi_in.hpp+      vendor/libremidi/backends/pipewire/midi_out.hpp+      vendor/libremidi/backends/pipewire/observer.hpp+      vendor/libremidi/backends/pipewire/shared_handler.hpp+      vendor/libremidi/backends/winmidi.hpp+      vendor/libremidi/backends/winmidi/config.hpp+      vendor/libremidi/backends/winmidi/helpers.hpp+      vendor/libremidi/backends/winmidi/midi_in.hpp+      vendor/libremidi/backends/winmidi/midi_out.hpp+      vendor/libremidi/backends/winmidi/observer.hpp+      vendor/libremidi/backends/winmm.hpp+      vendor/libremidi/backends/winmm/config.hpp+      vendor/libremidi/backends/winmm/error_domain.hpp+      vendor/libremidi/backends/winmm/helpers.hpp+      vendor/libremidi/backends/winmm/midi_in.hpp+      vendor/libremidi/backends/winmm/midi_out.hpp+      vendor/libremidi/backends/winmm/observer.hpp+      vendor/libremidi/backends/winuwp.hpp+      vendor/libremidi/backends/winuwp/config.hpp+      vendor/libremidi/backends/winuwp/helpers.hpp+      vendor/libremidi/backends/winuwp/midi_in.hpp+      vendor/libremidi/backends/winuwp/midi_out.hpp+      vendor/libremidi/backends/winuwp/observer.hpp+      vendor/libremidi/client.hpp+      vendor/libremidi/cmidi2.hpp+      vendor/libremidi/config.hpp+      vendor/libremidi/configurations.hpp+      vendor/libremidi/defaults.hpp+      vendor/libremidi/detail/conversion.hpp+      vendor/libremidi/detail/memory.hpp+      vendor/libremidi/detail/midi_api.hpp+      vendor/libremidi/detail/midi_in.hpp+      vendor/libremidi/detail/midi_out.hpp+      vendor/libremidi/detail/midi_stream_decoder.hpp+      vendor/libremidi/detail/observer.hpp+      vendor/libremidi/detail/semaphore.hpp+      vendor/libremidi/detail/ump_stream.hpp+      vendor/libremidi/error.hpp+      vendor/libremidi/error_handler.hpp+      vendor/libremidi/input_configuration.hpp+      vendor/libremidi/libremidi.hpp+      vendor/libremidi/message.hpp+      vendor/libremidi/observer_configuration.hpp+      vendor/libremidi/output_configuration.hpp+      vendor/libremidi/reader.hpp+      vendor/libremidi/shared_context.hpp+      vendor/libremidi/system_error2.hpp+      vendor/libremidi/ump.hpp+      vendor/libremidi/writer.hpp   cxx-sources:       vendor/libremidi/client.cpp       vendor/libremidi/libremidi-c.cpp@@ -141,8 +266,133 @@       TypeFamilies   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N   install-includes:+      vendor/atomicops.h       vendor/libremidi/api-c.h       vendor/libremidi/libremidi-c.h+      vendor/readerwriterqueue.h+      vendor/libremidi/api.hpp+      vendor/libremidi/backends.hpp+      vendor/libremidi/backends/alsa_raw.hpp+      vendor/libremidi/backends/alsa_raw/config.hpp+      vendor/libremidi/backends/alsa_raw/error_domain.hpp+      vendor/libremidi/backends/alsa_raw/helpers.hpp+      vendor/libremidi/backends/alsa_raw/midi_in.hpp+      vendor/libremidi/backends/alsa_raw/midi_out.hpp+      vendor/libremidi/backends/alsa_raw/observer.hpp+      vendor/libremidi/backends/alsa_raw_ump.hpp+      vendor/libremidi/backends/alsa_raw_ump/config.hpp+      vendor/libremidi/backends/alsa_raw_ump/helpers.hpp+      vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp+      vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp+      vendor/libremidi/backends/alsa_raw_ump/observer.hpp+      vendor/libremidi/backends/alsa_seq.hpp+      vendor/libremidi/backends/alsa_seq/config.hpp+      vendor/libremidi/backends/alsa_seq/helpers.hpp+      vendor/libremidi/backends/alsa_seq/midi_in.hpp+      vendor/libremidi/backends/alsa_seq/midi_out.hpp+      vendor/libremidi/backends/alsa_seq/observer.hpp+      vendor/libremidi/backends/alsa_seq/shared_handler.hpp+      vendor/libremidi/backends/alsa_seq_ump.hpp+      vendor/libremidi/backends/alsa_seq_ump/config.hpp+      vendor/libremidi/backends/alsa_seq_ump/helpers.hpp+      vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp+      vendor/libremidi/backends/coremidi.hpp+      vendor/libremidi/backends/coremidi/config.hpp+      vendor/libremidi/backends/coremidi/error_domain.hpp+      vendor/libremidi/backends/coremidi/helpers.hpp+      vendor/libremidi/backends/coremidi/midi_in.hpp+      vendor/libremidi/backends/coremidi/midi_out.hpp+      vendor/libremidi/backends/coremidi/observer.hpp+      vendor/libremidi/backends/coremidi_ump.hpp+      vendor/libremidi/backends/coremidi_ump/config.hpp+      vendor/libremidi/backends/coremidi_ump/helpers.hpp+      vendor/libremidi/backends/coremidi_ump/midi_in.hpp+      vendor/libremidi/backends/coremidi_ump/midi_out.hpp+      vendor/libremidi/backends/coremidi_ump/observer.hpp+      vendor/libremidi/backends/dummy.hpp+      vendor/libremidi/backends/emscripten.hpp+      vendor/libremidi/backends/emscripten/config.hpp+      vendor/libremidi/backends/emscripten/helpers.hpp+      vendor/libremidi/backends/emscripten/midi_access.hpp+      vendor/libremidi/backends/emscripten/midi_in.hpp+      vendor/libremidi/backends/emscripten/midi_out.hpp+      vendor/libremidi/backends/emscripten/observer.hpp+      vendor/libremidi/backends/jack.hpp+      vendor/libremidi/backends/jack/config.hpp+      vendor/libremidi/backends/jack/error_domain.hpp+      vendor/libremidi/backends/jack/helpers.hpp+      vendor/libremidi/backends/jack/midi_in.hpp+      vendor/libremidi/backends/jack/midi_out.hpp+      vendor/libremidi/backends/jack/observer.hpp+      vendor/libremidi/backends/jack/shared_handler.hpp+      vendor/libremidi/backends/keyboard.hpp+      vendor/libremidi/backends/keyboard/config.hpp+      vendor/libremidi/backends/keyboard/midi_in.hpp+      vendor/libremidi/backends/linux/alsa.hpp+      vendor/libremidi/backends/linux/dylib_loader.hpp+      vendor/libremidi/backends/linux/helpers.hpp+      vendor/libremidi/backends/linux/pipewire.hpp+      vendor/libremidi/backends/linux/udev.hpp+      vendor/libremidi/backends/net/config.hpp+      vendor/libremidi/backends/net/helpers.hpp+      vendor/libremidi/backends/net/midi_in.hpp+      vendor/libremidi/backends/net/midi_out.hpp+      vendor/libremidi/backends/net/observer.hpp+      vendor/libremidi/backends/network.hpp+      vendor/libremidi/backends/network_ump.hpp+      vendor/libremidi/backends/pipewire.hpp+      vendor/libremidi/backends/pipewire/config.hpp+      vendor/libremidi/backends/pipewire/context.hpp+      vendor/libremidi/backends/pipewire/helpers.hpp+      vendor/libremidi/backends/pipewire/midi_in.hpp+      vendor/libremidi/backends/pipewire/midi_out.hpp+      vendor/libremidi/backends/pipewire/observer.hpp+      vendor/libremidi/backends/pipewire/shared_handler.hpp+      vendor/libremidi/backends/winmidi.hpp+      vendor/libremidi/backends/winmidi/config.hpp+      vendor/libremidi/backends/winmidi/helpers.hpp+      vendor/libremidi/backends/winmidi/midi_in.hpp+      vendor/libremidi/backends/winmidi/midi_out.hpp+      vendor/libremidi/backends/winmidi/observer.hpp+      vendor/libremidi/backends/winmm.hpp+      vendor/libremidi/backends/winmm/config.hpp+      vendor/libremidi/backends/winmm/error_domain.hpp+      vendor/libremidi/backends/winmm/helpers.hpp+      vendor/libremidi/backends/winmm/midi_in.hpp+      vendor/libremidi/backends/winmm/midi_out.hpp+      vendor/libremidi/backends/winmm/observer.hpp+      vendor/libremidi/backends/winuwp.hpp+      vendor/libremidi/backends/winuwp/config.hpp+      vendor/libremidi/backends/winuwp/helpers.hpp+      vendor/libremidi/backends/winuwp/midi_in.hpp+      vendor/libremidi/backends/winuwp/midi_out.hpp+      vendor/libremidi/backends/winuwp/observer.hpp+      vendor/libremidi/client.hpp+      vendor/libremidi/cmidi2.hpp+      vendor/libremidi/config.hpp+      vendor/libremidi/configurations.hpp+      vendor/libremidi/defaults.hpp+      vendor/libremidi/detail/conversion.hpp+      vendor/libremidi/detail/memory.hpp+      vendor/libremidi/detail/midi_api.hpp+      vendor/libremidi/detail/midi_in.hpp+      vendor/libremidi/detail/midi_out.hpp+      vendor/libremidi/detail/midi_stream_decoder.hpp+      vendor/libremidi/detail/observer.hpp+      vendor/libremidi/detail/semaphore.hpp+      vendor/libremidi/detail/ump_stream.hpp+      vendor/libremidi/error.hpp+      vendor/libremidi/error_handler.hpp+      vendor/libremidi/input_configuration.hpp+      vendor/libremidi/libremidi.hpp+      vendor/libremidi/message.hpp+      vendor/libremidi/observer_configuration.hpp+      vendor/libremidi/output_configuration.hpp+      vendor/libremidi/reader.hpp+      vendor/libremidi/shared_context.hpp+      vendor/libremidi/system_error2.hpp+      vendor/libremidi/ump.hpp+      vendor/libremidi/writer.hpp   build-depends:       base >=4.12 && <5     , containers >=0.6.7 && <0.7@@ -194,8 +444,133 @@       TypeFamilies   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N   install-includes:+      vendor/atomicops.h       vendor/libremidi/api-c.h       vendor/libremidi/libremidi-c.h+      vendor/readerwriterqueue.h+      vendor/libremidi/api.hpp+      vendor/libremidi/backends.hpp+      vendor/libremidi/backends/alsa_raw.hpp+      vendor/libremidi/backends/alsa_raw/config.hpp+      vendor/libremidi/backends/alsa_raw/error_domain.hpp+      vendor/libremidi/backends/alsa_raw/helpers.hpp+      vendor/libremidi/backends/alsa_raw/midi_in.hpp+      vendor/libremidi/backends/alsa_raw/midi_out.hpp+      vendor/libremidi/backends/alsa_raw/observer.hpp+      vendor/libremidi/backends/alsa_raw_ump.hpp+      vendor/libremidi/backends/alsa_raw_ump/config.hpp+      vendor/libremidi/backends/alsa_raw_ump/helpers.hpp+      vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp+      vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp+      vendor/libremidi/backends/alsa_raw_ump/observer.hpp+      vendor/libremidi/backends/alsa_seq.hpp+      vendor/libremidi/backends/alsa_seq/config.hpp+      vendor/libremidi/backends/alsa_seq/helpers.hpp+      vendor/libremidi/backends/alsa_seq/midi_in.hpp+      vendor/libremidi/backends/alsa_seq/midi_out.hpp+      vendor/libremidi/backends/alsa_seq/observer.hpp+      vendor/libremidi/backends/alsa_seq/shared_handler.hpp+      vendor/libremidi/backends/alsa_seq_ump.hpp+      vendor/libremidi/backends/alsa_seq_ump/config.hpp+      vendor/libremidi/backends/alsa_seq_ump/helpers.hpp+      vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp+      vendor/libremidi/backends/coremidi.hpp+      vendor/libremidi/backends/coremidi/config.hpp+      vendor/libremidi/backends/coremidi/error_domain.hpp+      vendor/libremidi/backends/coremidi/helpers.hpp+      vendor/libremidi/backends/coremidi/midi_in.hpp+      vendor/libremidi/backends/coremidi/midi_out.hpp+      vendor/libremidi/backends/coremidi/observer.hpp+      vendor/libremidi/backends/coremidi_ump.hpp+      vendor/libremidi/backends/coremidi_ump/config.hpp+      vendor/libremidi/backends/coremidi_ump/helpers.hpp+      vendor/libremidi/backends/coremidi_ump/midi_in.hpp+      vendor/libremidi/backends/coremidi_ump/midi_out.hpp+      vendor/libremidi/backends/coremidi_ump/observer.hpp+      vendor/libremidi/backends/dummy.hpp+      vendor/libremidi/backends/emscripten.hpp+      vendor/libremidi/backends/emscripten/config.hpp+      vendor/libremidi/backends/emscripten/helpers.hpp+      vendor/libremidi/backends/emscripten/midi_access.hpp+      vendor/libremidi/backends/emscripten/midi_in.hpp+      vendor/libremidi/backends/emscripten/midi_out.hpp+      vendor/libremidi/backends/emscripten/observer.hpp+      vendor/libremidi/backends/jack.hpp+      vendor/libremidi/backends/jack/config.hpp+      vendor/libremidi/backends/jack/error_domain.hpp+      vendor/libremidi/backends/jack/helpers.hpp+      vendor/libremidi/backends/jack/midi_in.hpp+      vendor/libremidi/backends/jack/midi_out.hpp+      vendor/libremidi/backends/jack/observer.hpp+      vendor/libremidi/backends/jack/shared_handler.hpp+      vendor/libremidi/backends/keyboard.hpp+      vendor/libremidi/backends/keyboard/config.hpp+      vendor/libremidi/backends/keyboard/midi_in.hpp+      vendor/libremidi/backends/linux/alsa.hpp+      vendor/libremidi/backends/linux/dylib_loader.hpp+      vendor/libremidi/backends/linux/helpers.hpp+      vendor/libremidi/backends/linux/pipewire.hpp+      vendor/libremidi/backends/linux/udev.hpp+      vendor/libremidi/backends/net/config.hpp+      vendor/libremidi/backends/net/helpers.hpp+      vendor/libremidi/backends/net/midi_in.hpp+      vendor/libremidi/backends/net/midi_out.hpp+      vendor/libremidi/backends/net/observer.hpp+      vendor/libremidi/backends/network.hpp+      vendor/libremidi/backends/network_ump.hpp+      vendor/libremidi/backends/pipewire.hpp+      vendor/libremidi/backends/pipewire/config.hpp+      vendor/libremidi/backends/pipewire/context.hpp+      vendor/libremidi/backends/pipewire/helpers.hpp+      vendor/libremidi/backends/pipewire/midi_in.hpp+      vendor/libremidi/backends/pipewire/midi_out.hpp+      vendor/libremidi/backends/pipewire/observer.hpp+      vendor/libremidi/backends/pipewire/shared_handler.hpp+      vendor/libremidi/backends/winmidi.hpp+      vendor/libremidi/backends/winmidi/config.hpp+      vendor/libremidi/backends/winmidi/helpers.hpp+      vendor/libremidi/backends/winmidi/midi_in.hpp+      vendor/libremidi/backends/winmidi/midi_out.hpp+      vendor/libremidi/backends/winmidi/observer.hpp+      vendor/libremidi/backends/winmm.hpp+      vendor/libremidi/backends/winmm/config.hpp+      vendor/libremidi/backends/winmm/error_domain.hpp+      vendor/libremidi/backends/winmm/helpers.hpp+      vendor/libremidi/backends/winmm/midi_in.hpp+      vendor/libremidi/backends/winmm/midi_out.hpp+      vendor/libremidi/backends/winmm/observer.hpp+      vendor/libremidi/backends/winuwp.hpp+      vendor/libremidi/backends/winuwp/config.hpp+      vendor/libremidi/backends/winuwp/helpers.hpp+      vendor/libremidi/backends/winuwp/midi_in.hpp+      vendor/libremidi/backends/winuwp/midi_out.hpp+      vendor/libremidi/backends/winuwp/observer.hpp+      vendor/libremidi/client.hpp+      vendor/libremidi/cmidi2.hpp+      vendor/libremidi/config.hpp+      vendor/libremidi/configurations.hpp+      vendor/libremidi/defaults.hpp+      vendor/libremidi/detail/conversion.hpp+      vendor/libremidi/detail/memory.hpp+      vendor/libremidi/detail/midi_api.hpp+      vendor/libremidi/detail/midi_in.hpp+      vendor/libremidi/detail/midi_out.hpp+      vendor/libremidi/detail/midi_stream_decoder.hpp+      vendor/libremidi/detail/observer.hpp+      vendor/libremidi/detail/semaphore.hpp+      vendor/libremidi/detail/ump_stream.hpp+      vendor/libremidi/error.hpp+      vendor/libremidi/error_handler.hpp+      vendor/libremidi/input_configuration.hpp+      vendor/libremidi/libremidi.hpp+      vendor/libremidi/message.hpp+      vendor/libremidi/observer_configuration.hpp+      vendor/libremidi/output_configuration.hpp+      vendor/libremidi/reader.hpp+      vendor/libremidi/shared_context.hpp+      vendor/libremidi/system_error2.hpp+      vendor/libremidi/ump.hpp+      vendor/libremidi/writer.hpp   build-depends:       base >=4.12 && <5     , containers >=0.6.7 && <0.7
+ vendor/atomicops.h view
@@ -0,0 +1,772 @@+// ©2013-2016 Cameron Desrochers.+// Distributed under the simplified BSD license (see the license file that+// should have come with this header).+// Uses Jeff Preshing's semaphore implementation (under the terms of its+// separate zlib license, embedded below).++#pragma once++// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) implementation+// of low-level memory barriers, plus a few semi-portable utility macros (for inlining and alignment).+// Also has a basic atomic type (limited to hardware-supported atomics with no memory ordering guarantees).+// Uses the AE_* prefix for macros (historical reasons), and the "moodycamel" namespace for symbols.++#include <cerrno>+#include <cassert>+#include <type_traits>+#include <cerrno>+#include <cstdint>+#include <ctime>++// Platform detection+#if defined(__INTEL_COMPILER)+#define AE_ICC+#elif defined(_MSC_VER)+#define AE_VCPP+#elif defined(__GNUC__)+#define AE_GCC+#endif++#if defined(_M_IA64) || defined(__ia64__)+#define AE_ARCH_IA64+#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__)+#define AE_ARCH_X64+#elif defined(_M_IX86) || defined(__i386__)+#define AE_ARCH_X86+#elif defined(_M_PPC) || defined(__powerpc__)+#define AE_ARCH_PPC+#else+#define AE_ARCH_UNKNOWN+#endif+++// AE_UNUSED+#define AE_UNUSED(x) ((void)x)++// AE_NO_TSAN/AE_TSAN_ANNOTATE_*+// For GCC+#if defined(__SANITIZE_THREAD__)+#define AE_TSAN_IS_ENABLED+#endif+// For clang+#if defined(__has_feature)+#if __has_feature(thread_sanitizer) && !defined(AE_TSAN_IS_ENABLED)+#define AE_TSAN_IS_ENABLED+#endif+#endif++#ifdef AE_TSAN_IS_ENABLED+#if __cplusplus >= 201703L  // inline variables require C++17+namespace moodycamel { inline int ae_tsan_global; }+#define AE_TSAN_ANNOTATE_RELEASE() AnnotateHappensBefore(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))+#define AE_TSAN_ANNOTATE_ACQUIRE() AnnotateHappensAfter(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))+extern "C" void AnnotateHappensBefore(const char*, int, void*);+extern "C" void AnnotateHappensAfter(const char*, int, void*);+#else  // when we can't work with tsan, attempt to disable its warnings+#define AE_NO_TSAN __attribute__((no_sanitize("thread")))+#endif+#endif++#ifndef AE_NO_TSAN+#define AE_NO_TSAN+#endif++#ifndef AE_TSAN_ANNOTATE_RELEASE+#define AE_TSAN_ANNOTATE_RELEASE()+#define AE_TSAN_ANNOTATE_ACQUIRE()+#endif+++// AE_FORCEINLINE+#if defined(AE_VCPP) || defined(AE_ICC)+#define AE_FORCEINLINE __forceinline+#elif defined(AE_GCC)+//#define AE_FORCEINLINE __attribute__((always_inline)) +#define AE_FORCEINLINE inline+#else+#define AE_FORCEINLINE inline+#endif+++// AE_ALIGN+#if defined(AE_VCPP) || defined(AE_ICC)+#define AE_ALIGN(x) __declspec(align(x))+#elif defined(AE_GCC)+#define AE_ALIGN(x) __attribute__((aligned(x)))+#else+// Assume GCC compliant syntax...+#define AE_ALIGN(x) __attribute__((aligned(x)))+#endif+++// Portable atomic fences implemented below:++namespace moodycamel {++enum memory_order {+	memory_order_relaxed,+	memory_order_acquire,+	memory_order_release,+	memory_order_acq_rel,+	memory_order_seq_cst,++	// memory_order_sync: Forces a full sync:+	// #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad+	memory_order_sync = memory_order_seq_cst+};++}    // end namespace moodycamel++#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || (defined(AE_ICC) && __INTEL_COMPILER < 1600)+// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences++#include <intrin.h>++#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)+#define AeFullSync _mm_mfence+#define AeLiteSync _mm_mfence+#elif defined(AE_ARCH_IA64)+#define AeFullSync __mf+#define AeLiteSync __mf+#elif defined(AE_ARCH_PPC)+#include <ppcintrinsics.h>+#define AeFullSync __sync+#define AeLiteSync __lwsync+#endif+++#ifdef AE_VCPP+#pragma warning(push)+#pragma warning(disable: 4365)		// Disable erroneous 'conversion from long to unsigned int, signed/unsigned mismatch' error when using `assert`+#ifdef __cplusplus_cli+#pragma managed(push, off)+#endif+#endif++namespace moodycamel {++AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN+{+	switch (order) {+		case memory_order_relaxed: break;+		case memory_order_acquire: _ReadBarrier(); break;+		case memory_order_release: _WriteBarrier(); break;+		case memory_order_acq_rel: _ReadWriteBarrier(); break;+		case memory_order_seq_cst: _ReadWriteBarrier(); break;+		default: assert(false);+	}+}++// x86/x64 have a strong memory model -- all loads and stores have+// acquire and release semantics automatically (so only need compiler+// barriers for those).+#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64)+AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN+{+	switch (order) {+		case memory_order_relaxed: break;+		case memory_order_acquire: _ReadBarrier(); break;+		case memory_order_release: _WriteBarrier(); break;+		case memory_order_acq_rel: _ReadWriteBarrier(); break;+		case memory_order_seq_cst:+			_ReadWriteBarrier();+			AeFullSync();+			_ReadWriteBarrier();+			break;+		default: assert(false);+	}+}+#else+AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN+{+	// Non-specialized arch, use heavier memory barriers everywhere just in case :-(+	switch (order) {+		case memory_order_relaxed:+			break;+		case memory_order_acquire:+			_ReadBarrier();+			AeLiteSync();+			_ReadBarrier();+			break;+		case memory_order_release:+			_WriteBarrier();+			AeLiteSync();+			_WriteBarrier();+			break;+		case memory_order_acq_rel:+			_ReadWriteBarrier();+			AeLiteSync();+			_ReadWriteBarrier();+			break;+		case memory_order_seq_cst:+			_ReadWriteBarrier();+			AeFullSync();+			_ReadWriteBarrier();+			break;+		default: assert(false);+	}+}+#endif+}    // end namespace moodycamel+#else+// Use standard library of atomics+#include <atomic>++namespace moodycamel {++AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN+{+	switch (order) {+		case memory_order_relaxed: break;+		case memory_order_acquire: std::atomic_signal_fence(std::memory_order_acquire); break;+		case memory_order_release: std::atomic_signal_fence(std::memory_order_release); break;+		case memory_order_acq_rel: std::atomic_signal_fence(std::memory_order_acq_rel); break;+		case memory_order_seq_cst: std::atomic_signal_fence(std::memory_order_seq_cst); break;+		default: assert(false);+	}+}++AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN+{+	switch (order) {+		case memory_order_relaxed: break;+		case memory_order_acquire: AE_TSAN_ANNOTATE_ACQUIRE(); std::atomic_thread_fence(std::memory_order_acquire); break;+		case memory_order_release: AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_release); break;+		case memory_order_acq_rel: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_acq_rel); break;+		case memory_order_seq_cst: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_seq_cst); break;+		default: assert(false);+	}+}++}    // end namespace moodycamel++#endif+++#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli))+#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+#endif++#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+#include <atomic>+#endif+#include <utility>++// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY:+// Provides basic support for atomic variables -- no memory ordering guarantees are provided.+// The guarantee of atomicity is only made for types that already have atomic load and store guarantees+// at the hardware level -- on most platforms this generally means aligned pointers and integers (only).+namespace moodycamel {+template<typename T>+class weak_atomic+{+public:+	AE_NO_TSAN weak_atomic() : value() { }+#ifdef AE_VCPP+#pragma warning(push)+#pragma warning(disable: 4100)		// Get rid of (erroneous) 'unreferenced formal parameter' warning+#endif+	template<typename U> AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) {  }+#ifdef __cplusplus_cli+	// Work around bug with universal reference/nullptr combination that only appears when /clr is on+	AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) {  }+#endif+	AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) {  }+	AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) {  }+#ifdef AE_VCPP+#pragma warning(pop)+#endif++	AE_FORCEINLINE operator T() const AE_NO_TSAN { return load(); }++	+#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+	template<typename U> AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward<U>(x); return *this; }+	AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { value = other.value; return *this; }+	+	AE_FORCEINLINE T load() const AE_NO_TSAN { return value; }+	+	AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN+	{+#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)+		if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);+#if defined(_M_AMD64)+		else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);+#endif+#else+#error Unsupported platform+#endif+		assert(false && "T must be either a 32 or 64 bit type");+		return value;+	}+	+	AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN+	{+#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)+		if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);+#if defined(_M_AMD64)+		else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);+#endif+#else+#error Unsupported platform+#endif+		assert(false && "T must be either a 32 or 64 bit type");+		return value;+	}+#else+	template<typename U>+	AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN+	{+		value.store(std::forward<U>(x), std::memory_order_relaxed);+		return *this;+	}+	+	AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN+	{+		value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed);+		return *this;+	}++	AE_FORCEINLINE T load() const AE_NO_TSAN { return value.load(std::memory_order_relaxed); }+	+	AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN+	{+		return value.fetch_add(increment, std::memory_order_acquire);+	}+	+	AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN+	{+		return value.fetch_add(increment, std::memory_order_release);+	}+#endif+	++private:+#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+	// No std::atomic support, but still need to circumvent compiler optimizations.+	// `volatile` will make memory access slow, but is guaranteed to be reliable.+	volatile T value;+#else+	std::atomic<T> value;+#endif+};++}	// end namespace moodycamel++++// Portable single-producer, single-consumer semaphore below:++#if defined(_WIN32)+// Avoid including windows.h in a header; we only need a handful of+// items, so we'll redeclare them here (this is relatively safe since+// the API generally has to remain stable between Windows versions).+// I know this is an ugly hack but it still beats polluting the global+// namespace with thousands of generic names or adding a .cpp for nothing.+extern "C" {+	struct _SECURITY_ATTRIBUTES;+	__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);+	__declspec(dllimport) int __stdcall CloseHandle(void* hObject);+	__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);+	__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);+}+#elif defined(__MACH__)+#include <mach/mach.h>+#elif defined(__unix__)+#include <semaphore.h>+#elif defined(FREERTOS)+#include <FreeRTOS.h>+#include <semphr.h>+#include <task.h>+#endif++namespace moodycamel+{+	// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's+	// portable + lightweight semaphore implementations, originally from+	// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h+	// LICENSE:+	// Copyright (c) 2015 Jeff Preshing+	//+	// This software is provided 'as-is', without any express or implied+	// warranty. In no event will the authors be held liable for any damages+	// arising from the use of this software.+	//+	// Permission is granted to anyone to use this software for any purpose,+	// including commercial applications, and to alter it and redistribute it+	// freely, subject to the following restrictions:+	//+	// 1. The origin of this software must not be misrepresented; you must not+	//    claim that you wrote the original software. If you use this software+	//    in a product, an acknowledgement in the product documentation would be+	//    appreciated but is not required.+	// 2. Altered source versions must be plainly marked as such, and must not be+	//    misrepresented as being the original software.+	// 3. This notice may not be removed or altered from any source distribution.+	namespace spsc_sema+	{+#if defined(_WIN32)+		class Semaphore+		{+		private:+		    void* m_hSema;+		    +		    Semaphore(const Semaphore& other);+		    Semaphore& operator=(const Semaphore& other);++		public:+		    AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema()+		    {+		        assert(initialCount >= 0);+		        const long maxLong = 0x7fffffff;+		        m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);+		        assert(m_hSema);+		    }++		    AE_NO_TSAN ~Semaphore()+		    {+		        CloseHandle(m_hSema);+		    }++		    bool wait() AE_NO_TSAN+		    {+		    	const unsigned long infinite = 0xffffffff;+		        return WaitForSingleObject(m_hSema, infinite) == 0;+		    }++			bool try_wait() AE_NO_TSAN+			{+				return WaitForSingleObject(m_hSema, 0) == 0;+			}++			bool timed_wait(std::uint64_t usecs) AE_NO_TSAN+			{+				return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;+			}++		    void signal(int count = 1) AE_NO_TSAN+		    {+		        while (!ReleaseSemaphore(m_hSema, count, nullptr));+		    }+		};+#elif defined(__MACH__)+		//---------------------------------------------------------+		// Semaphore (Apple iOS and OSX)+		// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html+		//---------------------------------------------------------+		class Semaphore+		{+		private:+		    semaphore_t m_sema;++		    Semaphore(const Semaphore& other);+		    Semaphore& operator=(const Semaphore& other);++		public:+		    AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()+		    {+		        assert(initialCount >= 0);+		        kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);+		        assert(rc == KERN_SUCCESS);+		        AE_UNUSED(rc);+		    }++		    AE_NO_TSAN ~Semaphore()+		    {+		        semaphore_destroy(mach_task_self(), m_sema);+		    }++		    bool wait() AE_NO_TSAN+		    {+		        return semaphore_wait(m_sema) == KERN_SUCCESS;+		    }++			bool try_wait() AE_NO_TSAN+			{+				return timed_wait(0);+			}++			bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN+			{+				mach_timespec_t ts;+				ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);+				ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);++				// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html+				kern_return_t rc = semaphore_timedwait(m_sema, ts);+				return rc == KERN_SUCCESS;+			}++		    void signal() AE_NO_TSAN+		    {+		        while (semaphore_signal(m_sema) != KERN_SUCCESS);+		    }++		    void signal(int count) AE_NO_TSAN+		    {+		        while (count-- > 0)+		        {+		            while (semaphore_signal(m_sema) != KERN_SUCCESS);+		        }+		    }+		};+#elif defined(__unix__)+		//---------------------------------------------------------+		// Semaphore (POSIX, Linux)+		//---------------------------------------------------------+		class Semaphore+		{+		private:+		    sem_t m_sema;++		    Semaphore(const Semaphore& other);+		    Semaphore& operator=(const Semaphore& other);++		public:+		    AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()+		    {+		        assert(initialCount >= 0);+		        int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));+		        assert(rc == 0);+		        AE_UNUSED(rc);+		    }++		    AE_NO_TSAN ~Semaphore()+		    {+		        sem_destroy(&m_sema);+		    }++		    bool wait() AE_NO_TSAN+		    {+		        // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error+		        int rc;+		        do+		        {+		            rc = sem_wait(&m_sema);+		        }+		        while (rc == -1 && errno == EINTR);+		        return rc == 0;+		    }++			bool try_wait() AE_NO_TSAN+			{+				int rc;+				do {+					rc = sem_trywait(&m_sema);+				} while (rc == -1 && errno == EINTR);+				return rc == 0;+			}++			bool timed_wait(std::uint64_t usecs) AE_NO_TSAN+			{+				struct timespec ts;+				const int usecs_in_1_sec = 1000000;+				const int nsecs_in_1_sec = 1000000000;+				clock_gettime(CLOCK_REALTIME, &ts);+				ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec);+				ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000;+				// sem_timedwait bombs if you have more than 1e9 in tv_nsec+				// so we have to clean things up before passing it in+				if (ts.tv_nsec >= nsecs_in_1_sec) {+					ts.tv_nsec -= nsecs_in_1_sec;+					++ts.tv_sec;+				}++				int rc;+				do {+					rc = sem_timedwait(&m_sema, &ts);+				} while (rc == -1 && errno == EINTR);+				return rc == 0;+			}++		    void signal() AE_NO_TSAN+		    {+		        while (sem_post(&m_sema) == -1);+		    }++		    void signal(int count) AE_NO_TSAN+		    {+		        while (count-- > 0)+		        {+		            while (sem_post(&m_sema) == -1);+		        }+		    }+		};+#elif defined(FREERTOS)+		//---------------------------------------------------------+		// Semaphore (FreeRTOS)+		//---------------------------------------------------------+		class Semaphore+		{+		private:+			SemaphoreHandle_t m_sema;++			Semaphore(const Semaphore& other);+			Semaphore& operator=(const Semaphore& other);++		public:+			AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()+			{+				assert(initialCount >= 0);+				m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull), static_cast<UBaseType_t>(initialCount));+				assert(m_sema);+			}++			AE_NO_TSAN ~Semaphore()+			{+				vSemaphoreDelete(m_sema);+			}++			bool wait() AE_NO_TSAN+			{+				return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE;+			}++			bool try_wait() AE_NO_TSAN+			{+				// Note: In an ISR context, if this causes a task to unblock,+				// the caller won't know about it+				if (xPortIsInsideInterrupt())+					return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE;+				return xSemaphoreTake(m_sema, 0) == pdTRUE;+			}++			bool timed_wait(std::uint64_t usecs) AE_NO_TSAN+			{+				std::uint64_t msecs = usecs / 1000;+				TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS);+				if (ticks == 0)+					return try_wait();+				return xSemaphoreTake(m_sema, ticks) == pdTRUE;+			}++			void signal() AE_NO_TSAN+			{+				// Note: In an ISR context, if this causes a task to unblock,+				// the caller won't know about it+				BaseType_t rc;+				if (xPortIsInsideInterrupt())+					rc = xSemaphoreGiveFromISR(m_sema, NULL);+				else+					rc = xSemaphoreGive(m_sema);+				assert(rc == pdTRUE);+				AE_UNUSED(rc);+			}++			void signal(int count) AE_NO_TSAN+			{+				while (count-- > 0)+					signal();+			}+		};+#else+#error Unsupported platform! (No semaphore wrapper available)+#endif++		//---------------------------------------------------------+		// LightweightSemaphore+		//---------------------------------------------------------+		class LightweightSemaphore+		{+		public:+			typedef std::make_signed<std::size_t>::type ssize_t;+			+		private:+		    weak_atomic<ssize_t> m_count;+		    Semaphore m_sema;++		    bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN+		    {+		        ssize_t oldCount;+		        // Is there a better way to set the initial spin count?+		        // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,+		        // as threads start hitting the kernel semaphore.+		        int spin = 1024;+		        while (--spin >= 0)+		        {+		            if (m_count.load() > 0)+		            {+		                m_count.fetch_add_acquire(-1);+		                return true;+		            }+		            compiler_fence(memory_order_acquire);     // Prevent the compiler from collapsing the loop.+		        }+		        oldCount = m_count.fetch_add_acquire(-1);+				if (oldCount > 0)+					return true;+		        if (timeout_usecs < 0)+				{+					if (m_sema.wait())+						return true;+				}+				if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs)))+					return true;+				// At this point, we've timed out waiting for the semaphore, but the+				// count is still decremented indicating we may still be waiting on+				// it. So we have to re-adjust the count, but only if the semaphore+				// wasn't signaled enough times for us too since then. If it was, we+				// need to release the semaphore too.+				while (true)+				{+					oldCount = m_count.fetch_add_release(1);+					if (oldCount < 0)+						return false;    // successfully restored things to the way they were+					// Oh, the producer thread just signaled the semaphore after all. Try again:+					oldCount = m_count.fetch_add_acquire(-1);+					if (oldCount > 0 && m_sema.try_wait())+						return true;+				}+		    }++		public:+		    AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema()+		    {+		        assert(initialCount >= 0);+		    }++		    bool tryWait() AE_NO_TSAN+		    {+		        if (m_count.load() > 0)+		        {+		        	m_count.fetch_add_acquire(-1);+		        	return true;+		        }+		        return false;+		    }++		    bool wait() AE_NO_TSAN+		    {+		        return tryWait() || waitWithPartialSpinning();+		    }++			bool wait(std::int64_t timeout_usecs) AE_NO_TSAN+			{+				return tryWait() || waitWithPartialSpinning(timeout_usecs);+			}++		    void signal(ssize_t count = 1) AE_NO_TSAN+		    {+		    	assert(count >= 0);+		        ssize_t oldCount = m_count.fetch_add_release(count);+		        assert(oldCount >= -1);+		        if (oldCount < 0)+		        {+		            m_sema.signal(1);+		        }+		    }+		    +		    std::size_t availableApprox() const AE_NO_TSAN+		    {+		    	ssize_t count = m_count.load();+		    	return count > 0 ? static_cast<std::size_t>(count) : 0;+		    }+		};+	}	// end namespace spsc_sema+}	// end namespace moodycamel++#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))+#pragma warning(pop)+#ifdef __cplusplus_cli+#pragma managed(pop)+#endif+#endif
+ vendor/libremidi/api.hpp view
@@ -0,0 +1,95 @@+#pragma once+#include <libremidi/api-c.h>+#include <libremidi/config.hpp>++#include <any>+#include <string_view>+#include <vector>++namespace libremidi+{+//! 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.+using API = libremidi_api;++/**+ * \brief A function to determine the available compiled MIDI 1.0 APIs.++  The values returned in the std::vector can be compared against+  the enumerated list values.  Note that there can be more than one+  API compiled for certain operating systems.+*/+LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;++/**+ * \brief A function to determine the available compiled MIDI 2.0 APIs.++  The values returned in the std::vector can be compared against+  the enumerated list values.  Note that there can be more than one+  API compiled for certain operating systems.+*/+LIBREMIDI_EXPORT std::vector<libremidi::API> 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;++//! Map from and to API names+LIBREMIDI_EXPORT std::string_view get_api_name(libremidi::API api);+//! Map from and to API names+LIBREMIDI_EXPORT std::string_view get_api_display_name(libremidi::API api);+//! Look-up an API through its name+LIBREMIDI_EXPORT libremidi::API get_compiled_api_by_name(std::string_view api);++inline constexpr bool is_midi1(libremidi::API api)+{+  return (static_cast<int>(api) >= 0x1 && static_cast<int>(api) < 0x1000)+         || api == libremidi::API::DUMMY;+}++inline constexpr bool is_midi2(libremidi::API api)+{+  return static_cast<int>(api) >= 0x1000;+}++namespace midi1+{+//! Returns the default MIDI 1.0 backend to use for the target OS.+inline constexpr libremidi::API default_api() noexcept+{+#if defined(__APPLE__)+  return API::COREMIDI;+#elif defined(_WIN32)+  return API::WINDOWS_MM;+#elif defined(__linux__)+  return API::ALSA_SEQ;+#elif defined(__emscripten__)+  return API::EMSCRIPTEN_WEBMIDI;+#else+  return API::DUMMY;+#endif+}+}++namespace midi2+{+//! Returns the default MIDI 2.0 backend to use for the target OS.+inline constexpr libremidi::API default_api() noexcept+{+#if defined(__APPLE__)+  return API::COREMIDI_UMP;+#elif defined(_WIN32)+  return API::WINDOWS_MIDI_SERVICES;+#elif defined(__linux__)+  return API::ALSA_SEQ_UMP;+#elif defined(__emscripten__)+  return API::DUMMY;+#else+  return API::DUMMY;+#endif+}+}+}
+ vendor/libremidi/backends.hpp view
@@ -0,0 +1,250 @@+#pragma once+#include <libremidi/config.hpp>++#include <any>+#include <tuple>++#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)+  #if defined(LIBREMIDI_JACK)+    #undef LIBREMIDI_JACK+  #endif+#endif+#if !defined(LIBREMIDI_ALSA) && !defined(LIBREMIDI_JACK) && !defined(LIBREMIDI_COREMIDI) \+    && !defined(LIBREMIDI_WINMM)+  #define LIBREMIDI_DUMMY+#endif++#if defined(LIBREMIDI_ALSA)+  #include <libremidi/backends/alsa_seq.hpp>+  #include <libremidi/backends/linux/alsa.hpp>++  #if LIBREMIDI_ALSA_HAS_RAMWIDI+    #include <libremidi/backends/alsa_raw.hpp>+  #endif++  #if LIBREMIDI_ALSA_HAS_UMP+    #include <libremidi/backends/alsa_raw_ump.hpp>+    #include <libremidi/backends/alsa_seq_ump.hpp>+  #endif+#endif++#if defined(LIBREMIDI_JACK)+  #include <libremidi/backends/jack.hpp>+#endif++#if defined(LIBREMIDI_PIPEWIRE)+  #include <libremidi/backends/pipewire.hpp>+#endif++#if defined(LIBREMIDI_COREMIDI)+  #include <libremidi/backends/coremidi.hpp>+  #include <libremidi/backends/coremidi_ump.hpp>+#endif++#if defined(LIBREMIDI_WINMM)+  #include <libremidi/backends/winmm.hpp>+#endif++#if defined(LIBREMIDI_WINUWP)+  #include <libremidi/backends/winuwp.hpp>+#endif++#if defined(LIBREMIDI_WINMIDI)+  #include <libremidi/backends/winmidi.hpp>+#endif++#if defined(LIBREMIDI_EMSCRIPTEN)+  #include <libremidi/backends/emscripten.hpp>+#endif++#include <libremidi/backends/dummy.hpp>++#if defined(LIBREMIDI_KEYBOARD)+  #include <libremidi/backends/keyboard.hpp>+#endif++#if defined(LIBREMIDI_NETWORK)+  #include <libremidi/backends/network.hpp>+  #include <libremidi/backends/network_ump.hpp>+#endif++namespace libremidi+{+// The order here will control the order of the API search in+// the constructor.+template <typename unused, typename... Args>+constexpr auto make_tl(unused, Args...)+{+  return std::tuple<Args...>{};+}++namespace midi1+{+static constexpr auto available_backends = make_tl(+    0+#if defined(LIBREMIDI_ALSA)+    ,+    alsa_seq::backend{}+  #if LIBREMIDI_ALSA_HAS_RAMWIDI+    ,+    alsa_raw::backend{}+  #endif+#endif+#if defined(LIBREMIDI_COREMIDI)+    ,+    core_backend{}+#endif+#if defined(LIBREMIDI_WINMM)+    ,+    winmm_backend{}+#endif+#if defined(LIBREMIDI_WINUWP)+    ,+    winuwp_backend{}+#endif+#if defined(LIBREMIDI_EMSCRIPTEN)+    ,+    emscripten_backend{}+#endif+#if defined(LIBREMIDI_JACK)+    ,+    jack_backend{}+#endif+#if defined(LIBREMIDI_PIPEWIRE)+    ,+    pipewire::backend{}+#endif+#if defined(LIBREMIDI_KEYBOARD)+    ,+    kbd_backend{}+#endif+#if defined(LIBREMIDI_NETWORK)+    ,+    net::backend{}+#endif+    ,+    dummy_backend{});++// There should always be at least one back-end.+static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);++template <typename F>+auto for_all_backends(F&& f)+{+  std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends);+}++template <typename F>+auto for_backend(libremidi::API api, F&& f)+{+  static constexpr auto is_api = [](auto& backend, libremidi::API api) {+    return backend.available() && backend.API == api;+  };+  std::apply([&](auto&&... b) { ((is_api(b, api) && (f(b), true)) || ...); }, available_backends);+}+}++namespace midi2+{+static constexpr auto available_backends = make_tl(+    0+#if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP+    ,+    alsa_seq_ump::backend{}, alsa_raw_ump::backend{}+#endif+#if defined(LIBREMIDI_COREMIDI)+    ,+    coremidi_ump::backend{}+#endif+#if defined(LIBREMIDI_WINMIDI)+    ,+    winmidi::backend{}+#endif+#if defined(LIBREMIDI_NETWORK)+    ,+    net_ump::backend{}+#endif+    ,+    dummy_backend{});++// There should always be at least one back-end.+static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);++template <typename F>+auto for_all_backends(F&& f)+{+  std::apply([&](auto&&... x) { (f(x), ...); }, available_backends);+}++template <typename F>+auto for_backend(libremidi::API api, F&& f)+{+  static constexpr auto is_api+      = [](auto& backend, libremidi::API api) { return backend.API == api; };+  std::apply([&](auto&&... b) { ((is_api(b, api) && (f(b), true)) || ...); }, available_backends);+}+}++namespace midi_any+{++template <typename F>+auto for_all_backends(F&& f)+{+  midi1::for_all_backends(f);+  midi2::for_all_backends(f);+}++template <typename F>+auto for_backend(libremidi::API api, F&& f)+{+  midi1::for_backend(api, f);+  midi2::for_backend(api, f);+}++void for_input_configuration(auto f, std::any& api_conf)+{+  auto from_api = [&]<typename T>(T& /*backend*/) mutable {+    if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))+    {+      f(*conf);+      return true;+    }+    return false;+  };+  if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))+    return;+  std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);+}++void for_output_configuration(auto f, std::any& api_conf)+{+  auto from_api = [&]<typename T>(T& /*backend*/) mutable {+    if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))+    {+      f(*conf);+      return true;+    }+    return false;+  };+  if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))+    return;+  std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);+}++void for_observer_configuration(auto f, std::any& api_conf)+{+  auto from_api = [&]<typename T>(T& /*backend*/) mutable {+    if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))+    {+      f(*conf);+      return true;+    }+    return false;+  };+  if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))+    return;+  std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);+}+}+}
+ vendor/libremidi/backends/alsa_raw.hpp view
@@ -0,0 +1,31 @@+#pragma once+#include <libremidi/backends/alsa_raw/midi_in.hpp>+#include <libremidi/backends/alsa_raw/midi_out.hpp>+#include <libremidi/backends/alsa_raw/observer.hpp>++// 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 <craig@ccrma.stanford.edu>++namespace libremidi::alsa_raw+{+struct backend+{+  using midi_in = midi_in_impl;+  using midi_out = midi_out_impl;+  using midi_observer = observer_impl;+  using midi_in_configuration = alsa_raw_input_configuration;+  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 inline bool available() noexcept+  {+    static const libasound& snd = libasound::instance();+    return snd.available && snd.rawmidi.available;+  }+};+}
+ vendor/libremidi/backends/alsa_raw/config.hpp view
@@ -0,0 +1,80 @@+#pragma once+#include <libremidi/config.hpp>++#include <chrono>+#include <functional>+#include <optional>+#include <span>+#include <thread>++#if __has_include(<poll.h>)+  #include <poll.h>+namespace libremidi+{+using poll_descriptors = pollfd;+}+#else+namespace libremidi+{+struct poll_descriptors+{+  int fd;+  short int events;+  short int revents;+};+}+#endif++namespace libremidi+{++/**+ * Used to determine how large sent messages will be chunked.+ */+struct LIBREMIDI_EXPORT chunking_parameters+{+  std::chrono::milliseconds interval{};+  int32_t size{};++  /**+   * @brief Will be called by the chunking code to allow the API user to wait.+   *+   * By default just calls sleep.+   * Arguments are: the time that must be waited, the bytes currently written.+   * Return false if you want to abort the transfer, and true otherwise.+   */+  std::function<bool(std::chrono::microseconds, int64_t)> wait = chunking_parameters::default_wait;++  static bool default_wait(std::chrono::microseconds time_to_wait, int64_t /*written_bytes*/)+  {+    std::this_thread::sleep_for(time_to_wait);+    return true;+  }+};++struct manual_poll_parameters+{+  std::span<poll_descriptors> fds;+  std::function<int64_t(std::span<poll_descriptors> fds)> callback;+};++struct alsa_raw_input_configuration+{+  std::function<bool(const manual_poll_parameters&)> manual_poll;+  std::chrono::milliseconds poll_period{2};+};++struct alsa_raw_output_configuration+{+  /**+   * For large messages, chunk their content and wait.+   * Setting a null optional will disable chunking.+   */+  std::optional<chunking_parameters> chunking;+};++struct alsa_raw_observer_configuration+{+  std::chrono::milliseconds poll_period{100};+};+}
+ vendor/libremidi/backends/alsa_raw/error_domain.hpp view
+ vendor/libremidi/backends/alsa_raw/helpers.hpp view
@@ -0,0 +1,296 @@+#pragma once+#include <libremidi/backends/linux/alsa.hpp>+#include <libremidi/config.hpp>+#include <libremidi/detail/observer.hpp>++#include <functional>+#include <string>+#include <string_view>+#include <vector>++// 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 <craig@ccrma.stanford.edu>++namespace libremidi+{+namespace+{+struct alsa_raw_port_id+{+  int card{}, dev{}, port{};+  std::string to_string() const noexcept+  {+    return "hw:" + std::to_string(card) + "," + std::to_string(dev) + "," + std::to_string(port);+  }+};+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;+}+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.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16;+  ret.port = (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+{+  std::string device;+  std::string card_name;+  std::string device_name;+  std::string subdevice_name;+  int card{}, dev{}, sub{};++  std::string pretty_name() const+  {+    return device + ": " + card_name + " : " + device_name + " : " + subdevice_name;+  }++  bool operator==(const alsa_raw_port_info& other) const noexcept = default;+};++struct enumerator;+struct snd_ctl_wrapper+{+  const libasound& snd;+  snd_ctl_t* ctl{};+  inline snd_ctl_wrapper(enumerator& self, const char* name);++  ~snd_ctl_wrapper()+  {+    if (ctl)+    {+      snd.ctl.close(ctl);+    }+  }++  snd_ctl_t& operator*() const noexcept { return *ctl; }+  snd_ctl_t* operator->() const noexcept { return ctl; }+  operator snd_ctl_t*() const noexcept { return ctl; }+};+struct enumerator+{+  const libasound& snd = libasound::instance();+  const error_handler& handler;+  const observer_configuration& configuration;+  std::vector<alsa_raw_port_info> inputs;+  std::vector<alsa_raw_port_info> outputs;++  explicit enumerator(const auto& self)+      : handler{self}+      , configuration{self.configuration}+  {++  }++  // 1: is an input / output+  // 0: isn't an input / output+  // < 0: error+  int is(snd_rawmidi_stream_t stream, snd_ctl_t* ctl, int card, int device, int sub)+  {+    snd_rawmidi_info_t* info;++    snd_rawmidi_info_alloca(&info);+    snd.rawmidi.info_set_device(info, device);+    snd.rawmidi.info_set_subdevice(info, sub);+    snd.rawmidi.info_set_stream(info, stream);++    const int status = snd.ctl.rawmidi.info(ctl, info);+    if (status == 0)+    {+      return 1;+    }+    else if (status < 0 && status != -ENXIO)+    {+      handler.libremidi_handle_error(+          configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub)+                             + " : " + snd.strerror(status));+      return status;+    }+    else+    {+      return 0;+    }+  }++  int is_input(snd_ctl_t* ctl, int card, int device, int sub)+  {+    return is(SND_RAWMIDI_STREAM_INPUT, ctl, card, device, sub);+  }++  int is_output(snd_ctl_t* ctl, int card, int device, int sub)+  {+    return is(SND_RAWMIDI_STREAM_OUTPUT, ctl, card, device, sub);+  }++  std::string get_card_name(int card)+  {+    char* card_name{};+    snd.card.get_name(card, &card_name);++    std::string str = card_name;+    free(card_name);+    return str;+  }++  static std::string device_identifier(int card, int device, int sub)+  {+    std::string s;+    s.reserve(12);+    s += "hw:";+    s += std::to_string(card);+    s += ",";+    s += std::to_string(device);+    s += ",";+    s += std::to_string(sub);+    return s;+  }++  stdx::error enumerate_cards()+  {+    using namespace std::literals;+    int card = -1;++    int status = snd.card.next(&card);+    if (status < 0)+    {+      handler.libremidi_handle_error(+          configuration, "Cannot determine card number: "s + snd.strerror(status));+      return from_errc(status);+    }++    if (card < 0)+    {+      handler.libremidi_handle_error(configuration, "No sound cards found");+      return std::errc::no_such_device;+    }++    while (card >= 0)+    {+      enumerate_devices(card);++      if ((status = snd.card.next(&card)) < 0)+      {+        handler.libremidi_handle_error(+            configuration, "cannot determine card number: "s + snd.strerror(status));+        return std::errc::no_such_device;+      }+    }+    return stdx::error{};+  }++  virtual void enumerate_devices(int card) = 0;+};++inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)+    : snd{self.snd}+{+  using namespace std::literals;+  int status = snd.ctl.open(&ctl, name, 0);+  if (status < 0)+  {+    self.handler.libremidi_handle_error(+        self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status));+  }+}++struct midi1_enumerator : enumerator+{+  using alsa_raw::enumerator::enumerator;++  void enumerate_devices(int card) override+  {+    using namespace std::literals;+    char name[128];++    sprintf(name, "hw:%d", card);++    // Open card.+    snd_ctl_wrapper ctl{*this, name};+    if (!ctl)+      return;++    // Enumerate devices.+    int device = -1;+    do+    {+      const int status = snd.ctl.rawmidi.next_device(ctl, &device);+      if (device == -1)+        return;++      if (status < 0)+      {+        handler.libremidi_handle_error(+            configuration, "Cannot determine device number: "s + snd.strerror(status));+        break;+      }++      if (device >= 0)+        enumerate_subdevices(ctl, card, device);++    } while (device >= 0);+  }++  void enumerate_subdevices(snd_ctl_t* ctl, int card, int device)+  {+    snd_rawmidi_info_t* info;+    snd_rawmidi_info_alloca(&info);+    snd.rawmidi.info_set_device(info, device);++    snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);+    snd.ctl.rawmidi.info(ctl, info);+    const int subs_in = snd.rawmidi.info_get_subdevices_count(info);++    snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);+    snd.ctl.rawmidi.info(ctl, info);+    const int subs_out = snd.rawmidi.info_get_subdevices_count(info);++    alsa_raw_port_info d;+    d.card = card;+    d.dev = device;+    d.card_name = get_card_name(card);+    d.device_name = snd.rawmidi.info_get_name(info);++    auto read_subdevice_info = [&](int sub) {+      snd.rawmidi.info_set_subdevice(info, sub);+      snd.ctl.rawmidi.info(ctl, info);++      d.device = device_identifier(card, device, sub);+      d.subdevice_name = snd.rawmidi.info_get_subdevice_name(info);+      d.sub = sub;+    };++    if (subs_in > 0)+    {+      snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);+      for (int sub = 0; sub < subs_in; sub++)+      {+        read_subdevice_info(sub);+        inputs.push_back(d);+      }+    }++    if (subs_out > 0)+    {+      snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);+      for (int sub = 0; sub < subs_out; sub++)+      {+        read_subdevice_info(sub);+        outputs.push_back(d);+      }+    }+  }+};+}+}
+ vendor/libremidi/backends/alsa_raw/midi_in.hpp view
@@ -0,0 +1,356 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <alsa/asoundlib.h>++#include <chrono>+#include <thread>++namespace libremidi::alsa_raw+{+class midi_in_impl+    : public midi1::in_api+    , public error_handler+{+public:+  struct+      : input_configuration+      , alsa_raw_input_configuration+  {+  } configuration;++  const libasound& snd = libasound::instance();++  explicit midi_in_impl(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    fds_.reserve(4);+  }++  ~midi_in_impl() override { }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }++  // Must be a string such as: "hw:2,4,1"+  [[nodiscard]] stdx::error do_init_port(const char* portname)+  {+    constexpr int mode = SND_RAWMIDI_NONBLOCK;+    if (const int err = snd.rawmidi.open(&midiport_, nullptr, portname, mode); err < 0)+    {+      libremidi_handle_error(this->configuration, "cannot open device.");+      return from_errc(err);+    }++    snd_rawmidi_params_t* params{};+    snd_rawmidi_params_alloca(&params);++    if (const int err = snd.rawmidi.params_current(midiport_, params); err < 0)+      return from_errc(err);+    if (const int err = snd.rawmidi.params_set_no_active_sensing(midiport_, params, 1); err < 0)+      return from_errc(err);+#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD+    if (configuration.timestamps == timestamp_mode::NoTimestamp)+    {+      if (const int err+          = snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_STANDARD);+          err < 0)+        return from_errc(err);+      if (const int err+          = snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_NONE);+          err < 0)+        return from_errc(err);+    }+    else+    {+      if (const int err+          = snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_TSTAMP);+          err < 0)+        return from_errc(err);+      if (const int err+          = snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_MONOTONIC);+          err < 0)+        return from_errc(err);+    }+#endif++    if (const int err = snd.rawmidi.params(midiport_, params); err < 0)+      return from_errc(err);++    return init_pollfd();+  }++  [[nodiscard]] stdx::error init_port(const port_information& p)+  {+    return do_init_port(raw_from_port_handle(p.port).to_string().c_str());+  }++  [[nodiscard]] stdx::error init_pollfd()+  {+    const int num_fds = snd.rawmidi.poll_descriptors_count(this->midiport_);++    this->fds_.clear();+    this->fds_.resize(num_fds);++    int ret = snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), num_fds);+    if (ret < 0)+      return from_errc(ret);+    return stdx::error{};+  }++  ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)+  {+    // Read events+    if (fds.empty())+    {+      return (this->*parse_func)();+    }+    else+    {+      unsigned short res{};+      const int err = snd.rawmidi.poll_descriptors_revents(+          this->midiport_, fds.data(), static_cast<unsigned int>(fds.size()), &res);+      if (err < 0)+        return err;++      // Did we encounter an error during polling+      if (res & (POLLERR | POLLHUP))+        return -EIO;++      // Is there data to read+      if (res & POLLIN)+        return (this->*parse_func)();+    }++    return 0;+  }++  ssize_t read_input_buffer()+  {+    static const constexpr int nbytes = 1024;+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = false,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    unsigned char bytes[nbytes];++    ssize_t err = 0;+    // err is the amount of bytes read+    while ((err = snd.rawmidi.read(this->midiport_, bytes, nbytes)) > 0)+    {+      const auto to_ns = [this] { return absolute_timestamp(); };+      m_processing.on_bytes(+          {bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));+    }+    return err;+  }++#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD+  ssize_t read_input_buffer_with_timestamps()+  {+    static constexpr int nbytes = 1024;+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = true,+        .has_samples = false,+    };++    unsigned char bytes[nbytes];+    struct timespec ts;++    ssize_t err = 0;+    // err is the amount of bytes read+    while ((err = snd.rawmidi.tread(this->midiport_, &ts, bytes, nbytes)) > 0)+    {+      const auto to_ns = [ts] {+        return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+      };+      m_processing.on_bytes(+          {bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));+    }+    return err;+  }+#else+  ssize_t read_input_buffer_with_timestamps() { return read_input_buffer(); }+#endif++  stdx::error close_port() override+  {+    if (midiport_)+      snd.rawmidi.close(midiport_);+    midiport_ = nullptr;+    return stdx::error{};+  }++  timestamp absolute_timestamp() const noexcept final override { return system_ns(); }++  snd_rawmidi_t* midiport_{};+  std::vector<pollfd> fds_;+  midi1::input_state_machine m_processing{this->configuration};+};++class midi_in_alsa_raw_threaded : public midi_in_impl+{+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)+    {+      libremidi_handle_error(this->configuration, "error creating eventfd.");+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_in_alsa_raw_threaded() override+  {+    // Close a connection if it exists.+    this->midi_in_alsa_raw_threaded::close_port();++    client_open_ = std::errc::not_connected;+  }++private:+  void run_thread(auto parse_func)+  {+    fds_.push_back(this->termination_event);+    const auto period+        = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+              .count();++    for (;;)+    {+      // Poll+      ssize_t err = poll(fds_.data(), fds_.size(), period);+      if (err == -EAGAIN)+        continue;+      else if (err < 0)+        return;+      else if (termination_event.ready(fds_.back()))+        break;++      err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});+      if (err == -EAGAIN)+        continue;+      else if (err < 0)+        return;+    }+  }++  [[nodiscard]] stdx::error start_thread()+  {+    try+    {+      if (configuration.timestamps == timestamp_mode::NoTimestamp)+      {+        this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};+      }+      else+      {+        this->thread_ = std::thread{+            [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};+      }+      return stdx::error{};+    }+    catch (const std::system_error& e)+    {+      using namespace std::literals;++      libremidi_handle_error(+          this->configuration,+          "error starting MIDI input thread: "s + e.what());+      return e.code();+    }+    return stdx::error{};+  }++  stdx::error open_port(const input_port& port, std::string_view /*name*/) override+  {+    if (auto err = midi_in_impl::init_port(port); err != stdx::error{})+      return err;+    if (auto err = start_thread(); err != stdx::error{})+      return err;+    return stdx::error{};+  }++  stdx::error close_port() override+  {+    termination_event.notify();+    if (thread_.joinable())+      thread_.join();+    termination_event.consume(); // Reset to zero++    return midi_in_impl::close_port();+  }++  std::thread thread_;+  eventfd_notifier termination_event{};+};++class midi_in_alsa_raw_manual : public midi_in_impl+{+public:+  midi_in_alsa_raw_manual(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)+      : midi_in_impl{std::move(conf), std::move(apiconf)}+  {+    client_open_ = stdx::error{};+  }++  ~midi_in_alsa_raw_manual()+  {+    // Close a connection if it exists.+    this->close_port();++    client_open_ = std::errc::not_connected;+  }++private:+  void send_poll_callback()+  {+    if (configuration.timestamps == timestamp_mode::NoTimestamp)+    {+      configuration.manual_poll(manual_poll_parameters{+          .fds = {this->fds_.data(), this->fds_.size()},+          .callback = [this](std::span<pollfd> 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<pollfd> fds) {+            return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);+          }});+    }+  }++  stdx::error open_port(const input_port& p, std::string_view /*name*/) override+  {+    if (auto err = midi_in_impl::init_port(p); err != stdx::error{})+      return err;+    send_poll_callback();+    return stdx::error{};+  }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_in_api> make<alsa_raw::midi_in_impl>(+    libremidi::input_configuration&& conf, libremidi::alsa_raw_input_configuration&& api)+{+  if (api.manual_poll)+    return std::make_unique<alsa_raw::midi_in_alsa_raw_manual>(std::move(conf), std::move(api));+  else+    return std::make_unique<alsa_raw::midi_in_alsa_raw_threaded>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/alsa_raw/midi_out.hpp view
@@ -0,0 +1,169 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <alsa/asoundlib.h>++#include <atomic>+#include <thread>++namespace libremidi::alsa_raw+{+class midi_out_impl final+    : public midi1::out_api+    , public error_handler+{+public:+  struct+      : output_configuration+      , alsa_raw_output_configuration+  {+  } configuration;++  const libasound& snd = libasound::instance();++  midi_out_impl(output_configuration&& conf, alsa_raw_output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    client_open_ = stdx::error{};+  }++  ~midi_out_impl() override+  {+    // Close a connection if it exists.+    midi_out_impl::close_port();+    client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }++  stdx::error connect_port(const char* portname)+  {+    constexpr int mode = SND_RAWMIDI_SYNC;+    int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);+    if (status < 0)+    {+      libremidi_handle_error(+          this->configuration, "cannot open device.");+      return from_errc(status);+    }+    return stdx::error{};+  }++  stdx::error open_port(const output_port& p, std::string_view) override+  {+    return connect_port(raw_from_port_handle(p.port).to_string().c_str());+  }++  stdx::error close_port() override+  {+    if (midiport_)+      snd.rawmidi.close(midiport_);+    midiport_ = nullptr;++    return stdx::error{};+  }++  stdx::error send_message(const unsigned char* message, size_t size) override+  {+    if (!midiport_)+      libremidi_handle_error(+          this->configuration,+          "trying to send a message without an open "+          "port.");++    if (!this->configuration.chunking)+    {+      return write(message, size);+    }+    else+    {+      return write_chunked(message, size);+    }+  }++  stdx::error write(const unsigned char* message, size_t size)+  {+    if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0)+    {+      libremidi_handle_error(+          this->configuration, "cannot write message.");+      return from_errc(err);+    }++    return stdx::error{};+  }++  std::size_t get_chunk_size() const noexcept+  {+    snd_rawmidi_params_t* param;+    snd_rawmidi_params_alloca(&param);+    snd.rawmidi.params_current(midiport_, param);++    std::size_t buffer_size = snd.rawmidi.params_get_buffer_size(param);+    return std::min(buffer_size, (std::size_t)configuration.chunking->size);+  }++  std::size_t get_available_bytes_to_write() const noexcept+  {+    snd_rawmidi_status_t* st{};+    snd_rawmidi_status_alloca(&st);+    snd.rawmidi.status(midiport_, st);++    return snd.rawmidi.status_get_avail(st);+  }++  // inspired from ALSA amidi.c source code+  stdx::error write_chunked(const unsigned char* const begin, size_t size)+  {+    const unsigned char* data = begin;+    const unsigned char* end = begin + size;++    const std::size_t chunk_size = std::min(get_chunk_size(), size);++    // Send the first buffer+    std::size_t len = chunk_size;++    if (auto err = write(data, len); err != stdx::error{})+      return err;++    data += len;++    while (data < end)+    {+      // Wait for the buffer to have some space available+      const std::size_t written_bytes = data - begin;+      std::size_t available{};+      while ((available = get_available_bytes_to_write()) < chunk_size)+      {+        if (!configuration.chunking->wait(+                std::chrono::microseconds((chunk_size - available) * 320), written_bytes))+          return std::errc::protocol_error;+      };++      if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes))+        return std::errc::protocol_error;++      // Write more data+      len = end - data;++      // Maybe until the end of the sysex+      if (const auto sysex_end = static_cast<const unsigned char*>(memchr(data, 0xf7, len)))+        len = sysex_end - data + 1;++      if (len > chunk_size)+        len = chunk_size;++      if (auto err = write(data, len); err != stdx::error{})+        return err;++      data += len;+    }++    return stdx::error{};+  }++  snd_rawmidi_t* midiport_{};+};+}
+ vendor/libremidi/backends/alsa_raw/observer.hpp view
@@ -0,0 +1,240 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/backends/dummy.hpp>++#if LIBREMIDI_HAS_UDEV+  #include <libremidi/backends/linux/helpers.hpp>+  #include <libremidi/backends/linux/udev.hpp>+  #include <libremidi/detail/observer.hpp>++namespace libremidi::alsa_raw+{+template <typename Enumerator>+class observer_impl_base+    : public observer_api+    , public error_handler+{+public:+  struct+      : observer_configuration+      , alsa_raw_observer_configuration+  {+  } configuration;++  const libasound& snd = libasound::instance();++  explicit observer_impl_base(+      observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (!configuration.has_callbacks())+      return;++    fds[0] = this->udev;+    fds[1] = termination_event;+    fds[2] = timer_fd;++    // Set-up initial state+    if (configuration.notify_in_constructor)+      this->check_devices();++    // Start thread+    thread = std::thread{[this] { this->run(); }};+  }++  ~observer_impl_base()+  {+    termination_event.notify();++    if (thread.joinable())+      thread.join();+  }++  std::vector<libremidi::input_port> get_input_ports() const noexcept override+  {+    std::vector<libremidi::input_port> ret;+    Enumerator new_devs{*this};++    new_devs.enumerate_cards();+    for (auto& d : new_devs.inputs)+    {+      ret.push_back(to_port_info<true>(d));+    }+    return ret;+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept override+  {+    std::vector<libremidi::output_port> ret;+    Enumerator new_devs{*this};++    new_devs.enumerate_cards();+    for (auto& d : new_devs.outputs)+    {+      ret.push_back(to_port_info<false>(d));+    }+    return ret;+  }++private:+  void run()+  {+    for (;;)+    {+      if (int err = poll(fds, 3, -1); err < 0)+      {+        if (err == -EAGAIN)+          continue;+        else+          return;+      }++      // Check udev+      if (fds[0].revents & POLLIN)+      {+        udev_device* dev = udev.udev.monitor_receive_device(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);+        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;+          }+        }++        udev.udev.device_unref(dev);++        fds[0].revents = 0;+      }++      // Check eventfd+      if (fds[1].revents & POLLIN)+      {+        break;+      }++      // Check timer+      if (fds[2].revents & POLLIN)+      {+        if (this->timer_check_counts-- <= 0)+          this->timer_fd.cancel();+        fds[2].revents = 0;++        check_devices();+      }+    }+  }++  template <bool Input>+  auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept+      -> std::conditional_t<Input, input_port, output_port>+  {+    return {+        {.client = 0,+         .port = raw_to_port_handle({p.card, p.dev, p.sub}),+         .manufacturer = p.card_name,+         .device_name = p.device_name,+         .port_name = p.subdevice_name,+         .display_name = p.subdevice_name}};+  }++  void check_devices()+  {+    Enumerator new_devs{*this};++    new_devs.enumerate_cards();++    for (auto& in_prev : current_inputs)+    {+      if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);+          it == new_devs.inputs.end())+      {+        if (auto& cb = this->configuration.input_removed)+        {+          cb(to_port_info<true>(in_prev));+        }+      }+    }++    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& cb = this->configuration.input_added)+        {+          cb(to_port_info<true>(in_next));+        }+      }+    }++    for (auto& out_prev : current_outputs)+    {+      if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);+          it == new_devs.outputs.end())+      {+        if (auto& cb = this->configuration.output_removed)+        {+          cb(to_port_info<false>(out_prev));+        }+      }+    }++    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& cb = this->configuration.output_added)+        {+          cb(to_port_info<false>(out_next));+        }+      }+    }+    current_inputs = std::move(new_devs.inputs);+    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<alsa_raw_port_info> current_inputs;+  std::vector<alsa_raw_port_info> current_outputs;++  pollfd fds[3]{};+};+}+#else+  #include <libremidi/backends/dummy.hpp>+namespace libremidi::alsa_raw+{+template <typename Enumerator>+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{}}+  {+  }+};+}+#endif++namespace libremidi::alsa_raw+{+struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>+{+  using alsa_raw::observer_impl_base<midi1_enumerator>::observer_impl_base;+  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }+};+}
+ vendor/libremidi/backends/alsa_raw_ump.hpp view
@@ -0,0 +1,30 @@+#pragma once+#include <libremidi/backends/alsa_raw/observer.hpp>+#include <libremidi/backends/alsa_raw_ump/midi_in.hpp>+#include <libremidi/backends/alsa_raw_ump/midi_out.hpp>+#include <libremidi/backends/alsa_raw_ump/observer.hpp>+#include <libremidi/backends/dummy.hpp>++namespace libremidi::alsa_raw_ump+{+struct backend+{+  using midi_in = midi_in_impl;+  using midi_out = midi_out_impl;+  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+  {+  };+  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 inline bool available() noexcept+  {+    static const libasound& snd = libasound::instance();+    return snd.available && snd.rawmidi.available && snd.ump.available;+  }+};+}
+ vendor/libremidi/backends/alsa_raw_ump/config.hpp view
@@ -0,0 +1,25 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>++namespace libremidi::alsa_raw_ump+{+struct input_configuration+{+  std::function<bool(const manual_poll_parameters&)> manual_poll;+  std::chrono::milliseconds poll_period{2};+};++struct output_configuration+{+  /**+   * For large messages, chunk their content and wait.+   * Setting a null optional will disable chunking.+   */+  std::optional<chunking_parameters> chunking;+};++struct observer_configuration+{+  std::chrono::milliseconds poll_period{100};+};+}
+ vendor/libremidi/backends/alsa_raw_ump/helpers.hpp view
@@ -0,0 +1,66 @@+#pragma once+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/config.hpp>+#include <libremidi/detail/observer.hpp>++namespace libremidi::alsa_raw_ump+{+struct midi2_enumerator : alsa_raw::enumerator+{+  using alsa_raw::enumerator::enumerator;+  void enumerate_devices(int card) override+  {+    using namespace std::literals;+    char name[128];++    sprintf(name, "hw:%d", card);++    // Open card.+    alsa_raw::snd_ctl_wrapper ctl{*this, name};+    if (!ctl)+      return;++    // Enumerate devices.+    int device = -1;+    do+    {+      const int status = snd.ctl.ump.next_device(ctl, &device);+      if (device == -1)+        return;++      if (status < 0)+      {+        handler.libremidi_handle_error(+            configuration, "Cannot determine device number: "s + snd.strerror(status));+        break;+      }++      if (device >= 0)+      {+        enumerate_blocks(ctl, card, device);+        enumerate_endpoints(ctl, card, device);+      }++    } while (device >= 0);+  }++  void enumerate_endpoints(snd_ctl_t* ctl, [[maybe_unused]] int card, [[maybe_unused]] int device)+  {+    snd_ump_endpoint_info_t* info{};+    snd_ump_endpoint_info_alloca(&info);+    snd.ctl.ump.endpoint_info(ctl, info);++    fprintf(stderr, "UMP endpoint: %s", snd.ump.endpoint_info_get_name(info));+  }++  void enumerate_blocks(snd_ctl_t* ctl, [[maybe_unused]] int card, [[maybe_unused]] int device)+  {+    snd_ump_block_info_t* info{};+    snd_ump_block_info_alloca(&info);+    snd.ctl.ump.block_info(ctl, info);++    fprintf(stderr, "UMP block: %s", snd.ump.block_info_get_name(info));+  }+};++}
+ vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp view
@@ -0,0 +1,349 @@+#pragma once+#include <libremidi/backends/alsa_raw_ump/config.hpp>+#include <libremidi/backends/alsa_raw_ump/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <alsa/asoundlib.h>++#include <atomic>+#include <thread>++namespace libremidi::alsa_raw_ump+{+class midi_in_impl+    : public midi2::in_api+    , public error_handler+{+public:+  struct+      : libremidi::ump_input_configuration+      , alsa_raw_ump::input_configuration+  {+  } configuration;++  const libasound& snd = libasound::instance();++  explicit midi_in_impl(+      libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    fds_.reserve(4);+    assert(snd.ump.available);+  }++  ~midi_in_impl() override { }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }++  // Must be a string such as: "hw:2,4,1"+  [[nodiscard]] stdx::error do_init_port(const char* portname)+  {+    constexpr int mode = 0;+    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.");+      return from_errc(err);+    }++    snd_rawmidi_params_t* params{};+    snd_rawmidi_params_alloca(&params);++    auto rawmidi = snd.ump.rawmidi(midiport_);++    if (int err = snd.ump.rawmidi_params_current(midiport_, params); err < 0)+      return from_errc(err);+    if (int err = snd.rawmidi.params_set_no_active_sensing(rawmidi, params, 1); err < 0)+      return from_errc(err);++    if (configuration.timestamps == timestamp_mode::NoTimestamp)+    {+      if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_STANDARD);+          err < 0)+        return from_errc(err);+      if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_NONE);+          err < 0)+        return from_errc(err);+    }+    else+    {+      if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_TSTAMP);+          err < 0)+        return from_errc(err);+      if (int err+          = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_MONOTONIC);+          err < 0)+        return from_errc(err);+    }++    if (int err = snd.ump.rawmidi_params(midiport_, params); err < 0)+      return from_errc(err);++    return init_pollfd();+  }++  [[nodiscard]] stdx::error init_port(const port_information& p)+  {+    return do_init_port(raw_from_port_handle(p.port).to_string().c_str());+  }++  [[nodiscard]] stdx::error init_pollfd()+  {+    int num_fds = snd.ump.poll_descriptors_count(this->midiport_);++    this->fds_.clear();+    this->fds_.resize(num_fds);++    int ret = snd.ump.poll_descriptors(this->midiport_, fds_.data(), num_fds);+    if (ret < 0)+      return from_errc(ret);+    return stdx::error{};+  }++  ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)+  {+    // Read events+    if (fds.empty())+    {+      return (this->*parse_func)();+    }+    else+    {+      unsigned short res{};+      ssize_t err = snd.ump.poll_descriptors_revents(+          this->midiport_, fds.data(), static_cast<unsigned int>(fds.size()), &res);+      if (err < 0)+        return err;++      // Did we encounter an error during polling+      if (res & (POLLERR | POLLHUP))+        return -EIO;++      // Is there data to read+      if (res & POLLIN)+        return (this->*parse_func)();+    }++    return 0;+  }++  ssize_t read_input_buffer()+  {+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = false,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };+    static const constexpr int nwords = 64;+    uint32_t words[nwords];++    ssize_t err = 0;+    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<timestamp_info>(to_ns, 0));+    }+    return err;+  }++  ssize_t read_input_buffer_with_timestamps()+  {+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = true,+        .has_samples = false,+    };++    static const constexpr int nwords = 64;+    uint32_t words[nwords];+    struct timespec ts;++    ssize_t err = 0;+    while ((err = snd.ump.tread(this->midiport_, &ts, words, nwords * 4)) > 0)+    {+      const auto to_ns = [ts] {+        return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+      };+      m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));+    }+    return err;+  }++  stdx::error close_port() override+  {+    if (midiport_)+      snd.ump.close(midiport_);+    midiport_ = nullptr;++    return stdx::error{};+  }++  timestamp absolute_timestamp() const noexcept override { return system_ns(); }++  snd_ump_t* midiport_{};+  std::vector<pollfd> fds_;+  midi2::input_state_machine m_processing{this->configuration};+};++class midi_in_impl_threaded : public midi_in_impl+{+public:+  midi_in_impl_threaded(+      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)+    {+      libremidi_handle_error(this->configuration, "error creating eventfd.");+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_in_impl_threaded()+  {+    // Close a connection if it exists.+    this->close_port();+    client_open_ = std::errc::not_connected;+  }++private:+  void run_thread(auto parse_func)+  {+    fds_.push_back(this->termination_event);+    const auto period+        = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+              .count();++    for (;;)+    {+      // Poll+      ssize_t err = poll(fds_.data(), fds_.size(), period);+      if (err == -EAGAIN)+        continue;+      else if (err < 0)+        return;+      else if (termination_event.ready(fds_.back()))+        break;++      err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});+      if (err == -EAGAIN)+        continue;+      else if (err < 0)+        return;+    }+  }++  [[nodiscard]] stdx::error start_thread()+  {+    try+    {+      if (configuration.timestamps == timestamp_mode::NoTimestamp)+      {+        this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};+      }+      else+      {+        this->thread_ = std::thread{+            [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};+      }+      return stdx::error{};+    }+    catch (const std::system_error& e)+    {+      using namespace std::literals;++      libremidi_handle_error(+          this->configuration,+          "error starting MIDI input thread: "s + e.what());+      return e.code();+    }+    return stdx::error{};+  }++  stdx::error open_port(const input_port& port, [[maybe_unused]] std::string_view name) override+  {+    if (auto err = midi_in_impl::init_port(port); err != stdx::error{})+      return err;+    if (auto err = start_thread(); err != stdx::error{})+      return err;+    return stdx::error{};+  }++  stdx::error close_port() override+  {+    termination_event.notify();+    if (thread_.joinable())+      thread_.join();+    termination_event.consume(); // Reset to zero++    return midi_in_impl::close_port();+  }++  std::thread thread_;+  eventfd_notifier termination_event{};+};++class midi_in_impl_manual : public midi_in_impl+{+public:+  midi_in_impl_manual(ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)+      : midi_in_impl{std::move(conf), std::move(apiconf)}+  {+    client_open_ = stdx::error{};+  }++  ~midi_in_impl_manual()+  {+    // Close a connection if it exists.+    this->close_port();++    client_open_ = std::errc::not_connected;+  }++private:+  void send_poll_callback()+  {+    if (configuration.timestamps == timestamp_mode::NoTimestamp)+    {+      configuration.manual_poll(manual_poll_parameters{+          .fds = {this->fds_.data(), this->fds_.size()},+          .callback = [this](std::span<pollfd> 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<pollfd> fds) {+            return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);+          }});+    }+  }++  stdx::error open_port(const input_port& p, [[maybe_unused]] std::string_view name) override+  {+    if (auto err = midi_in_impl::init_port(p); err != stdx::error{})+      return err;+    send_poll_callback();+    return stdx::error{};+  }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_in_api> make<alsa_raw_ump::midi_in_impl>(+    libremidi::ump_input_configuration&& conf, libremidi::alsa_raw_ump::input_configuration&& api)+{+  if (api.manual_poll)+    return std::make_unique<alsa_raw_ump::midi_in_impl_manual>(std::move(conf), std::move(api));+  else+    return std::make_unique<alsa_raw_ump::midi_in_impl_threaded>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp view
@@ -0,0 +1,94 @@+#pragma once+#include <libremidi/backends/alsa_raw_ump/config.hpp>+#include <libremidi/backends/alsa_raw_ump/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <alsa/asoundlib.h>++#include <atomic>+#include <thread>++namespace libremidi::alsa_raw_ump+{+class midi_out_impl final+    : public midi2::out_api+    , public error_handler+{+public:+  struct+      : libremidi::output_configuration+      , libremidi::alsa_raw_ump::output_configuration+  {+  } configuration;++  const libasound& snd = libasound::instance();++  midi_out_impl(+      libremidi::output_configuration&& conf,+      libremidi::alsa_raw_ump::output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    assert(snd.ump.available);+    client_open_ = stdx::error{};+  }++  ~midi_out_impl() override+  {+    // Close a connection if it exists.+    midi_out_impl::close_port();+    client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }++  stdx::error connect_port(const char* portname)+  {+    constexpr int mode = SND_RAWMIDI_SYNC;+    int ret = snd.ump.open(NULL, &midiport_, portname, mode);+    if (ret < 0)+    {+      libremidi_handle_error(this->configuration, "cannot open device.");+      return from_errc(ret);+    }+    return stdx::error{};+  }++  stdx::error open_port(const output_port& p, std::string_view) override+  {+    return connect_port(raw_from_port_handle(p.port).to_string().c_str());+  }++  stdx::error close_port() override+  {+    if (midiport_)+      snd.ump.close(midiport_);+    midiport_ = nullptr;+    return stdx::error{};+  }++  stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override+  {+    if (!midiport_)+      libremidi_handle_error(+          this->configuration,+          "trying to send a message without an open "+          "port.");++    return write(ump_stream, count * sizeof(uint32_t));+  }++  stdx::error write(const uint32_t* ump_stream, size_t bytes)+  {+    if (auto err = snd.ump.write(midiport_, ump_stream, bytes); err < 0)+    {+      libremidi_handle_error(this->configuration, "cannot write message.");+      return from_errc(err);+    }++    return stdx::error{};+  }++  snd_ump_t* midiport_{};+};++}
+ vendor/libremidi/backends/alsa_raw_ump/observer.hpp view
@@ -0,0 +1,14 @@+#pragma once+#include <libremidi/backends/alsa_raw/observer.hpp>+#include <libremidi/backends/alsa_raw_ump/helpers.hpp>++namespace libremidi::alsa_raw_ump+{++class observer_impl : public alsa_raw::observer_impl_base<midi2_enumerator>+{+  using alsa_raw::observer_impl_base<midi2_enumerator>::observer_impl_base;+  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }+};++}
+ vendor/libremidi/backends/alsa_seq.hpp view
@@ -0,0 +1,42 @@+#pragma once+//*********************************************************************//+//  API: LINUX ALSA SEQUENCER+//*********************************************************************//++// API information found at:+//   - http://www.alsa-project.org/documentation.php#Library++// The ALSA Sequencer API is based on the use of a callback function for+// MIDI input.+//+// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer+// time stamps and other assorted fixes!!!++#include <libremidi/backends/alsa_seq/midi_in.hpp>+#include <libremidi/backends/alsa_seq/midi_out.hpp>+#include <libremidi/backends/alsa_seq/observer.hpp>++namespace libremidi::alsa_seq+{++struct backend+{+  using midi_in+      = alsa_seq::midi_in_impl<libremidi::input_configuration, alsa_seq::input_configuration>;+  using midi_out = alsa_seq::midi_out_impl;+  using midi_observer = alsa_seq::observer_impl<alsa_seq::observer_configuration>;+  using midi_in_configuration = alsa_seq::input_configuration;+  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 inline bool available() noexcept+  {+    static const libasound& snd = libasound::instance();+    return snd.available && snd.seq.available;+  }+};++}
+ vendor/libremidi/backends/alsa_seq/config.hpp view
@@ -0,0 +1,65 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>++#if __has_include(<alsa/asoundlib.h>)+  #include <alsa/asoundlib.h>+#else+extern "C" {+typedef struct _snd_seq snd_seq_t;+typedef struct snd_seq_event snd_seq_event_t;+typedef struct snd_seq_addr+{+  unsigned char client;+  unsigned char port;+} snd_seq_addr_t;+}+#endif++namespace libremidi::alsa_seq+{++// Possible parameters:+// - Direct / non-direct output+// - Timer source used (high resolution, etc)+// - Timestamping+// - Tempo, ppq?++struct poll_parameters+{+  snd_seq_addr_t addr{};+  std::function<int(const snd_seq_event_t&)> callback;+};++struct input_configuration+{+  using poll_parameters_type = poll_parameters;++  std::string client_name = "libremidi client";+  snd_seq_t* context{};+  std::function<bool(const poll_parameters&)> manual_poll;+  std::function<bool(snd_seq_addr_t)> stop_poll;+  std::chrono::milliseconds poll_period{2};++  static constexpr int midi_version = 1;+};++struct output_configuration+{+  std::string client_name = "libremidi client";+  snd_seq_t* context{};++  static constexpr int midi_version = 1;+};++struct observer_configuration+{+  std::string client_name = "libremidi client";+  snd_seq_t* context{};+  std::function<bool(const poll_parameters&)> manual_poll;+  std::function<bool(snd_seq_addr_t)> stop_poll;+  std::chrono::milliseconds poll_period{100};++  static constexpr int midi_version = 1;+};++}
+ vendor/libremidi/backends/alsa_seq/helpers.hpp view
@@ -0,0 +1,308 @@+#pragma once+#include <libremidi/backends/linux/alsa.hpp>+#include <libremidi/config.hpp>+#include <libremidi/detail/observer.hpp>++#include <sys/time.h>++#include <cstring>+#include <optional>+#include <string>++namespace libremidi::alsa_seq+{+struct event_handle+{+  const libasound& snd;+  snd_seq_event_t* ev{};++  explicit event_handle(const libasound& snd) noexcept+      : snd{snd}+  {+  }++  explicit event_handle(const libasound& snd, snd_seq_event_t* ev) noexcept+      : snd{snd}+      , ev{ev}+  {+  }++  event_handle(const event_handle&) noexcept = delete;+  event_handle& operator=(const event_handle&) noexcept = delete;+  event_handle(event_handle&&) noexcept = delete;+  event_handle& operator=(event_handle&&) noexcept = delete;++  void reset(snd_seq_event_t* new_ev) noexcept+  {+    if (ev)+      snd.seq.free_event(ev);+    ev = new_ev;+  }++  ~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;+}++inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept+{+  int client = p >> 32;+  int port = p & 0xFFFFFFFF;+  return {client, port};+}++inline void for_all_ports(+    const libasound& snd, snd_seq_t* seq,+    std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)> 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;++    // Reset query info+    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(*cinfo, *pinfo);+    }+  }+}++// This function is used to count or get the pinfo structure for a given port+// number.+inline unsigned int iterate_port_info(+    const libasound& snd, snd_seq_t* seq, snd_seq_port_info_t* pinfo, unsigned int type,+    int portNumber)+{+  snd_seq_client_info_t* cinfo{};+  int count = 0;+  snd_seq_client_info_alloca(&cinfo);++  snd.seq.client_info_set_client(cinfo, -1);+  while (snd.seq.query_next_client(seq, cinfo) >= 0)+  {+    const int client = snd.seq.client_info_get_client(cinfo);+    if (client == 0)+      continue;++    // Reset query info+    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)+    {+      const unsigned int atyp = snd.seq.port_info_get_type(pinfo);+      if (((atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC) == 0) && ((atyp & SND_SEQ_PORT_TYPE_SYNTH) == 0)+          && ((atyp & SND_SEQ_PORT_TYPE_APPLICATION) == 0))+        continue;++      const unsigned int caps = snd.seq.port_info_get_capability(pinfo);+      if ((caps & type) != type)+        continue;+      if ((caps & SND_SEQ_PORT_CAP_NO_EXPORT) != 0)+        continue;+      if (count == portNumber)+        return 1;+      ++count;+    }+  }++  // If a negative portNumber was used, return the port count.+  if (portNumber < 0)+    return count;+  return 0;+}+}++// A structure to hold variables related to the ALSA API+// implementation.+struct alsa_data+{+  const libasound& snd = libasound::instance();+  snd_seq_t* seq{};+  int vport{-1};+  snd_seq_addr_t vaddr{};++  snd_seq_port_subscribe_t* subscription{};+  snd_midi_event_t* coder{};++  [[nodiscard]] int init_client(auto& configuration)+  {+    // Initialize or use the snd_seq client+    if (configuration.context)+    {+      seq = configuration.context;+      return 0;+    }+    else+    {+      // Set up the ALSA sequencer client.+      int ret = snd.seq.open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);+      if (ret < 0)+        return ret;++      // Set client name.+      if (!configuration.client_name.empty())+        snd.seq.set_client_name(seq, configuration.client_name.data());++#if __has_include(<alsa/ump.h>)+      if (snd.seq.ump.set_client_midi_version)+      {+        switch (configuration.midi_version)+        {+          case 1:+            snd.seq.ump.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);+            break;+        }+      }+#endif++      return 0;+    }+  }++  stdx::error set_client_name(std::string_view clientName)+  {+    int ret = snd.seq.set_client_name(seq, clientName.data());+    return from_errc(ret);+  }++  stdx::error set_port_name(std::string_view portName)+  {+    snd_seq_port_info_t* pinfo;+    snd_seq_port_info_alloca(&pinfo);+    snd.seq.get_port_info(seq, vport, pinfo);+    snd.seq.port_info_set_name(pinfo, portName.data());+    int ret = snd.seq.set_port_info(seq, vport, pinfo);+    return from_errc(ret);+  }++  unsigned int get_port_count(int caps) const+  {+    snd_seq_port_info_t* pinfo;+    snd_seq_port_info_alloca(&pinfo);++    return alsa_seq::iterate_port_info(snd, seq, pinfo, caps, -1);+  }++  std::optional<snd_seq_addr_t> get_port_info(const port_information& portNumber)+  {+    auto [client, port] = alsa_seq::seq_from_port_handle(portNumber.port);+    // FIXME check that the {client, port} pair actually exists+    // snd_seq_port_info_t* src_pinfo{};+    // snd_seq_port_info_alloca(&src_pinfo);+    // snd.seq.port_info_set_client(src_pinfo, client);+    // snd.seq.port_info_set_port(src_pinfo, port);++    // {+    //   self.libremidi_handle_error(+    //       self.configuration,+    //       "invalid 'portNumber' argument: " + std::to_string(portNumber));+    //   return {};+    // }+    snd_seq_addr_t addr;+    addr.client = client;+    addr.port = port;+    return addr;+  }++  [[nodiscard]] int create_port(+      auto& /*self*/, std::string_view portName, unsigned int caps, unsigned int type,+      std::optional<int> queue)+  {+    if (this->vport < 0)+    {+      snd_seq_port_info_t* pinfo{};+      snd_seq_port_info_alloca(&pinfo);++      snd.seq.port_info_set_name(pinfo, portName.data());+      snd.seq.port_info_set_client(pinfo, 0);+      snd.seq.port_info_set_port(pinfo, 0);+      snd.seq.port_info_set_capability(pinfo, caps);+      snd.seq.port_info_set_type(pinfo, type);++      if (type & SND_SEQ_PORT_TYPE_MIDI_GENERIC)+      {+        snd.seq.port_info_set_midi_channels(pinfo, 16);+      }++      if (queue)+      {+        snd.seq.port_info_set_timestamping(pinfo, 1);+        snd.seq.port_info_set_timestamp_real(pinfo, 1);+        snd.seq.port_info_set_timestamp_queue(pinfo, *queue);+      }++      if (int err = snd.seq.create_port(this->seq, pinfo); err < 0)+        return err;++      this->vport = snd.seq.port_info_get_port(pinfo);+      if (int err = snd.seq.get_port_info(this->seq, this->vport, pinfo); err < 0)+        return err;++      if (auto addr = snd.seq.port_info_get_addr(pinfo))+        this->vaddr = *addr;+      else+        return -1;++      return this->vport;+    }+    return 0;+  }++  int create_connection(auto& self, snd_seq_addr_t sender, snd_seq_addr_t receiver, bool realtime)+  {+    // Create the connection between ports+    // 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.");+      return err;+    }++    snd.seq.port_subscribe_set_sender(this->subscription, &sender);+    snd.seq.port_subscribe_set_dest(this->subscription, &receiver);++    if (realtime)+    {+      snd.seq.port_subscribe_set_time_update(this->subscription, 1);+      snd.seq.port_subscribe_set_time_real(this->subscription, 1);+    }++    if (int err = snd.seq.subscribe_port(this->seq, this->subscription); err != 0)+    {+      snd.seq.port_subscribe_free(this->subscription);+      this->subscription = nullptr;+      return err;+    }+    return 0;+  }++  void unsubscribe()+  {+    if (this->subscription)+    {+      snd.seq.unsubscribe_port(this->seq, this->subscription);+      snd.seq.port_subscribe_free(this->subscription);+      this->subscription = nullptr;+    }+  }+};+}
+ vendor/libremidi/backends/alsa_seq/midi_in.hpp view
@@ -0,0 +1,549 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi::alsa_seq+{+struct dummy_processing+{+  explicit dummy_processing(auto&&...) { }+};++template <typename ConfigurationImpl>+using midi_in_base+    = std::conditional_t<ConfigurationImpl::midi_version == 1, midi1::in_api, midi2::in_api>;+template <typename ConfigurationImpl>+using midi_in_processing = std::conditional_t<+    ConfigurationImpl::midi_version == 1, midi1::input_state_machine, midi2::input_state_machine>;++template <typename ConfigurationBase, typename ConfigurationImpl>+class midi_in_impl+    : public midi_in_base<ConfigurationImpl>+    , protected alsa_data+    , public error_handler+{+public:+  struct+      : ConfigurationBase+      , ConfigurationImpl+  {+  } configuration;+  midi_in_processing<ConfigurationImpl> m_processing{this->configuration};++  bool require_timestamps() const noexcept+  {+    switch (configuration.timestamps)+    {+      case timestamp_mode::NoTimestamp:+      case timestamp_mode::SystemMonotonic:+      case timestamp_mode::AudioFrame:+        return false;+      case timestamp_mode::Absolute:+      case timestamp_mode::Relative:+      case timestamp_mode::Custom:+        return true;+    }+    return true;+  }++  explicit midi_in_impl(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)+      : midi_in_base<ConfigurationImpl>{}+      , configuration{std::move(conf), std::move(apiconf)}+  {+    if (init_client(configuration) < 0)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating ALSA sequencer client "+          "object.");+      return;+    }++    // Create the input queue+    if (require_timestamps())+    {+      this->queue_id = snd.seq.alloc_queue(seq);+      // Set arbitrary tempo (mm=100) and resolution (240)+      snd_seq_queue_tempo_t* qtempo{};+      snd_seq_queue_tempo_alloca(&qtempo);+      snd.seq.queue_tempo_set_tempo(qtempo, 600000);+      snd.seq.queue_tempo_set_ppq(qtempo, 240);+      snd.seq.set_queue_tempo(this->seq, this->queue_id, qtempo);+      snd.seq.drain_output(this->seq);+    }++    // Create the event -> midi encoder+    {+      int result = snd.midi.event_new(0, &coder);+      if (result < 0)+      {+        libremidi_handle_error(this->configuration, "error during snd_midi_event_new.");+        return;+      }+      snd.midi.event_init(coder);+      snd.midi.event_no_status(coder, 1);+    }+  }++  ~midi_in_impl() override+  {+    // Cleanup.+    if (this->vport >= 0)+      snd.seq.delete_port(this->seq, this->vport);++    if (require_timestamps())+      snd.seq.free_queue(this->seq, this->queue_id);++    snd.midi.event_free(coder);++    // Close if we do not have an user-provided client object+    if (!configuration.context)+      snd.seq.close(this->seq);+  }++  libremidi::API get_current_api() const noexcept override+  {+    if constexpr (ConfigurationImpl::midi_version == 1)+      return libremidi::API::ALSA_SEQ;+    else+      return libremidi::API::ALSA_SEQ_UMP;+  }++  [[nodiscard]] int create_port(std::string_view portName)+  {+    return alsa_data::create_port(+        *this, portName, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,+        SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION,+        require_timestamps() ? std::optional<int>{this->queue_id} : std::nullopt);+  }++  void start_queue()+  {+    if (require_timestamps())+    {+      snd.seq.control_queue(this->seq, this->queue_id, SND_SEQ_EVENT_START, 0, nullptr);+      this->queue_creation_time = std::chrono::steady_clock::now();+      snd.seq.drain_output(this->seq);+    }+  }++  void stop_queue()+  {+    if (require_timestamps())+    {+      snd.seq.control_queue(this->seq, this->queue_id, SND_SEQ_EVENT_STOP, 0, nullptr);+      snd.seq.drain_output(this->seq);+    }+  }++  int connect_port(snd_seq_addr_t sender)+  {+    snd_seq_addr_t receiver{};+    receiver.client = snd.seq.client_id(this->seq);+    receiver.port = this->vport;++    return create_connection(*this, sender, receiver, false);+  }++  std::optional<snd_seq_addr_t> to_address(const port_information& p)+  {+    return alsa_data::get_port_info(p);+  }++  int init_port(std::optional<snd_seq_addr_t> source, std::string_view portName)+  {+    this->close_port();++    if (!source)+      return -1;++    if (int ret = create_port(portName); ret < 0)+    {+      libremidi_handle_error(configuration, "ALSA error creating port.");+      return ret;+    }++    if (int ret = connect_port(*source); ret < 0)+    {+      libremidi_handle_error(configuration, "ALSA error making port connection.");+      return ret;+    }++    start_queue();++    return 0;+  }++  int init_virtual_port(std::string_view portName)+  {+    this->close_port();++    if (int ret = create_port(portName); ret < 0)+      return ret;++    start_queue();+    return 0;+  }++  stdx::error close_port() override+  {+    unsubscribe();+    stop_queue();+    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);+  }++  timestamp absolute_timestamp() const noexcept override+  {+    return std::chrono::duration_cast<std::chrono::nanoseconds>(+               std::chrono::steady_clock::now() - this->queue_creation_time)+        .count();+  }++  int process_event(const snd_seq_event_t& ev)+  {+    if constexpr (ConfigurationImpl::midi_version == 1)+    {+      switch (ev.type)+      {+        case SND_SEQ_EVENT_PORT_SUBSCRIBED:+        case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:+          return 0;+        case SND_SEQ_EVENT_SYSEX: {+          if (configuration.ignore_sysex)+            return 0;+          else if (ev.data.ext.len > decoding_buffer.size())+            decoding_buffer.resize(ev.data.ext.len);+          break;+        }+      }++      static constexpr timestamp_backend_info timestamp_info{+          .has_absolute_timestamps = true,+          .absolute_is_monotonic = false,+          .has_samples = false,+      };++      const auto to_ns = [ts = ev.time.time] {+        return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+      };+      auto buf = decoding_buffer.data();+      auto buf_space = decoding_buffer.size();++      // FIXME according to the doc snd_midi_event_decode can apparently return multiple events????+      const auto avail = snd.midi.event_decode(coder, buf, buf_space, &ev);+      if (avail > 0)+      {+        m_processing.on_bytes(+            {buf, buf + avail}, m_processing.template timestamp<timestamp_info>(to_ns, 0));+        return 0;+      }+      else+      {+        return avail;+      }+    }+    return 0;+  }++  int process_events()+  {+    if constexpr (ConfigurationImpl::midi_version == 1)+    {+      snd_seq_event_t* ev{};+      event_handle handle{snd};+      int result = 0;+      if ((result = snd.seq.event_input(seq, &ev)) > 0)+      {+        handle.reset(ev);+        if (int err = process_event(*ev); err < 0)+        {+          return err;+        }+      }+      return result;+    }+    else+    {+      return 0;+    }+  }++#if __has_include(<alsa/ump.h>)+  int process_ump_event(const snd_seq_ump_event_t& ev)+  {+    // Filter the message types before any decoding+    switch (ev.type)+    {+      case SND_SEQ_EVENT_PORT_SUBSCRIBED:+      case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:+        return 0;++      case SND_SEQ_EVENT_QFRAME: // MIDI time code+      case SND_SEQ_EVENT_TICK:   // 0xF9 ... MIDI timing tick+      case SND_SEQ_EVENT_CLOCK:  // 0xF8 ... MIDI timing (clock) tick+        if (configuration.ignore_timing)+          return 0;+        break;++      case SND_SEQ_EVENT_SENSING: // Active sensing+        if (configuration.ignore_sensing)+          return 0;+        break;++      case SND_SEQ_EVENT_SYSEX: {+        if (configuration.ignore_sysex)+          return 0;+        break;+      }+    }++    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };+    const auto to_ns = [ts = ev.time.time] {+      return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+    };++    m_processing.on_bytes_multi(+        {ev.ump, ev.ump + 4}, m_processing.template timestamp<timestamp_info>(to_ns, 0));+    return 0;+  }++  int process_ump_events()+  {+    snd_seq_ump_event_t* ev{};+    event_handle handle{snd};+    int result = 0;+    while ((result = snd.seq.ump.event_input(seq, &ev)) > 0)+    {+      handle.reset((snd_seq_event_t*)ev);+      if (int err = process_ump_event(*ev); err < 0)+        return err;+    }+    return result;+  }+#endif++  int queue_id{}; // an input queue is needed to get timestamped events++  // Only needed for midi 1+  std::vector<unsigned char> decoding_buffer = std::vector<unsigned char>(4096);+  std::chrono::steady_clock::time_point queue_creation_time;+};++template <typename ConfigurationBase, typename ConfigurationImpl>+class midi_in_alsa_threaded : public midi_in_impl<ConfigurationBase, ConfigurationImpl>+{+public:+  midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)+      : midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}+  {+    if (this->termination_event < 0)+    {+      this->libremidi_handle_error(this->configuration, "error creating eventfd.");+      return;+    }++    this->client_open_ = stdx::error{};+  }++  ~midi_in_alsa_threaded()+  {+    this->close_port();+    this->client_open_ = std::errc::not_connected;+  }++private:+  stdx::error open_port(const input_port& pt, std::string_view local_port_name) override+  {+    if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)+      return from_errc(err);++    return this->start_thread();+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    if (int err = this->init_virtual_port(portName); err < 0)+      return from_errc(err);++    return this->start_thread();+  }++  stdx::error close_port() override+  {+    // FIXME shouldn't we always close the thread+    auto err = midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();++    stop_thread();++    return err;+  }++  [[nodiscard]] stdx::error start_thread()+  {+    try+    {+      this->thread = std::thread([this] { thread_handler(); });+      return stdx::error{};+    }+    catch (const std::system_error& e)+    {+      using namespace std::literals;+      this->unsubscribe();++      this->libremidi_handle_error(+          this->configuration, "error starting MIDI input thread: "s + e.what());+      return e.code();+    }+  }++  stdx::error stop_thread()+  {+    termination_event.notify();++    if (this->thread.joinable())+      this->thread.join();++    termination_event.consume();+    return stdx::error{};+  }++  void thread_handler()+  {+    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;+    alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);++    const auto period+        = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+              .count();+    for (;;)+    {+      if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)+      {+        // No data pending+        if (poll(poll_fds, poll_fd_count, period) >= 0)+        {+          // We got our stop-thread signal+          if (termination_event.ready(poll_fds[0]))+          {+            break;+          }+        }+        continue;+      }++      int res{};+      if constexpr (ConfigurationImpl::midi_version == 1)+      {+        res = this->process_events();+      }+#if __has_include(<alsa/ump.h>)+      else if constexpr (ConfigurationImpl::midi_version == 2)+      {+        res = this->process_ump_events();+      }+#endif++      if (res < 0)+        LIBREMIDI_LOG("MIDI input error: ", this->snd.strerror(res));+    }+  }++  std::thread thread{};+  eventfd_notifier termination_event{};+};++template <typename ConfigurationBase, typename ConfigurationImpl>+class midi_in_alsa_manual : public midi_in_impl<ConfigurationBase, ConfigurationImpl>+{+public:+  midi_in_alsa_manual(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)+      : midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}+  {+    assert(this->configuration.manual_poll);+    assert(this->configuration.stop_poll);+  }++  [[nodiscard]] int init_callback()+  {+    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 __has_include(<alsa/ump.h>)+                      else+                        return this->process_ump_event(ev);+#endif+                    }});+    return 0;+  }++  ~midi_in_alsa_manual() { this->close_port(); }++  stdx::error open_port(const input_port& pt, std::string_view local_port_name) override+  {+    if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)+      return from_errc(err);++    if (int err = init_callback(); err < 0)+      return from_errc(err);++    return stdx::error{};+  }++  stdx::error open_virtual_port(std::string_view name) override+  {+    if (int err = this->init_virtual_port(name); err < 0)+      return from_errc(err);++    if (int err = init_callback(); err < 0)+      return from_errc(err);++    return stdx::error{};+  }++  stdx::error close_port() override+  {+    this->configuration.stop_poll(this->vaddr);++    return midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();+  }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_in_api>+make<alsa_seq::midi_in_impl<libremidi::input_configuration, alsa_seq::input_configuration>>(+    libremidi::input_configuration&& conf, libremidi::alsa_seq::input_configuration&& api)+{+  if (api.manual_poll)+    return std::make_unique<alsa_seq::midi_in_alsa_manual<+        libremidi::input_configuration, alsa_seq::input_configuration>>(+        std::move(conf), std::move(api));+  else+    return std::make_unique<alsa_seq::midi_in_alsa_threaded<+        libremidi::input_configuration, alsa_seq::input_configuration>>(+        std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/alsa_seq/midi_out.hpp view
@@ -0,0 +1,179 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi::alsa_seq+{++class midi_out_impl final+    : public midi1::out_api+    , private alsa_data+    , public error_handler+{+public:+  struct+      : libremidi::output_configuration+      , alsa_seq::output_configuration+  {+  } configuration;++  midi_out_impl(libremidi::output_configuration&& conf, alsa_seq::output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (init_client(configuration) < 0)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating ALSA sequencer client "+          "object.");+      return;+    }++    if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)+    {+      libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");+      return;+    }+    snd.midi.event_init(this->coder);++    this->client_open_ = stdx::error{};+  }++  ~midi_out_impl() override+  {+    // Close a connection if it exists.+    midi_out_impl::close_port();++    // Cleanup.+    if (this->vport >= 0)+      snd.seq.delete_port(this->seq, this->vport);+    if (this->coder)+      snd.midi.event_free(this->coder);++    if (!configuration.context)+      snd.seq.close(this->seq);++    client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }++  [[nodiscard]] int create_port(std::string_view portName)+  {+    return alsa_data::create_port(+        *this, portName, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,+        SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);+  }++  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)+    {+      libremidi_handle_error(this->configuration, "no MIDI output sources found!");+      return make_error_code(std::errc::no_such_device);+    }++    auto sink = get_port_info(p);+    if (!sink)+      return std::errc::invalid_argument;++    if (int err = create_port(portName); err < 0)+    {+      libremidi_handle_error(configuration, "ALSA error creating port.");+      return from_errc(err);+    }++    snd_seq_addr_t source{+        .client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};+    if (int err = create_connection(*this, source, *sink, true); err < 0)+    {+      libremidi_handle_error(configuration, "ALSA error making port connection.");+      return from_errc(err);+    }++    return stdx::error{};+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    if (int err = create_port(portName); err < 0)+      return from_errc(err);+    return stdx::error{};+  }++  stdx::error close_port() override+  {+    unsubscribe();+    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);+  }++  stdx::error send_message(const unsigned char* message, std::size_t size) override+  {+    int64_t result{};+    if (size > this->bufferSize)+    {+      this->bufferSize = size;+      result = snd.midi.event_resize_buffer(this->coder, size);+      if (result != 0)+      {+        libremidi_handle_error(+            this->configuration,+            "ALSA error resizing MIDI event "+            "buffer.");+        return std::errc::no_buffer_space;+      }+    }++    std::size_t offset = 0;+    while (offset < size)+    {+      snd_seq_event_t ev;+      snd_seq_ev_clear(&ev);+      snd_seq_ev_set_source(&ev, this->vport);+      snd_seq_ev_set_subs(&ev);+      // 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);+      if (result < 0)+      {+        libremidi_handle_warning(this->configuration, "event parsing error!");+        return std::errc::bad_message;+      }++      if (ev.type == SND_SEQ_EVENT_NONE)+      {+        libremidi_handle_warning(this->configuration, "incomplete message!");+        return std::errc::message_size;+      }++      offset += result;++      result = snd.seq.event_output(this->seq, &ev);+      if (result < 0)+      {+        libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");+        return std::errc::io_error;+      }+    }+    snd.seq.drain_output(this->seq);+    return stdx::error{};+  }++private:+  uint64_t bufferSize{32};+};+}
+ vendor/libremidi/backends/alsa_seq/observer.hpp view
@@ -0,0 +1,355 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/detail/observer.hpp>++#include <alsa/asoundlib.h>++#include <bitset>+#include <map>++namespace libremidi::alsa_seq+{++struct port_info+{+  std::string client_name;+  std::string port_name;+  int client{};+  int port{};+  bool isInput{};+  bool isOutput{};+};++template <typename ConfigurationImpl>+class observer_impl+    : public observer_api+    , public alsa_data+    , public error_handler+{+public:+  struct+      : libremidi::observer_configuration+      , ConfigurationImpl+  {+  } configuration;++  explicit observer_impl(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    using namespace std::literals;+    if (int err = init_client(configuration); err < 0)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating ALSA sequencer client "+          "object.");+      return;+    }++    if (!configuration.has_callbacks())+      return;++    // Init with the existing ports+    if (configuration.notify_in_constructor)+      init_all_ports();++    // Create the port to listen on the server events+    {+#if __has_include(<alsa/ump.h>)+      constexpr int midi2_cap+          = ConfigurationImpl::midi_version == 2 ? SND_SEQ_PORT_CAP_UMP_ENDPOINT : 0;+#else+      constexpr int midi2_cap = 0;+#endif++      constexpr int caps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE+                           | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE | midi2_cap;+      int err = alsa_data::create_port(+          *this, "libremidi-observe", caps, SND_SEQ_PORT_TYPE_APPLICATION, false);+      if (err < 0)+      {+        libremidi_handle_error(this->configuration, "error creating ALSA sequencer port.");+        return;+      }+    }++    // Connect the ALSA server events to our port+    {+      int err+          = snd.seq.connect_from(seq, vport, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);+      if (err < 0)+      {+        libremidi_handle_error(this->configuration, "error connecting to ALSA sequencer.");+        return;+      }+    }+  }++  std::optional<port_info> get_info(int client, int port) const noexcept+  {+    port_info p;+    p.client = client;+    p.port = port;++    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)+      return std::nullopt;++    snd_seq_port_info_t* pinfo;+    snd_seq_port_info_alloca(&pinfo);+    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;+  }++  template <bool Input>+  auto to_port_info(port_info p) const noexcept+      -> std::conditional_t<Input, input_port, output_port>+  {+    static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle));+    static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle));+    return {+        {.client = std::uintptr_t(this->seq),+         .port = alsa_seq::seq_to_port_handle(p.client, p.port),+         .manufacturer = "",+         .device_name = p.client_name,+         .port_name = p.port_name,+         .display_name = p.port_name}};+  }++  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);+        });+  }++  libremidi::API get_current_api() const noexcept override+  {+    if constexpr (ConfigurationImpl::midi_version == 1)+      return libremidi::API::ALSA_SEQ;+    else+      return libremidi::API::ALSA_SEQ_UMP;+  }++  std::vector<libremidi::input_port> get_input_ports() const noexcept override+  {+    std::vector<libremidi::input_port> 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<true>(*p));+        });+    return ret;+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept override+  {+    std::vector<libremidi::output_port> 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<false>(*p));+        });+    return ret;+  }++  void register_port(int client, int port)+  {+    auto pp = get_info(client, port);+    if (!pp)+      return;+    auto& p = *pp;+    if (p.client == snd.seq.client_id(seq))+      return;++    knownClients_[{p.client, p.port}] = p;+    if (p.isInput && configuration.input_added)+    {+      configuration.input_added(to_port_info<true>(p));+    }++    if (p.isOutput && configuration.output_added)+    {+      configuration.output_added(to_port_info<false>(p));+    }+  }++  void unregister_port(int client, int port)+  {+    auto it = knownClients_.find({client, port});+    if (it != knownClients_.end())+    {+      auto p = it->second;+      knownClients_.erase(it);++      if (p.isInput && configuration.input_removed)+      {+        configuration.input_removed(to_port_info<true>(p));+      }++      if (p.isOutput && configuration.output_removed)+      {+        configuration.output_removed(to_port_info<false>(p));+      }+    }+  }++  void handle_event(const snd_seq_event_t& ev)+  {+    switch (ev.type)+    {+      case SND_SEQ_EVENT_PORT_START: {+        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);+        break;+      }+      case SND_SEQ_EVENT_PORT_CHANGE:+      default:+        break;+    }+  }++  ~observer_impl()+  {+    if (seq)+    {+      if (vport)+        snd.seq.delete_port(seq, vport);++      if (!configuration.context)+        snd.seq.close(seq);+    }+  }++private:+  std::map<std::pair<int, int>, port_info> knownClients_;+};++template <typename ConfigurationImpl>+class observer_threaded : public observer_impl<ConfigurationImpl>+{+public:+  observer_threaded(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)+      : observer_impl<ConfigurationImpl>{std::move(conf), std::move(apiconf)}+  {+    // 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;++    // Start the listening thread+    thread = std::thread{[this] {+      auto& snd = alsa_data::snd;+      const auto period+          = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+                .count();+      for (;;)+      {+        int err = poll(descriptors_.data(), descriptors_.size(), period);+        if (err >= 0)+        {+          // We got our stop-thread signal+          if (descriptors_.back().revents & POLLIN)+            break;++          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);+          }+        }+      }+    }};+  }++  ~observer_threaded()+  {+    termination_event.notify();++    if (thread.joinable())+      thread.join();+  }++  eventfd_notifier termination_event{};+  std::thread thread;+  std::vector<pollfd> descriptors_;+};++template <typename ConfigurationImpl>+class observer_manual : public observer_impl<ConfigurationImpl>+{+public:+  observer_manual(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)+      : observer_impl<ConfigurationImpl>{std::move(conf), std::move(apiconf)}+  {+    this->configuration.manual_poll(+        poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) {+                          this->handle_event(v);+                          return 0;+                        }});+  }++  ~observer_manual() { this->configuration.stop_poll(this->vaddr); }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<observer_api>+make<alsa_seq::observer_impl<alsa_seq::observer_configuration>>(+    libremidi::observer_configuration&& conf, libremidi::alsa_seq::observer_configuration&& api)+{+  if (api.manual_poll)+    return std::make_unique<alsa_seq::observer_manual<alsa_seq::observer_configuration>>(+        std::move(conf), std::move(api));+  else+    return std::make_unique<alsa_seq::observer_threaded<alsa_seq::observer_configuration>>(+        std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/alsa_seq/shared_handler.hpp view
@@ -0,0 +1,197 @@+#pragma once+#if __has_include(<boost/lockfree/spsc_queue.hpp>)+  #include <libremidi/backends/alsa_seq/config.hpp>+  #include <libremidi/backends/alsa_seq/helpers.hpp>+  #include <libremidi/backends/linux/helpers.hpp>+  #include <libremidi/shared_context.hpp>++  #include <boost/lockfree/spsc_queue.hpp>++  #include <variant>++namespace libremidi::alsa_seq+{++struct shared_handler : public libremidi::shared_context+{+  const libasound& snd = libasound::instance();+  struct equals_addr+  {+    constexpr bool operator()(const snd_seq_addr_t& lhs, const snd_seq_addr_t& rhs) noexcept+    {+      return lhs.client == rhs.client && lhs.port == rhs.port;+    }+  };++  explicit shared_handler(std::string_view v)+  {+    if (int err = snd.seq.open(&client, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); err < 0)+    {+      client = nullptr;+      // fixme throw?+      return;+    }++    if (!v.empty())+      snd.seq.set_client_name(client, v.data());++    // Last descriptor is the eventfd one+    int fds_size = snd.seq.poll_descriptors_count(client, POLLIN);+    fds.reserve(fds_size + 2);+    fds.resize(fds_size);+    snd.seq.poll_descriptors(client, fds.data(), fds_size, POLLIN);+    fds.push_back(termination_event);+    fds.push_back(queue_event);+  }++  void start_processing() override+  {+    thread = std::thread{[this] { process(); }};+  }++  void stop_processing() override+  {+    termination_event.notify();+    if (thread.joinable())+      thread.join();+    termination_event.consume();+  }++  static shared_configurations make(std::string_view client_name)+  {+    auto clt = std::make_shared<shared_handler>(client_name);++    auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) {+      if (auto clt = client.lock())+      {+        clt->events.push(+            {.type = shared_handler::event_type::callback_added, .payload = std::move(params)});+        clt->queue_event.notify();+      }+      return true;+    };++    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->queue_event.notify();+      }+      return true;+    };+    return {+        .context = clt,+        .observer = alsa_seq::+            observer_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb},+        .in = alsa_seq::+            input_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb},+        .out = alsa_seq::output_configuration{.context = clt->client},+    };+  }++  int64_t index_of_address(snd_seq_addr_t addr)+  {+    auto it = std::find_if(addresses.begin(), addresses.end(), [=](snd_seq_addr_t other) {+      return equals_addr{}(addr, other);+    });+    if (it != addresses.end())+    {+      return std::distance(addresses.begin(), it);+    }+    else+    {+      return -1;+    }+  }++  void process()+  {+    for (;;)+    {+      int err = poll(fds.data(), fds.size(), -1);+      if (err < 0)+        return;+      // Check for termination signal+      if (termination_event.ready(fds[fds.size() - 2]))+        return;++      // Check for queue processing signal+      if (queue_event.ready(fds[fds.size() - 1]))+      {+        this->queue_event.consume();++        event ev;+        while (this->events.pop(ev))+        {+          switch (ev.type)+          {+            case callback_added: {+              auto [addr, cb]+                  = std::move(*std::get_if<libremidi::alsa_seq::poll_parameters>(&ev.payload));+              addresses.push_back(addr);+              callbacks.push_back(std::move(cb));+              break;+            }+            case callback_removed:+              auto addr = std::move(*std::get_if<snd_seq_addr_t>(&ev.payload));+              if (auto index = index_of_address(addr); index >= 0)+              {+                addresses.erase(addresses.begin() + index);+                callbacks.erase(callbacks.begin() + index);+              }+              break;+          }+        }+      }++      // Look for who's ready+      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)+          {+            handle.reset(ev);++            if (auto index = index_of_address(ev->dest); index >= 0)+            {+              // Dispatch the event to the correct observer or midi_in object+              int err = callbacks[index](*ev);+              if (err < 0 && err != -EAGAIN)+                return;+            }+          }+        }+      }+    }+  }++  ~shared_handler() { snd.seq.close(client); }++  enum event_type+  {+    callback_added,+    callback_removed,+  };+  struct event+  {+    event_type type;+    std::variant<libremidi::alsa_seq::poll_parameters, snd_seq_addr_t> payload;+  };++  snd_seq_t* client{};++  boost::lockfree::spsc_queue<event> events{16};+  std::vector<snd_seq_addr_t> addresses;+  std::vector<std::function<int(const snd_seq_event_t&)>> callbacks;+  std::vector<pollfd> fds;+  eventfd_notifier termination_event, queue_event{false};+  std::thread thread;+};+}+#endif
+ vendor/libremidi/backends/alsa_seq_ump.hpp view
@@ -0,0 +1,62 @@+#pragma once++#include <libremidi/backends/alsa_seq/midi_in.hpp>+#include <libremidi/backends/alsa_seq/observer.hpp>+#include <libremidi/backends/alsa_seq_ump/config.hpp>+#include <libremidi/backends/alsa_seq_ump/midi_out.hpp>++namespace libremidi+{+template <>+inline std::unique_ptr<observer_api>+make<alsa_seq::observer_impl<alsa_seq_ump::observer_configuration>>(+    libremidi::observer_configuration&& conf,+    libremidi::alsa_seq_ump::observer_configuration&& api)+{+  if (api.manual_poll)+    return std::make_unique<alsa_seq::observer_manual<alsa_seq_ump::observer_configuration>>(+        std::move(conf), std::move(api));+  else+    return std::make_unique<alsa_seq::observer_threaded<alsa_seq_ump::observer_configuration>>(+        std::move(conf), std::move(api));+}++template <>+inline std::unique_ptr<midi_in_api> make<+    alsa_seq::midi_in_impl<libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(+    libremidi::ump_input_configuration&& conf, libremidi::alsa_seq_ump::input_configuration&& api)+{+  if (api.manual_poll)+    return std::make_unique<alsa_seq::midi_in_alsa_manual<+        libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(+        std::move(conf), std::move(api));+  else+    return std::make_unique<alsa_seq::midi_in_alsa_threaded<+        libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(+        std::move(conf), std::move(api));+}++}++namespace libremidi::alsa_seq_ump+{+struct backend+{+  using midi_in = alsa_seq::midi_in_impl<+      libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>;+  using midi_out = alsa_seq_ump::midi_out_impl;+  using midi_observer = alsa_seq::observer_impl<alsa_seq_ump::observer_configuration>;+  using midi_in_configuration = alsa_seq_ump::input_configuration;+  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 inline bool available() noexcept+  {+    static const libasound& snd = libasound::instance();+    return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available;+  }+};+}
+ vendor/libremidi/backends/alsa_seq_ump/config.hpp view
@@ -0,0 +1,55 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>++#if __has_include(<alsa/asoundlib.h>)+  #include <alsa/asoundlib.h>+#endif++// Necessary for the versions of ALSA that did have UMP+// through snd_seq_event, before snd_seq_ump_event existed.+extern "C" {+typedef struct snd_seq_ump_event snd_seq_ump_event_t;+}++namespace libremidi::alsa_seq_ump+{++struct poll_parameters+{+  snd_seq_addr_t addr{};+  std::function<int(const snd_seq_ump_event_t&)> callback;+};++struct input_configuration+{+  using poll_parameters_type = poll_parameters;++  std::string client_name = "libremidi client";+  snd_seq_t* context{};+  std::function<bool(const poll_parameters&)> manual_poll;+  std::function<bool(snd_seq_addr_t)> stop_poll;+  std::chrono::milliseconds poll_period{2};++  static constexpr int midi_version = 2;+};++struct output_configuration+{+  std::string client_name = "libremidi client";+  snd_seq_t* context{};++  static constexpr int midi_version = 2;+};++struct observer_configuration+{+  std::string client_name = "libremidi client";+  snd_seq_t* context{};+  std::function<bool(const libremidi::alsa_seq::poll_parameters&)> manual_poll;+  std::function<bool(snd_seq_addr_t)> stop_poll;+  std::chrono::milliseconds poll_period{100};++  static constexpr int midi_version = 2;+};++}
+ vendor/libremidi/backends/alsa_seq_ump/helpers.hpp view
+ vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp view
@@ -0,0 +1,144 @@+#pragma once+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/backends/alsa_seq_ump/config.hpp>+#include <libremidi/backends/alsa_seq_ump/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>+#include <libremidi/detail/ump_stream.hpp>++namespace libremidi::alsa_seq_ump+{++class midi_out_impl final+    : public midi2::out_api+    , private alsa_seq::alsa_data+    , public error_handler+{+public:+  struct+      : libremidi::output_configuration+      , alsa_seq_ump::output_configuration+  {+  } configuration;++  midi_out_impl(+      libremidi::output_configuration&& conf, alsa_seq_ump::output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    assert(snd.seq.ump.available);+    if (init_client(configuration) < 0)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating ALSA sequencer client "+          "object.");+      return;+    }++    this->client_open_ = stdx::error{};+  }++  ~midi_out_impl() override+  {+    // Close a connection if it exists.+    midi_out_impl::close_port();++    // Cleanup.+    if (this->vport >= 0)+      snd.seq.delete_port(this->seq, this->vport);++    if (!configuration.context)+      snd.seq.close(this->seq);++    client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ_UMP; }++  [[nodiscard]] int create_port(std::string_view portName)+  {+    return alsa_data::create_port(+        *this, portName,+        SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_UMP_ENDPOINT,+        SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);+  }++  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)+    {+      libremidi_handle_error(this->configuration, "no MIDI output sources found!");+      return make_error_code(std::errc::no_such_device);+    }++    auto sink = get_port_info(p);+    if (!sink)+      return std::errc::invalid_argument;++    if (int err = create_port(portName); err < 0)+    {+      libremidi_handle_error(configuration, "ALSA error creating port.");+      return from_errc(err);+    }++    snd_seq_addr_t source{+        .client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};+    if (int err = create_connection(*this, source, *sink, true); err < 0)+    {+      libremidi_handle_error(configuration, "ALSA error making port connection.");+      return from_errc(err);+    }++    return stdx::error{};+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    if (int err = create_port(portName); err < 0)+      return from_errc(err);+    return stdx::error{};+  }++  stdx::error close_port() override+  {+    unsubscribe();+    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);+  }++  stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override+  {+    snd_seq_ump_event_t ev;++    memset(&ev, 0, sizeof(snd_seq_ump_event_t));+    snd_seq_ev_set_ump(&ev);+    snd_seq_ev_set_source(&ev, this->vport);+    snd_seq_ev_set_subs(&ev);+    snd_seq_ev_set_direct(&ev);++    auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) -> std::errc {+      std::memcpy(ev.ump, ump, bytes);+      const int ret = snd.seq.ump.event_output_direct(this->seq, &ev);+      if (ret < 0)+      {+        libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");+        return static_cast<std::errc>(-ret);+      }+      return std::errc{0};+    };+    segment_ump_stream(ump_stream, count, write_func, []() {});++    snd.seq.drain_output(this->seq);+    return stdx::error{};+  }+};+}
+ vendor/libremidi/backends/coremidi.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/coremidi/midi_in.hpp>+#include <libremidi/backends/coremidi/midi_out.hpp>+#include <libremidi/backends/coremidi/observer.hpp>++namespace libremidi+{+struct core_backend+{+  using midi_in = midi_in_core;+  using midi_out = midi_out_core;+  using midi_observer = observer_core;+  using midi_in_configuration = coremidi_input_configuration;+  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 constexpr inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/coremidi/config.hpp view
@@ -0,0 +1,42 @@+#pragma once+#include <libremidi/config.hpp>++#include <cstdint>+#include <functional>+#include <optional>+#include <string>++#if defined(__APPLE__)+  #if __LP64__+typedef unsigned int UInt32;+  #else+typedef unsigned long UInt32;+  #endif+typedef UInt32 MIDIObjectRef;+typedef MIDIObjectRef MIDIClientRef;+#else+using MIDIClientRef = uint32_t;+using MIDIObjectRef = uint32_t;+#endif+namespace libremidi+{++struct coremidi_input_configuration+{+  std::string client_name = "libremidi client";+  std::optional<MIDIClientRef> context{};+};++struct coremidi_output_configuration+{+  std::string client_name = "libremidi client";+  std::optional<MIDIClientRef> context{};+};++struct coremidi_observer_configuration+{+  std::string client_name = "libremidi client";+  std::function<void(MIDIClientRef)> on_create_context{};+};++}
+ vendor/libremidi/backends/coremidi/error_domain.hpp view
@@ -0,0 +1,72 @@+#pragma once+#include <libremidi/error.hpp>++#include <CoreMIDI/CoreMIDI.h>++namespace libremidi+{+struct coremidi_error_domain : public stdx::error_domain+{+public:+  constexpr coremidi_error_domain() noexcept+      : error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}+  {+  }++  stdx::string_ref name() const noexcept override { return "coremidi"; }++  bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override+  {+    if (lhs.domain() == rhs.domain())+      return error_cast<std::errc>(lhs) == error_cast<std::errc>(rhs);++    return false;+  }++  stdx::string_ref message(const stdx::error& e) const noexcept override+  {+    switch (error_cast<OSStatus>(e))+    {+      case kMIDIInvalidClient:+        return "Invalid Client";+      case kMIDIInvalidPort:+        return "Invalid Port";+      case kMIDIWrongEndpointType:+        return "Wrong EndpointT ype";+      case kMIDINoConnection:+        return "No Connection";+      case kMIDIUnknownEndpoint:+        return "Unknown Endpoint";+      case kMIDIUnknownProperty:+        return "Unknown Property";+      case kMIDIWrongPropertyType:+        return "Wrong Property Type";+      case kMIDINoCurrentSetup:+        return "No Current Setup";+      case kMIDIMessageSendErr:+        return "Message Send Error";+      case kMIDIServerStartErr:+        return "Server Start Error";+      case kMIDISetupFormatErr:+        return "Setup Format Error";+      case kMIDIWrongThread:+        return "Wrong Thread";+      case kMIDIObjectNotFound:+        return "Object Not Found";+      case kMIDIIDNotUnique:+        return "ID Not Unique";+      case kMIDINotPermitted:+        return "Not Permitted";+      case kMIDIUnknownError:+        return "Unknown Error";+    }+    return "Unknown error code";+  }+};++inline stdx::error from_osstatus(OSStatus ret) noexcept+{+  static constexpr coremidi_error_domain domain{};+  return {ret, domain};+}+}
+ vendor/libremidi/backends/coremidi/helpers.hpp view
@@ -0,0 +1,351 @@+#pragma once+#include <libremidi/detail/memory.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/error_handler.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/backends/coremidi/error_domain.hpp>++#include <CoreMIDI/CoreMIDI.h>+#include <CoreServices/CoreServices.h>++#include <cmath>++#include <bit>++#if TARGET_OS_IPHONE+  #include <CoreAudio/CoreAudioTypes.h>+  #include <mach/mach_time.h>+  #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME mach_absolute_time+#else+  #include <CoreAudio/HostTime.h>+  #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime+#endif++namespace libremidi+{+using CFString_handle = unique_handle<const __CFString, CFRelease>;+using CFStringMutable_handle = unique_handle<__CFString, CFRelease>;+namespace+{+static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept+{+  CFStringRef res;+  if (MIDIObjectGetStringProperty(object, property, &res) || !res)+    return {};++  char name[256];+  CFStringGetCString(res, name, sizeof(name), kCFStringEncodingUTF8);+  CFRelease(res);+  return name;+}++static inline 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+{+  return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)};+}++#if TARGET_OS_IPHONE+inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)+{+  static const struct mach_timebase_info timebase = [] {+    struct mach_timebase_info theTimeBaseInfo;+    mach_timebase_info(&theTimeBaseInfo);+    return theTimeBaseInfo;+  }();+  const auto numer = timebase.numer;+  const auto denom = timebase.denom;++  __uint128_t res = hostTime;+  if (numer != denom)+  {+    res *= numer;+    res /= denom;+  }+  return static_cast<uint64_t>(res);+}+#endif+// 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))+  {+    CFStringAppend(result, endpoint_name.get());+  }++  // some MIDI devices have a leading space in endpoint name. trim+  CFStringTrim(result, CFSTR(" "));++  MIDIEntityRef entity = 0;+  MIDIDeviceRef device = 0;+  MIDIEndpointGetEntity(endpoint, &entity);+  if (entity == 0)+    goto finish;++  if (CFStringGetLength(result) == 0)+  {+    // endpoint name has zero length -- try the entity+    if (auto entity_name = getProp(entity))+    {+      CFStringAppend(result, entity_name.get());+    }+  }++  // now consider the device's name+  MIDIEntityGetDevice(entity, &device);+  if (device == 0)+    goto finish;++  if (auto dev_name = getProp(device))+  {+    const auto dev_strlen = CFStringGetLength(dev_name.get());++    // if an external device has only one entity, throw away+    // the endpoint name and just use the device name+    if (CFStringGetLength(result) == 0+        || (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2))+    {+      CFStringAppend(result, dev_name.get());+      goto finish;+    }++    // does the entity name already start with the device name?+    // (some drivers do this though they shouldn't)+    // if so, do not prepend+    if (CFStringCompareWithOptions(result, dev_name.get(), CFRangeMake(0, dev_strlen), 0)+        != kCFCompareEqualTo)+    {+      // prepend the device name to the entity name+      if (CFStringGetLength(result) > 0)+        CFStringInsert(result, 0, CFSTR(" "));+      CFStringInsert(result, 0, dev_name.get());+    }+  }++finish:+  if (CFStringGetLength(result) == 0)+    return CFSTR("No name");+  else+    return result;+}++// This function was submitted by Douglas Casey Tucker and apparently+// derived largely from PortMidi.+inline CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)+{+  CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);+  CFStringRef str{};++  // Does the endpoint have connections?+  CFDataRef connections = nullptr;+  std::size_t nConnected = 0;+  bool anyStrings = false;+  MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);+  if (connections != nullptr)+  {+    // It has connections, follow them+    // Concatenate the names of all connected devices+    nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID);+    if (nConnected)+    {+      const SInt32* pid = (const SInt32*)(CFDataGetBytePtr(connections));+      for (std::size_t i = 0; i < nConnected; ++i, ++pid)+      {+        MIDIUniqueID id = CFSwapInt32BigToHost(*pid);+        MIDIObjectRef connObject;+        MIDIObjectType connObjectType;+        auto err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);+        if (err == noErr)+        {+          if (connObjectType == kMIDIObjectType_ExternalSource+              || connObjectType == kMIDIObjectType_ExternalDestination)+          {+            // Connected to an external device's endpoint (10.3 and later).+            str = EndpointName((MIDIEndpointRef)(connObject), true);+          }+          else+          {+            // Connected to an external device (10.2) (or something else,+            // catch-+            str = nullptr;+            MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);+          }+          if (str != nullptr)+          {+            if (anyStrings)+              CFStringAppend(result, CFSTR(", "));+            else+              anyStrings = true;+            CFStringAppend(result, str);+            CFRelease(str);+          }+        }+      }+    }+    CFRelease(connections);+  }+  if (anyStrings)+    return result;++  CFRelease(result);++  // Here, either the endpoint had no connections, or we failed to obtain names+  return EndpointName(endpoint, false);+}++inline MIDIObjectRef+locate_object(auto& self, const port_information& info, MIDIObjectType requested_type)+{+  auto uid = std::bit_cast<std::int32_t>((uint32_t)info.port);+  MIDIObjectRef object{};+  MIDIObjectType type{};+  auto ret = MIDIObjectFindByUniqueID(uid, &object, &type);+  if (ret != noErr)+  {+    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));+    return 0;+  }++  return object;+}+}++// A structure to hold variables related to the CoreMIDI API+// implementation.+struct coremidi_data+{+  MIDIClientRef client{};+  MIDIPortRef port{};+  MIDIEndpointRef endpoint = 0;++  [[nodiscard]] OSStatus init_client(auto& configuration)+  {+    if (configuration.context)+    {+      client = *configuration.context;+      return noErr;+    }+    else+    {+      // Set up our client.+      return MIDIClientCreate(+          toCFString(configuration.client_name).get(), nullptr, nullptr, &client);+    }+  }++  void close_client(auto& self)+  {+    self.client_open_ = std::errc::not_connected;+    if (!self.configuration.context)+      MIDIClientDispose(self.client);+  }++  static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept+  {+    if (tp == 0)+    { // this happens when receiving asynchronous sysex messages+      return clock_gettime_nsec_np(CLOCK_UPTIME_RAW);+    }+    else+    {+      return AudioConvertHostTimeToNanos(tp);+    }+  }++  static void set_timestamp(auto& self, MIDITimeStamp packet, timestamp& msg) noexcept+  {+    // packet.timeStamp is in mach_absolute_time units+    // We want a timestamp in nanoseconds++    switch (self.configuration.timestamps)+    {+      case timestamp_mode::NoTimestamp:+        msg = 0;+        return;+      case timestamp_mode::Relative: {+        if (self.firstMessage)+        {+          self.firstMessage = false;+          msg = 0;+          return;+        }+        else+        {+          if constexpr (requires { self.continueSysex; })+          {+            if (self.continueSysex)+              return;+          }++          auto time = time_in_nanos(packet);+          time -= self.last_time;+          msg = time;+        }+        break;+      }+      case timestamp_mode::Absolute:+      case timestamp_mode::SystemMonotonic:+        if constexpr (requires { self.continueSysex; })+        {+          if (self.continueSysex)+            return;+        }+        msg = time_in_nanos(packet);+        break;++      case timestamp_mode::Custom: {+        if constexpr (requires { self.continueSysex; })+        {+          if (self.continueSysex)+            return;+        }+        msg = self.configuration.get_timestamp(time_in_nanos(packet));+        break;+      }+    }+  }++  stdx::error close_port()+  {+    if (this->endpoint)+    {+      MIDIEndpointDispose(this->endpoint);+      this->endpoint = 0;+    }++    if (this->port)+    {+      MIDIPortDispose(this->port);+      this->port = 0;+    }++    return stdx::error{};+  }+};++}
+ vendor/libremidi/backends/coremidi/midi_in.hpp view
@@ -0,0 +1,154 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+class midi_in_core final+    : public midi1::in_api+    , public coremidi_data+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : input_configuration+      , coremidi_input_configuration+  {+  } configuration;++  midi_in_core(input_configuration&& conf, coremidi_input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (auto result = init_client(configuration); result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating MIDI client object: " + std::to_string(result));+      client_open_ = from_osstatus(result);+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_in_core() override+  {+    // Close a connection if it exists.+    midi_in_core::close_port();++    if (this->endpoint)+      MIDIEndpointDispose(this->endpoint);++    close_client(*this);+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }++  stdx::error open_port(const input_port& info, std::string_view portName) override+  {+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++    auto source = locate_object(*this, info, kMIDIObjectType_Source);+    if (source == 0)+      return std::errc::invalid_argument;++    // Create our local sink+    MIDIPortRef port;+    OSStatus result = MIDIInputPortCreate(+        this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &port);++    if (result != noErr)+    {+      close_client(*this);+      libremidi_handle_error(+          this->configuration, "error creating macOS MIDI input port: "+                                   + std::to_string(result));+      return from_osstatus(result);+    }++    // Make the connection.+    if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)+    {+      MIDIPortDispose(port);+      close_client(*this);+      libremidi_handle_error(+          this->configuration, "error connecting macOS MIDI input port.");+      return from_osstatus(result);+    }++    // Save our api-specific port information.+    this->port = port;+    return stdx::error{};+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    // Create a virtual MIDI input destination.+    MIDIEndpointRef endpoint;+    OSStatus result = MIDIDestinationCreate(+        this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &endpoint);++    if (result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating virtual macOS MIDI "+          "destination.");+      return from_osstatus(result);+    }++    // Save our api-specific connection information.+    this->endpoint = endpoint;+    return stdx::error{};+  }++  stdx::error close_port() override+  {+    return coremidi_data::close_port();+  }++  timestamp absolute_timestamp() const noexcept override+  {+    return coremidi_data::time_in_nanos(LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME());+  }++  static void midiInputCallback(const MIDIPacketList* list, void* procRef, void* /*srcRef*/)+  {+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    auto& self = *(midi_in_core*)procRef;++    const MIDIPacket* packet = &list->packet[0];+    for (unsigned int i = 0; i < list->numPackets; ++i)+    {+      // My interpretation of the CoreMIDI documentation: all message+      // types, except sysex, are complete within a packet and there may+      // be several of them in a single packet.  Sysex messages can be+      // broken across multiple packets and PacketLists but are bundled+      // alone within each packet (these packets do not contain other+      // message types).  If sysex messages are split across multiple+      // MIDIPacketLists, they must be handled by multiple calls to this+      // function.++      if (packet->length > 0)+      {+        auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };+        self.m_processing.on_bytes_multi(+            {packet->data, packet->data + packet->length},+            self.m_processing.timestamp<timestamp_info>(to_ns, 0));+      }++      packet = MIDIPacketNext(packet);+    }+  }++  midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/coremidi/midi_out.hpp view
@@ -0,0 +1,178 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{+class midi_out_core final+    : public midi1::out_api+    , public coremidi_data+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : output_configuration+      , coremidi_output_configuration+  {+  } configuration;++  midi_out_core(output_configuration&& conf, coremidi_output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (auto result = init_client(configuration); result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating MIDI client object: " + std::to_string(result));+      client_open_ = from_osstatus(result);+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_out_core()+  {+    midi_out_core::close_port();++    if (this->endpoint)+      MIDIEndpointDispose(this->endpoint);++    close_client(*this);+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }++  stdx::error open_port(const output_port& info, std::string_view portName) override+  {+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++    // Find where we want to send+    auto destination = locate_object(*this, info, kMIDIObjectType_Destination);+    if (destination == 0)+      return std::errc::invalid_argument;++    // Create our local source+    MIDIPortRef port;+    OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);+    if (result != noErr)+    {+      close_client(*this);+      libremidi_handle_error(+          this->configuration, "error creating macOS MIDI output port.");+      return from_osstatus(result);+    }++    // Save our api-specific connection information.+    this->port = port;+    this->destinationId = destination;++    return stdx::error{};+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    // Create a virtual MIDI output source.+    MIDIEndpointRef endpoint;+    OSStatus result = MIDISourceCreate(this->client, toCFString(portName).get(), &endpoint);++    if (result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating macOS virtual MIDI source.");++      return from_osstatus(result);+    }++    // Save our api-specific connection information.+    this->endpoint = endpoint;+    return stdx::error{};+  }++  stdx::error close_port() override+  {+    return coremidi_data::close_port();+  }++  stdx::error send_message(const unsigned char* message, size_t size) override+  {+    unsigned int nBytes = static_cast<unsigned int>(size);+    if (nBytes == 0)+    {+      libremidi_handle_warning(configuration, "no data in message argument!");+      return std::errc::invalid_argument;+    }++    if (message[0] != 0xF0 && nBytes > 3)+    {+      libremidi_handle_warning(+          configuration,+          "message format problem ... not sysex but "+          "> 3 bytes?");+      return std::errc::bad_message;+    }++    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);+    MIDIPacketList* packetList = (MIDIPacketList*)buffer;++    ByteCount remainingBytes = nBytes;+    while (remainingBytes)+    {+      MIDIPacket* packet = MIDIPacketListInit(packetList);+      // A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer,+      // break it up into chunks of 64K or less and send out as a MIDIPacketList with only one+      // MIDIPacket. Here, we reuse the memory allocated above on the stack for all.+      ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes;+      const Byte* dataStartPtr = (const Byte*)&message[nBytes - remainingBytes];+      packet = MIDIPacketListAdd(+          packetList, listSize, packet, timestamp, bytesForPacket, dataStartPtr);+      remainingBytes -= bytesForPacket;++      if (!packet)+      {+        libremidi_handle_error(+            this->configuration, "could not allocate packet list");++        return std::errc::message_size;+      }++      // Send to any destinations that may have connected to us.+      if (this->endpoint)+      {+        auto result = MIDIReceived(this->endpoint, packetList);+        if (result != noErr)+        {+          libremidi_handle_warning(+              this->configuration,+              "error sending MIDI to virtual "+              "destinations.");+          return std::errc::io_error;+        }+      }++      // And send to an explicit destination port if we're connected.+      if (this->destinationId != 0)+      {+        auto result = MIDISend(this->port, this->destinationId, packetList);+        if (result != noErr)+        {+          libremidi_handle_warning(+              this->configuration,+              "error sending MIDI message to port.");+          return std::errc::io_error;+        }+      }+    }+    return stdx::error{};+  }++  MIDIEndpointRef destinationId{};+};+}
+ vendor/libremidi/backends/coremidi/observer.hpp view
@@ -0,0 +1,175 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <CoreMIDI/CoreMIDI.h>++#include <bit>++namespace libremidi+{+class observer_core+    : public observer_api+    , public error_handler+{+public:+  struct+      : observer_configuration+      , coremidi_observer_configuration+  {+  } configuration;++  explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (configuration.client_name.empty())+      configuration.client_name = "libremidi observer";++    if (!configuration.has_callbacks())+      return;++    auto result = MIDIClientCreate(+        toCFString(configuration.client_name).get(),+        +[](const MIDINotification* message, void* ctx) {+          ((observer_core*)ctx)->notify(message);+        },+        this, &client);++    if (result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating MIDI client object: " + std::to_string(result));+      return;+    }++    if (configuration.on_create_context)+      configuration.on_create_context(client);++    if (configuration.notify_in_constructor)+    {+      if (this->configuration.input_added)+        for (auto& p : get_input_ports())+          this->configuration.input_added(p);++      if (this->configuration.output_added)+        for (auto& p : get_output_ports())+          this->configuration.output_added(p);+    }+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }++  template <bool Input>+  auto to_port_info(MIDIObjectRef obj) const noexcept+      -> std::optional<std::conditional_t<Input, input_port, output_port>>+  {+    MIDIEntityRef e{};+    MIDIEndpointGetEntity(obj, &e);+    bool physical = bool(e);++    bool ok = this->configuration.track_any;+    if (physical && this->configuration.track_hardware)+      ok |= true;+    else if ((!physical) && this->configuration.track_virtual)+      ok |= true;++    if (!ok)+      return {};++    return std::conditional_t<Input, input_port, output_port>{+        {.client = (std::uintptr_t)this->client,+         .port = std::bit_cast<uint32_t>(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)}};+  }++  std::vector<libremidi::input_port> get_input_ports() const noexcept override+  {+    std::vector<libremidi::input_port> ret;++    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);+    for (ItemCount i = 0; i < MIDIGetNumberOfSources(); i++)+    {+      if (auto p = to_port_info<true>(MIDIGetSource(i)))+        ret.push_back(std::move(*p));+    }++    return ret;+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept override+  {+    std::vector<libremidi::output_port> ret;++    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);+    for (ItemCount i = 0; i < MIDIGetNumberOfDestinations(); i++)+    {+      if (auto p = to_port_info<false>(MIDIGetDestination(i)))+        ret.push_back(std::move(*p));+    }+    return ret;+  }++  void notify(const MIDINotification* message)+  {+    switch (message->messageID)+    {+      case kMIDIMsgObjectAdded: {+        auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);++        switch (obj->childType)+        {+          case kMIDIObjectType_Source:+            if (auto& cb = configuration.input_added)+              if (auto p = to_port_info<true>(obj->child))+                cb(std::move(*p));+            break;+          case kMIDIObjectType_Destination:+            if (auto& cb = configuration.output_added)+              if (auto p = to_port_info<false>(obj->child))+                cb(std::move(*p));+            break;+          default:+            break;+        }++        break;+      }++      case kMIDIMsgObjectRemoved: {+        auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);++        switch (obj->childType)+        {+          case kMIDIObjectType_Source:+            if (auto& cb = configuration.input_removed)+              if (auto p = to_port_info<true>(obj->child))+                cb(std::move(*p));+            break;+          case kMIDIObjectType_Destination:+            if (auto& cb = configuration.output_removed)+              if (auto p = to_port_info<false>(obj->child))+                cb(std::move(*p));+            break;+          default:+            break;+        }++        break;+      }++      default:+        break;+    }+  }++  ~observer_core() { MIDIClientDispose(this->client); }++private:+  MIDIClientRef client{};+};+}
+ vendor/libremidi/backends/coremidi_ump.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/coremidi_ump/midi_in.hpp>+#include <libremidi/backends/coremidi_ump/midi_out.hpp>+#include <libremidi/backends/coremidi_ump/observer.hpp>++namespace libremidi::coremidi_ump+{+struct backend+{+  using midi_in = midi_in_impl;+  using midi_out = midi_out_impl;+  using midi_observer = observer_impl;+  using midi_in_configuration = coremidi_ump::input_configuration;+  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 constexpr inline bool available() noexcept { return true; /* todo? */ }+};+}
+ vendor/libremidi/backends/coremidi_ump/config.hpp view
@@ -0,0 +1,23 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>++namespace libremidi::coremidi_ump+{+struct input_configuration+{+  std::string client_name = "libremidi client";+  std::optional<MIDIClientRef> context{};+};++struct output_configuration+{+  std::string client_name = "libremidi client";+  std::optional<MIDIClientRef> context{};+};++struct observer_configuration+{+  std::string client_name = "libremidi client";+  std::function<void(MIDIClientRef)> on_create_context{};+};+}
+ vendor/libremidi/backends/coremidi_ump/helpers.hpp view
@@ -0,0 +1,2 @@+#pragma once+#include <libremidi/backends/coremidi/helpers.hpp>
+ vendor/libremidi/backends/coremidi_ump/midi_in.hpp view
@@ -0,0 +1,152 @@+#pragma once+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/coremidi_ump/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi::coremidi_ump+{++class midi_in_impl final+    : public midi2::in_api+    , public coremidi_data+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : ump_input_configuration+      , coremidi_ump::input_configuration+  {+  } configuration;++  midi_in_impl(ump_input_configuration&& conf, coremidi_ump::input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (auto result = init_client(configuration); result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating MIDI client object: " + std::to_string(result));+      client_open_ = from_osstatus(result);+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_in_impl() override+  {+    // Close a connection if it exists.+    close_port();++    if (this->endpoint)+      MIDIEndpointDispose(this->endpoint);++    close_client(*this);+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }++  stdx::error open_port(const input_port& info, std::string_view portName) override+  {+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++    auto source = locate_object(*this, info, kMIDIObjectType_Source);+    if (source == 0)+      return std::errc::invalid_argument;++    // Create our local sink+    MIDIPortRef port;++    OSStatus result = MIDIInputPortCreateWithProtocol(+        this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &port,+        ^(const MIDIEventList* evtlist, void* __nullable srcConnRefCon) {+            this->midiInputCallback(evtlist, srcConnRefCon);+        });++    if (result != noErr)+    {+      close_client(*this);+      libremidi_handle_error(+          this->configuration, "error creating macOS MIDI input port: "+                                   + std::to_string(result));+      return from_osstatus(result);+    }++    // Make the connection.+    if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)+    {+      MIDIPortDispose(port);+      close_client(*this);+      libremidi_handle_error(+          this->configuration, "error connecting macOS MIDI input port.");+      return from_osstatus(result);+    }++    // Save our api-specific port information.+    this->port = port;+    return stdx::error{};+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    // Create a virtual MIDI input destination.+    MIDIEndpointRef endpoint;+    OSStatus result = MIDIDestinationCreateWithProtocol(+        this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &endpoint,+        ^(const MIDIEventList* evtlist, void* __nullable srcConnRefCon) {+            this->midiInputCallback(evtlist, srcConnRefCon);+        });++    if (result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating virtual macOS MIDI "+          "destination.");+      return from_osstatus(result);+    }++    // Save our api-specific connection information.+    this->endpoint = endpoint;+    return stdx::error{};+  }++  stdx::error close_port() override+  {+    return coremidi_data::close_port();+  }++  timestamp absolute_timestamp() const noexcept override+  {+    return coremidi_data::time_in_nanos(LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME());+  }++  void midiInputCallback(const MIDIEventList* list, void* /*srcRef*/)+  {+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    const MIDIEventPacket* packet = &list->packet[0];+    for (unsigned int i = 0; i < list->numPackets; ++i)+    {+      if (packet->wordCount > 0)+      {+        auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };+        m_processing.on_bytes_multi(+            {packet->words, packet->words + packet->wordCount},+            m_processing.timestamp<timestamp_info>(to_ns, 0));+      }++      packet = MIDIEventPacketNext(packet);+    }+  }++  midi2::input_state_machine m_processing{this->configuration};+};++}
+ vendor/libremidi/backends/coremidi_ump/midi_out.hpp view
@@ -0,0 +1,159 @@+#pragma once+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/coremidi_ump/helpers.hpp>+#include <libremidi/cmidi2.hpp>+#include <libremidi/detail/midi_out.hpp>+#include <libremidi/detail/ump_stream.hpp>++namespace libremidi::coremidi_ump+{+class midi_out_impl final+    : public midi2::out_api+    , public coremidi_data+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : libremidi::output_configuration+      , coremidi_ump::output_configuration+  {+  } configuration;++  midi_out_impl(+      libremidi::output_configuration&& conf, coremidi_ump::output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (auto result = init_client(configuration); result != noErr)+    {+      libremidi_handle_error(+          this->configuration,+          "error creating MIDI client object: " + std::to_string(result));+      client_open_ = from_osstatus(result);+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_out_impl()+  {+    midi_out_impl::close_port();++    if (this->endpoint)+      MIDIEndpointDispose(this->endpoint);++    close_client(*this);+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }++  stdx::error open_port(const output_port& info, std::string_view portName) override+  {+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++    // Find where we want to send+    auto destination = locate_object(*this, info, kMIDIObjectType_Destination);+    if (destination == 0)+      return std::errc::invalid_argument;++    // Create our local source+    MIDIPortRef port;+    OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);+    if (result != noErr)+    {+      close_client(*this);+      libremidi_handle_error(+          this->configuration, "error creating macOS MIDI output port.");+      return from_osstatus(result);+    }++    // Save our api-specific connection information.+    this->port = port;+    this->destinationId = destination;++    return stdx::error{};+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    // Create a virtual MIDI output source.+    OSStatus result = MIDISourceCreateWithProtocol(+        this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &this->endpoint);++    if (result != noErr)+    {+      this->endpoint = 0;+      libremidi_handle_error(+          this->configuration,+          "error creating macOS virtual MIDI source.");++      return from_osstatus(result);+    }++    return stdx::error{};+  }++  stdx::error close_port() override+  {+    return coremidi_data::close_port();+  }++  stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override+  {+    MIDIEventList* eventList = reinterpret_cast<MIDIEventList*>(m_eventListBuffer);+    MIDIEventPacket* packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);+    const MIDITimeStamp ts = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();++    auto write_fun = [ts, &packet, &eventList](const uint32_t* ump, int bytes) -> std::errc {+      packet = MIDIEventListAdd(eventList, event_list_max_size, packet, ts, bytes / 4, ump);+      if (packet)+        return std::errc{0};+      else+        return std::errc::not_enough_memory;+    };++    auto realloc_fun = [this, &packet, &eventList] {+      push_event_list(eventList);+      packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);+    };++    segment_ump_stream(ump_stream, count, write_fun, realloc_fun);+    return push_event_list(eventList);+  }++  stdx::error push_event_list(MIDIEventList* eventList)+  {+    if (this->endpoint)+    {+      auto result = MIDIReceivedEventList(this->endpoint, eventList);+      if (result != noErr)+      {+        libremidi_handle_warning(+            this->configuration,+            "error sending MIDI to virtual "+            "destinations.");+        return std::errc::io_error;+      }+    }++    if (this->destinationId != 0)+    {+      auto result = MIDISendEventList(this->port, this->destinationId, eventList);+      if (result != noErr)+      {+        libremidi_handle_warning(+            this->configuration,+            "error sending MIDI message to port.");+        return std::errc::io_error;+      }+    }+    return stdx::error{};+  }++  MIDIEndpointRef destinationId{};++  static constexpr int event_list_max_size = 65535;+  unsigned char m_eventListBuffer[sizeof(MIDIEventList) + event_list_max_size];+};+}
+ vendor/libremidi/backends/coremidi_ump/observer.hpp view
@@ -0,0 +1,20 @@+#pragma once+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/coremidi/observer.hpp>++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}}+  {++  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }+};++}
+ vendor/libremidi/backends/dummy.hpp view
@@ -0,0 +1,105 @@+#pragma once+#include <libremidi/configurations.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_out.hpp>+#include <libremidi/detail/observer.hpp>++namespace libremidi+{+class observer_dummy : public observer_api+{+public:+  explicit observer_dummy(const auto& /*configuration*/, const auto&) { }++  ~observer_dummy() { }+  libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }+  std::vector<libremidi::input_port> get_input_ports() const noexcept override { return {}; }+  std::vector<libremidi::output_port> get_output_ports() const noexcept override { return {}; }+};++class midi_in_dummy final+    : public midi1::in_api+    , public error_handler+{+public:+  explicit midi_in_dummy(const input_configuration& configuration, std::any)+  {+    libremidi_handle_warning(configuration, "This class provides no functionality.");+  }+  explicit midi_in_dummy(const ump_input_configuration& configuration, std::any)+  {+    libremidi_handle_warning(configuration, "This class provides no functionality.");+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }+  stdx::error open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) 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{};+  }+  timestamp absolute_timestamp() const noexcept override { return 0; }+};++class midi_out_dummy final+    : public midi1::out_api+    , public error_handler+{+public:+  explicit midi_out_dummy(const output_configuration& configuration, std::any)+  {+    libremidi_handle_warning(configuration, "This class provides no functionality.");+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }+  stdx::error open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) 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 send_message(const unsigned char* /*message*/, size_t /*size*/) override+  {+    return stdx::error{};+  }+};++struct dummy_backend+{+  using midi_in = midi_in_dummy;+  using midi_out = midi_out_dummy;+  using midi_observer = observer_dummy;+  using midi_in_configuration = dummy_configuration;+  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 constexpr inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/emscripten.hpp view
@@ -0,0 +1,30 @@+#pragma once++#if defined(__EMSCRIPTEN__)++  #include <libremidi/backends/emscripten/config.hpp>+  #include <libremidi/backends/emscripten/midi_in.hpp>+  #include <libremidi/backends/emscripten/midi_out.hpp>+  #include <libremidi/backends/emscripten/observer.hpp>++namespace libremidi+{++struct emscripten_backend+{+  using midi_in = midi_in_emscripten;+  using midi_out = midi_out_emscripten;+  using midi_observer = observer_emscripten;+  using midi_in_configuration = emscripten_input_configuration;+  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 constexpr inline bool available() noexcept { return true; }+};++}++#endif
+ vendor/libremidi/backends/emscripten/config.hpp view
@@ -0,0 +1,17 @@+#pragma once+#include <libremidi/config.hpp>++namespace libremidi+{++struct emscripten_input_configuration+{+};+struct emscripten_output_configuration+{+};+struct emscripten_observer_configuration+{+};++}
+ vendor/libremidi/backends/emscripten/helpers.hpp view
@@ -0,0 +1,17 @@+#pragma once+#include <libremidi/config.hpp>++#include <string>++namespace libremidi+{+namespace webmidi_helpers+{+struct device_information+{+  std::string id;+  std::string name;+  bool connected{};+};+}+}
+ vendor/libremidi/backends/emscripten/midi_access.hpp view
@@ -0,0 +1,303 @@+#pragma once+#include <libremidi/backends/emscripten/midi_in.hpp>+#include <libremidi/backends/emscripten/midi_out.hpp>+#include <libremidi/backends/emscripten/observer.hpp>+#include <libremidi/libremidi.hpp>++#include <emscripten.h>++#include <map>++extern "C" {+EMSCRIPTEN_KEEPALIVE+void libremidi_devices_poll();++EMSCRIPTEN_KEEPALIVE+void libremidi_devices_input(int port, double timestamp, int len, char* bytes);+}++namespace libremidi+{+namespace webmidi_helpers+{++class midi_access_emscripten+{+public:+  static midi_access_emscripten& instance() noexcept+  {+    static midi_access_emscripten inst;+    return inst;+  }++  bool available() const noexcept+  {+    return EM_ASM_INT(return typeof globalThis.__libreMidi_access !== undefined;);+  }++  int input_count() const noexcept+  {+    return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;);+  }++  int output_count() const noexcept+  {+    return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;);+  }++  void load_current_infos() noexcept+  {+#define get_js_string(variable_to_read, ...) \+  (char*)EM_ASM_INT(                            \+        {                                         \+              var jsstr = variable_to_read;           \+              var bytes = lengthBytesUTF8(jsstr) + 1; \+              var str = _malloc(bytes);               \+              stringToUTF8(jsstr, str, bytes);        \+              return str;                             \+        },                                        \+        __VA_ARGS__);++    EM_ASM_INT({+      let inputs = [];+      let outputs = [];+      for (let inpt of globalThis.__libreMidi_access.inputs.values())+      {+        inputs.push(inpt);+      }+      for (let outpt of globalThis.__libreMidi_access.outputs.values())+      {+        outputs.push(outpt);+      }+      globalThis.__libreMidi_currentInputs = inputs;+      globalThis.__libreMidi_currentOutputs = outputs;+    });+    const int inputs = input_count();+    const int outputs = output_count();++    m_current_inputs.resize(inputs);+    m_current_outputs.resize(outputs);++    for (int i = 0; i < inputs; i++)+    {+      int device_index = -1;+      char* midi_id = get_js_string(globalThis.__libreMidi_currentInputs[$0].id, i);+      auto it = m_input_indices.find(midi_id); // TODO transparent comparator, string_view...+      if (it == m_input_indices.end())+      {+        device_index = m_input_indices.size();+        m_input_indices[midi_id] = device_index;+      }+      else+      {+        device_index = it->second;+      }++      char* midi_name = get_js_string(globalThis.__libreMidi_currentInputs[$0].name, i);++      const bool connected+          = EM_ASM_INT(return globalThis.__libreMidi_currentInputs[$0].state === "connected", i);++      m_current_inputs[device_index].id = midi_id;+      m_current_inputs[device_index].name = midi_name;+      m_current_inputs[device_index].connected = connected;++      free(midi_id);+      free(midi_name);+    }++    for (int i = 0; i < outputs; i++)+    {+      int device_index = -1;+      char* midi_id = get_js_string(globalThis.__libreMidi_currentOutputs[$0].id, i);++      auto it = m_output_indices.find(midi_id); // TODO transparent comparator, string_view...+      if (it == m_output_indices.end())+      {+        device_index = m_output_indices.size();+        m_output_indices[midi_id] = device_index;+      }+      else+      {+        device_index = it->second;+      }++      char* midi_name = get_js_string(globalThis.__libreMidi_currentOutputs[$0].name, i);++      const bool connected+          = EM_ASM_INT(return globalThis.__libreMidi_currentOutputs[$0].state === "connected", i);++      m_current_outputs[device_index].id = midi_id;+      m_current_outputs[device_index].name = midi_name;+      m_current_outputs[device_index].connected = connected;++      free(midi_id);+      free(midi_name);+    }++#undef get_js_string+  }++  void register_observer(observer_emscripten& obs)+  {+    m_observers.push_back(&obs);+    if (m_observers.size() == 1)+    {+      start_observing();+    }+  }++  void unregister_observer(observer_emscripten& obs)+  {+    if (m_observers.size() == 1)+    {+      stop_observing();+    }+    auto it = std::find(m_observers.begin(), m_observers.end(), &obs);+    if (it != m_observers.end())+    {+      m_observers.erase(it);+    }+  }++  void devices_poll()+  {+    load_current_infos();++    for (auto& obs : m_observers)+    {+      obs->update(m_current_inputs, m_current_outputs);+    }+  }++  void open_input(int port_index, midi_in_emscripten& input)+  {+    auto& vec = m_opened_inputs[port_index];+    vec.push_back(&input);+    if (vec.size() != 1)+      return;++    start_stream(port_index);+  }++  void close_input(int port_index, midi_in_emscripten& input)+  {+    auto& vec = m_opened_inputs[port_index];+    auto it = std::find(vec.begin(), vec.end(), &input);+    if (it != vec.end())+    {+      vec.erase(it);+    }++    if (vec.empty())+    {+      stop_stream(port_index);+    }+  }++  void devices_input(int port, double timestamp, int len, char* data)+  {+    unsigned char* bytes = reinterpret_cast<unsigned char*>(data);+    for (auto input : m_opened_inputs[port])+    {+      input->on_input(timestamp, bytes, bytes + len);+    }+  }++  stdx::error send_message(int port_index, const char* bytes, int len)+  {+    const auto& id = m_current_outputs[port_index].id;+    EM_ASM(+        {+          let data = HEAPU8.subarray($0, $0 + $1);+          const id = UTF8ToString($2);+          let output = globalThis.__libreMidi_access.outputs.get(id);+          let bytes = HEAPU8.subarray($0, $0 + $1);+          output.send(Array.from(bytes));+        },+        bytes, len, id.c_str());+  }++  const std::vector<device_information>& inputs() const noexcept { return m_current_inputs; }+  const std::vector<device_information>& outputs() const noexcept { return m_current_outputs; }++private:+  midi_access_emscripten() noexcept+  {+    EM_ASM(+        if (navigator.requestMIDIAccess) {+              navigator.requestMIDIAccess().then(+                  (midiAccess) => { globalThis.__libreMidi_access = midiAccess; },+                  () => console.log('MIDI support rejected, MIDI will not be available;'));+        } else { console.log('WebMIDI is not supported in this browser.'); });+  }++  ~midi_access_emscripten() { stop_observing(); }++  void start_observing()+  {+    EM_ASM(+      let id = setInterval(Module._libremidi_devices_poll, 100);+      globalThis.__libreMidi_timer = id;);+  }++  void stop_observing()+  {+    EM_ASM(clearInterval(globalThis.__libreMidi_timer); globalThis.__libreMidi_timer = undefined;);+  }++  void start_stream(int port_index)+  {+    // Isn't life great...+    // https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays+    const auto& id = m_current_inputs[port_index].id;+    EM_ASM(+      const port_index = $0;+      const id = UTF8ToString($1);++      let input = globalThis.__libreMidi_access.inputs.get(id);++      function _arrayToHeap(typedArray){+                      const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;+                      const ptr = Module._malloc(numBytes);+                      const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes);+                      heapBytes.set(new Uint8Array(typedArray.buffer));+                      return heapBytes;+      }++      function _freeArray(heapBytes){+                      Module._free(heapBytes.byteOffset);+      }++      input.onmidimessage = (message) => {+                          let bytes = message.data;+                          var heapBytes = _arrayToHeap(bytes);+                          Module._libremidi_devices_input(port_index, message.timeStamp, bytes.length, heapBytes.byteOffset);+                          _freeArray(heapBytes);+      };+    , port_index+    , id.c_str()+    );+  }++  void stop_stream(int port_index)+  {+    const auto& id = m_current_inputs[port_index].id;+    EM_ASM(const id = UTF8ToString($1);++           let input = globalThis.__libreMidi_access.inputs.get(id);+           input.onmidimessage = undefined;, id.c_str());+  }++  std::vector<observer_emscripten*> m_observers;+  std::vector<device_information> m_current_inputs;+  std::vector<device_information> m_current_outputs;++  std::map<int, std::vector<midi_in_emscripten*>> m_opened_inputs;++  std::map<std::string, int> m_input_indices;+  std::map<std::string, int> m_output_indices;+};++}+}
+ vendor/libremidi/backends/emscripten/midi_in.hpp view
@@ -0,0 +1,38 @@+#pragma once+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/emscripten/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+class midi_in_emscripten final+    : public midi1::in_api+    , public error_handler+{+public:+  struct+      : input_configuration+      , emscripten_input_configuration+  {+  } configuration;++  midi_in_emscripten(input_configuration&& conf, emscripten_input_configuration&& apiconf);+  ~midi_in_emscripten() override;++  libremidi::API get_current_api() const noexcept override;++  stdx::error open_port(int portNumber, std::string_view);+  stdx::error open_port(const input_port& p, std::string_view) override;+  stdx::error close_port() override;++  timestamp absolute_timestamp() const noexcept override;++  void on_input(double ts, unsigned char* begin, unsigned char* end);++private:+  int portNumber_{};++  midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/emscripten/midi_out.hpp view
@@ -0,0 +1,33 @@+#pragma once+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/emscripten/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{+class midi_out_emscripten final+    : public midi1::out_api+    , public error_handler+{+public:+  struct+      : output_configuration+      , emscripten_output_configuration+  {+  } configuration;++  midi_out_emscripten(output_configuration&& conf, emscripten_output_configuration&& apiconf);+  ~midi_out_emscripten() override;++  libremidi::API get_current_api() const noexcept override;++  stdx::error open_port(int portNumber, std::string_view);+  stdx::error open_port(const output_port& p, std::string_view) override;+  stdx::error close_port() override;++  stdx::error send_message(const unsigned char* message, size_t size) override;++private:+  int portNumber_{-1};+};+}
+ vendor/libremidi/backends/emscripten/observer.hpp view
@@ -0,0 +1,37 @@+#pragma once+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/emscripten/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <vector>++namespace libremidi+{+class observer_emscripten final : public observer_api+{+  using device = webmidi_helpers::device_information;++public:+  struct+      : observer_configuration+      , emscripten_observer_configuration+  {+  } configuration;++  explicit observer_emscripten(+      observer_configuration&& conf, emscripten_observer_configuration&& apiconf);+  ~observer_emscripten();++  void+  update(const std::vector<device>& current_inputs, const std::vector<device>& current_outputs);++  libremidi::API get_current_api() const noexcept override;++  std::vector<libremidi::input_port> get_input_ports() const noexcept override;+  std::vector<libremidi::output_port> get_output_ports() const noexcept override;++private:+  std::vector<device> m_known_inputs;+  std::vector<device> m_known_outputs;+};+}
+ vendor/libremidi/backends/jack.hpp view
@@ -0,0 +1,37 @@+#pragma once+#include <libremidi/backends/jack/midi_in.hpp>+#include <libremidi/backends/jack/midi_out.hpp>+#include <libremidi/backends/jack/observer.hpp>++//*********************************************************************//+//  API: UNIX JACK+//+//  Written primarily by Alexander Svetalkin, with updates for delta+//  time by Gary Scavone, April 2011.+//+//  *********************************************************************//++namespace libremidi+{+struct jack_backend+{+  using midi_in = midi_in_jack;+  using midi_out = midi_out_jack;+  using midi_observer = observer_jack;+  using midi_in_configuration = jack_input_configuration;+  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 inline bool available() noexcept+  {+#if LIBREMIDI_WEAKJACK+    return WeakJack::instance().available() == 0;+#else+    return true;+#endif+  }+};+}
+ vendor/libremidi/backends/jack/config.hpp view
@@ -0,0 +1,49 @@+#pragma once+#include <libremidi/config.hpp>++#include <cstdint>+#include <functional>+#include <string>++extern "C" {+typedef struct _jack_client jack_client_t;+typedef uint32_t jack_nframes_t;+typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg);+}++namespace libremidi+{+using jack_callback_function = std::function<void(int nframes)>;+struct jack_callback+{+  int64_t token;+  std::function<void(int nframes)> callback;+};+struct jack_input_configuration+{+  std::string client_name = "libremidi client";++  jack_client_t* context{};+  std::function<void(jack_callback)> set_process_func{};+  std::function<void(int64_t)> clear_process_func{};+};++struct jack_output_configuration+{+  std::string client_name = "libremidi client";++  jack_client_t* context{};+  std::function<void(jack_callback)> set_process_func{};+  std::function<void(int64_t)> clear_process_func{};++  int32_t ringbuffer_size = 16384;+  bool direct = false;+};++struct jack_observer_configuration+{+  std::string client_name = "libremidi client";+  jack_client_t* context{};+};++}
+ vendor/libremidi/backends/jack/error_domain.hpp view
@@ -0,0 +1,78 @@+#pragma once+#include <libremidi/error.hpp>++#if __has_include(<weakjack/weak_libjack.h>)+  #include <weakjack/weak_libjack.h>+#elif __has_include(<weak_libjack.h>)+  #include <weak_libjack.h>+#elif __has_include(<jack/jack.h> )+  #include <jack/jack.h>+  #include <jack/midiport.h>+  #include <jack/ringbuffer.h>+#endif++namespace libremidi+{+struct jack_error_domain : public stdx::error_domain+{+public:+  constexpr jack_error_domain() noexcept+      : error_domain{{0xc714ea32b705080aULL, 0xe409a437daf5f921ULL}}+  {+  }++  stdx::string_ref name() const noexcept override { return "jack"; }++  bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override+  {+    if (lhs.domain() == rhs.domain())+      return error_cast<jack_status_t>(lhs) == error_cast<jack_status_t>(rhs);++    return false;+  }++  stdx::string_ref message(const stdx::error& e) const noexcept override+  {+    const auto status = error_cast<jack_status_t>(e);+    if(status == jack_status_t{})+      return "Success";++    if(status & JackInvalidOption)+      return "The operation contained an invalid or unsupported option";+    if(status & JackServerFailed)+      return "Unable to connect to the JACK server";+    if(status & JackServerError)+      return "Communication error with the JACK server";+    if(status & JackNoSuchClient)+      return "Requested client does not exist";+    if(status & JackLoadFailure)+      return "Unable to load internal client";+    if(status & JackInitFailure)+      return "Unable to initialize client";+    if(status & JackShmFailure)+      return "Unable to access shared memory";+    if(status & JackVersionError)+      return "Client's protocol version does not match";+    if(status & JackBackendError)+      return "Backend error";+    if(status & JackClientZombie)+      return "Client zombified failure";+    if(status & JackFailure)+      return "Failure";+    if(status & JackNameNotUnique)+      return "The desired client name was not unique";++    // Can't happen in libremidi as we set JackNoStartServer+    if(status & JackServerStarted)+      return "Server was started";++    return "Unknown JACK status code";+  }+};++inline stdx::error from_jack_status(jack_status_t ret) noexcept+{+  static constexpr jack_error_domain domain{};+  return {ret, domain};+}+}
+ vendor/libremidi/backends/jack/helpers.hpp view
@@ -0,0 +1,241 @@+#pragma once++#include <libremidi/backends/jack/error_domain.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/semaphore.hpp>++#include <atomic>+#include <semaphore>++namespace libremidi+{+struct jack_client+{+  jack_client_t* client{};++  static std::string get_port_display_name(jack_port_t* port)+  {+    auto p1 = std::make_unique<char[]>(jack_port_name_size());+    auto p2 = std::make_unique<char[]>(jack_port_name_size());+    char* aliases[3] = {p1.get(), p2.get(), nullptr};+    int n = jack_port_get_aliases(port, aliases);+    if (n > 1)+    {+      return aliases[1];+    }+    else if (n > 0)+    {+      std::string str = aliases[0];+      if (str.starts_with("alsa_pcm:"))+        str.erase(0, strlen("alsa_pcm:"));+      return str;+    }+    else+    {+      const auto short_name = jack_port_short_name(port);+      if (short_name && strlen(short_name) > 0)+        return short_name;+      return jack_port_name(port);+    }+  }++  template <bool Input>+  static auto to_port_info(jack_client_t* client, jack_port_t* port)+      -> std::conditional_t<Input, input_port, output_port>+  {+    return {{+        .client = reinterpret_cast<std::uintptr_t>(client),+        .port = 0,+        .manufacturer = "",+        .device_name = "",+        .port_name = jack_port_name(port),+        .display_name = get_port_display_name(port),+    }};+  }++  template <bool Input>+  static auto+  get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept+      -> std::vector<std::conditional_t<Input, input_port, output_port>>+  {+    std::vector<std::conditional_t<Input, input_port, output_port>> ret;++    if (!client)+      return {};++    const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags);++    if (ports == nullptr)+      return {};++    int i = 0;+    while (ports[i] != nullptr)+    {+      // 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<Input>(client, port));+      i++;+    }++    jack_free(ports);++    return ret;+  }+};++struct jack_helpers : jack_client+{+  struct port_handle+  {+    port_handle& operator=(jack_port_t* p)+    {+      impl.get()->store(p);+      return *this;+    }++    operator jack_port_t*() const noexcept+    {+      if (impl)+        return impl.get()->load();+      return {};+    }++    std::shared_ptr<std::atomic<jack_port_t*>> impl+        = std::make_shared<std::atomic<jack_port_t*>>(nullptr);+  } port;++  int64_t this_instance{};++  semaphore_pair_lock thread_lock;++  jack_helpers()+  {+    static std::atomic_int64_t instance{};+    this_instance = ++instance;+  }++  template <typename Self>+  jack_status_t connect(Self& self)+  {+    auto& configuration = self.configuration;++    if (this->client)+      return jack_status_t{};++    // Initialize JACK client+    if (configuration.context)+    {+      if (!configuration.set_process_func)+        return JackFailure;++      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);++             self.thread_lock.check_client_released();+             return 0;+           }});++      this->client = configuration.context;+      return jack_status_t{};+    }+    else+    {+      jack_status_t status{};+      this->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.");+        }++        jack_set_process_callback(+            this->client,+            +[](jack_nframes_t nf, void* ctx) -> int {+              auto& self = *static_cast<Self*>(ctx);+              jack_port_t* port = self.port;++              // Is port created?+              if (port == nullptr)+                return 0;++              self.process(nf);++              self.thread_lock.check_client_released();+              return 0;+            },+            &self);+        jack_activate(this->client);+      }+      return status;+    }+  }++  template <typename Self>+  void disconnect(Self& self)+  {+    if (self.configuration.context)+    {+      if (self.configuration.clear_process_func)+      {+        self.configuration.clear_process_func(this_instance);+      }+    }++    if (this->client && !self.configuration.context)+      jack_client_close(this->client);++    self.client_open_ = std::errc::not_connected;+  }++  stdx::error+  create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)+  {+    // full name: "client_name:port_name\0"+    if (portName.empty())+      portName = flags & JackPortIsInput ? "i" : "o";++    if (self.configuration.client_name.size() + portName.size() + 2u+        >= static_cast<size_t>(jack_port_name_size()))+    {+      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);+    }++    if (!this->port)+    {+      self.libremidi_handle_error(self.configuration, "error creating port");+      return std::errc::operation_not_supported;+    }+    return stdx::error{};+  }++  stdx::error do_close_port()+  {+    if (this->port == nullptr)+      return stdx::error{};++    // 1. Ensure that the next time the cycle runs it sees the port as nullptr+    jack_port_t* port_ptr = this->port.impl->load();+    this->port = nullptr;++    // 2. Signal through the semaphore and wait for the signal return+    this->thread_lock.prepare_release_client();++    // 3. Now we are sure that the client is not going to use the port anymore+    int err = jack_port_unregister(this->client, port_ptr);+    return from_errc(err);+  }+};+}
+ vendor/libremidi/backends/jack/midi_in.hpp view
@@ -0,0 +1,117 @@+#pragma once+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/jack/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <chrono>++namespace libremidi+{+class midi_in_jack final+    : public midi1::in_api+    , public jack_helpers+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : input_configuration+      , jack_input_configuration+  {+  } configuration;++  explicit midi_in_jack(input_configuration&& conf, jack_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_MIDI; }++  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{})+      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, JackPortIsInput);+  }++  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, &current_frames, &current_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++)+    {+      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(+          {event.buffer, event.buffer + event.size},+          m_processing.timestamp<timestamp_info>(to_ns, event.time));+    }++    return 0;+  }++  midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/jack/midi_out.hpp view
@@ -0,0 +1,244 @@+#pragma once+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/jack/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <semaphore>++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<std::size_t>(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<char*>(&sz), size_sz);+    jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);++    return stdx::error{};+  }++  void read(void* jack_events) const noexcept+  {+    int32_t sz;+    while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&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<char*>(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 error_handler+{+public:+  using midi_api::client_open_;+  struct+      : output_configuration+      , jack_output_configuration+  {+  } configuration;++  midi_out_jack(output_configuration&& conf, jack_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_MIDI; }++  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{})+      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, JackPortIsOutput);+  }++  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(output_configuration&& conf, jack_output_configuration&& apiconf)+      : midi_out_jack{std::move(conf), std::move(apiconf)}+      , 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_message(const unsigned char* message, std::size_t size) override+  {+    return queue.write(message, size);+  }++  int process(jack_nframes_t nframes)+  {+    void* buff = jack_port_get_buffer(this->port, nframes);+    jack_midi_clear_buffer(buff);++    this->queue.read(buff);++    return 0;+  }++private:+  jack_queue queue;+};++class midi_out_jack_direct final : public midi_out_jack+{+public:+  midi_out_jack_direct(output_configuration&& conf, jack_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_message(const unsigned char* message, size_t size) override+  {+    void* buff = jack_port_get_buffer(this->port, buffer_size);+    int ret = jack_midi_event_write(buff, 0, message, size);+    return from_errc(ret);+  }++  int convert_timestamp(int64_t user) const noexcept+  {+    switch (configuration.timestamps)+    {+      case timestamp_mode::AudioFrame:+        return static_cast<int>(user);++      default:+        // TODO+        return 0;+    }+  }++  stdx::error schedule_message(int64_t ts, const unsigned char* 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), message, size);+    return from_errc(ret);+  }++  int buffer_size{};+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_out_api> make<midi_out_jack>(+    libremidi::output_configuration&& conf, libremidi::jack_output_configuration&& api)+{+  if (api.direct)+    return std::make_unique<midi_out_jack_direct>(std::move(conf), std::move(api));+  else+    return std::make_unique<midi_out_jack_queued>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/jack/observer.hpp view
@@ -0,0 +1,224 @@+#pragma once+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/jack/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <unordered_set>++namespace libremidi+{+class observer_jack final+    : public observer_api+    , private jack_client+    , private error_handler+{+public:+  struct+      : observer_configuration+      , jack_observer_configuration+  {+  } configuration;++  explicit observer_jack(observer_configuration&& conf, jack_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, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);++      if (ports != nullptr)+      {+        int i = 0;+        while (ports[i] != nullptr)+        {+          auto port = jack_port_by_name(client, ports[i]);+          auto flags = jack_port_flags(port);++          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<true>(client, port));+          }+          i++;+        }+      }++      jack_free(ports);+    }++    {+      const char** ports+          = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);++      if (ports != nullptr)+      {+        int i = 0;+        while (ports[i] != nullptr)+        {+          auto port = jack_port_by_name(client, ports[i]);+          auto flags = jack_port_flags(port);++          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<false>(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 != JACK_DEFAULT_MIDI_TYPE)+        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<true>(client, port));+      }+      else if (flags & JackPortIsInput)+      {+        seen_output_ports.insert(name);+        if (this->configuration.output_added)+          this->configuration.output_added(to_port_info<false>(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<true>(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<false>(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<observer_jack*>(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_MIDI; }++  std::vector<libremidi::input_port> get_input_ports() const noexcept override+  {+    return get_ports<true>(this->client, nullptr, JackPortIsOutput);+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept override+  {+    return get_ports<false>(this->client, nullptr, JackPortIsInput);+  }++  ~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<std::string> seen_input_ports;+  std::unordered_set<std::string> seen_output_ports;+};+}
+ vendor/libremidi/backends/jack/shared_handler.hpp view
@@ -0,0 +1,153 @@+#pragma once+#if __has_include(<boost/lockfree/spsc_queue.hpp>)+  #include <libremidi/backends/jack/config.hpp>+  #include <libremidi/backends/jack/helpers.hpp>+  #include <libremidi/shared_context.hpp>++  #include <boost/lockfree/spsc_queue.hpp>++  #include <variant>++namespace libremidi::jack+{++// Create a JACK client which will be shared across objects+struct shared_handler : public libremidi::shared_context+{+  explicit shared_handler(std::string_view v)+  {+    midiin_callbacks.reserve(64);+    midiout_callbacks.reserve(64);++    jack_status_t status{};+    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);+  }++  virtual void start_processing() override { jack_activate(client); }+  virtual void stop_processing() override { jack_deactivate(client); }++  static shared_configurations make(std::string_view client_name)+  {+    auto clt = std::make_shared<shared_handler>(client_name);+    auto add_in_cb = [client = std::weak_ptr{clt}](libremidi::jack_callback cb) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::in_callback_added, std::move(cb)});+    };+    auto clear_in_cb = [client = std::weak_ptr{clt}](int64_t index) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::in_callback_removed, index});+    };+    auto add_out_cb = [client = std::weak_ptr{clt}](libremidi::jack_callback cb) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::out_callback_added, std::move(cb)});+    };+    auto clear_out_cb = [client = std::weak_ptr{clt}](int64_t index) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::out_callback_removed, index});+    };+    return {+        .context = clt,+        .observer = jack_observer_configuration{.context = clt->client},+        .in+        = jack_input_configuration{.context = clt->client, .set_process_func = add_in_cb, .clear_process_func = clear_in_cb},+        .out+        = jack_output_configuration{.context = clt->client, .set_process_func = add_out_cb, .clear_process_func = clear_out_cb},+    };+  }++  int jack_callback(jack_nframes_t cnt)+  {+    // 1. Process the events that will change the callback list+    event ev;+    while (events.pop(ev))+    {+      switch (ev.type)+      {+        case in_callback_added:+          midiin_callbacks.push_back(+              std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));+          break;+        case in_callback_removed: {+          auto idx = *std::get_if<int64_t>(&ev.payload);+          for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)+          {+            if (it->token == idx)+            {+              midiin_callbacks.erase(it);+              break;+            }+            else+            {+              ++it;+            }+          }+          break;+        }+        case out_callback_added:+          midiout_callbacks.push_back(+              std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));+          break;+        case out_callback_removed:+          auto idx = *std::get_if<int64_t>(&ev.payload);+          for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)+          {+            if (it->token == idx)+            {+              midiout_callbacks.erase(it);+              break;+            }+            else+            {+              ++it;+            }+          }+          break;+      }+    }++    for (auto& cb : midiin_callbacks)+      cb.callback(cnt);++    for (auto& cb : midiout_callbacks)+      cb.callback(cnt);++    return 0;+  }++  ~shared_handler()+  {+    jack_deactivate(client);+    jack_client_close(client);+  }++  jack_client_t* client{};++  enum event_type+  {+    in_callback_added,+    in_callback_removed,+    out_callback_added,+    out_callback_removed,+  };+  struct event+  {+    event_type type;+    std::variant<libremidi::jack_callback, int64_t> payload;+  };++  boost::lockfree::spsc_queue<event> events{16};++  std::vector<libremidi::jack_callback> midiin_callbacks;+  std::vector<libremidi::jack_callback> midiout_callbacks;+};+}+#endif
+ vendor/libremidi/backends/keyboard.hpp view
@@ -0,0 +1,21 @@+#pragma once+#include <libremidi/backends/dummy.hpp>+#include <libremidi/backends/keyboard/midi_in.hpp>++namespace libremidi+{+struct kbd_backend+{+  using midi_in = midi_in_kbd;+  using midi_in_configuration = kbd_input_configuration;+  using midi_out = midi_out_dummy;+  using midi_observer = observer_dummy;+  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 inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/keyboard/config.hpp view
@@ -0,0 +1,92 @@+#pragma once+#include <libremidi/config.hpp>++#include <functional>+#include <map>++namespace libremidi+{+/**+ * Used to set up keyboard input.+ * Your app should pass a function that will give you a callback+ * that you should call whenever you are getting a key input (press or release).+ */+struct kbd_input_configuration+{+  using scancode_callback = std::function<void(int)>;+  // First argument is on key press, second on key release+  std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks+      = [](scancode_callback, 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<int, int> 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+};++}
+ vendor/libremidi/backends/keyboard/midi_in.hpp view
@@ -0,0 +1,112 @@+#pragma once+#include <libremidi/backends/keyboard/config.hpp>+#include <libremidi/detail/midi_in.hpp>++#include <chrono>+#include <unordered_map>++namespace libremidi+{+class midi_in_kbd final+    : public midi1::in_api+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : input_configuration+      , kbd_input_configuration+  {+  } configuration;++  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_virtual_port(std::string_view) override { 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();+  }++  void on_keypress(int scancode)+  {+    using kevent = kbd_input_configuration::kbd_event;++    auto it = configuration.scancode_map.find(scancode);+    if (it == configuration.scancode_map.end())+      return;++    if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))+    {+      int note = it->second - kevent::NOTE_0 + 12 * m_current_octave;+      this->configuration.on_message(+          libremidi::channel_events::note_on(0, note, m_current_velocity));+      m_current_notes_scancodes[scancode] = note;+    }+    else if (it->second >= kevent::VEL_0 && it->second < (kevent::VEL_0 + 128))+    {+      m_current_velocity = it->second - kevent::VEL_0;+    }+    else if (it->second >= kevent::OCT_0 && it->second < (kevent::OCT_0 + 128))+    {+      m_current_octave = it->second - kevent::OCT_0;+    }+    else+    {+      switch (it->second)+      {+        case kevent::VELOCITY_MINUS:+          m_current_velocity = std::clamp(m_current_velocity - 10, 0, 127);+          break;+        case kevent::VELOCITY_PLUS:+          m_current_velocity = std::clamp(m_current_velocity + 10, 0, 127);+          break;+        case kevent::OCTAVE_MINUS:+          m_current_octave = std::clamp(m_current_octave - 1, 0, 127);+          break;+        case kevent::OCTAVE_PLUS:+          m_current_octave = std::clamp(m_current_octave + 1, 0, 127);+          break;+      }+    }+  }++  void on_keyrelease(int scancode)+  {+    using kevent = kbd_input_configuration::kbd_event;++    auto it = configuration.scancode_map.find(scancode);+    if (it == configuration.scancode_map.end())+      return;++    if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))+    {+      if (auto note_it = m_current_notes_scancodes.find(scancode);+          note_it != m_current_notes_scancodes.end())+      {+        this->configuration.on_message(libremidi::channel_events::note_off(0, note_it->second, 0));+        m_current_notes_scancodes.erase(note_it);+      }+    }+  }++  int m_current_octave{3};+  int m_current_velocity{80};+  std::unordered_map<int, int> m_current_notes_scancodes;+};+}
+ vendor/libremidi/backends/linux/alsa.hpp view
@@ -0,0 +1,459 @@+#pragma once++#include <libremidi/backends/linux/dylib_loader.hpp>++#include <alsa/asoundlib.h>++#if defined(SND_LIB_VERSION)+  #if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)+    #define LIBREMIDI_ALSA_HAS_RAMWIDI 1+    #define LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD 1+  #endif++  #if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10)+    #define LIBREMIDI_ALSA_HAS_UMP 1+  #endif+#endif++namespace libremidi+{++struct libasound+{+  // Useful one-liner:+  // nm -A * | grep ' snd_' | grep -v '@' | cut -f 2 -d 'U' | sort | uniq  | sed 's/ snd_//' | sed 's/_/, /' | awk ' { print "LIBREMIDI_SYMBOL_DEF(snd_"$1 " " $2 ");" }'++  explicit libasound()+      : library{"libasound.so.2"}+  {+    if (!library)+    {+      available = false;+      return;+    }++    strerror = library.symbol<decltype(&::snd_strerror)>("snd_strerror");+    if (!strerror)+      available = false;+  }++  static const libasound& instance()+  {+    static const libasound self;+    return self;+  }++  dylib_loader library;+  decltype(&::snd_strerror) strerror{};+  bool available{true};++  struct card_t+  {+    explicit card_t(const dylib_loader& library)+    {+      if (!library)+      {+        available = false;+        return;+      }++      LIBREMIDI_SYMBOL_INIT(snd_card, get_name)+      LIBREMIDI_SYMBOL_INIT(snd_card, next)+    }+    bool available{true};++    LIBREMIDI_SYMBOL_DEF(snd_card, get_name)+    LIBREMIDI_SYMBOL_DEF(snd_card, next)+  } card{library};++  struct ctl_t+  {+    explicit ctl_t(const dylib_loader& library)+        : rawmidi{library}+#if LIBREMIDI_ALSA_HAS_UMP+        , ump{library}+#endif+    {+      if (!library)+      {+        available = false;+        return;+      }++      LIBREMIDI_SYMBOL_INIT(snd_ctl, close)+      LIBREMIDI_SYMBOL_INIT(snd_ctl, open)+    }+    bool available{true};++    LIBREMIDI_SYMBOL_DEF(snd_ctl, close)+    LIBREMIDI_SYMBOL_DEF(snd_ctl, open)++    struct rawmidi_t+    {+      explicit rawmidi_t(const dylib_loader& library)+      {+        if (!library)+        {+          available = false;+          return;+        }++        LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info)+        LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device)+      }+      bool available{true};+      LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info)+      LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device)+    } rawmidi;++#if LIBREMIDI_ALSA_HAS_UMP+    struct ump_t+    {+      explicit ump_t(const dylib_loader& library)+      {+        if (!library)+        {+          available = false;+          return;+        }++        LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, block_info)+        LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, endpoint_info)+        LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, next_device)+      }+      bool available{true};+      LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, block_info)+      LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, endpoint_info)+      LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, next_device)+    } ump;+#endif+  } ctl{library};++  struct midi_t+  {+    explicit midi_t(const dylib_loader& library)+    {+      if (!library)+      {+        available = false;+        return;+      }++      LIBREMIDI_SYMBOL_INIT(snd_midi, event_decode)+      LIBREMIDI_SYMBOL_INIT(snd_midi, event_encode)+      LIBREMIDI_SYMBOL_INIT(snd_midi, event_free)+      LIBREMIDI_SYMBOL_INIT(snd_midi, event_init)+      LIBREMIDI_SYMBOL_INIT(snd_midi, event_new)+      LIBREMIDI_SYMBOL_INIT(snd_midi, event_no_status)+      LIBREMIDI_SYMBOL_INIT(snd_midi, event_resize_buffer)+    }++    bool available{true};+    LIBREMIDI_SYMBOL_DEF(snd_midi, event_decode)+    LIBREMIDI_SYMBOL_DEF(snd_midi, event_encode)+    LIBREMIDI_SYMBOL_DEF(snd_midi, event_free)+    LIBREMIDI_SYMBOL_DEF(snd_midi, event_init)+    LIBREMIDI_SYMBOL_DEF(snd_midi, event_new)+    LIBREMIDI_SYMBOL_DEF(snd_midi, event_no_status)+    LIBREMIDI_SYMBOL_DEF(snd_midi, event_resize_buffer)+  } midi{library};++#if LIBREMIDI_ALSA_HAS_RAMWIDI+  struct rawmidi_t+  {+    explicit rawmidi_t(const dylib_loader& library)+    {+      if (!library)+      {+        available = false;+        return;+      }+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, close)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_name)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevice_name)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevices_count)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_device)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_stream)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_subdevice)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_sizeof)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, open)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_current)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_get_buffer_size)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_clock_type)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_no_active_sensing)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_read_mode)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_sizeof)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_count)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_revents)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, read)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_get_avail)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_sizeof)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, tread)+      LIBREMIDI_SYMBOL_INIT(snd_rawmidi, write)+    }++    bool available{true};+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, close)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_name)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevice_name)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevices_count)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_device)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_stream)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_subdevice)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_sizeof)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, open)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_current)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_get_buffer_size)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_clock_type)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_no_active_sensing)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_read_mode)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_sizeof)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_count)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_revents)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, read)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_get_avail)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_sizeof)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, tread)+    LIBREMIDI_SYMBOL_DEF(snd_rawmidi, write)+  } rawmidi{library};+#endif++  struct seq_t+  {+    explicit seq_t(const dylib_loader& library)+#if LIBREMIDI_ALSA_HAS_UMP+        : ump{library}+#endif+    {+      if (!library)+      {+        available = false;+        return;+      }++      LIBREMIDI_SYMBOL_INIT(snd_seq, alloc_queue)+      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_set_client)+      LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)+      LIBREMIDI_SYMBOL_INIT(snd_seq, close)+      LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from)+      LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue)+      LIBREMIDI_SYMBOL_INIT(snd_seq, create_port)+      LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port)+      LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output)+      LIBREMIDI_SYMBOL_INIT(snd_seq, event_input)+      LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending)+      LIBREMIDI_SYMBOL_INIT(snd_seq, event_output)+      LIBREMIDI_SYMBOL_INIT(snd_seq, free_event)+      LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue)+      LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info)+      LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info)+      LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info)+      LIBREMIDI_SYMBOL_INIT(snd_seq, open)+      LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors)+      LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type)+      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_set_dest)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)+      LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update)+      LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client)+      LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port)+      LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq)+      LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo)+      LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof)+      LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name)+      LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info)+      LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo)+      LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port)+      LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port)+    }++    bool available{true};+    LIBREMIDI_SYMBOL_DEF(snd_seq, alloc_queue)+    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_set_client)+    LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)+    LIBREMIDI_SYMBOL_DEF(snd_seq, close)+    LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from)+    LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue)+    LIBREMIDI_SYMBOL_DEF(snd_seq, create_port)+    LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port)+    LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output)+    LIBREMIDI_SYMBOL_DEF(snd_seq, event_input)+    LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending)+    LIBREMIDI_SYMBOL_DEF(snd_seq, event_output)+    LIBREMIDI_SYMBOL_DEF(snd_seq, free_event)+    LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue)+    LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info)+    LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info)+    LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info)+    LIBREMIDI_SYMBOL_DEF(snd_seq, open)+    LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors)+    LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type)+    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_set_dest)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real)+    LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update)+    LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client)+    LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port)+    LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq)+    LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo)+    LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof)++    LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_name)+    LIBREMIDI_SYMBOL_DEF(snd_seq, set_port_info)+    LIBREMIDI_SYMBOL_DEF(snd_seq, set_queue_tempo)+    LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port)+    LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port)++#if LIBREMIDI_ALSA_HAS_UMP+    struct ump_t+    {+      explicit ump_t(const dylib_loader& library)+      {+        if (!library)+        {+          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)+      }++      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};++#if LIBREMIDI_ALSA_HAS_UMP+  struct ump_t+  {+    explicit ump_t(const dylib_loader& library)+    {+      if (!library)+      {+        available = false;+        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)+      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, 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)+    }++    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)+    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, 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)+  } ump{library};+#endif+};++#undef snd_dylib_alloca+#define snd_dylib_alloca(ptr, access, type)                                \+  {                                                                        \+    *ptr = (snd_##access##_##type##_t*)alloca(snd.access.type##_sizeof()); \+    memset(*ptr, 0, snd.access.type##_sizeof());                           \+  }+#define snd_dylib_alloca2(ptr, access1, access2, type)                                         \+  {                                                                                            \+    *ptr = (snd_##access1##_access2##_##type##_t*)alloca(snd.access1.access2.type##_sizeof()); \+    memset(*ptr, 0, snd.access1.access2.type##_sizeof());                                      \+  }++#undef snd_rawmidi_info_alloca+#define snd_rawmidi_info_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, info)+#undef snd_rawmidi_params_alloca+#define snd_rawmidi_params_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, params)+#undef snd_rawmidi_status_alloca+#define snd_rawmidi_status_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, status)++#undef snd_seq_client_info_alloca+#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_queue_tempo_alloca+#define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo)++#if LIBREMIDI_ALSA_HAS_UMP+  #undef snd_ump_block_info_alloca+  #define snd_ump_block_info_alloca(ptr) snd_dylib_alloca(ptr, ump, block_info)+  #undef snd_ump_endpoint_info_alloca+  #define snd_ump_endpoint_info_alloca(ptr) snd_dylib_alloca(ptr, ump, endpoint_info)+#endif+}
+ vendor/libremidi/backends/linux/dylib_loader.hpp view
@@ -0,0 +1,85 @@+#pragma once+#if __has_include(<dlfcn.h>)+  #include <dlfcn.h>++  #include <cassert>++namespace libremidi+{+class dylib_loader+{+public:+  explicit dylib_loader(const char* const so)+  {+    impl = dlopen(so, RTLD_LAZY | RTLD_LOCAL | RTLD_NODELETE);+  }++  dylib_loader(const dylib_loader&) noexcept = delete;+  dylib_loader& operator=(const dylib_loader&) noexcept = delete;+  dylib_loader(dylib_loader&& other) noexcept+  {+    impl = other.impl;+    other.impl = nullptr;+  }++  dylib_loader& operator=(dylib_loader&& other) noexcept+  {+    impl = other.impl;+    other.impl = nullptr;+    return *this;+  }++  ~dylib_loader()+  {+    if (impl)+    {+      dlclose(impl);+    }+  }++  template <typename T>+  T symbol(const char* const sym) const noexcept+  {+    assert(impl);+    return reinterpret_cast<T>(dlsym(impl, sym));+  }++  operator bool() const noexcept { return bool(impl); }++private:+  void* impl{};+};++}++  #define LIBREMIDI_SYMBOL_NAME_S(prefix, name) #prefix "_" #name+  #define LIBREMIDI_SYMBOL_NAME(prefix, name) prefix##_##name+  #define LIBREMIDI_SYMBOL_DEF(prefix, name) \+    decltype(&::LIBREMIDI_SYMBOL_NAME(prefix, name)) name{};+  #define LIBREMIDI_SYMBOL_INIT(prefix, name)                                  \+    {                                                                          \+      name = library.symbol<decltype(&::LIBREMIDI_SYMBOL_NAME(prefix, name))>( \+          LIBREMIDI_SYMBOL_NAME_S(prefix, name));                              \+      if (!name)                                                               \+      {                                                                        \+        available = false;                                                     \+        return;                                                                \+      }                                                                        \+    }++// Because some libs have names that are C++ keywords, e.g. udev_new:+  #define LIBREMIDI_SYMBOL_NAME2_S(prefix, name, varname) #prefix "_" #name+  #define LIBREMIDI_SYMBOL_NAME2(prefix, name, varname) prefix##_##name+  #define LIBREMIDI_SYMBOL_DEF2(prefix, name, varname) \+    decltype(&::LIBREMIDI_SYMBOL_NAME2(prefix, name, varname)) varname{};+  #define LIBREMIDI_SYMBOL_INIT2(prefix, name, varname)                                     \+    {                                                                                       \+      varname = library.symbol<decltype(&::LIBREMIDI_SYMBOL_NAME2(prefix, name, varname))>( \+          LIBREMIDI_SYMBOL_NAME2_S(prefix, name, varname));                                 \+      if (!varname)                                                                         \+      {                                                                                     \+        available = false;                                                                  \+        return;                                                                             \+      }                                                                                     \+    }+#endif
+ vendor/libremidi/backends/linux/helpers.hpp view
@@ -0,0 +1,77 @@+#pragma once+#include <sys/eventfd.h>+#include <sys/timerfd.h>++#include <poll.h>+#include <unistd.h>++#include <iostream>++namespace libremidi+{+struct eventfd_notifier+{+  eventfd_notifier(bool semaphore = true)+  {+    if (semaphore)+      this->fd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);+    else+      this->fd = eventfd(0, EFD_NONBLOCK);+  }+  ~eventfd_notifier() { close(this->fd); }++  eventfd_notifier(const eventfd_notifier&) = delete;+  eventfd_notifier(eventfd_notifier&&) = delete;+  eventfd_notifier& operator=(const eventfd_notifier&) = delete;+  eventfd_notifier& operator=(eventfd_notifier&&) = delete;++  void notify() noexcept { eventfd_write(fd, 1); }+  static bool ready(pollfd res) noexcept { return res.revents & POLLIN; }+  eventfd_t consume() noexcept+  {+    eventfd_t val;+    eventfd_read(fd, &val);+    return val;+  }++  operator int() const noexcept { return fd; }+  operator pollfd() const noexcept { return {.fd = fd, .events = POLLIN, .revents = 0}; }+  int fd{-1};+};++struct timerfd_timer+{+  timerfd_timer() { this->fd = timerfd_create(CLOCK_MONOTONIC, 0); }+  ~timerfd_timer() { close(this->fd); }++  timerfd_timer(const timerfd_timer&) = delete;+  timerfd_timer(timerfd_timer&&) = delete;+  timerfd_timer& operator=(const timerfd_timer&) = delete;+  timerfd_timer& operator=(timerfd_timer&&) = delete;++  void oneshot(int64_t nsec)+  {+    itimerspec t{};+    t.it_value.tv_nsec = nsec;+    timerfd_settime(this->fd, 0, &t, nullptr);+  }++  void restart(int64_t nsec)+  {+    itimerspec t{};+    t.it_value.tv_nsec = nsec;+    t.it_interval.tv_nsec = nsec;+    timerfd_settime(this->fd, 0, &t, nullptr);+  }++  void cancel()+  {+    itimerspec t{};+    timerfd_settime(this->fd, 0, &t, nullptr);+  }++  operator int() const noexcept { return fd; }+  operator pollfd() const noexcept { return {.fd = fd, .events = POLLIN, .revents = 0}; }+  int fd{-1};+};+}
+ vendor/libremidi/backends/linux/pipewire.hpp view
@@ -0,0 +1,169 @@+#pragma once++#include <libremidi/backends/linux/dylib_loader.hpp>++#include <pipewire/pipewire.h>++namespace libremidi+{++#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"+class libpipewire+{+public:+  decltype(&::pw_init) init{};+  decltype(&::pw_deinit) deinit{};++  decltype(&::pw_context_new) context_new{};+  decltype(&::pw_context_connect) context_connect{};+  decltype(&::pw_context_destroy) context_destroy{};++  decltype(&::pw_core_disconnect) core_disconnect{};++  decltype(&::pw_proxy_add_listener) proxy_add_listener{};+  decltype(&::pw_proxy_destroy) proxy_destroy{};++  decltype(&::pw_main_loop_new) main_loop_new{};+  decltype(&::pw_main_loop_destroy) main_loop_destroy{};+  decltype(&::pw_main_loop_quit) main_loop_quit{};+  decltype(&::pw_main_loop_run) main_loop_run{};+  decltype(&::pw_main_loop_get_loop) main_loop_get_loop{};+  /*+  decltype(&::pw_thread_loop_new) thread_loop_new{};+  decltype(&::pw_thread_loop_destroy) thread_loop_destroy{};+  decltype(&::pw_thread_loop_wait) thread_loop_wait{};+  decltype(&::pw_thread_loop_start) thread_loop_start{};+  decltype(&::pw_thread_loop_stop) thread_loop_stop;+  decltype(&::pw_thread_loop_lock) thread_loop_lock{};+  decltype(&::pw_thread_loop_unlock) thread_loop_unlock;+  decltype(&::pw_thread_loop_get_loop) thread_loop_get_loop{};+*/+  decltype(&::pw_properties_new) properties_new{};+  decltype(&::pw_properties_free) properties_free{};+  decltype(&::pw_properties_get) properties_get{};++  decltype(&::pw_filter_new_simple) filter_new_simple{};+  decltype(&::pw_filter_get_node_id) filter_get_node_id{};+  decltype(&::pw_filter_get_properties) filter_get_properties{};+  decltype(&::pw_filter_add_port) filter_add_port{};+  decltype(&::pw_filter_remove_port) filter_remove_port{};+  decltype(&::pw_filter_update_properties) filter_update_properties{};+  decltype(&::pw_filter_update_params) filter_update_params{};+  decltype(&::pw_filter_get_time) filter_get_time{};+  decltype(&::pw_filter_destroy) filter_destroy{};+  decltype(&::pw_filter_connect) filter_connect{};+  decltype(&::pw_filter_get_dsp_buffer) filter_get_dsp_buffer{};+  decltype(&::pw_filter_queue_buffer) filter_queue_buffer{};+  decltype(&::pw_filter_dequeue_buffer) filter_dequeue_buffer{};+  decltype(&::pw_filter_flush) filter_flush{};++  static const libpipewire& instance()+  {+    static const libpipewire self;+    return self;+  }++  bool available{true};++private:+  dylib_loader library;++  libpipewire()+      : library("libpipewire-0.3.so.0")+  {+    if (!library)+    {+      available = false;+      return;+    }++    // in terms of regex:+    // decltype\(&::([a-z_]+)\) [a-z_]+{};+    // \1 = library.symbol<decltype(&::\1)>("\1");+    init = library.symbol<decltype(&::pw_init)>("pw_init");+    deinit = library.symbol<decltype(&::pw_deinit)>("pw_deinit");++    context_new = library.symbol<decltype(&::pw_context_new)>("pw_context_new");+    context_connect = library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");+    context_destroy = library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");++    core_disconnect = library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");++    proxy_add_listener+        = library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");+    proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");++    main_loop_new = library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");+    main_loop_destroy = library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");+    main_loop_quit = library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");+    main_loop_run = library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");+    main_loop_get_loop+        = library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");++    properties_new = library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");+    properties_free = library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");+    properties_get = library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");++    filter_new_simple = library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");+    filter_get_node_id+        = library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");+    filter_get_properties+        = library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");+    filter_add_port = library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");+    filter_remove_port+        = library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");+    filter_update_properties+        = library.symbol<decltype(&::pw_filter_update_properties)>("pw_filter_update_properties");+    filter_update_params+        = library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");+    filter_get_time = library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");+    filter_destroy = library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");+    filter_connect = library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");+    filter_get_dsp_buffer+        = library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");+    filter_dequeue_buffer+        = library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");+    filter_queue_buffer+        = library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");+    filter_flush = library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");++    assert(init);+    assert(deinit);++    assert(context_new);+    assert(context_connect);+    assert(context_destroy);++    assert(core_disconnect);++    assert(proxy_destroy);++    assert(main_loop_new);+    assert(main_loop_destroy);+    assert(main_loop_quit);+    assert(main_loop_run);+    assert(main_loop_get_loop);++    assert(properties_new);+    assert(properties_free);+    assert(properties_get);++    assert(filter_new_simple);+    assert(filter_get_node_id);+    assert(filter_get_properties);+    assert(filter_add_port);+    assert(filter_remove_port);+    assert(filter_update_properties);+    assert(filter_update_params);+    assert(filter_get_time);+    assert(filter_destroy);+    assert(filter_connect);+    assert(filter_get_dsp_buffer);+    assert(filter_dequeue_buffer);+    assert(filter_queue_buffer);+    assert(filter_flush);+  }+};+#pragma GCC diagnostic pop+}
+ vendor/libremidi/backends/linux/udev.hpp view
@@ -0,0 +1,92 @@+#pragma once+#include <libremidi/backends/linux/dylib_loader.hpp>+#include <libremidi/backends/linux/helpers.hpp>++#include <libudev.h>++#include <cassert>++namespace libremidi+{++struct libudev+{+  // Useful one-liner:+  // nm -A * | grep ' udev_'  | grep -v '@' | cut -f 2 -d 'U' | sort | uniq   | sed 's/ udev_//'| awk ' { print "LIBREMIDI_SYMBOL_DEF(udev, "$1");" }'++  explicit libudev()+      : library{"libudev.so.1"}+  {+    if (!library)+    {+      available = false;+      return;+    }++    LIBREMIDI_SYMBOL_INIT(udev, device_get_action)+    LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem)+    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_INIT2(udev, new, create)+    LIBREMIDI_SYMBOL_INIT(udev, unref)+  }++  static const libudev& instance()+  {+    static const libudev self;+    return self;+  }++  dylib_loader library;+  bool available{true};++  LIBREMIDI_SYMBOL_DEF(udev, device_get_action)+  LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem)+  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_DEF2(udev, new, create)+  LIBREMIDI_SYMBOL_DEF(udev, unref)+};++struct udev_helper+{+  udev_helper()+  {+    instance = udev.create();+    assert(instance);++    monitor = udev.monitor_new_from_netlink(instance, "udev");+    assert(monitor);+    udev.monitor_enable_receiving(monitor);+  }++  ~udev_helper()+  {+    udev.monitor_unref(monitor);+    udev.unref(instance);+  }++  udev_helper(const udev_helper&) = delete;+  udev_helper(udev_helper&&) = delete;+  udev_helper& operator=(const udev_helper&) = delete;+  udev_helper& operator=(udev_helper&&) = delete;++  operator pollfd() const noexcept+  {+    return {.fd = udev.monitor_get_fd(monitor), .events = POLLIN, .revents = 0};+  }++  const libudev& udev = libudev::instance();+  struct udev* instance{};+  udev_monitor* monitor{};+};++}
+ vendor/libremidi/backends/net/config.hpp view
@@ -0,0 +1,86 @@+#pragma once+#include <libremidi/config.hpp>++#include <string>+namespace boost::asio+{+struct io_context;+}++namespace libremidi::net+{+enum class protocol+{+  OSC_MIDI,+};++struct dgram_input_configuration+{+  std::string client_name = "libremidi client";++  enum protocol protocol = protocol::OSC_MIDI;+  std::string accept = "0.0.0.0";+  int port{};++  boost::asio::io_context* io_context{};+};++struct dgram_output_configuration+{+  std::string client_name = "libremidi client";++  enum protocol protocol = protocol::OSC_MIDI;+  std::string host = "127.0.0.1";+  int port{};+  bool broadcast{};++  boost::asio::io_context* io_context{};+};++struct dgram_observer_configuration+{+  std::string client_name = "libremidi client";++  boost::asio::io_context* io_context{};+};++}++namespace libremidi::net_ump+{+enum class protocol+{+  OSC_MIDI2,+};++struct dgram_input_configuration+{+  std::string client_name = "libremidi client";++  enum protocol protocol = protocol::OSC_MIDI2;+  std::string accept = "0.0.0.0";+  int port{};++  boost::asio::io_context* io_context{};+};++struct dgram_output_configuration+{+  std::string client_name = "libremidi client";++  enum protocol protocol = protocol::OSC_MIDI2;+  std::string host = "127.0.0.1";+  int port{};+  bool broadcast{};++  boost::asio::io_context* io_context{};+};++struct dgram_observer_configuration+{+  std::string client_name = "libremidi client";++  boost::asio::io_context* io_context{};+};++}
+ vendor/libremidi/backends/net/helpers.hpp view
@@ -0,0 +1,91 @@+#pragma once+#include <libremidi/config.hpp>++#include <boost/asio/ip/udp.hpp>++#include <memory>+namespace stdx+{ /*++class boost_system_error_domain : public error_domain+{+public:+  constexpr boost_system_error_domain() noexcept+      : error_domain{+            {1 - 0x3c223c0aa3cf45e5ULL, 1 - 0x80dac24345cfb9fcULL},+            default_error_resource_management_t<detail::exception_ptr_wrapper>{}}+  {+  }++  string_ref name() const noexcept override { return "boost::system_error domain"; }++  bool equivalent(const error& lhs, const error& rhs) const noexcept override { return false; }++  string_ref message(const error& e) const noexcept override {+    auto err = e.m_value;+    +  }++  [[noreturn]] void throw_exception(const error& e) const override+  {+    assert(e.domain() == *this);+    std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());+  }+};+*/+}++namespace libremidi+{+template <typename T>+struct optionally_owned+{+public:+  explicit optionally_owned(T* maybe_existing)+      : storage{.ref = maybe_existing}+  {+    if (storage.ref)+    {+      ownership = unowned;+      return;+    }+    else+    {+      std::destroy_at(&storage.ref);+      std::construct_at<T>(reinterpret_cast<T*>(storage.object));+      ownership = owned;+    }+  }++  ~optionally_owned()+  {+    if (is_owned())+      std::destroy_at(reinterpret_cast<T*>(storage.object));+  }++  T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); }+  const T& get() const noexcept+  {+    return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref);+  }++  optionally_owned(const optionally_owned&) = delete;+  optionally_owned(optionally_owned&&) noexcept = delete;+  optionally_owned& operator=(const optionally_owned&) = delete;+  optionally_owned& operator=(optionally_owned&&) noexcept = delete;++  bool is_owned() const noexcept { return ownership == owned; }++private:+  union+  {+    alignas(T) unsigned char object[sizeof(T)];+    T* ref;+  } storage;+  enum+  {+    owned,+    unowned+  } ownership{};+};+}
+ vendor/libremidi/backends/net/midi_in.hpp view
@@ -0,0 +1,440 @@+#pragma once+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+template <typename Impl, typename F>+struct osc_parser+{+  [[no_unique_address]]+  Impl impl;+  F on_message;+  std::string_view port_name;++  stdx::error parse_bundle(const char* data, std::size_t sz)+  {+    std::string_view txt(data, sz);+    if (!txt.starts_with("#bundle"))+      return std::errc::bad_message;+    return std::errc::protocol_not_supported;+  }++  stdx::error parse_message(const char* data, std::size_t sz)+  {+    const auto begin = data;+    const auto end = data + sz;+    std::size_t pattern_len = strnlen(data, sz);+    if (pattern_len == sz)+      return std::errc::bad_message;++    if (auto pat = std::string_view(data, pattern_len); pat != port_name)+      return std::errc::bad_address;++    data += pattern_len;+    for (; data < end; ++data)+      if (*data == 0)+        continue;+      else+        break;++    // Now we shold reach the typetag beginning, ","+    if (data == end)+      return std::errc::bad_message;+    if ((data - begin) % 4 != 0)+      return std::errc::bad_message;+    if (*data++ != ',')+      return std::errc::bad_message;++    // Count the ,mmmmm arguments... yummy+    int num_msgs = 0;+    for (; data < end; ++data)+      if (*data == Impl::typetag)+        num_msgs++;+      else if (*data == 0)+        break;+      else+        return std::errc::bad_message;++    if (num_msgs == 0)+      return std::errc::no_message;++    // By now there's at least 5 bytes left as the ,mmm has to be round-up to 4 and padded with zeros:+    if (end - data < 4)+      return std::errc::bad_message;++    switch ((1 + num_msgs) % 4)+    {+      case 0:+        data += 4;+        break;+      case 1:+        data += 3;+        break;+      case 2:+        data += 2;+        break;+      case 3:+        data += 1;+        break;+    }++    // Data starts+    return impl.process_midi_bytes(on_message, num_msgs, data, end - data);+  }++  stdx::error parse_int_message(const char* /* data */, std::size_t /* sz */)+  {+    return std::errc::protocol_not_supported;+  }++  stdx::error parse_packet(const char* data, std::size_t sz)+  {+    if (sz == 0)+      return std::errc::no_message;++    switch (data[0])+    {+      case '#':+        return parse_bundle(data, sz);+        break;+      case '/':+        return parse_message(data, sz);+        break;+      case '\0':+        return parse_int_message(data, sz);+        break;+      default:+        return std::errc::bad_message;+    }+  }+};++}++namespace libremidi::net+{++struct osc_parser_midi1+{+  static constexpr char typetag = 'm';++  stdx::error+  process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t sz)+  {+    if (sz != num_msgs * 4)+      return std::errc::bad_message;++    auto end = data + sz;+    for (; data < end; data += 4)+      on_message(data + 1);++    return {};+  }+};++class midi_in final+    : public midi1::in_api+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : input_configuration+      , dgram_input_configuration+  {+  } configuration;++  explicit midi_in(input_configuration&& conf, dgram_input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+      , m_ctx{configuration.io_context}+      , m_socket{m_ctx.get()}+  {+    m_socket.open(boost::asio::ip::udp::v4());+    m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));++    m_endpoint+        = {boost::asio::ip::make_address(this->configuration.accept),+           static_cast<unsigned short>(this->configuration.port)};++    boost::system::error_code ec;+    m_socket.bind(m_endpoint, ec);+    if (ec != boost::system::error_code{})+    {+      client_open_ = std::errc::address_in_use;+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_in() override { close_port(); }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }++  stdx::error open_port(const input_port&, std::string_view port) override+  {+    return open_virtual_port(port);+  }++  stdx::error open_virtual_port(std::string_view port) override+  {+    m_portname = std::string(port);++    receive();++    if (m_ctx.is_owned())+    {+      m_thread = std::jthread{[this, &ctx = m_ctx.get()] {+        auto wg = boost::asio::make_work_guard(m_ctx);+        ctx.run();+      }};+    }++    return stdx::error{};+  }++  void receive()+  {+    m_socket.async_receive_from(+        boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,+        [this](auto ec, std::size_t sz) {+      if (ec == boost::asio::error::operation_aborted)+        return;++      if (!ec && sz > 0)+        this->on_bytes(reinterpret_cast<const char*>(m_data), sz);++      this->receive();+    });+  }++  stdx::error close_port() override+  {+    // FIXME async close+    if (m_socket.is_open())+      m_socket.close();+    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();+  }++  void on_bytes(const char* data, std::size_t size)+  {+    const auto on_msg = [this](const char* bytes) { this->on_message(bytes); };+    osc_parser<osc_parser_midi1, decltype(on_msg)> parser{{}, on_msg, m_portname};+    parser.parse_packet(data, size);+  }++  void on_message(const char* data)+  {+    // OSC enforces always 3 bytes+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = false,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    static constexpr auto to_ns = []() {+      return std::chrono::duration_cast<std::chrono::nanoseconds>(+                 std::chrono::steady_clock::now().time_since_epoch())+          .count();+    };++    auto ptr = reinterpret_cast<const uint8_t*>(data);+    m_processing.on_bytes({ptr, ptr + 3}, m_processing.timestamp<timestamp_info>(to_ns, 0));+  }++  midi1::input_state_machine m_processing{this->configuration};++  libremidi::optionally_owned<boost::asio::io_context> m_ctx;+  boost::asio::ip::udp::endpoint m_endpoint;+  boost::asio::ip::udp::socket m_socket;++  std::jthread m_thread;++  std::string m_portname;+  alignas(16) unsigned char m_data[65535];+};+}++namespace libremidi::net_ump+{+struct osc_parser_midi2+{+  static constexpr char typetag = 'M';++  stdx::error+  process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t byte_sz)+  {+    if (byte_sz % 4 != 0)+      return std::errc::bad_message;++    auto sz = byte_sz / 4;++    auto begin = reinterpret_cast<const uint32_t*>(data);+    auto end = begin + sz;+    std::size_t accounted = 0;+    for (auto it = begin; it < end && accounted < num_msgs;)+    {+      const auto N = cmidi2_ump_get_message_size_bytes(it) / 4;+      switch (N)+      {+        case 1:+        case 2:+        case 4:+          if (it + N <= end)+          {+            on_message(it, N);+            accounted++;+          }+          else+            return std::errc::bad_message;+          break;+        default:+          return std::errc::bad_message;+      }+      it += N;+    }++    return {};+  }+};++class midi_in final+    : public midi2::in_api+    , public error_handler+{+public:+  using midi_api::client_open_;+  struct+      : ump_input_configuration+      , dgram_input_configuration+  {+  } configuration;++  explicit midi_in(ump_input_configuration&& conf, dgram_input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+      , m_ctx{configuration.io_context}+      , m_socket{m_ctx.get()}+  {+    m_socket.open(boost::asio::ip::udp::v4());+    m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));++    m_endpoint+        = {boost::asio::ip::make_address(this->configuration.accept),+           static_cast<unsigned short>(this->configuration.port)};++    boost::system::error_code ec;+    m_socket.bind(m_endpoint, ec);+    if (ec != boost::system::error_code{})+    {+      client_open_ = std::errc::address_in_use;+      return;+    }++    client_open_ = stdx::error{};+  }++  ~midi_in() override { close_port(); }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }++  stdx::error open_port(const input_port&, std::string_view port) override+  {+    return open_virtual_port(port);+  }++  stdx::error open_virtual_port(std::string_view port) override+  {+    m_portname = std::string(port);++    receive();++    if (m_ctx.is_owned())+    {+      m_thread = std::jthread{[this, &ctx = m_ctx.get()] {+        auto wg = boost::asio::make_work_guard(m_ctx);+        ctx.run();+      }};+    }+    return stdx::error{};+  }++  void receive()+  {+    m_socket.async_receive_from(+        boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,+        [this](auto ec, std::size_t sz) {+      if (ec == boost::asio::error::operation_aborted)+        return;++      if (!ec && sz > 0)+        this->on_bytes(reinterpret_cast<const char*>(m_data), sz);++      this->receive();+    });+  }++  stdx::error close_port() override+  {+    // FIXME async close+    if (m_ctx.is_owned())+      m_ctx.get().stop();++    if (m_socket.is_open())+      m_socket.close();+    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();+  }++  void on_bytes(const char* data, std::size_t size)+  {+    const auto on_msg+        = [this](const uint32_t* bytes, std::size_t N) { this->on_message(bytes, N); };+    osc_parser<osc_parser_midi2, decltype(on_msg)> parser{{}, on_msg, m_portname};+    parser.parse_packet(data, size);+  }++  void on_message(const uint32_t* ump, std::size_t sz)+  {+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = false,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    static constexpr auto to_ns = []() {+      return std::chrono::duration_cast<std::chrono::nanoseconds>(+                 std::chrono::steady_clock::now().time_since_epoch())+          .count();+    };++    m_processing.on_bytes({ump, sz}, m_processing.timestamp<timestamp_info>(to_ns, 0));+  }++  midi2::input_state_machine m_processing{this->configuration};++  libremidi::optionally_owned<boost::asio::io_context> m_ctx;+  boost::asio::ip::udp::endpoint m_endpoint;+  boost::asio::ip::udp::socket m_socket;++  std::jthread m_thread;++  std::string m_portname;+  alignas(16) unsigned char m_data[65535];+};+}
+ vendor/libremidi/backends/net/midi_out.hpp view
@@ -0,0 +1,288 @@+#pragma once+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/helpers.hpp>+#include <libremidi/cmidi2.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <boost/asio/io_context.hpp>+#include <boost/asio/ip/udp.hpp>+#include <boost/endian.hpp>++namespace libremidi::net+{++struct osc_midi1_packet+{+  stdx::error init_packet(std::string_view osc_pattern)+  {+    int i = 0;+    for (; i < std::ssize(osc_pattern); i++)+      if (osc_pattern.data()[i] != 0)+        bytes[i] = osc_pattern.data()[i];+      else+        break;+    bytes[i] = 0;+    while (i % 4 != 3)+    {+      ++i;+      bytes[i] = 0;+    }++    bytes[++i] = ',';+    bytes[++i] = 'm';+    bytes[++i] = 0;+    bytes[++i] = 0;++    // MIDI message (m) spec: port n°, status byte, data 1, data 2+    bytes[++i] = 0;+    message_size = ++i + 3;++    return stdx::error{}; // FIXME+  }++  stdx::error deinit()+  {+    message_size = 0;+    return stdx::error{};+  }++  stdx::error set_packet_content(const unsigned char* message, size_t size)+  {+    if (message_size == 0)+      return std::errc::not_connected;++    if (size != 3)+      return std::errc::message_size;++    std::memcpy(this->bytes + message_size - 3, message, 3);++    return stdx::error{};+  }++  std::span<const char> get_data() { return std::span(this->bytes, this->message_size); }+  char bytes[512 + 8 + 8];+  int message_size{};+};++class midi_out final+    : public midi1::out_api+    , public error_handler+{+public:+  struct+      : output_configuration+      , dgram_output_configuration+  {+  } configuration;++  midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+      , ctx{configuration.io_context}+      , m_socket{ctx.get()}+  {+    m_socket.open(boost::asio::ip::udp::v4());+    m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));+    m_socket.set_option(boost::asio::socket_base::broadcast(true));++    m_endpoint+        = {boost::asio::ip::make_address(this->configuration.host),+           static_cast<unsigned short>(this->configuration.port)};++    this->client_open_ = stdx::error{};+  }++  ~midi_out() override { close_port(); }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }++  stdx::error open_port(const output_port& /* port */, std::string_view portName) override+  {+    return open_virtual_port(portName);+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    // Random arbitrary limit to avoid abuse+    if (portName.size() >= 512)+      return std::errc::invalid_argument;++    pkt.init_packet(portName);++    return stdx::error{};+  }++  stdx::error close_port() override+  {+    pkt.deinit();+    // FIXME async close+    if (m_socket.is_open())+      m_socket.close();+    return {};+  }++  stdx::error send_message(const unsigned char* message, size_t size) override+  {+    if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})+      return err;++    auto dat = pkt.get_data();+    boost::system::error_code ec;+    m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);+    return stdx::error{}; // FIXME+  }++  stdx::error schedule_message(int64_t, const unsigned char*, size_t) override+  {+    int ret = 0;+    return from_errc(ret);+  }++  libremidi::optionally_owned<boost::asio::io_context> ctx;+  boost::asio::ip::udp::endpoint m_endpoint;+  boost::asio::ip::udp::socket m_socket;++  osc_midi1_packet pkt;+};++}++namespace libremidi::net_ump+{+struct osc_midi2_packet+{+  stdx::error init_packet(std::string_view osc_pattern)+  {+    int i = 0;+    for (; i < std::ssize(osc_pattern); i++)+      if (osc_pattern.data()[i] != 0)+        bytes[i] = osc_pattern.data()[i];+      else+        break;+    bytes[i] = 0;+    while (i % 4 != 3)+    {+      ++i;+      bytes[i] = 0;+    }++    bytes[++i] = ',';+    bytes[++i] = 'M';+    bytes[++i] = 0;+    bytes[++i] = 0;++    // MIDI 2 message (M) spec: an UMP+    header_size = ++i;++    return stdx::error{}; // FIXME+  }++  stdx::error deinit()+  {+    header_size = 0;+    return stdx::error{};+  }++  stdx::error set_packet_content(const uint32_t* message, size_t size)+  {+    if (header_size == 0)+      return std::errc::not_connected;++    if (size > 4)+      return std::errc::message_size;++    message_size = 4 * size;++    std::memcpy(this->bytes + header_size, message, message_size);++    return stdx::error{};+  }++  std::span<const char> get_data()+  {+    return std::span(this->bytes, this->header_size + this->message_size);+  }+  char bytes[512 + 8 + 64];+  int header_size{};+  int message_size{};+};++class midi_out final+    : public midi2::out_api+    , public error_handler+{+public:+  struct+      : output_configuration+      , dgram_output_configuration+  {+  } configuration;++  midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+      , ctx{configuration.io_context}+      , m_socket{ctx.get()}+  {+    m_socket.open(boost::asio::ip::udp::v4());+    m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));+    m_socket.set_option(boost::asio::socket_base::broadcast(true));++    m_endpoint+        = {boost::asio::ip::make_address(this->configuration.host),+           static_cast<unsigned short>(this->configuration.port)};++    this->client_open_ = stdx::error{};+  }++  ~midi_out() override { close_port(); }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }++  stdx::error open_port(const output_port& /* port */, std::string_view portName) override+  {+    return open_virtual_port(portName);+  }++  stdx::error open_virtual_port(std::string_view portName) override+  {+    // Random arbitrary limit to avoid abuse+    if (portName.size() >= 512)+      return std::errc::invalid_argument;++    pkt.init_packet(portName);++    return stdx::error{};+  }++  stdx::error close_port() override+  {+    pkt.deinit();+    if (m_socket.is_open())+      m_socket.close();+    return {};+  }++  stdx::error send_ump(const uint32_t* message, size_t size) override+  {+    if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})+      return err;++    auto dat = pkt.get_data();+    boost::system::error_code ec;+    m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);+    return stdx::error{}; // FIXME+  }++  stdx::error schedule_ump(int64_t, const uint32_t*, size_t) override+  {+    int ret = 0;+    return from_errc(ret);+  }++  libremidi::optionally_owned<boost::asio::io_context> ctx;+  boost::asio::ip::udp::endpoint m_endpoint;+  boost::asio::ip::udp::socket m_socket;++  osc_midi2_packet pkt;+};++}
+ vendor/libremidi/backends/net/observer.hpp view
@@ -0,0 +1,14 @@+#pragma once+#include <libremidi/backends/dummy.hpp>++namespace libremidi::net+{+using net_observer_configuration = libremidi::dummy_configuration;+using observer = libremidi::observer_dummy;+}++namespace libremidi::net_ump+{+using net_observer_configuration = libremidi::dummy_configuration;+using observer = libremidi::observer_dummy;+}
+ vendor/libremidi/backends/network.hpp view
@@ -0,0 +1,24 @@+#pragma once+#include <libremidi/backends/dummy.hpp>+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/midi_in.hpp>+#include <libremidi/backends/net/midi_out.hpp>+#include <libremidi/backends/net/observer.hpp>++namespace libremidi::net+{+struct backend+{+  using midi_in = libremidi::net::midi_in;+  using midi_out = libremidi::net::midi_out;+  using midi_observer = libremidi::net::observer;+  using midi_in_configuration = libremidi::net::dgram_input_configuration;+  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 inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/network_ump.hpp view
@@ -0,0 +1,23 @@+#pragma once+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/midi_in.hpp>+#include <libremidi/backends/net/midi_out.hpp>+#include <libremidi/backends/net/observer.hpp>++namespace libremidi::net_ump+{+struct backend+{+  using midi_in = libremidi::net_ump::midi_in;+  using midi_out = libremidi::net_ump::midi_out;+  using midi_observer = libremidi::net_ump::observer;+  using midi_in_configuration = libremidi::net_ump::dgram_input_configuration;+  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 inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/pipewire.hpp view
@@ -0,0 +1,30 @@+#pragma once+#include <libremidi/backends/dummy.hpp>+#include <libremidi/backends/linux/pipewire.hpp>+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/backends/pipewire/midi_in.hpp>+#include <libremidi/backends/pipewire/midi_out.hpp>+#include <libremidi/backends/pipewire/observer.hpp>++namespace libremidi::pipewire+{+struct backend+{+  using midi_in = midi_in_pipewire;+  using midi_out = midi_out_pipewire;+  using midi_observer = observer_pipewire;+  using midi_in_configuration = pipewire_input_configuration;+  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 inline bool available() noexcept+  {+    static const libpipewire& pw = libpipewire::instance();+    return pw.available;+  }+};+}
+ vendor/libremidi/backends/pipewire/config.hpp view
@@ -0,0 +1,53 @@+#pragma once+#include <libremidi/config.hpp>++#include <cinttypes>+#include <cstdint>+#include <functional>+#include <string>++extern "C" {+struct pw_main_loop;+struct pw_filter;+struct spa_io_position;+}++namespace libremidi+{+using pipewire_callback_function = std::function<void(spa_io_position*)>;+struct pipewire_callback+{+  int64_t token;+  pipewire_callback_function callback;+};++struct pipewire_input_configuration+{+  std::string client_name = "libremidi client";++  pw_main_loop* context{};+  pw_filter* filter{};+  std::function<void(pipewire_callback)> set_process_func{};+  std::function<void(int64_t)> clear_process_func{};+};++struct pipewire_output_configuration+{+  std::string client_name = "libremidi client";++  pw_main_loop* context{};+  pw_filter* filter{};+  std::function<void(pipewire_callback)> set_process_func{};+  std::function<void(int64_t)> clear_process_func{};++  int64_t output_buffer_size{65536};+};++struct pipewire_observer_configuration+{+  std::string client_name = "libremidi client";++  pw_main_loop* context{};+};++}
+ vendor/libremidi/backends/pipewire/context.hpp view
@@ -0,0 +1,612 @@+#pragma once+#include <libremidi/backends/linux/pipewire.hpp>+#include <libremidi/detail/memory.hpp>+#include <libremidi/error.hpp>++#include <pipewire/filter.h>+#include <pipewire/pipewire.h>+#include <spa/control/control.h>+#include <spa/param/props.h>+#include <spa/utils/defs.h>+#include <spa/utils/result.h>++#include <algorithm>+#include <array>+#include <atomic>+#include <functional>+#include <iostream>+#include <memory>+#include <string>+#include <unordered_map>+#include <vector>++#include <spa/param/audio/format-utils.h>++#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"+namespace libremidi+{+template <typename K, typename V>+using hash_map = std::unordered_map<K, V>;++struct pipewire_instance+{+  const libpipewire& pw = libpipewire::instance();+  pipewire_instance()+  {+    /// Initialize the PipeWire main loop, context, etc.+    int argc = 0;+    char* argv[] = {NULL};+    char** aa = argv;+    pw.init(&argc, &aa);+  }++  ~pipewire_instance() { pw.deinit(); }+};++struct pipewire_context+{+  struct listened_port+  {+    uint32_t id{};+    pw_port* port{};+    std::unique_ptr<spa_hook> listener;+  };++  struct port_info+  {+    uint32_t id{};++    std::string format;+    std::string port_name;+    std::string port_alias;+    std::string object_path;+    std::string node_id;+    std::string port_id;++    bool physical{};+    bool terminal{};+    bool monitor{};+    pw_direction direction{};+  };++  struct node+  {+    std::vector<port_info> inputs;+    std::vector<port_info> outputs;+  };++  struct graph+  {+    mutable std::mutex mtx;+    libremidi::hash_map<uint32_t, node> physical_audio;+    libremidi::hash_map<uint32_t, node> physical_midi;+    libremidi::hash_map<uint32_t, node> software_audio;+    libremidi::hash_map<uint32_t, node> software_midi;+    libremidi::hash_map<uint32_t, port_info> port_cache;++    void for_each_port(auto func)+    {+      for (auto& map : {physical_audio, physical_midi, software_audio, software_midi})+      {+        for (auto& [id, node] : map)+        {+          for (auto& port : node.inputs)+            func(port);+          for (auto& port : node.outputs)+            func(port);+        }+      }+    }++    void remove_port(uint32_t id)+    {+      port_cache.erase(id);+      for (auto map : {&physical_audio, &physical_midi, &software_audio, &software_midi})+      {+        for (auto& [_, node] : *map)+        {+          std::erase_if(node.inputs, [id](const port_info& p) { return p.id == id; });+          std::erase_if(node.outputs, [id](const port_info& p) { return p.id == id; });+        }+      }+    }+  } current_graph;++  explicit pipewire_context(pw_main_loop* inst)+      : main_loop{inst}+      , owns_main_loop{false}+  {+    assert(main_loop);++    initialize();+  }++  explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)+      : global_instance{inst}+      , owns_main_loop{true}+  {+    this->main_loop = pw.main_loop_new(nullptr);+    if (!this->main_loop)+    {+      // libremidi::logger().libremidi_handle_error("main_loop_new failed!");+      return;+    }+    initialize();+  }++  void initialize()+  {+    this->lp = pw.main_loop_get_loop(this->main_loop);+    if (!lp)+    {+      // libremidi::logger().libremidi_handle_error("main_loop_get_loop failed!");+      return;+    }++    this->context = pw.context_new(lp, nullptr, 0);+    if (!this->context)+    {+      // libremidi::logger().libremidi_handle_error("context_new failed!");+      return;+    }++    this->core = pw.context_connect(this->context, nullptr, 0);+    if (!this->core)+    {+      // libremidi::logger().libremidi_handle_error("context_connect failed!");+      return;+    }++    this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);+    if (!this->registry)+    {+      // libremidi::logger().libremidi_handle_error("core_get_registry failed!");+      return;+    }++    initialize_observation();++    synchronize();++    // Add a manual 1ms event loop iteration at the end of+    // ctor to ensure synchronous clients will still see the ports+    pw_loop_iterate(this->lp, 1);+  }++  void initialize_observation()+  {+    // Register a listener which will listen on when ports are added / removed+    spa_zero(registry_listener);++    static constexpr const struct pw_registry_events registry_events = {+        .version = PW_VERSION_REGISTRY_EVENTS,+        .global =+            [](void* object, uint32_t id, uint32_t /*permissions*/, const char* type,+               uint32_t /*version*/, const struct spa_dict* /*props*/) {+      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, &registry_events, this);+  }++  void register_port(uint32_t id, const char* type)+  {+    auto port = (pw_port*)pw_registry_bind(registry, id, type, PW_VERSION_PORT, 0);+    port_listener.push_back({id, port, std::make_unique<spa_hook>()});+    auto& l = port_listener.back();++    static constexpr const struct pw_port_events port_events = {+        .version = PW_VERSION_PORT_EVENTS,+        .info+        = [](void* object,+             const pw_port_info* info) { ((pipewire_context*)object)->update_port_info(info); },+    };+    pw_port_add_listener(l.port, l.listener.get(), &port_events, this);+  }++  void unregister_port(uint32_t id)+  {+    // When a port is removed:+    // Notify+    std::unique_lock _{current_graph.mtx, std::defer_lock};+    if (on_port_removed)+    {+      _.lock();+      if (auto it = current_graph.port_cache.find(id); it != current_graph.port_cache.end())+      {+        auto copy = it->second;+        _.unlock();+        on_port_removed(copy);+      }+      else+      {+        _.unlock();+      }+    }++    // Remove from the graph+    {+      _.lock();+      current_graph.remove_port(id);+      _.unlock();+    }++    // Remove from the listeners+    auto it+        = std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) {+            return l.id == id;+          });+    if (it != port_listener.end())+    {+      pw.proxy_destroy((pw_proxy*)it->port);+      port_listener.erase(it);+    }+  }++  void synchronize()+  {+    pending = 0;+    done = 0;++    if (!core)+      return;++    spa_hook core_listener;++    static constexpr struct pw_core_events core_events = {+        .version = PW_VERSION_CORE_EVENTS,+        .done =+        [](void* object, uint32_t id, int seq) {+      auto& self = *(pipewire_context*)object;+      if(id == PW_ID_CORE && seq == self.pending)+      {+        self.done = 1;+        libpipewire::instance().main_loop_quit(self.main_loop);+      }+        },+    };++    spa_zero(core_listener);+    pw_core_add_listener(core, &core_listener, &core_events, this);++    pending = pw_core_sync(core, PW_ID_CORE, 0);+    while (!done)+    {+      pw.main_loop_run(this->main_loop);+    }+    spa_hook_remove(&core_listener);+  }++  [[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_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,+        std::to_string(in_port).c_str(), nullptr);++    auto proxy = (pw_proxy*)pw_core_create_object(+        this->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);++    if (!proxy)+    {+      pw.properties_free(props);+      return nullptr;+    }++    synchronize();+    pw.properties_free(props);+    return proxy;+  }++  void unlink_ports(pw_proxy* link) { pw.proxy_destroy(link); }++  void update_port_info(const pw_port_info* info)+  {+    const spa_dict_item* item{};++    port_info p;+    p.id = info->id;++    spa_dict_for_each(item, info->props)+    {+      std::string_view k{item->key}, v{item->value};+      if (k == "format.dsp")+        p.format = v;+      else if (k == "port.name")+        p.port_name = v;+      else if (k == "port.alias")+        p.port_alias = v;+      else if (k == "object.path")+        p.object_path = v;+      else if (k == "port.id")+        p.port_id = v;+      else if (k == "node.id")+        p.node_id = v;+      else if (k == "port.physical" && v == "true")+        p.physical = true;+      else if (k == "port.terminal" && v == "true")+        p.terminal = true;+      else if (k == "port.monitor" && v == "true")+        p.monitor = true;+      else if (k == "port.direction")+      {+        if (v == "out")+        {+          p.direction = pw_direction::SPA_DIRECTION_OUTPUT;+        }+        else+        {+          p.direction = pw_direction::SPA_DIRECTION_INPUT;+        }+      }+    }++    if (p.node_id.empty())+      return;++    const auto nid = std::stoul(p.node_id);+    auto get_node = [&]() -> node* {+      if (p.physical)+      {+        if (p.format.find("audio") != p.format.npos)+          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("audio") != p.format.npos)+          return &this->current_graph.software_audio[nid];+        else if (p.format.find("midi") != p.format.npos)+          return &this->current_graph.software_midi[nid];+      }+      return nullptr;+    };++    {+      std::lock_guard _{current_graph.mtx};+      current_graph.port_cache[p.id] = p;+      if (auto node = get_node())+      {+        if (p.direction == pw_direction::SPA_DIRECTION_OUTPUT)+          node->outputs.push_back(p);+        else+          node->inputs.push_back(p);+      }+    }++    if (on_port_added)+      on_port_added(p);+  }++  int get_fd() const noexcept+  {+    if (!this->lp)+      return -1;++    auto spa_callbacks = this->lp->control->iface.cb;+    auto spa_loop_methods = (const spa_loop_control_methods*)spa_callbacks.funcs;+    if (spa_loop_methods->get_fd)+      return spa_loop_methods->get_fd(spa_callbacks.data);+    else+      return -1;+  }++  ~pipewire_context()+  {+    if (this->registry)+      pw.proxy_destroy((pw_proxy*)this->registry);+    for (auto& [id, p, l] : this->port_listener)+      if (l)+        pw.proxy_destroy((pw_proxy*)p);+    if (this->core)+      pw.core_disconnect(this->core);+    if (this->context)+      pw.context_destroy(this->context);+    if (owns_main_loop && this->main_loop)+      pw.main_loop_destroy(this->main_loop);+  }++  friend struct pipewire_filter;+  const libpipewire& pw = libpipewire::instance();+  std::shared_ptr<pipewire_instance> global_instance;++  pw_main_loop* main_loop{};+  pw_loop* lp{};++  pw_context* context{};+  pw_core* core{};++  pw_registry* registry{};+  spa_hook registry_listener{};++  std::function<void(const port_info&)> on_port_added;+  std::function<void(const port_info&)> on_port_removed;++  std::vector<listened_port> port_listener{};++  std::atomic<int> pending{};+  std::atomic<int> done{};+  bool owns_main_loop{true};+  int sync{};+};++struct pipewire_filter+{+  const libpipewire& pw = libpipewire::instance();+  std::shared_ptr<pipewire_context> loop{};+  pw_filter* filter{};+  std::vector<pw_proxy*> links{};++  struct port+  {+    void* data;+  }* port{};++  explicit pipewire_filter(std::shared_ptr<pipewire_context> loop)+      : loop{loop}+  {+  }++  explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)+      : loop{loop}+      , filter{filter}+  {+  }++  void create_filter(std::string_view filter_name, const pw_filter_events& events, void* context)+  {+    assert(!filter);++    auto& pw = libpipewire::instance();+    // clang-format off+    this->filter = pw.filter_new_simple(+        loop->lp,+        filter_name.data(),+        pw.properties_new(+            PW_KEY_MEDIA_TYPE, "Midi",+            PW_KEY_MEDIA_CATEGORY, "Filter",+            PW_KEY_MEDIA_ROLE, "DSP",+            PW_KEY_MEDIA_NAME, "",+#if defined(PW_KEY_NODE_LOCK_RATE)+            PW_KEY_NODE_LOCK_RATE, "true",+#endif+            PW_KEY_NODE_ALWAYS_PROCESS, "true",+            PW_KEY_NODE_PAUSE_ON_IDLE, "false",+#if defined(PW_KEY_NODE_SUSPEND_ON_IDLE)+            PW_KEY_NODE_SUSPEND_ON_IDLE, "false",+#endif+            nullptr),+        &events,+        context);+    // clang-format on+    assert(filter);+  }++  void destroy()+  {+    if (this->filter)+      pw.filter_destroy(this->filter);+  }++  stdx::error create_local_port(std::string_view port_name, spa_direction direction)+  {+    // clang-format off+    this->port = (struct port*)pw.filter_add_port(+        this->filter,+        direction,+        PW_FILTER_PORT_FLAG_MAP_BUFFERS,+        sizeof(struct port),+        pw.properties_new(+            PW_KEY_FORMAT_DSP, "8 bit raw midi",+            PW_KEY_PORT_NAME, port_name.data(),+            nullptr),+        nullptr, 0);+    // clang-format on+    if (!port)+      return std::errc::invalid_argument;+    return stdx::error{};+  }++  void set_port_buffer(int bytes)+  {+    uint8_t buffer[1024];+    struct spa_pod_builder builder;+    spa_pod_builder_init(&builder, buffer, sizeof(buffer));++    // clang-format off+    const struct spa_pod* params[1] = {+      (spa_pod*) spa_pod_builder_add_object(+        &builder,+        SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,+        SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 32),+        SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),+        SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(bytes, 4096, INT32_MAX),+        SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)+      )+    };+    // clang-format on++    pw.filter_update_params(this->filter, this->port, params, 1);+  }++  stdx::error remove_port()+  {+    assert(this->port);+    int ret = pw.filter_remove_port(this->port);+    this->port = nullptr;+    return from_errc(ret);+  }++  stdx::error rename_port(std::string_view port_name)+  {+    if (this->port)+    {+      spa_dict_item items[1] = {+          SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name.data()),+      };++      auto properties = SPA_DICT_INIT(items, 1);+      int ret = pw.filter_update_properties(this->filter, this->port, &properties);+      return from_errc(ret);+    }+    else+    {+      return std::errc::not_connected;+    }+  }++  [[nodiscard]] stdx::error start_filter()+  {+    if (int ret = pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0); ret < 0)+    {+      return from_errc(ret);+    }+    else+    {+      return stdx::error{};+    }+  }++  uint32_t filter_node_id() { return this->loop->pw.filter_get_node_id(this->filter); }++  void synchronize_node()+  {+    this->loop->synchronize();+    int k = 0;+    auto node_id = filter_node_id();+    while (node_id == 4294967295)+    {+      this->loop->synchronize();+      node_id = filter_node_id();++      if (k++; k > 100)+        return;+    }+  }+  void synchronize_ports(const pipewire_context::node& this_node)+  {+    // Leave some time to resolve the ports+    int k = 0;+    const auto num_local_ins = 1;+    const auto num_local_outs = 0;+    while (this_node.inputs.size() < num_local_ins || this_node.outputs.size() < num_local_outs)+    {+      this->loop->synchronize();+      if (k++; k > 100)+        return;+    }+  }+};+}++#pragma GCC diagnostic pop
+ vendor/libremidi/backends/pipewire/helpers.hpp view
@@ -0,0 +1,473 @@+#pragma once++#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/backends/linux/pipewire.hpp>+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/context.hpp>+#include <libremidi/detail/memory.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/semaphore.hpp>++#include <atomic>+#include <semaphore>+#include <stop_token>+#include <thread>++namespace libremidi+{+struct pipewire_helpers+{+  struct port+  {+    void* data{};+  };++  // All pipewire operations have to happen in the same thread+  // - and pipewire checks that internally.+  std::jthread main_loop_thread;+  const libpipewire& pw = libpipewire::instance();+  std::shared_ptr<pipewire_instance> global_instance;+  std::shared_ptr<pipewire_context> global_context;+  std::unique_ptr<pipewire_filter> filter;+  pw_proxy* link{};++  int64_t this_instance{};++  eventfd_notifier termination_event{};+  pollfd fds[2]{};++  semaphore_pair_lock thread_lock;+  std::shared_ptr<void> canary = std::make_shared<int>();++  enum poll_state+  {+    start_poll,+    in_poll,+    not_in_poll+  };+  std::atomic<poll_state> current_state{not_in_poll};++  pipewire_helpers()+  {+    static std::atomic_int64_t instance{};+    this_instance = ++instance;++    fds[1] = termination_event;+  }++  template <typename Self>+  stdx::error create_filter(Self& self)+  {+    if (this->filter)+      return stdx::error{};++    auto& configuration = self.configuration;+    if (configuration.context && configuration.filter && configuration.set_process_func)+    {+      this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);++      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);++            self.thread_lock.check_client_released();+          }};+      configuration.set_process_func(cbs);+    }+    else+    {+      this->filter = std::make_unique<pipewire_filter>(this->global_context);+#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"+      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<Self*>(_data);+               self.process(position);+             }};+#pragma GCC diagnostic pop++      this->filter->create_filter(self.configuration.client_name, filter_events, &self);+      return this->filter->start_filter();+    }+    return stdx::error{};+  }++  template <typename Self>+  void destroy_filter(Self& self)+  {+    assert(global_context);+    if (!global_context->owns_main_loop)+    {+      if (self.configuration.clear_process_func)+      {+        self.configuration.clear_process_func(this_instance);+      }+    }+    else+    {+      if (this->filter)+      {+        this->filter->destroy();+      }+    }++    this->filter.reset();+  }++  template <typename Self>+  stdx::error create_context(Self& self)+  {+    if (this->global_context)+      return stdx::error{};++    // Initialize PipeWire client+    auto& configuration = self.configuration;+    if (configuration.context)+    {+      this->global_context = std::make_shared<pipewire_context>(configuration.context);+    }+    else+    {+      this->global_instance = std::make_shared<pipewire_instance>();+      this->global_context = std::make_shared<pipewire_context>(this->global_instance);+    }+    if (!this->global_context->main_loop)+      return std::errc::connection_refused;++    return stdx::error{};+  }++  void destroy_context()+  {+    assert(this->global_context);+    this->global_context.reset();+    this->global_instance.reset();+  }++  void run_poll_loop()+  try+  {+    // Note: called from a std::jthread.+    assert(this->global_context);+    if (int fd = this->global_context->get_fd(); fd != -1)+    {+      fds[0] = {.fd = fd, .events = POLLIN, .revents = 0};+      current_state = poll_state::in_poll;++      for (;;)+      {+        if (int err = poll(fds, 2, -1); err < 0)+        {+          if (err == -EAGAIN)+            continue;+          else+            break;+        }++        // Check pipewire fd:+        if (fds[0].revents & POLLIN)+        {+          if (auto lp = this->global_context->lp)+          {+            int result = pw_loop_iterate(lp, 0);+            if (result < 0)+            {+              LIBREMIDI_LOG(spa_strerror(result));+            }+          }+          fds[0].revents = 0;+        }++        // Check exit fd:+        if (fds[1].revents & POLLIN)+        {+          break;+        }+      }+    }+    current_state = poll_state::not_in_poll;+  }+  catch (...)+  {+    current_state = poll_state::not_in_poll;+  }++  template <typename Self>+  stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction)+  {+    assert(this->global_context);+    assert(this->filter);++    if (portName.empty())+      portName = direction == SPA_DIRECTION_INPUT ? "i" : "o";++    if (!this->filter->port)+    {+      auto ret = this->filter->create_local_port(portName.data(), direction);+      if (ret != stdx::error{})+      {+        self.libremidi_handle_error(self.configuration, "error creating port");+        return ret;+      }+    }++    return stdx::error{};+  }++  void add_callbacks(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)+        return;++      bool unfiltered = conf.track_any;+      unfiltered |= (port.physical && conf.track_hardware);+      unfiltered |= (!port.physical && conf.track_virtual);+      if (unfiltered)+      {+        if (port.direction == SPA_DIRECTION_INPUT)+        {+          if (conf.output_added)+            conf.output_added(to_port_info<SPA_DIRECTION_INPUT>(port));+        }+        else+        {+          if (conf.input_added)+            conf.input_added(to_port_info<SPA_DIRECTION_OUTPUT>(port));+        }+      }+    };++    global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) {+      if (port.format.find("midi") == std::string::npos)+        return;++      bool unfiltered = conf.track_any;+      unfiltered |= (port.physical && conf.track_hardware);+      unfiltered |= (!port.physical && conf.track_virtual);+      if (unfiltered)+      {+        if (port.direction == SPA_DIRECTION_INPUT)+        {+          if (conf.output_removed)+            conf.output_removed(to_port_info<SPA_DIRECTION_INPUT>(port));+        }+        else+        {+          if (conf.input_removed)+            conf.input_removed(to_port_info<SPA_DIRECTION_OUTPUT>(port));+        }+      }+    };+  }++  void start_thread()+  {+    if (!this->global_context->owns_main_loop)+      return;++    current_state = poll_state::start_poll;+    main_loop_thread = std::jthread{[this]() { run_poll_loop(); }};+  }++  void stop_thread()+  {+    assert(this->global_context);+    if (!this->global_context->owns_main_loop)+      return;++    if (main_loop_thread.joinable() || current_state != poll_state::not_in_poll)+    {+      termination_event.notify();+      main_loop_thread.request_stop();++      termination_event.notify();+      for (int i = 0; i < 100; i++)+      {+        if (current_state == poll_state::not_in_poll)+          break;+        std::this_thread::sleep_for(std::chrono::milliseconds(10));+        termination_event.notify();+      }++      if (main_loop_thread.joinable())+        main_loop_thread.join();+    }+  }++  stdx::error do_close_port()+  {+    if (!this->filter)+      return stdx::error{};+    if (!this->filter->port)+      return stdx::error{};++    if (!this->global_context->owns_main_loop)+    {+      this->canary.reset();+      this->thread_lock.prepare_release_client();+    }++    unlink_ports();+    return this->filter->remove_port();+  }++  stdx::error rename_port(std::string_view port_name)+  {+    if (this->filter)+    {+      return this->filter->rename_port(port_name);+    }+    else+    {+      return std::errc::not_connected;+    }+  }++  void unlink_ports()+  {+    if (link)+    {+      this->global_context->unlink_ports(link);+      link = nullptr;+    }+  }++  stdx::error link_ports(auto& self, const input_port& in_port)+  {+    // Wait for the pipewire server to send us back our node's info+    for (int i = 0; i < 1000; i++)+      this->filter->synchronize_node();++    auto this_node = this->filter->filter_node_id();+    auto& midi = this->global_context->current_graph.software_midi;+    auto node_it = midi.find(this_node);+    if (node_it == midi.end())+      return std::errc::invalid_argument;++    // Wait for the pipewire server to send us back our node's ports+    this->filter->synchronize_ports(node_it->second);++    if (node_it->second.inputs.empty())+      return std::errc::no_link;++    // Link ports+    const auto& p = node_it->second.inputs.front();+    link = this->global_context->link_ports(in_port.port, p.id);+    pw_loop_iterate(this->global_context->lp, 1);+    if (!link)+    {+      self.libremidi_handle_error(+          self.configuration,+          "could not connect to port: " + in_port.port_name + " -> " + p.port_name);+      return std::errc::no_link;+    }++    return stdx::error{};+  }++  stdx::error link_ports(auto& self, const output_port& out_port)+  {+    // Wait for the pipewire server to send us back our node's info+    for (int i = 0; i < 1000; i++)+      this->filter->synchronize_node();++    auto this_node = this->filter->filter_node_id();+    auto& midi = this->global_context->current_graph.software_midi;+    auto node_it = midi.find(this_node);+    if (node_it == midi.end())+    {+      return std::errc::invalid_argument;+    }++    // Wait for the pipewire server to send us back our node's ports+    this->filter->synchronize_ports(node_it->second);++    if (node_it->second.outputs.empty())+    {+      return std::errc::no_link;+    }++    // Link ports+    const auto& p = node_it->second.outputs.front();+    link = this->global_context->link_ports(p.id, out_port.port);+    pw_loop_iterate(this->global_context->lp, 1);+    if (!link)+    {+      self.libremidi_handle_error(+          self.configuration,+          "could not connect to port: " + p.port_name + " -> " + out_port.port_name);+      return std::errc::no_link;+    }++    return stdx::error{};+  }++  template <spa_direction Direction>+  static auto to_port_info(const pipewire_context::port_info& port)+      -> std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>+  {+    std::string device_name, port_name;+    auto name_colon = port.port_alias.find(':');+    if (name_colon != std::string::npos)+    {+      device_name = port.port_alias.substr(0, name_colon);+      port_name = port.port_alias.substr(name_colon + 1);+    }+    else+    {+      port_name = port.port_alias;+    }++    return {{+        .client = 0,+        .port = port.id,+        .manufacturer = "",+        .device_name = device_name,+        .port_name = port.port_name,+        .display_name = port_name,+    }};+  }++  // 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 <spa_direction Direction>+  static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept+      -> std::vector<+          std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>+  {+    std::vector<std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>+        ret;++    {+      std::lock_guard _{ctx.current_graph.mtx};+      if (conf.track_any || conf.track_hardware)+        for (auto& node : ctx.current_graph.physical_midi)+        {+          for (auto& port :+               (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))+          {+            ret.push_back(to_port_info<Direction>(port));+          }+        }++      if (conf.track_any || conf.track_virtual)+        for (auto& node : ctx.current_graph.software_midi)+        {+          for (auto& port :+               (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))+          {+            ret.push_back(to_port_info<Direction>(port));+          }+        }+    }++    return ret;+  }+};+}
+ vendor/libremidi/backends/pipewire/midi_in.hpp view
@@ -0,0 +1,131 @@+#pragma once+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+class midi_in_pipewire final+    : public midi1::in_api+    , public pipewire_helpers+    , public error_handler+{+public:+  struct+      : input_configuration+      , pipewire_input_configuration+  {+  } configuration;++  explicit midi_in_pipewire(input_configuration&& conf, pipewire_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; }++  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{})+      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); 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_Midi)+        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(+          {data, data + size}, m_processing.timestamp<timestamp_info>(to_ns, c->offset));+    }++    pw.filter_queue_buffer(this->filter->port, b);+  }++  midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/pipewire/midi_out.hpp view
@@ -0,0 +1,188 @@+#pragma once+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <readerwriterqueue.h>++#include <semaphore>++namespace libremidi+{+class midi_out_pipewire+    : public midi1::out_api+    , public pipewire_helpers+    , public error_handler+{+public:+  struct+      : output_configuration+      , pipewire_output_configuration+  {+  } configuration;++  midi_out_pipewire(output_configuration&& conf, pipewire_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; }++  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{})+      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); 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;+      if (m.empty())+      {+        m_queue.pop();+        continue;+      }++      // TODO why+      if (m.bytes[0] == 0xff)+      {+        m_queue.pop();+        continue;+      }++      spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi);+      int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size());++      // Try again next buffer+      if (res == -ENOSPC)+        break;++      // Recycle the memory+      m_gcqueue.enqueue(std::move(m));+      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_message(const unsigned char* message, size_t size) override+  {+    libremidi::message m;+    m_gcqueue.try_dequeue(m);+    m.bytes.assign(message, message + size);+    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<int>(user);++      default:+        // TODO+        return 0;+    }+  }++  stdx::error schedule_message(int64_t ts, const unsigned char* message, size_t size) override+  {+    m_queue.enqueue(+        libremidi::message(midi_bytes{message, message + size}, convert_timestamp(ts)));+    return stdx::error{};+  }++  moodycamel::ReaderWriterQueue<libremidi::message> m_queue;+  moodycamel::ReaderWriterQueue<libremidi::message> m_gcqueue;+  std::atomic_int64_t m_process_clock = 0;+};+}
+ vendor/libremidi/backends/pipewire/observer.hpp view
@@ -0,0 +1,85 @@+#pragma once+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <unordered_set>++namespace libremidi+{+class observer_pipewire final+    : public observer_api+    , private pipewire_helpers+    , private error_handler+{+public:+  struct+      : observer_configuration+      , pipewire_observer_configuration+  {+  } configuration;++  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->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; }++  std::vector<libremidi::input_port> get_input_ports() const noexcept override+  {+    return get_ports<SPA_DIRECTION_OUTPUT>(this->configuration, *this->global_context);+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept override+  {+    return get_ports<SPA_DIRECTION_INPUT>(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<std::string> seen_input_ports;+  std::unordered_set<std::string> seen_output_ports;+};+}
+ vendor/libremidi/backends/pipewire/shared_handler.hpp view
@@ -0,0 +1,153 @@+#pragma once+#if __has_include(<boost/lockfree/spsc_queue.hpp>)+  #include <libremidi/backends/pipewire/config.hpp>+  #include <libremidi/backends/pipewire/helpers.hpp>+  #include <libremidi/shared_context.hpp>++  #include <boost/lockfree/spsc_queue.hpp>++  #include <variant>++namespace libremidi::pipewire+{++// Create a PipeWire client which will be shared across objects+struct shared_handler : public libremidi::shared_context+{+  explicit shared_handler(std::string_view v)+  {+    midiin_callbacks.reserve(64);+    midiout_callbacks.reserve(64);++    pipewire_status_t status{};+    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);+  }++  virtual void start_processing() override { pipewire_activate(client); }+  virtual void stop_processing() override { pipewire_deactivate(client); }++  static shared_configurations make(std::string_view client_name)+  {+    auto clt = std::make_shared<shared_handler>(client_name);+    auto add_in_cb = [client = std::weak_ptr{clt}](libremidi::pipewire_callback cb) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::in_callback_added, std::move(cb)});+    };+    auto clear_in_cb = [client = std::weak_ptr{clt}](int64_t index) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::in_callback_removed, index});+    };+    auto add_out_cb = [client = std::weak_ptr{clt}](libremidi::pipewire_callback cb) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::out_callback_added, std::move(cb)});+    };+    auto clear_out_cb = [client = std::weak_ptr{clt}](int64_t index) {+      if (auto clt = client.lock())+        clt->events.push({shared_handler::event_type::out_callback_removed, index});+    };+    return {+        .context = clt,+        .observer = pipewire_observer_configuration{.context = clt->client},+        .in+        = pipewire_input_configuration{.context = clt->client, .set_process_func = add_in_cb, .clear_process_func = clear_in_cb},+        .out+        = pipewire_output_configuration{.context = clt->client, .set_process_func = add_out_cb, .clear_process_func = clear_out_cb},+    };+  }++  int pipewire_callback(pipewire_nframes_t cnt)+  {+    // 1. Process the events that will change the callback list+    event ev;+    while (events.pop(ev))+    {+      switch (ev.type)+      {+        case in_callback_added:+          midiin_callbacks.push_back(+              std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));+          break;+        case in_callback_removed: {+          auto idx = *std::get_if<int64_t>(&ev.payload);+          for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)+          {+            if (it->token == idx)+            {+              midiin_callbacks.erase(it);+              break;+            }+            else+            {+              ++it;+            }+          }+          break;+        }+        case out_callback_added:+          midiout_callbacks.push_back(+              std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));+          break;+        case out_callback_removed:+          auto idx = *std::get_if<int64_t>(&ev.payload);+          for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)+          {+            if (it->token == idx)+            {+              midiout_callbacks.erase(it);+              break;+            }+            else+            {+              ++it;+            }+          }+          break;+      }+    }++    for (auto& cb : midiin_callbacks)+      cb.callback(cnt);++    for (auto& cb : midiout_callbacks)+      cb.callback(cnt);++    return 0;+  }++  ~shared_handler()+  {+    pipewire_deactivate(client);+    pipewire_client_close(client);+  }++  pipewire_client_t* client{};++  enum event_type+  {+    in_callback_added,+    in_callback_removed,+    out_callback_added,+    out_callback_removed,+  };+  struct event+  {+    event_type type;+    std::variant<libremidi::pipewire_callback, int64_t> payload;+  };++  boost::lockfree::spsc_queue<event> events{16};++  std::vector<libremidi::pipewire_callback> midiin_callbacks;+  std::vector<libremidi::pipewire_callback> midiout_callbacks;+};+}+#endif
+ vendor/libremidi/backends/winmidi.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/winmidi/midi_in.hpp>+#include <libremidi/backends/winmidi/midi_out.hpp>+#include <libremidi/backends/winmidi/observer.hpp>++namespace libremidi::winmidi+{+struct backend+{+  using midi_in = midi_in_impl;+  using midi_out = midi_out_impl;+  using midi_observer = observer_impl;+  using midi_in_configuration = winmidi::input_configuration;+  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 constexpr inline bool available() noexcept { return true; }+};+} // namespace libremidi
+ vendor/libremidi/backends/winmidi/config.hpp view
@@ -0,0 +1,19 @@+#pragma once+#include <libremidi/config.hpp>++namespace libremidi::winmidi+{++struct input_configuration+{+};++struct output_configuration+{+};++struct observer_configuration+{+};++}
+ vendor/libremidi/backends/winmidi/helpers.hpp view
@@ -0,0 +1,60 @@+#pragma once+// clang-format off+#define NOMINMAX 1+#define WIN32_LEAN_AND_MEAN 1+#include <libremidi/detail/midi_api.hpp>++#include <mutex>+#include <cctype>+#include <string>+#include <thread>+#include <vector>+#include <guiddef.h>++#include <winrt/Windows.Foundation.h>+#include <winrt/Windows.Foundation.Collections.h>+#include <winrt/Windows.Devices.Enumeration.h>+#include <winrt/Windows.Devices.Midi2.h>+++namespace midi2 = winrt::Windows::Devices::Midi2;+namespace foundation = winrt::Windows::Foundation;+namespace collections = winrt::Windows::Foundation::Collections;+++// clang-format on++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;++inline bool ichar_equals(char a, char b)+{+  return std::tolower(static_cast<unsigned char>(a)) ==+         std::tolower(static_cast<unsigned char>(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) {+  auto eps = MidiEndpointDeviceInformation::FindAll();+  for (const auto& ep : eps)+  {+    auto str = to_string(ep.Id());+    if (str.empty())+      continue;++    if (iequals(str, port))+      return ep;+  }+  return {nullptr};+}+}
+ vendor/libremidi/backends/winmidi/midi_in.hpp view
@@ -0,0 +1,97 @@+#pragma once+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmidi/helpers.hpp>+#include <libremidi/backends/winmidi/observer.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <iostream>++namespace libremidi::winmidi+{++class midi_in_impl final+    : public midi2::in_api+    , public error_handler+{+public:+  struct+      : libremidi::ump_input_configuration+      , winmidi::input_configuration+  {+  } configuration;++  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")}+  {+    this->client_open_ = stdx::error{};+  }++  ~midi_in_impl() override+  {+    close_port();+    this->client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override+  {+    return libremidi::API::WINDOWS_MIDI_SERVICES;+  }++  stdx::error open_port(const input_port& port, std::string_view) override+  {+    auto ep = get_port_by_name(port.port_name);+    if (!ep)+      return std::errc::address_not_available;++    m_endpoint = m_session.CreateEndpointConnection(ep.Id());++    m_revoke_token = m_endpoint.MessageReceived(+        [&](const foundation::IInspectable& sender,+            const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {+      process_message(args);+    });++    m_endpoint.Open();++    return stdx::error{};+  }++  void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg)+  {+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    const auto& ump = msg.GetMessagePacket();+    const auto& b = ump.GetAllWords();++    uint32_t ump_space[64];+    array_view<uint32_t> ref{ump_space};+    b.GetMany(0, ref);++    auto to_ns = [t = ump.Timestamp()] { return t; };+    m_processing.on_bytes(+        {ump_space, ump_space + b.Size()}, m_processing.timestamp<timestamp_info>(to_ns, 0));+  }++  stdx::error close_port() override+  {+    m_endpoint.MessageReceived(m_revoke_token);+    m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());+    return stdx::error{};+  }++  virtual timestamp absolute_timestamp() const noexcept override { return {}; }++private:+  MidiSession m_session;+  winrt::event_token m_revoke_token{};+  winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};+  midi2::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/winmidi/midi_out.hpp view
@@ -0,0 +1,89 @@+#pragma once+#include <libremidi/detail/ump_stream.hpp>+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmidi/helpers.hpp>+#include <libremidi/backends/winmidi/observer.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi::winmidi+{++class midi_out_impl final+    : public midi2::out_api+    , public error_handler+{+public:+  struct+      : libremidi::output_configuration+      , winmidi::output_configuration+  {+  } configuration;++  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")}+  {+    this->client_open_ = stdx::error{};+  }++  ~midi_out_impl() override { close_port(); }++  libremidi::API get_current_api() const noexcept override+  {+    return libremidi::API::WINDOWS_MIDI_SERVICES;+  }++  stdx::error open_port(const output_port& port, std::string_view) override+  {+    auto ep = get_port_by_name(port.port_name);+    if (!ep)+      return std::errc::address_not_available;++    m_endpoint = m_session.CreateEndpointConnection(ep.Id());+    m_endpoint.Open();++    return stdx::error{};+  }++  stdx::error close_port() override+  {+    m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());+    return stdx::error{};+  }++  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 {+      MidiSendMessageResults ret{};+      switch(bytes / 4)+      {+        case 1:+          ret = m_endpoint.SendSingleMessagePacket(MidiMessage32(0, ump[0]));+          break;+        case 2:+          ret = m_endpoint.SendSingleMessagePacket(MidiMessage64(0, ump[0], ump[1]));+          break;+        case 3:+          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]));+          break;+        default:+          return std::errc::bad_message;+      }++      if(ret != MidiSendMessageResults::Succeeded)+        return std::errc::bad_message;+      return std::errc{0};+    };++    return segment_ump_stream(message, size, write_func, []() {});+  }++private:+  MidiSession m_session;+  winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};+};++}
+ vendor/libremidi/backends/winmidi/observer.hpp view
@@ -0,0 +1,148 @@+#pragma once+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmidi/helpers.hpp>+#include <libremidi/detail/observer.hpp>+;+namespace libremidi::winmidi+{+struct port_info+{+  hstring id;+  hstring name;+};++class observer_impl final : public observer_api+{+public:+  struct+      : libremidi::observer_configuration+      , winmidi::observer_configuration+  {+  } configuration;+++  explicit observer_impl(+      libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+      , session{MidiSession::CreateSession(L"libremidi session")}+  {+    if (!configuration.has_callbacks())+      return;++    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);+    }++    /*+    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_);+  }++  libremidi::API get_current_api() const noexcept override+  {+    return libremidi::API::WINDOWS_MIDI_SERVICES;+  }++  template <bool Input>+  auto to_port_info(const DeviceInformation& p) const noexcept+      -> std::conditional_t<Input, input_port, output_port>+  {+    return {+        {.client = 0,+         .port = 0,+         .manufacturer = "",+         .device_name = "",+         .port_name = to_string(p.Id()),+         .display_name = to_string(p.Name())}};+  }++  std::vector<libremidi::input_port> get_input_ports() const noexcept override+  {+    std::vector<libremidi::input_port> ret;++    auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();+    auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();+    for (const auto& ep : endpointDevices)+    {+      if(ep.Name().starts_with(L"Diagnostics")) {+        continue;+      }+      // FIXME if(has input...)++      ret.emplace_back(to_port_info<true>(ep));+    }++    return ret;+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept override+  {+    std::vector<libremidi::output_port> ret;++    auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();+    auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();+    for (const auto& ep : endpointDevices)+    {+      if(ep.Name().starts_with(L"Diagnostics")) {+        continue;+      }+      // FIXME if(has output...)+      ret.emplace_back(to_port_info<false>(ep));+    }++    return ret;+  }++  void on_input_added(const DeviceInformation& name)+  {+    if (configuration.input_added)+      configuration.input_added(to_port_info<true>(name));+  }++  void on_input_removed(const DeviceInformation& name)+  {+    if (configuration.input_removed)+      configuration.input_removed(to_port_info<true>(name));+  }++  void on_output_added(const DeviceInformation& name)+  {+    if (configuration.output_added)+      configuration.output_added(to_port_info<false>(name));+  }++  void on_output_removed(const DeviceInformation& name)+  {+    if (configuration.output_removed)+      configuration.output_removed(to_port_info<false>(name));+  }++private:+  MidiSession session;+};++}
+ vendor/libremidi/backends/winmm.hpp view
@@ -0,0 +1,36 @@+#pragma once+#include <libremidi/backends/winmm/midi_in.hpp>+#include <libremidi/backends/winmm/midi_out.hpp>+#include <libremidi/backends/winmm/observer.hpp>++// 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++//*********************************************************************//+//  API: Windows Multimedia Library (MM)+//*********************************************************************//++// API information deciphered from:+//  -+//  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+{++struct winmm_backend+{+  using midi_in = midi_in_winmm;+  using midi_out = midi_out_winmm;+  using midi_observer = observer_winmm;+  using midi_in_configuration = winmm_input_configuration;+  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 constexpr inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/winmm/config.hpp view
@@ -0,0 +1,31 @@+#pragma once+#include <libremidi/config.hpp>++#include <chrono>+#include <functional>++namespace libremidi+{++struct winmm_input_configuration+{+  int sysex_buffer_size = 1024;+  int sysex_buffer_count = 4;+};++struct winmm_output_configuration+{+};++struct poll_parameters+{+  std::function<void()> callback;+};++struct winmm_observer_configuration+{+  std::chrono::milliseconds poll_period{100};+  std::function<void(const poll_parameters&)> manual_poll;+};++}
+ vendor/libremidi/backends/winmm/error_domain.hpp view
@@ -0,0 +1,70 @@+#pragma once+// clang-format off+#define NOMINMAX 1+#define WIN32_LEAN_AND_MEAN 1++#include <windows.h>+#include <mmsystem.h>+// clang-format on++#include <libremidi/error.hpp>++namespace libremidi+{++struct winmm_error_domain : public stdx::error_domain+{+public:+  constexpr winmm_error_domain() noexcept+      : error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}+  {+  }++  stdx::string_ref name() const noexcept override { return "winmm"; }++  bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override+  {+    if (lhs.domain() == rhs.domain())+      return error_cast<int>(lhs) == error_cast<int>(rhs);++    return false;+  }++  stdx::string_ref message(const stdx::error& e) const noexcept override+  {+    switch (error_cast<int>(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";+    }+    return "Unknown error code";+  }+};++inline stdx::error from_mmerr(int ret) noexcept+{+  static constexpr winmm_error_domain domain;+  return {ret, domain};+}++}
+ vendor/libremidi/backends/winmm/helpers.hpp view
@@ -0,0 +1,74 @@+#pragma once+#include <libremidi/backends/winmm/error_domain.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <algorithm>++namespace libremidi+{++// Convert a nullptr-terminated wide string or ANSI-encoded string to UTF-8.+inline std::string ConvertToUTF8(const TCHAR* str)+{+  std::string u8str;+  const WCHAR* wstr = L"";+#if defined(UNICODE) || defined(_UNICODE)+  wstr = str;+#else+  // Convert from ANSI encoding to wide string+  int wlength = MultiByteToWideChar(CP_ACP, 0, str, -1, nullptr, 0);+  std::wstring wstrtemp;+  if (wlength)+  {+    wstrtemp.assign(wlength - 1, 0);+    MultiByteToWideChar(CP_ACP, 0, str, -1, &wstrtemp[0], wlength);+    wstr = &wstrtemp[0];+  }+#endif+  // Convert from wide string to UTF-8+  int length = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);+  if (length)+  {+    u8str.assign(static_cast<std::string::size_type>(length - 1), 0);+    /*length =*/WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &u8str[0], length, nullptr, nullptr);+  }+  return u8str;+}++// Next functions add the portNumber to the name so that+// the device's names are sure to be listed with individual names+// even when they have the same brand name+inline void MakeUniqueInPortName(std::string& deviceName, std::size_t portNumber)+{+  int x = 1;+  for (std::size_t i = 0; i < portNumber; i++)+  {+    MIDIINCAPS deviceCaps;+    midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS));+    auto stringName = ConvertToUTF8(deviceCaps.szPname);+    if (deviceName == stringName)+    {+      x++;+    }+  }+  deviceName += " ";+  deviceName += std::to_string(x);+}++inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumber)+{+  int x = 1;+  for (std::size_t i = 0; i < portNumber; i++)+  {+    MIDIOUTCAPS deviceCaps;+    midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS));+    auto stringName = ConvertToUTF8(deviceCaps.szPname);+    if (deviceName == stringName)+    {+      x++;+    }+  }+  deviceName += " ";+  deviceName += std::to_string(x);+}++}
+ vendor/libremidi/backends/winmm/midi_in.hpp view
@@ -0,0 +1,292 @@+#pragma once+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winmm/helpers.hpp>+#include <libremidi/backends/winmm/observer.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{++class midi_in_winmm final+    : public midi1::in_api+    , public error_handler+{+public:+  struct+      : input_configuration+      , winmm_input_configuration+  {+  } configuration;++  explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400))+    {+      libremidi_handle_error(+          configuration,+          "InitializeCriticalSectionAndSpinCount failed.");++      this->client_open_ = std::errc::too_many_files_open;+      return;+    }++    this->client_open_ = stdx::error{};+  }++  ~midi_in_winmm() override+  {+    // Close a connection if it exists.+    midi_in_winmm::close_port();++    if(this->client_open_ == stdx::error{})+      DeleteCriticalSection(&(this->_mutex));+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }++  stdx::error do_open(std::size_t portNumber)+  {+    MMRESULT result = midiInOpen(+        &this->inHandle, portNumber, std::bit_cast<DWORD_PTR>(&midiInputCallback),+        std::bit_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);+    if (result != MMSYSERR_NOERROR)+    {+      libremidi_handle_error(+          configuration, "error creating Windows MM MIDI input port.");+      return from_mmerr(result);+    }++    // Allocate and init the sysex buffers.+    const auto bufferCount = static_cast<std::size_t>(configuration.sysex_buffer_count);+    this->sysexBuffer.resize(bufferCount);+    for (std::size_t i = 0; i < bufferCount; ++i)+    {+      this->sysexBuffer[i] = new MIDIHDR;+      this->sysexBuffer[i]->lpData = new char[configuration.sysex_buffer_size];+      this->sysexBuffer[i]->dwBufferLength = static_cast<DWORD>(configuration.sysex_buffer_size);+      this->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator+      this->sysexBuffer[i]->dwFlags = 0;++      result = midiInPrepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));+      if (result != MMSYSERR_NOERROR)+      {+        midiInClose(this->inHandle);+        this->inHandle = nullptr;+        libremidi_handle_error(+            configuration,+            "error starting Windows MM MIDI input port "+            "(PrepareHeader).");+        return from_mmerr(result);+      }++      // Register the buffer.+      result = midiInAddBuffer(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));+      if (result != MMSYSERR_NOERROR)+      {+        midiInClose(this->inHandle);+        this->inHandle = nullptr;+        libremidi_handle_error(+            configuration,+            "error starting Windows MM MIDI input port "+            "(AddBuffer).");+        return from_mmerr(result);+      }+    }++    result = midiInStart(this->inHandle);+    midi_start_timestamp = std::chrono::steady_clock::now();+    if (result != MMSYSERR_NOERROR)+    {+      midiInClose(this->inHandle);+      this->inHandle = nullptr;+      libremidi_handle_error(+          configuration, "error starting Windows MM MIDI input port.");+      return from_mmerr(result);+    }++    return stdx::error{};+  }++  stdx::error open_port(const input_port& p, std::string_view) override+  {+    observer_winmm obs{{}, winmm_observer_configuration{}};+    auto ports = obs.get_input_ports();++    // First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2+    for (auto& port : ports)+    {+      if (p.display_name == port.display_name)+        return do_open(port.port);+    }+    // If nothing is found, try to check with the raw name+    for (auto& port : ports)+    {+      if (p.port_name == port.port_name)+        return do_open(port.port);+    }+    libremidi_handle_error(+        configuration, "port not found: " + p.port_name);+    return std::errc::invalid_argument;+  }++  stdx::error close_port() override+  {+    if (connected_)+    {+      EnterCriticalSection(&(this->_mutex));+      midiInReset(this->inHandle);+      midiInStop(this->inHandle);++      for (std::size_t i = 0; i < static_cast<std::size_t>(configuration.sysex_buffer_count); ++i)+      {+        MMRESULT res{};++        int wait_count = 5;+        while (+            ((res = midiInUnprepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR)))+             == MIDIERR_STILLPLAYING)+            && wait_count-- >= 0)+        {+          Sleep(1);+        }++        if (res != MMSYSERR_NOERROR)+        {+          libremidi_handle_warning(+              configuration,+              "error closing Windows MM MIDI input "+              "port (midiInUnprepareHeader).");+          continue;+        }+        else+        {+          delete[] this->sysexBuffer[i]->lpData;+          delete[] this->sysexBuffer[i];+        }+      }++      midiInClose(this->inHandle);+      this->inHandle = nullptr;+      LeaveCriticalSection(&(this->_mutex));+    }+    return stdx::error{};+  }++private:+  timestamp absolute_timestamp() const noexcept override+  {+    return std::chrono::duration_cast<std::chrono::nanoseconds>(+               std::chrono::steady_clock::now() - midi_start_timestamp)+        .count();+  }++  static constexpr int bytes_for_message(uint8_t status)+  {+    if (status < 0xC0)+      return 3;+    else if (status < 0xE0)+      return 2;+    else if (status < 0xF0)+      return 3;+    else if (status == 0xF1)+      return 2;+    else if (status == 0xF2)+      return 3;+    else if (status == 0xF3)+      return 2;+    else if (status == 0xF8)+      return 1;+    else if (status == 0xFE)+      return 1;+    else+      return 0;+  }++  static void CALLBACK midiInputCallback(+      HMIDIIN /*hmin*/, UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage,+      DWORD_PTR timestamp)+  {+    if (inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR)+      return;++    auto& self = *reinterpret_cast<midi_in_winmm*>(instancePtr);+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    const auto to_ns = [timestamp] { return timestamp * 1'000'000; };++    if (inputStatus == MIM_DATA)+    {+      // Channel or system message+      uint8_t message[sizeof(DWORD_PTR)];+      memcpy(message, &midiMessage, sizeof(DWORD_PTR));++      // Make sure the first byte is a status byte.+      if (message[0] & 0x80)+      {+        self.m_processing.on_bytes(+            {message, message + bytes_for_message(message[0])},+            self.m_processing.timestamp<timestamp_info>(to_ns, 0));+      }+    }+    else+    {+      // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )+      const auto* sysex = reinterpret_cast<MIDIHDR*>(midiMessage);+      if(inputStatus == MIM_LONGERROR)+      {+        self.m_processing.reset();+      }+      else if (!self.configuration.ignore_sysex)+      {+        if(sysex->dwBytesRecorded > 0)+        {+          const auto sysex_bytes = reinterpret_cast<uint8_t*>(sysex->lpData);++          self.m_processing.on_bytes(+              {sysex_bytes, sysex_bytes + sysex->dwBytesRecorded},+              self.m_processing.timestamp<timestamp_info>(to_ns, 0));+        }+      }++      // The WinMM API requires that the sysex buffer be requeued after+      // input of each sysex message.  Even if we are ignoring sysex+      // messages, we still need to requeue the buffer in case the user+      // decides to not ignore sysex messages in the future.  However,+      // it seems that WinMM calls this function with an empty sysex+      // buffer when an application closes and in this case, we should+      // avoid requeueing it, else the computer suddenly reboots after+      // one or two minutes.+      if (self.sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0)+      {+        EnterCriticalSection(&(self._mutex));+        MMRESULT result+            = midiInAddBuffer(self.inHandle, self.sysexBuffer[sysex->dwUser], sizeof(MIDIHDR));+        LeaveCriticalSection(&(self._mutex));+        if (result != MMSYSERR_NOERROR)+        {+          LIBREMIDI_LOG(+              "error sending sysex to "+              "Midi device!!");+        }+      }+    }+  }++  HMIDIIN inHandle; // Handle to Midi Input Device++  std::vector<LPMIDIHDR> sysexBuffer;+  // [Patrice] see+  // https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo+  CRITICAL_SECTION _mutex;+  std::chrono::steady_clock::time_point midi_start_timestamp;++  midi1::input_state_machine m_processing{this->configuration};+};++}
+ vendor/libremidi/backends/winmm/midi_out.hpp view
@@ -0,0 +1,164 @@+#pragma once+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winmm/helpers.hpp>+#include <libremidi/backends/winmm/observer.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{++class midi_out_winmm final+    : public midi1::out_api+    , public error_handler+{+public:+  struct+      : output_configuration+      , winmm_output_configuration+  {+  } configuration;++  midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    this->client_open_ = stdx::error{};+  }++  ~midi_out_winmm() override+  {+    // Close a connection if it exists.+    midi_out_winmm::close_port();+    this->client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }++  [[nodiscard]] stdx::error do_open(unsigned int portNumber)+  {+    MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL);+    if (result != MMSYSERR_NOERROR)+    {+      libremidi_handle_error(+          configuration,+          "error creating Windows MM MIDI output "+          "port.");+      return from_mmerr(result);+    }++    return stdx::error{};+  }++  stdx::error open_port(const output_port& p, std::string_view) override+  {+    observer_winmm obs{{}, winmm_observer_configuration{}};+    auto ports = obs.get_output_ports();++    // First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2+    for (auto& port : ports)+    {+      if (p.display_name == port.display_name)+        return do_open(port.port);+    }+    // If nothing is found, try to check with the raw name+    for (auto& port : ports)+    {+      if (p.port_name == port.port_name)+        return do_open(port.port);+    }+    libremidi_handle_error(+        configuration, "port not found: " + p.port_name);+    return std::errc::invalid_argument;+  }++  stdx::error close_port() override+  {+    if (this->outHandle)+      midiOutClose(this->outHandle);++    this->outHandle = nullptr;+    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;+    }++    if (message[0] == 0xF0)+    { // Sysex message++      buffer.assign(message, message + size);++      // FIXME this can be made asynchronous... see Chrome source.+      // But need to know whe buffers are freed.++      // Create and prepare MIDIHDR structure.+      MIDIHDR sysex{};+      sysex.lpData = (LPSTR)buffer.data();+      sysex.dwBufferLength = size;+      sysex.dwFlags = 0;+      auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR));+      if (result != MMSYSERR_NOERROR)+      {+        libremidi_handle_error(+            configuration, "error preparing sysex header.");+        return from_mmerr(result);+      }++      // Send the message.+      result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR));+      if (result != MMSYSERR_NOERROR)+      {+        libremidi_handle_error(+            configuration, "error sending sysex message.");+        return from_mmerr(result);+      }++      // Unprepare the buffer and MIDIHDR.+      // FIXME yuck+      while (MIDIERR_STILLPLAYING+             == midiOutUnprepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)))+        Sleep(1);+    }+    else+    { // Channel or system message.++      // Make sure the message size isn't too big.+      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 double word.+      DWORD packet;+      std::copy_n(message, size, (unsigned char*)&packet);++      // Send the message immediately.+      auto result = midiOutShortMsg(this->outHandle, packet);+      if (result != MMSYSERR_NOERROR)+      {+        libremidi_handle_error(+            configuration, "error sending MIDI message.");+        return from_mmerr(result);+      }+    }+    return stdx::error{};+  }++private:+  HMIDIOUT outHandle; // Handle to Midi Output Device+  std::vector<char> buffer;+};++}
+ vendor/libremidi/backends/winmm/observer.hpp view
@@ -0,0 +1,271 @@+#pragma once+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winmm/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <condition_variable>+#include <mutex>+#include <ranges>+#include <thread>++namespace libremidi+{++class observer_winmm : public observer_api+{+public:+  struct+      : observer_configuration+      , winmm_observer_configuration+  {+  } configuration;++  explicit observer_winmm(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (!configuration.has_callbacks())+      return;++    if (configuration.notify_in_constructor)+      check_new_ports<true>();+    else+      check_new_ports<false>();+  }++  ~observer_winmm() { }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }++  std::vector<input_port> get_input_ports() const noexcept override+  {+    return get_port_list<true>();+  }++  std::vector<output_port> get_output_ports() const noexcept override+  {+    return get_port_list<false>();+  }++protected:+  template <bool Notify>+  void check_new_ports()+  {+    auto currInputPortList = get_port_list<true>();++    if constexpr (Notify)+    {+      compare_port_lists_and_notify_clients(+          inputPortList, currInputPortList, configuration.input_added,+          configuration.input_removed);+    }+    inputPortList = std::move(currInputPortList);++    auto currOutputPortList = get_port_list<false>();++    if constexpr (Notify)+    {+      compare_port_lists_and_notify_clients(+          outputPortList, currOutputPortList, configuration.output_added,+          configuration.output_removed);+    }+    outputPortList = std::move(currOutputPortList);+  }++  void compare_port_lists_and_notify_clients(+      const auto& prevList, const auto& currList, const auto& portAddedFunc,+      const auto& portRemovedFunc)+  {+    if (portAddedFunc)+    {+      for (const auto& port : currList)+      {+        auto iter+            = std::ranges::find(prevList, port.display_name, &port_information::display_name);+        if (iter == prevList.end())+          portAddedFunc(port);+      }+    }++    if (portRemovedFunc)+    {+      for (const auto& port : prevList)+      {+        auto iter+            = std::ranges::find(currList, port.display_name, &port_information::display_name);+        if (iter == currList.end())+          portRemovedFunc(port);+      }+    }+  }++  input_port to_in_port_info(std::size_t index) const noexcept+  {+    MIDIINCAPS deviceCaps;+    midiInGetDevCaps(index, &deviceCaps, sizeof(MIDIINCAPS));++    auto rawName = ConvertToUTF8(deviceCaps.szPname);+    auto portName = rawName;+    MakeUniqueInPortName(portName, index);+    return {+        {.client = 0,+         .port = index,+         .manufacturer = "",+         .device_name = "",+         .port_name = rawName,+         .display_name = portName}};+  }++  output_port to_out_port_info(std::size_t index) const noexcept+  {+    MIDIOUTCAPS deviceCaps;+    midiOutGetDevCaps(index, &deviceCaps, sizeof(MIDIOUTCAPS));++    auto rawName = ConvertToUTF8(deviceCaps.szPname);+    auto portName = rawName;+    MakeUniqueOutPortName(portName, index);+    return {+        {.client = 0,+         .port = index,+         .manufacturer = "",+         .device_name = "",+         .port_name = rawName,+         .display_name = portName}};+  }++  template <bool Input>+  auto get_port_list() const noexcept+      -> std::vector<std::conditional_t<Input, input_port, output_port>>+  {+    std::vector<std::conditional_t<Input, input_port, output_port>> portList;++    if constexpr (Input)+    {+      std::size_t nDevices = midiInGetNumDevs();+      for (std::size_t i = 0; i < nDevices; ++i)+      {+        portList.push_back(to_in_port_info(i));+      }+    }+    else+    {+      std::size_t nDevices = midiOutGetNumDevs();+      for (std::size_t i = 0; i < nDevices; ++i)+      {+        portList.push_back(to_out_port_info(i));+      }+    }+    return portList;+  }++  static constexpr bool INPUT = true;+  static constexpr bool OUTPUT = false;++  std::vector<input_port> inputPortList;+  std::vector<output_port> outputPortList;+};+}++#if __has_include(<stop_token>) && __cpp_lib_jthread >= 201911L+  #include <stop_token>+namespace libremidi::winmm+{+class observer_threaded final : public observer_winmm+{+public:+  struct+      : observer_configuration+      , winmm_observer_configuration+  {+  } configuration;++  explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+      : observer_winmm{std::move(conf), std::move(apiconf)}+  {+    thread = std::jthread([this](std::stop_token tk) {+      while (!tk.stop_requested())+      {+        check_new_ports<true>();+        std::this_thread::sleep_for(this->configuration.poll_period);+      }+    });+  }++private:+  std::jthread thread;+};+}+#else+  #include <atomic>+  #include <semaphore>+namespace libremidi::winmm+{+class observer_threaded final : public observer_winmm+{+public:+  struct+      : observer_configuration+      , winmm_observer_configuration+  {+  } configuration;++  explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+      : observer_winmm{std::move(conf), std::move(apiconf)}+      , sema{0}+  {+    thread = std::thread([this] {+      while (!stop_flag.test(std::memory_order_acquire))+      {+        check_new_ports<true>();+        std::this_thread::sleep_for(this->configuration.poll_period);+      }+      sema.release();+    });+  }++  ~observer_threaded()+  {+    stop_flag.test_and_set();+    sema.acquire();+    thread.join();+  }++private:+  std::thread thread;+  std::atomic_flag stop_flag = ATOMIC_FLAG_INIT;+  std::binary_semaphore sema;+};+}+#endif++namespace libremidi::winmm+{+class observer_manual final : public observer_winmm+{+public:+  struct+      : observer_configuration+      , winmm_observer_configuration+  {+  } configuration;++  explicit observer_manual(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+      : observer_winmm{std::move(conf), std::move(apiconf)}+  {+    this->configuration.manual_poll({.callback = [this] { this->check_new_ports<true>(); }});+  }++  ~observer_manual() { }+};+}+namespace libremidi+{+template <>+inline std::unique_ptr<observer_api> make<observer_winmm>(+    libremidi::observer_configuration&& conf, libremidi::winmm_observer_configuration&& api)+{+  if (api.manual_poll)+    return std::make_unique<winmm::observer_manual>(std::move(conf), std::move(api));+  else+    return std::make_unique<winmm::observer_threaded>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/winuwp.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/winuwp/midi_in.hpp>+#include <libremidi/backends/winuwp/midi_out.hpp>+#include <libremidi/backends/winuwp/observer.hpp>++namespace libremidi+{+struct winuwp_backend+{+  using midi_in = midi_in_winuwp;+  using midi_out = midi_out_winuwp;+  using midi_observer = observer_winuwp;+  using midi_in_configuration = winuwp_input_configuration;+  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 constexpr inline bool available() noexcept { return true; }+};+} // namespace libremidi
+ vendor/libremidi/backends/winuwp/config.hpp view
@@ -0,0 +1,19 @@+#pragma once+#include <libremidi/config.hpp>++namespace libremidi+{++struct winuwp_input_configuration+{+};++struct winuwp_output_configuration+{+};++struct winuwp_observer_configuration+{+};++}
+ vendor/libremidi/backends/winuwp/helpers.hpp view
@@ -0,0 +1,63 @@+#pragma once+#define NOMINMAX 1+#define WIN32_LEAN_AND_MEAN 1+#include <libremidi/detail/midi_api.hpp>++#include <mutex>+#include <string>+#include <thread>+#include <vector>++#include <winrt/Windows.Devices.Enumeration.h>+#include <winrt/Windows.Devices.Midi.h>+#include <winrt/Windows.Foundation.h>+#include <winrt/Windows.Storage.Streams.h>++namespace libremidi+{+inline void winrt_init()+{+  // init_apartment should only be called on the threads we own.+  // Since we're the library we don't own the threads we are called from,+  // so we should not perform this initialization ourselves.+  // winrt::init_apartment();+}++namespace+{+using namespace winrt;+using namespace winrt::Windows::Foundation;+using namespace winrt::Windows::Devices::Midi;+using namespace winrt::Windows::Devices::Enumeration;+using namespace winrt::Windows::Storage::Streams;++// Helper function to allow waiting for aynchronous operation completion+// from the thread in STA. The only benefit from it compared to the+// get() function from winrt is that we avoid an assertion if waiting+// from the STA thread.+template <typename T>+auto get(T const& async)+{+  if (async.Status() != AsyncStatus::Completed)+  {+    slim_mutex m;+    slim_condition_variable cv;+    bool completed = false;++    async.Completed([&](auto&&, auto&&) {+      {+        slim_lock_guard const guard(m);+        completed = true;+      }++      cv.notify_one();+    });++    slim_lock_guard guard(m);+    cv.wait(m, [&] { return completed; });+  }++  return async.GetResults();+}+}+}
+ vendor/libremidi/backends/winuwp/midi_in.hpp view
@@ -0,0 +1,97 @@+#pragma once+#include <libremidi/backends/winuwp/config.hpp>+#include <libremidi/backends/winuwp/helpers.hpp>+#include <libremidi/backends/winuwp/observer.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{++class midi_in_winuwp final+    : public midi1::in_api+    , public error_handler+{+public:+  struct+      : input_configuration+      , winuwp_input_configuration+  {+  } configuration;++  explicit midi_in_winuwp(input_configuration&& conf, winuwp_input_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    winrt_init();+    this->client_open_ = stdx::error{};+  }++  ~midi_in_winuwp() override+  {+    close_port();+    this->client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }++  stdx::error open_port(const input_port& port, std::string_view) override+  {+    const auto id = winrt::to_hstring(port.port_name);+    if (id.empty())+      return std::errc::invalid_argument;++    port_ = get(MidiInPort::FromIdAsync(id));+    if (!port_)+      return std::errc::io_error;++    midi_start_timestamp = std::chrono::steady_clock::now();++    port_.MessageReceived(+        [=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort,+            const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) {+      this->process_message(args.Message());+    });++    return stdx::error{};+  }++  void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)+  {+    static constexpr timestamp_backend_info timestamp_info{+        .has_absolute_timestamps = true,+        .absolute_is_monotonic = false,+        .has_samples = false,+    };++    auto reader = DataReader::FromBuffer(msg.RawData());+    auto begin = msg.RawData().data();+    auto end = begin + msg.RawData().Length();++    const auto to_ns = [&msg] { return msg.Timestamp().count() * 1'000'000; };+    m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0));+  }++  stdx::error close_port() override+  {+    if (port_)+    {+      port_.Close();+      port_ = nullptr;+    }+    return stdx::error{};+  }++  timestamp absolute_timestamp() const noexcept override+  {+    return std::chrono::duration_cast<std::chrono::nanoseconds>(+               std::chrono::steady_clock::now() - midi_start_timestamp)+        .count();+  }++private:+  winrt::Windows::Devices::Midi::IMidiInPort port_{nullptr};+  std::chrono::steady_clock::time_point midi_start_timestamp;++  midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/winuwp/midi_out.hpp view
@@ -0,0 +1,77 @@+#pragma once+#include <libremidi/backends/winuwp/config.hpp>+#include <libremidi/backends/winuwp/helpers.hpp>+#include <libremidi/backends/winuwp/observer.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{++class midi_out_winuwp final+    : public midi1::out_api+    , public error_handler+{+public:+  struct+      : output_configuration+      , winuwp_output_configuration+  {+  } configuration;++  midi_out_winuwp(output_configuration&& conf, winuwp_output_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    winrt_init();+    this->client_open_ = stdx::error{};+  }++  ~midi_out_winuwp() override+  {+    close_port();+    this->client_open_ = std::errc::not_connected;+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }++  stdx::error open_port(const output_port& port, std::string_view) override+  {+    const auto id = winrt::to_hstring(port.port_name);+    if (id.empty())+      return std::errc::invalid_argument;++    port_ = get(MidiOutPort::FromIdAsync(id));+    if (!bool(port_))+      return std::errc::io_error;++    return stdx::error{};+  }++  stdx::error close_port() override+  {+    if (port_)+    {+      port_.Close();+      port_ = {};+    }+    return stdx::error{};+  }++  stdx::error send_message(const unsigned char* message, size_t size) override+  {+    if (!port_)+      return std::errc::not_connected;++    InMemoryRandomAccessStream str;+    DataWriter rb(str);+    rb.WriteBytes(+        winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size});+    port_.SendBuffer(rb.DetachBuffer());++    return stdx::error{};+  }++private:+  winrt::Windows::Devices::Midi::IMidiOutPort port_{nullptr};+};++}
+ vendor/libremidi/backends/winuwp/observer.hpp view
@@ -0,0 +1,296 @@+#pragma once+#include <libremidi/backends/winuwp/config.hpp>+#include <libremidi/detail/observer.hpp>++namespace libremidi+{+class observer_winuwp_internal+{+public:+  struct port_info+  {+    hstring id;+    hstring name;+  };+  struct callback+  {+    int token{};+    std::function<void(const port_info&)> function;+  };+  struct callbacks+  {+    std::vector<callback> cbs;+    int current_token{};+    void operator()(const port_info& p)+    {+      for (auto& cb : cbs)+        cb.function(p);+    }++    int add(std::function<void(const port_info&)> f)+    {+      int tk = current_token++;+      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); }+  ~observer_winuwp_internal() { terminate(); }++  std::vector<port_info> get_ports() const+  {+    std::lock_guard<std::mutex> lock(portListMutex_);+    return portList_;+  }++  unsigned int get_port_count() const+  {+    std::lock_guard<std::mutex> lock(portListMutex_);+    return static_cast<unsigned int>(portList_.size());+  }++  bool get_port_info(unsigned int portNumber, port_info& portInfo) const+  {+    std::lock_guard<std::mutex> lock(portListMutex_);+    if (portNumber >= portList_.size())+      return false;+    portInfo = portList_[portNumber];+    return true;+  }++  hstring get_port_id(unsigned int portNumber) const+  {+    std::lock_guard<std::mutex> lock(portListMutex_);+    return portNumber < portList_.size() ? portList_[portNumber].id : hstring{};+  }++  std::string get_port_name(unsigned int portNumber) const+  {+    std::lock_guard<std::mutex> lock(portListMutex_);+    return portNumber < portList_.size() ? to_string(portList_[portNumber].name) : std::string{};+  }++  int PortAdded(const std::function<void(port_info)>& handler)+  {+    return portAddedEvent_.add(handler);+  }++  void PortAdded(int token) noexcept { portAddedEvent_.remove(token); }++  int PortRemoved(const std::function<void(port_info)>& handler)+  {+    return portRemovedEvent_.add(handler);+  }++  void PortRemoved(int token) noexcept { portRemovedEvent_.remove(token); }++private:+  observer_winuwp_internal(const observer_winuwp_internal&) = delete;+  observer_winuwp_internal& operator=(const observer_winuwp_internal&) = delete;++private:+  void initialize(hstring deviceSelector)+  {+    deviceWatcher_ = DeviceInformation::CreateWatcher(deviceSelector);++    evTokenOnDeviceAdded_+        = deviceWatcher_.Added({this, &observer_winuwp_internal::on_device_added});+    evTokenOnDeviceRemoved_+        = deviceWatcher_.Removed({this, &observer_winuwp_internal::on_device_removed});+    evTokenOnDeviceUpdated_+        = deviceWatcher_.Updated({this, &observer_winuwp_internal::on_device_updated});+    evTokenOnDeviceEnumerationCompleted_ = deviceWatcher_.EnumerationCompleted(+        {this, &observer_winuwp_internal::on_device_enumeration_completed});++    deviceWatcher_.Start();+  }++  void terminate()+  {+    deviceWatcher_.Stop();+    deviceWatcher_.EnumerationCompleted(evTokenOnDeviceEnumerationCompleted_);+    deviceWatcher_.Updated(evTokenOnDeviceUpdated_);+    deviceWatcher_.Removed(evTokenOnDeviceRemoved_);+    deviceWatcher_.Added(evTokenOnDeviceAdded_);+  }++  void on_device_added(DeviceWatcher sender, DeviceInformation deviceInfo)+  {+    port_info p;+    {+      std::lock_guard<std::mutex> lock(portListMutex_);+      p = port_info{deviceInfo.Id(), deviceInfo.Name()};+      portList_.push_back(p);+    }+    portAddedEvent_(p);+  }++  void on_device_removed(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate)+  {+    const auto id = deviceUpdate.Id();+    auto pred = [&id](const port_info& portInfo) { return portInfo.id == id; };+    std::optional<port_info> p;+    hstring name;+    {+      std::lock_guard<std::mutex> lock(portListMutex_);+      auto iter = std::find_if(portList_.begin(), portList_.end(), pred);+      if (iter != portList_.end())+      {+        p = *iter;+        portList_.erase(iter);+      }+    }+    if (p)+      portRemovedEvent_(*p);+  }++  void on_device_updated(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) { }++  void on_device_enumeration_completed(DeviceWatcher sender, IInspectable const&) { }++private:+  std::vector<port_info> portList_;+  mutable std::mutex portListMutex_;++  DeviceWatcher deviceWatcher_{nullptr};+  event_token evTokenOnDeviceAdded_;+  event_token evTokenOnDeviceRemoved_;+  event_token evTokenOnDeviceUpdated_;+  event_token evTokenOnDeviceEnumerationCompleted_;++  callbacks portAddedEvent_;+  callbacks portRemovedEvent_;+};++class observer_winuwp final : public observer_api+{+public:+  struct+      : observer_configuration+      , winuwp_observer_configuration+  {+  } configuration;++  using port_info = observer_winuwp_internal::port_info;+  explicit observer_winuwp(observer_configuration&& conf, winuwp_observer_configuration&& apiconf)+      : configuration{std::move(conf), std::move(apiconf)}+  {+    if (!configuration.has_callbacks())+      return;++    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);+    }++    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_winuwp()+  {+    if (!configuration.has_callbacks())+      return;+    internalInPortObserver_.PortAdded(evTokenOnInputAdded_);+    internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);+    internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);+    internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);+  }++  libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }++  template <bool Input>+  auto to_port_info(const observer_winuwp_internal::port_info& p) const noexcept+      -> std::conditional_t<Input, input_port, output_port>+  {+    return {+        {.client = 0,+         .port = 0,+         .manufacturer = "",+         .device_name = "",+         .port_name = to_string(p.id),+         .display_name = to_string(p.name)}};+  }++  std::vector<libremidi::input_port> get_input_ports() const noexcept override+  {+    std::vector<libremidi::input_port> ret;+    for (auto& port : internalInPortObserver_.get_ports())+      ret.push_back(to_port_info<true>(port));+    return ret;+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept override+  {+    std::vector<libremidi::output_port> ret;+    for (auto& port : internalOutPortObserver_.get_ports())+      ret.push_back(to_port_info<false>(port));+    return ret;+  }++  static observer_winuwp_internal& get_internal_in_port_observer()+  {+    return internalInPortObserver_;+  }++  static observer_winuwp_internal& get_internal_out_port_observer()+  {+    return internalOutPortObserver_;+  }++  void on_input_added(const observer_winuwp_internal::port_info& name)+  {+    if (configuration.input_added)+      configuration.input_added(to_port_info<true>(name));+  }++  void on_input_removed(const observer_winuwp_internal::port_info& name)+  {+    if (configuration.input_removed)+      configuration.input_removed(to_port_info<true>(name));+  }++  void on_output_added(const observer_winuwp_internal::port_info& name)+  {+    if (configuration.output_added)+      configuration.output_added(to_port_info<false>(name));+  }++  void on_output_removed(const observer_winuwp_internal::port_info& name)+  {+    if (configuration.output_removed)+      configuration.output_removed(to_port_info<false>(name));+  }++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};+  int evTokenOnOutputRemoved_{-1};+};++}
+ vendor/libremidi/client.hpp view
@@ -0,0 +1,204 @@+#pragma once+#include <libremidi/configurations.hpp>+#include <libremidi/libremidi.hpp>+#include <libremidi/shared_context.hpp>++#include <map>++namespace libremidi::midi1+{+struct client_configuration+{+  libremidi::API api = libremidi::midi1::default_api();++  //! A client name, if the backend allows it (e.g. JACK, ALSA SEQ...)+  std::string_view client_name = "libremidi client";++  //! Set a callback function to be invoked for incoming MIDI messages.+  //! Mandatory!+  std::function<void(const libremidi::input_port&, message&&)> on_message+      = [](const libremidi::input_port& /*port*/, libremidi::message&&) {};++  //! Observation callbacks for when ports are added or removed+  input_port_callback input_added;+  input_port_callback input_removed;+  output_port_callback output_added;+  output_port_callback output_removed;++  //! Error callback function to be invoked when an error has occured.+  /*!+    The callback function will be called whenever an error has occured. It is+    best to set the error callback function before opening a port.+  */+  midi_error_callback on_error{};+  midi_warning_callback on_warning{};++  //! Poll period for observation polling operations, if relevant to the backend+  std::chrono::milliseconds poll_period{100};++  //! Specify whether certain MIDI message types should be queued or ignored+  //! during input.+  /*!+    By default, MIDI timing and active sensing messages are ignored+    during message input because of their relative high data rates.+    MIDI sysex messages are ignored by default as well.  Variable+    values of "true" imply that the respective message type will be+    ignored.+  */+  uint32_t ignore_sysex : 1 = true;+  uint32_t ignore_timing : 1 = true;+  uint32_t ignore_sensing : 1 = true;+  uint32_t timestamps : 3 = timestamp_mode::Absolute;++  //! Observe hardware ports+  uint32_t track_hardware : 1 = true;++  //! Observe software (virtual) ports if the API provides it+  uint32_t track_virtual : 1 = false;+};++class client+{+public:+  explicit client(const client_configuration& conf)+      : client{conf, create_shared_context(conf.api, conf.client_name)}+  {+  }++  explicit client(const client_configuration& conf, shared_configurations ctx)+      : configuration{conf}+      , context{ctx}+      , m_observer{+            observer_configuration{+                .on_error = conf.on_error,+                .on_warning = conf.on_warning,++                .input_added = conf.input_added,+                .input_removed = conf.input_removed,+                .output_added = conf.output_added,+                .output_removed = conf.output_removed,++                .track_hardware = conf.track_hardware,+                .track_virtual = conf.track_virtual,+                .notify_in_constructor = false},+            context.observer}+  {+    if (context.context)+      context.context->start_processing();+  }++  ~client()+  {+    m_inputs.clear();+    m_outputs.clear();++    if (context.context)+      context.context->stop_processing();+  }++  std::vector<libremidi::input_port> get_input_ports() const noexcept+  {+    return m_observer.get_input_ports();+  }++  std::vector<libremidi::output_port> get_output_ports() const noexcept+  {+    return m_observer.get_output_ports();+  }++  void add_input(const input_port& port, std::string_view name)+  {+    if (m_inputs.find(port) != m_inputs.end())+      return;++    auto res = m_inputs.try_emplace(+        port,+        input_configuration{+            .on_message+            = [this,+               port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); },+            .on_raw_data = {},+            .get_timestamp = {},++            .on_error = configuration.on_error,+            .on_warning = configuration.on_warning,++            .ignore_sysex = configuration.ignore_sysex,+            .ignore_timing = configuration.ignore_timing,+            .ignore_sensing = configuration.ignore_sensing,++            .timestamps = configuration.timestamps},+        context.in);++    res.first->second.open_port(port, name);+  }++  void add_output(const output_port& port, std::string_view name)+  {+    if (m_outputs.find(port) != m_outputs.end())+      return;++    auto res = m_outputs.try_emplace(+        port,+        output_configuration{+            .on_error = configuration.on_error,+            .on_warning = configuration.on_warning,++            .timestamps = configuration.timestamps},+        context.out);++    res.first->second.open_port(port, name);+  }++  void remove_input(const input_port& port) { m_inputs.erase(port); }+  void remove_output(const output_port& port) { m_outputs.erase(port); }++  stdx::error send_message(const unsigned char* message, size_t size)+  {+    for (auto& [_, out] : m_outputs)+    {+      if (auto err = out.send_message(message, size); err != stdx::error{})+        return err;+    }+    return stdx::error{};+  }++  stdx::error send_ump(const uint32_t* message, size_t size)+  {+    for (auto& [_, out] : m_outputs)+    {+      if (auto err = out.send_ump(message, size); err != stdx::error{})+        return err;+    }+    return stdx::error{};+  }++  stdx::error send_message(const output_port& port, const unsigned char* message, size_t size)+  {+    if (auto it = m_outputs.find(port); it != m_outputs.end())+      return it->second.send_message(message, size);++    return stdx::error{};+  }++  stdx::error send_ump(const output_port& port, const uint32_t* message, size_t size)+  {+    if (auto it = m_outputs.find(port); it != m_outputs.end())+      return it->second.send_ump(message, size);++    return stdx::error{};+  }++private:+  client_configuration configuration;+  shared_configurations context;++  std::map<input_port, midi_in> m_inputs;+  std::map<output_port, midi_out> m_outputs;++  observer m_observer;+};+}+#if defined(LIBREMIDI_HEADER_ONLY)+  #include "client.cpp"+#endif
+ vendor/libremidi/cmidi2.hpp view
@@ -0,0 +1,3266 @@+#ifndef CMIDI2_H_INCLUDED+#define CMIDI2_H_INCLUDED++#include <memory.h>+#include <stdbool.h>+#include <stdint.h>++#define MIDI_2_0_RESERVED 0+#define JR_TIMESTAMP_TICKS_PER_SECOND 31250++#pragma GCC system_header+#pragma clang system_header++#ifdef __cplusplus+extern "C" {+#endif++enum cmidi2_status_code+{+  CMIDI2_STATUS_NOTE_OFF = 0x80,+  CMIDI2_STATUS_NOTE_ON = 0x90,+  CMIDI2_STATUS_PAF = 0xA0,+  CMIDI2_STATUS_CC = 0xB0,+  CMIDI2_STATUS_PROGRAM = 0xC0,+  CMIDI2_STATUS_CAF = 0xD0,+  CMIDI2_STATUS_PITCH_BEND = 0xE0,+  CMIDI2_STATUS_PER_NOTE_RCC = 0x00,+  CMIDI2_STATUS_PER_NOTE_ACC = 0x10,+  CMIDI2_STATUS_RPN = 0x20,+  CMIDI2_STATUS_NRPN = 0x30,+  CMIDI2_STATUS_RELATIVE_RPN = 0x40,+  CMIDI2_STATUS_RELATIVE_NRPN = 0x50,+  CMIDI2_STATUS_PER_NOTE_PITCH_BEND = 0x60,+  CMIDI2_STATUS_PER_NOTE_MANAGEMENT = 0xF0,+};++enum cmidi2_message_type+{+  CMIDI2_MESSAGE_TYPE_UTILITY = 0,+  CMIDI2_MESSAGE_TYPE_SYSTEM = 1,+  CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL = 2,+  CMIDI2_MESSAGE_TYPE_SYSEX7 = 3,+  CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL = 4,+  CMIDI2_MESSAGE_TYPE_SYSEX8_MDS = 5,+  CMIDI2_MESSAGE_TYPE_FLEX_DATA = 0xD,+  CMIDI2_MESSAGE_TYPE_UMP_STREAM = 0xF,+};++enum cmidi2_ci_protocol_bytes+{+  CMIDI2_PROTOCOL_BYTES_TYPE = 1,+  CMIDI2_PROTOCOL_BYTES_VERSION = 2,+  CMIDI2_PROTOCOL_BYTES_EXTENSIONS = 3,+};++enum cmidi2_ci_protocol_values+{+  CMIDI2_PROTOCOL_TYPE_MIDI1 = 1,+  CMIDI2_PROTOCOL_TYPE_MIDI2 = 2,+  CMIDI2_PROTOCOL_VERSION_MIDI1 = 0,+  CMIDI2_PROTOCOL_VERSION_MIDI2_V1 = 0,+};++enum cmidi2_ci_protocol_extensions+{+  CMIDI2_PROTOCOL_EXTENSIONS_JITTER = 1,+  CMIDI2_PROTOCOL_EXTENSIONS_LARGER = 2, // only for MIDI 1.0 compat UMP+};++typedef struct cmidi2_ci_protocol_tag+{+  uint8_t protocol_type; // cmidi2_ci_protocol_bytes+  uint8_t version;       // cmidi2_ci_protocol_values+  uint8_t extensions;    // cmidi2_ci_protocol_extensions (flags)+  uint8_t reserved1;+  uint8_t reserved2;+} cmidi2_ci_protocol;++enum cmidi2_cc+{+  CMIDI2_CC_BANK_SELECT = 0x00,+  CMIDI2_CC_MODULATION = 0x01,+  CMIDI2_CC_BREATH = 0x02,+  CMIDI2_CC_FOOT = 0x04,+  CMIDI2_CC_PORTAMENTO_TIME = 0x05,+  CMIDI2_CC_DTE_MSB = 0x06,+  CMIDI2_CC_VOLUME = 0x07,+  CMIDI2_CC_BALANCE = 0x08,+  CMIDI2_CC_PAN = 0x0A,+  CMIDI2_CC_EXPRESSION = 0x0B,+  CMIDI2_CC_EFFECT_CONTROL_1 = 0x0C,+  CMIDI2_CC_EFFECT_CONTROL_2 = 0x0D,+  CMIDI2_CC_GENERAL_1 = 0x10,+  CMIDI2_CC_GENERAL_2 = 0x11,+  CMIDI2_CC_GENERAL_3 = 0x12,+  CMIDI2_CC_GENERAL_4 = 0x13,+  CMIDI2_CC_BANK_SELECT_LSB = 0x20,+  CMIDI2_CC_MODULATION_LSB = 0x21,+  CMIDI2_CC_BREATH_LSB = 0x22,+  CMIDI2_CC_FOOT_LSB = 0x24,+  CMIDI2_CC_PORTAMENTO_TIME_LSB = 0x25,+  CMIDI2_CC_DTE_LSB = 0x26,+  CMIDI2_CC_VOLUME_LSB = 0x27,+  CMIDI2_CC_BALANCE_LSB = 0x28,+  CMIDI2_CC_PAN_LSB = 0x2A,+  CMIDI2_CC_EXPRESSION_LSB = 0x2B,+  CMIDI2_CC_EFFECT1_LSB = 0x2C,+  CMIDI2_CC_EFFECT2_LSB = 0x2D,+  CMIDI2_CC_GENERAL_1_LSB = 0x30,+  CMIDI2_CC_GENERAL_2_LSB = 0x31,+  CMIDI2_CC_GENERAL_3_LSB = 0x32,+  CMIDI2_CC_GENERAL_4_LSB = 0x33,+  CMIDI2_CC_HOLD = 0x40,+  CMIDI2_CC_PORTAMENTO_SWITCH = 0x41,+  CMIDI2_CC_SOSTENUTO = 0x42,+  CMIDI2_CC_SOFT_PEDAL = 0x43,+  CMIDI2_CC_LEGATO = 0x44,+  CMIDI2_CC_HOLD_2 = 0x45,+  CMIDI2_CC_SOUND_CONTROLLER_1 = 0x46,+  CMIDI2_CC_SOUND_CONTROLLER_2 = 0x47,+  CMIDI2_CC_SOUND_CONTROLLER_3 = 0x48,+  CMIDI2_CC_SOUND_CONTROLLER_4 = 0x49,+  CMIDI2_CC_SOUND_CONTROLLER_5 = 0x4A,+  CMIDI2_CC_SOUND_CONTROLLER_6 = 0x4B,+  CMIDI2_CC_SOUND_CONTROLLER_7 = 0x4C,+  CMIDI2_CC_SOUND_CONTROLLER_8 = 0x4D,+  CMIDI2_CC_SOUND_CONTROLLER_9 = 0x4E,+  CMIDI2_CC_SOUND_CONTROLLER_10 = 0x4F,+  CMIDI2_CC_GENERAL_5 = 0x50,+  CMIDI2_CC_GENERAL_6 = 0x51,+  CMIDI2_CC_GENERAL_7 = 0x52,+  CMIDI2_CC_GENERAL_8 = 0x53,+  CMIDI2_CC_PORTAMENTO_CONTROL = 0x54,+  CMIDI2_CC_RSD = 0x5B,+  CMIDI2_CC_EFFECT_1 = 0x5B,+  CMIDI2_CC_TREMOLO = 0x5C,+  CMIDI2_CC_EFFECT_2 = 0x5C,+  CMIDI2_CC_CSD = 0x5D,+  CMIDI2_CC_EFFECT_3 = 0x5D,+  CMIDI2_CC_CELESTE = 0x5E,+  CMIDI2_CC_EFFECT_4 = 0x5E,+  CMIDI2_CC_PHASER = 0x5F,+  CMIDI2_CC_EFFECT_5 = 0x5F,+  CMIDI2_CC_DTE_INCREMENT = 0x60,+  CMIDI2_CC_DTE_DECREMENT = 0x61,+  CMIDI2_CC_NRPN_LSB = 0x62,+  CMIDI2_CC_NRPN_MSB = 0x63,+  CMIDI2_CC_RPN_LSB = 0x64,+  CMIDI2_CC_RPN_MSB = 0x65,+  // CHANNEL MODE MESSAGES+  CMIDI2_CC_ALL_SOUND_OFF = 0x78,+  CMIDI2_CC_RESET_ALL_CONTROLLERS = 0x79,+  CMIDI2_CC_LOCAL_CONTROL = 0x7A,+  CMIDI2_CC_ALL_NOTES_OFF = 0x7B,+  CMIDI2_CC_OMNI_MODE_OFF = 0x7C,+  CMIDI2_CC_OMNI_MODE_ON = 0x7D,+  CMIDI2_CC_POLY_MODE_ON_OFF = 0x7E,+  CMIDI2_CC_POLY_MODE_ON = 0x7F,+};++enum cmidi2_rpn+{+  CMIDI2_RPN_PITCH_BEND_SENSITIVITY = 0,+  CMIDI2_RPN_FINE_TUNING = 1,+  CMIDI2_RPN_COARSE_TUNING = 2,+  CMIDI2_RPN_TUNING_PROGRAM = 3,+  CMIDI2_RPN_TUNING_BANK_SELECT = 4,+  CMIDI2_RPN_MODULATION_DEPTH = 5,+};++enum cmidi2_meta_event_type+{+  CMIDI2_META_SEQUENCE_NUMBER = 0X00,+  CMIDI2_META_TEXT = 0X01,+  CMIDI2_META_COPYRIGHT = 0X02,+  CMIDI2_META_TRACK_NAME = 0X03,+  CMIDI2_META_INSTRUMENT_NAME = 0X04,+  CMIDI2_META_LYRIC = 0X05,+  CMIDI2_META_MARKER = 0X06,+  CMIDI2_META_CUE = 0X07,+  CMIDI2_META_CHANNEL_PREFIX = 0X20,+  CMIDI2_META_END_OF_TRACK = 0X2F,+  CMIDI2_META_TEMPO = 0X51,+  CMIDI2_META_SMPTE_OFFSET = 0X54,+  CMIDI2_META_TIME_SIGNATURE = 0X58,+  CMIDI2_META_KEY_SIGNATURE = 0X59,+  CMIDI2_META_SEQUENCER_SPECIFIC = 0X7F,+};++enum cmidi2_per_note_management_flags+{+  CMIDI2_PER_NOTE_MANAGEMENT_RESET = 1,+  CMIDI2_PER_NOTE_MANAGEMENT_DETACH = 2,+};++enum cmidi2_note_attribute_type+{+  CMIDI2_ATTRIBUTE_TYPE_NONE = 0,+  CMIDI2_ATTRIBUTE_TYPE_MANUFACTURER = 1,+  CMIDI2_ATTRIBUTE_TYPE_PROFILE = 2,+  CMIDI2_ATTRIBUTE_TYPE_PITCH7_9 = 3,+};++enum cmidi2_program_change_option_flags+{+  CMIDI2_PROGRAM_CHANGE_OPTION_NONE = 0,+  CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID = 1,+};++enum cmidi2_sysex_status+{+  CMIDI2_SYSEX_IN_ONE_UMP = 0,+  CMIDI2_SYSEX_START = 0x10,+  CMIDI2_SYSEX_CONTINUE = 0x20,+  CMIDI2_SYSEX_END = 0x30,+};++enum cmidi2_mixed_data_set_status+{+  CMIDI2_MIXED_DATA_STATUS_HEADER = 0x80,+  CMIDI2_MIXED_DATA_STATUS_PAYLOAD = 0x90,+};++enum cmidi2_system_message_status+{+  CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE = 0xF1,+  CMIDI2_SYSTEM_STATUS_SONG_POSITION = 0xF2,+  CMIDI2_SYSTEM_STATUS_SONG_SELECT = 0xF3,+  CMIDI2_SYSTEM_STATUS_TUNE_REQUEST = 0xF6,+  CMIDI2_SYSTEM_STATUS_TIMING_CLOCK = 0xF8,+  CMIDI2_SYSTEM_STATUS_START = 0xFA,+  CMIDI2_SYSTEM_STATUS_CONTINUE = 0xFB,+  CMIDI2_SYSTEM_STATUS_STOP = 0xFC,+  CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING = 0xFE,+  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,+  CMIDI2_UTILITY_STATUS_JR_CLOCK = 0x10,+  CMIDI2_UTILITY_STATUS_JR_TIMESTAMP = 0x20,+  CMIDI2_UTILITY_STATUS_DCTPQ = 0x30,+  CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP = 0x40,+};++enum cmidi2_flex_data_status_bank+{+  CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE = 0,+  CMIDI2_FLEX_DATA_BANK_METADATA_TEXT = 1,+  CMIDI2_FLEX_DATA_BANK_PERFORMANCE_TEXT = 2+};+enum cmidi2_flex_data_setup_status+{+  CMIDI2_FLEX_DATA_STATUS_SET_TEMPO = 0,+  CMIDI2_FLEX_DATA_STATUS_SET_TIME_SIGNATURE = 1,+  CMIDI2_FLEX_DATA_STATUS_SET_METRONOME = 2,+  CMIDI2_FLEX_DATA_STATUS_SET_KEY_SIGNATURE = 5,+  CMIDI2_FLEX_DATA_STATUS_SET_CHORD_NAME = 6+};+enum cmidi2_flex_data_metadata_text_status+{+  CMIDI2_FLEX_DATA_STATUS_UNKNOWN_METADATA_TEXT = 0,+  CMIDI2_FLEX_DATA_STATUS_PROJECT_NAME = 1,+  CMIDI2_FLEX_DATA_STATUS_SONG_NAME = 2,+  CMIDI2_FLEX_DATA_STATUS_CLIP_NAME = 3,+  CMIDI2_FLEX_DATA_STATUS_COPYRIGHT_NAME = 4,+  CMIDI2_FLEX_DATA_STATUS_COMPOSER_NAME = 5,+  CMIDI2_FLEX_DATA_STATUS_LYRICIST_NAME = 6,+  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_RECORDING_DATE = 11,+  CMIDI2_FLEX_DATA_STATUS_RECORDING_LOCATION = 12+};+enum cmidi2_flex_data_performance_text_status+{+  CMIDI2_FLEX_DATA_STATUS_UNKNOWN_PERFORMANCE_TEXT = 0,+  CMIDI2_FLEX_DATA_STATUS_LYRICS = 1,+  CMIDI2_FLEX_DATA_STATUS_LYRICS_LANGUAGE = 2,+  CMIDI2_FLEX_DATA_STATUS_RUBY = 3,+  CMIDI2_FLEX_DATA_STATUS_RUBY_LANGUAGE = 4+};++enum cmidi2_ump_chord_name_sharps_flats+{+  CMIDI2_UMP_CHORD_NAME_DOUBLE_SHARP = 2,+  CMIDI2_UMP_CHORD_NAME_SHARP = 1,+  CMIDI2_UMP_CHORD_NAME_NATURAL = 0,+  CMIDI2_UMP_CHORD_NAME_FLAT = 0xF,+  CMIDI2_UMP_CHORD_NAME_DOUBLE_FLAT = 0xE,+};++enum cmidi2_ump_chord_name_tonic_note+{+  CMIDI2_UMP_CHORD_NAME_UNKNOWN = 0,+  CMIDI2_UMP_CHORD_NAME_A = 1,+  CMIDI2_UMP_CHORD_NAME_B = 2,+  CMIDI2_UMP_CHORD_NAME_C = 3,+  CMIDI2_UMP_CHORD_NAME_D = 4,+  CMIDI2_UMP_CHORD_NAME_E = 5,+  CMIDI2_UMP_CHORD_NAME_F = 6,+  CMIDI2_UMP_CHORD_NAME_G = 7,+};++enum cmidi2_ump_chor_name_chord_type+{+  CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0,+  CMIDI2_UMP_CHORD_TYPE_MAJOR = 1,+  CMIDI2_UMP_CHORD_TYPE_MAJOR_6TH = 2,+  CMIDI2_UMP_CHORD_TYPE_MAJOR_7TH = 3,+  CMIDI2_UMP_CHORD_TYPE_MAJOR_9TH = 4,+  CMIDI2_UMP_CHORD_TYPE_MAJOR_11TH = 5,+  CMIDI2_UMP_CHORD_TYPE_MAJOR_13TH = 6,+  CMIDI2_UMP_CHORD_TYPE_MINOR = 7,+  CMIDI2_UMP_CHORD_TYPE_MINOR_6TH = 8,+  CMIDI2_UMP_CHORD_TYPE_MINOR_7TH = 9,+  CMIDI2_UMP_CHORD_TYPE_MINOR_9TH = 10,+  CMIDI2_UMP_CHORD_TYPE_MINOR_11TH = 11,+  CMIDI2_UMP_CHORD_TYPE_MINOR_13TH = 12,+  CMIDI2_UMP_CHORD_TYPE_DOMINANT = 13,+  CMIDI2_UMP_CHORD_TYPE_DOMINANT_9TH = 14,+  CMIDI2_UMP_CHORD_TYPE_DOMINANT_11TH = 15,+  CMIDI2_UMP_CHORD_TYPE_DOMINANT_13TH = 16,+  CMIDI2_UMP_CHORD_TYPE_AUGMENTED = 17,+  CMIDI2_UMP_CHORD_TYPE_AUGMENTED_7TH = 18,+  CMIDI2_UMP_CHORD_TYPE_DIMINISHED = 19,+  CMIDI2_UMP_CHORD_TYPE_DIMINISHED_7TH = 20,+  CMIDI2_UMP_CHORD_TYPE_HALF_DIMINISHED = 21,+  CMIDI2_UMP_CHORD_TYPE_MAJOR_MINOR = 22,+  CMIDI2_UMP_CHORD_TYPE_MINOR_MAJOR = 22, // same+  CMIDI2_UMP_CHORD_TYPE_PEDAL = 23,+  CMIDI2_UMP_CHORD_TYPE_POWER = 24,+  CMIDI2_UMP_CHORD_TYPE_SUSPENDED_2ND = 25,+  CMIDI2_UMP_CHORD_TYPE_SUSPENDED_4TH = 26,+  CMIDI2_UMP_CHORD_TYPE_7_SUSPENDED_4TH = 27,+};++static inline uint8_t cmidi2_ump_get_num_bytes(uint32_t data)+{+  switch (((data & 0xF0000000) >> 28) & 0xF)+  {+    case CMIDI2_MESSAGE_TYPE_UTILITY:+    case CMIDI2_MESSAGE_TYPE_SYSTEM:+    case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:+      return 4;+    case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:+    case CMIDI2_MESSAGE_TYPE_SYSEX7:+      return 8;+    case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:+      return 16;+  }+  return 0xFF; /* wrong */+}++typedef struct cmidi2_ump128+{+  uint32_t p1;+  uint32_t p2;+  uint32_t p3;+  uint32_t p4;+} cmidi2_ump128_t;++typedef struct cmidi2_ump_version+{+  uint8_t major;+  uint8_t minor;+} cmidi2_ump_version_t;++enum cmidi2_ump_stream_status+{+  CMIDI2_UMP_STREAM_STATUS_ENDPOINT_DISCOVERY = 0,+  CMIDI2_UMP_STREAM_STATUS_ENDPOINT_INFO = 1,+  CMIDI2_UMP_STREAM_STATUS_DEVICE_IDENTITY = 2,+  CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME = 3,+  CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID = 4,+  CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_REQUEST = 5,+  CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_NOTIFICATION = 6,+  CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_DISCOVERY = 0x10,+  CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_INFO = 0x11,+  CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME = 0x12,+  CMIDI2_UMP_STREAM_STATUS_START_OF_CLIP = 0x20,+  CMIDI2_UMP_STREAM_STATUS_END_OF_CLIP = 0x21,+};++enum cmidi2_ump_endpoint_filter_flags+{+  CMIDI2_UMP_ENDPOINT_FILTER_ENDPOINT_INFO = 1,+  CMIDI2_UMP_ENDPOINT_FILTER_DEVICE_IDENTITY = 2,+  CMIDI2_UMP_ENDPOINT_FILTER_ENDPOINT_NAME = 4,+  CMIDI2_UMP_ENDPOINT_FILTER_PRODUCT_INSTANCE_ID = 8,+  CMIDI2_UMP_ENDPOINT_FILTER_STREAM_CONFIGURATION = 0x10,+};++enum cmidi2_ump_function_block_discovery_flags+{+  CMIDI2_UMP_FUNCTION_BLOCK_FILTER_INFO = 1,+  CMIDI2_UMP_FUNCTION_BLOCK_FILTER_NAME = 2,+};++// --------+// UMP generators++// 7.1 UMP Stream Messages++static inline cmidi2_ump128_t+cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap)+{+  cmidi2_ump128_t ret+      = {(uint32_t)((CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+                    + (CMIDI2_UMP_STREAM_STATUS_ENDPOINT_DISCOVERY << 16) + (version.major << 8)+                    + version.minor),+         (uint32_t)(filterBitmap & 0x1F), 0, 0};+  return ret;+}++static inline 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)+{+  cmidi2_ump128_t ret = {+      (uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+          + (CMIDI2_UMP_STREAM_STATUS_ENDPOINT_INFO << 16) + (version.major << 8) + version.minor,+      (uint32_t)(isStaticFunctionBlock ? 1 << 31 : 0) + (numFunctionBlocks << 24)+          + (midi2Capable ? 0x1000 : 0) + (midi1Capable ? 0x100 : 0) + (rxJR ? 2 : 0)+          + (txJR ? 1 : 0),+      0, 0};+  return ret;+}++static inline 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)+{+  (void)deviceFamilyModelMSB;+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+             + (CMIDI2_UMP_STREAM_STATUS_DEVICE_IDENTITY << 16),+         manufacturerIdIn7bitArray,+         (uint32_t)(deviceFamilyLSB << 24) + (deviceFamilyMSB << 16) + (deviceFamilyModelLSB << 8)+             + deviceFamilyMSB,+         softwareRevisionIn7bitArray};+  return ret;+}++static inline cmidi2_ump128_t+cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14])+{+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) + (statusCode << 16) + (name[0] << 8)+             + name[1],+         (uint32_t)(name[2] << 24) + (name[3] << 16) + (name[4] << 8) + name[5],+         (uint32_t)(name[6] << 24) + (name[7] << 16) + (name[8] << 8) + name[9],+         (uint32_t)(name[10] << 24) + (name[11] << 16) + (name[12] << 8) + name[13]};+  return ret;+}++static inline 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])+{+  return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID, id);+}++static inline cmidi2_ump128_t+cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR)+{+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+             + (CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_REQUEST << 16) + (protocol << 8)+             + (rxJR ? 2 : 0) + (txJR ? 1 : 0),+         0, 0, 0};+  return ret;+}++static inline cmidi2_ump128_t+cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool txJR)+{+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+             + (CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_NOTIFICATION << 16) + (protocol << 8)+             + (rxJR ? 2 : 0) + (txJR ? 1 : 0),+         0, 0, 0};+  return ret;+}++static inline cmidi2_ump128_t+cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter)+{+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+             + (CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_DISCOVERY << 16) + (numFunctionBlocks << 8)+             + filter,+         0, 0, 0};+  return ret;+}++static inline 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)+{+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+             + (CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_INFO << 16) + (active ? 0x8000 : 0)+             + (numFunctionBlocks << 8) + (uiHint << 4) + (midi1 << 2) + direction,+         (uint32_t)(firstGroup << 24) + (numSpannedGroup << 16) + (midiCIVersionFormat << 8)+             + maxNumSysEx8Streams,+         0, 0};+  return ret;+}++static inline 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()+{+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+             + (CMIDI2_UMP_STREAM_STATUS_START_OF_CLIP << 16),+         0, 0, 0};+  return ret;+}++static inline cmidi2_ump128_t cmidi2_ump_end_of_clip()+{+  cmidi2_ump128_t ret+      = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+             + (CMIDI2_UMP_STREAM_STATUS_END_OF_CLIP << 16),+         0, 0, 0};+  return ret;+}++// 7.2 Utility Messages+static inline uint32_t cmidi2_ump_noop(uint8_t group)+{+  return (group & 0xF) << 24;+}++static inline uint32_t cmidi2_ump_jr_clock_direct(uint8_t group, uint32_t senderClockTime)+{+  return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime;+}++static inline uint32_t cmidi2_ump_jr_clock(uint8_t group, 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;+}++static inline uint32_t cmidi2_ump_jr_timestamp_direct(uint8_t group, uint16_t senderClockTimestamp)+{+  return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16)+         + senderClockTimestamp;+}++static inline uint32_t cmidi2_ump_jr_timestamp(uint8_t group, 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;+}++static inline uint32_t cmidi2_ump_dctpq(uint8_t group, uint32_t dctpq)+{+  return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq;+}++static inline uint32_t cmidi2_ump_dcs(uint8_t group, 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);+}++// 7.6 System Common and System Real Time Messages+static inline 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)+         + ((midi1Byte2 & 0x7F) << 8) + (midi1Byte3 & 0x7F);+}++// 7.3 MIDI 1.0 Channel Voice Messages+static inline 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+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+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+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+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)+{+  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)+{+  return cmidi2_ump_midi1_message(+      group, CMIDI2_STATUS_CAF, channel, data & 0x7F, MIDI_2_0_RESERVED);+}++static inline 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+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)+{+  data += 8192;+  return cmidi2_ump_midi1_message(+      group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F);+}++// 7.4 MIDI 2.0 Channel Voice Messages+static inline 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)+{+  return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24)+           + (((code & 0xF0) + (channel & 0xF)) << 16) + (byte3 << 8) + byte4)+          << 32)+         + ((uint64_t)short1 << 16) + short2;+}++static inline 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)+           + (((code & 0xF0) + (channel & 0xF)) << 16) + (byte3 << 8) + byte4)+          << 32)+         + rest;+}++static inline 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;+  double microtone = actual - dec;+  return (dec << 9) + (int)(microtone * 512.0);+}++static inline 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;+  ret += (int)(actual * 512.0);+  return ret;+}++static inline 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)+{+  return cmidi2_ump_midi2_channel_message_8_8_16_16(+      group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, attributeType, velocity, attributeData);+}++static inline 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)+{+  return cmidi2_ump_midi2_channel_message_8_8_16_16(+      group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, attributeType, velocity, attributeData);+}++static inline 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(+    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(+    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(+    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+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(+    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(+    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(+    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_RELATIVE_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F,+      dataAkaDTE);+}++static inline 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(+      group, CMIDI2_STATUS_RELATIVE_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F,+      dataAkaDTE);+}++static inline int64_t cmidi2_ump_midi2_program(+    uint8_t group, uint8_t channel, uint8_t optionFlags, uint8_t program, uint8_t bankMSB,+    uint8_t bankLSB)+{+  return cmidi2_ump_midi2_channel_message_8_8_32(+      group, CMIDI2_STATUS_PROGRAM, channel, MIDI_2_0_RESERVED, optionFlags & 1,+      ((program & 0x7F) << 24) + (bankMSB << 8) + bankLSB);+}++static inline 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+cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsignedData)+{+  return cmidi2_ump_midi2_channel_message_8_8_32(+      group, CMIDI2_STATUS_PITCH_BEND, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED,+      unsignedData);+}++static inline 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(+    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+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);+}++// Common utility functions for sysex support++static inline 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)+{+  return (uint8_t)(src >> ((7 - index) * 8) & 0xFF);+}++static inline uint8_t cmidi2_ump_sysex_get_num_packets(uint8_t numBytes, uint8_t radix)+{+  return numBytes <= radix ? 1 : numBytes / radix + (numBytes % radix ? 1 : 0);+}++static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence)+{+  const uint8_t* bytes = (const uint8_t*)sequence;+  uint32_t ret = 0;+  for (int i = 0; i < 4; i++)+    ret += ((uint32_t)bytes[i]) << (i * 8);+  return ret;+}++static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence)+{+  const uint8_t* bytes = (const uint8_t*)sequence;+  uint32_t ret = 0;+  for (int i = 0; i < 4; i++)+    ret += ((uint32_t)bytes[i]) << ((3 - i) * 8);+  return ret;+}++static inline 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)+{+  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)+{+  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)+{+  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)+{+  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,+    int32_t index, enum cmidi2_message_type messageType, int radix, bool hasStreamId,+    uint8_t streamId)+{+  uint8_t dst8[16];+  memset(dst8, 0, 16);+  const uint8_t* src8 = (const uint8_t*)srcData;++  dst8[0] = (messageType << 4) + (group & 0xF);++  enum cmidi2_sysex_status status;+  uint8_t size;+  if (numBytes <= radix)+  {+    status = CMIDI2_SYSEX_IN_ONE_UMP;+    size = numBytes; // single packet message+  }+  else if (index == 0)+  {+    status = CMIDI2_SYSEX_START;+    size = radix;+  }+  else+  {+    uint8_t isEnd = index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1;+    if (isEnd)+    {+      size = numBytes % radix ? numBytes % radix : radix;+      status = CMIDI2_SYSEX_END;+    }+    else+    {+      size = radix;+      status = CMIDI2_SYSEX_CONTINUE;+    }+  }+  dst8[1] = status + size + (hasStreamId ? 1 : 0);++  if (hasStreamId)+    dst8[2] = streamId;++  uint8_t dstOffset = hasStreamId ? 3 : 2;+  for (uint8_t i = 0, j = index * radix; i < size; i++, j++)+    dst8[i + dstOffset] = src8[j];++  *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+  if (result2)+    *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++// 7.7 System Exclusive 7-Bit Messages+static inline 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)+{+  return (((uint64_t)((CMIDI2_MESSAGE_TYPE_SYSEX7 << 28) + ((group & 0xF) << 24)+                      + ((status + numBytes) << 16)))+          << 32)+         + ((uint64_t)data1 << 40) + ((uint64_t)data2 << 32) + (data3 << 24) + (data4 << 16)+         + (data5 << 8) + data6;+}++static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData)+{+  int i = 0;+  const uint8_t* csrc = (const uint8_t*)srcData;+  while (csrc[i] != 0xF7)+    i++;+  /* This function automatically detects if 0xF0 is prepended and reduce length if it is. */+  return i - (csrc[0] == 0xF0 ? 1 : 0);+}++static inline uint8_t cmidi2_ump_sysex7_get_num_packets(uint8_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)+{+  uint64_t result;+  int srcOffset = numBytes > 0 && ((const uint8_t*)srcData)[0] == 0xF0 ? 1 : 0;+  cmidi2_ump_sysex_get_packet_of(+      &result, NULL, group, numBytes, (const uint8_t*)srcData + srcOffset, index,+      CMIDI2_MESSAGE_TYPE_SYSEX7, 6, false, 0);+  return result;+}++/* process() - more complicated function */++// This returns NULL for success, or anything else for failure.+typedef void* (*cmidi2_ump_handler_u64)(uint64_t data, void* context);++// 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)+{+  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++)+  {+    int64_t ump = cmidi2_ump_sysex7_get_packet_of(group, length, sysex, p);+    void* retCode = sendUMP(ump, context);+    if (retCode != 0)+      return retCode;+  }+  return NULL;+}++// 7.8 System Exclusive 8-Bit Messages++static inline int8_t cmidi2_ump_sysex8_get_num_packets(uint8_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,+    uint64_t* result1, uint64_t* result2)+{+  cmidi2_ump_sysex_get_packet_of(+      result1, result2, group, numBytes, srcData, index, CMIDI2_MESSAGE_TYPE_SYSEX8_MDS, 13, true,+      streamId);+}++/* process() - more complicated function */++// This returns NULL for success, or anything else for failure.+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(+    uint8_t group, void* sysex, uint32_t length, uint8_t streamId, cmidi2_ump_handler_u128 sendUMP,+    void* context)+{+  uint32_t numPackets = cmidi2_ump_sysex8_get_num_packets(length);+  for (size_t p = 0; p < numPackets; p++)+  {+    uint64_t result1, result2;+    cmidi2_ump_sysex8_get_packet_of(group, streamId, length, sysex, p, &result1, &result2);+    void* retCode = sendUMP(result1, result2, p, context);+    if (retCode != 0)+      return retCode;+  }+  return NULL;+}++// 7.9 Mixed Data Set Message++static inline 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)+{+  if (numTotalBytesinChunk > 14 * 65535)+    return -1;+  return numTotalBytesinChunk / 14 + (numTotalBytesinChunk % 14 ? 1 : 0);+}++static inline 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)+{+  uint8_t dst8[16];+  memset(dst8, 0, 16);++  dst8[0] = (CMIDI2_MESSAGE_TYPE_SYSEX8_MDS << 4) + (group & 0xF);+  dst8[1] = CMIDI2_MIXED_DATA_STATUS_HEADER + mdsId;+  *((uint16_t*)(void*)(dst8 + 2)) = numBytesInChunk;+  *((uint16_t*)(void*)(dst8 + 4)) = numChunks;+  *((uint16_t*)(void*)(dst8 + 6)) = chunkIndex;+  *((uint16_t*)(void*)(dst8 + 8)) = manufacturerId;+  *((uint16_t*)(void*)(dst8 + 10)) = deviceId;+  *((uint16_t*)(void*)(dst8 + 12)) = subId;+  *((uint16_t*)(void*)(dst8 + 14)) = subId2;++  *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+  if (result2)+    *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++// srcData points to exact start of the source data.+static inline 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)+{+  uint8_t dst8[16];+  memset(dst8, 0, 16);+  const uint8_t* src8 = (const uint8_t*)srcData;++  dst8[0] = (CMIDI2_MESSAGE_TYPE_SYSEX8_MDS << 4) + (group & 0xF);+  dst8[1] = CMIDI2_MIXED_DATA_STATUS_PAYLOAD + mdsId;++  uint8_t radix = 14;+  uint8_t size = numBytes < radix ? numBytes % radix : radix;++  for (uint8_t i = 0; i < size; i++)+    dst8[i + 2] = src8[i];++  *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+  if (result2)+    *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++/* process() - more complicated function */+// This returns NULL for success, or anything else for failure.+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(+    uint8_t group, uint8_t mdsId, void* data, uint32_t length, cmidi2_mds_handler sendUMP,+    void* context)+{+  int32_t numChunks = cmidi2_ump_mds_get_num_chunks(length);+  for (int c = 0; c < numChunks; c++)+  {+    int32_t maxChunkSize = 14 * 65535;+    int32_t chunkSize = c + 1 == numChunks ? (int32_t)(length % maxChunkSize) : maxChunkSize;+    int32_t numPayloads = cmidi2_ump_mds_get_num_payloads(chunkSize);+    for (int p = 0; p < numPayloads; p++)+    {+      uint64_t result1, result2;+      size_t offset = 14 * (65536 * c + p);+      cmidi2_ump_mds_get_payload_of(+          group, mdsId, chunkSize, (uint8_t*)data + offset, &result1, &result2);+      void* retCode = sendUMP(result1, result2, c, p, context);+      if (retCode)+        return retCode;+    }+  }+  return NULL;+}++// 7.5 Flex Data Message++static inline 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(+    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)+{+  uint8_t dst8[4];+  memset(dst8, 0, 4);+  dst8[0] = (CMIDI2_MESSAGE_TYPE_FLEX_DATA << 4) + (group & 0xF);+  dst8[1] = (CMIDI2_SYSEX_IN_ONE_UMP << 2) + ((addressing & 0x3) << 4) + (channel & 0xF);+  dst8[2] = statusBank;+  dst8[3] = statusCode;++  *result1 = ((uint64_t)cmidi2_ump_read_uint32_bytes_be(dst8) << 32) + data1;+  *result2 = ((uint64_t)data2 << 32) + data3;+}++static inline 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)+{+  uint8_t dst8[16];+  memset(dst8, 0, 16);+  const uint8_t* src8 = (const uint8_t*)srcData;++  dst8[0] = (CMIDI2_MESSAGE_TYPE_FLEX_DATA << 4) + (group & 0xF);++  const size_t radix = 12;+  enum cmidi2_sysex_status format;+  uint8_t size;+  if (numBytes <= radix)+  {+    format = CMIDI2_SYSEX_IN_ONE_UMP;+    size = numBytes; // single packet message+  }+  else if (currentPacket == 0)+  {+    format = CMIDI2_SYSEX_START;+    size = radix;+  }+  else+  {+    uint8_t isEnd = currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1;+    if (isEnd)+    {+      size = numBytes % radix ? numBytes % radix : radix;+      format = CMIDI2_SYSEX_END;+    }+    else+    {+      size = radix;+      format = CMIDI2_SYSEX_CONTINUE;+    }+  }+  dst8[1] = (format << 2) + ((addressing & 0x3) << 4) + (channel & 0xF);+  dst8[2] = statusBank;+  dst8[3] = statusCode;++  for (uint8_t i = 0; i < size; i++)+    dst8[i + 4] = src8[i];++  *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+  *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++/* process() - more complicated function */+// This returns NULL for success, or anything else for failure.+typedef void* (*cmidi2_flex_data_handler)(uint64_t data1, uint64_t data2, void* context);++// 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(+    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)+{+  int32_t numPackets = cmidi2_ump_flex_data_get_num_packets(length);+  for (int p = 0; p < numPackets; p++)+  {+    uint64_t result1, result2;+    cmidi2_ump_flex_data_get_packet_of(+        group, addressing, channel, statusBank, statusCode, length, text, p, &result1, &result2);+    void* retCode = sendUMP(result1, result2, context);+    if (retCode != 0)+      return retCode;+  }+  return NULL;+}++// individual flex data message generators++static inline void cmidi2_ump_flex_data_set_tempo_direct(+    uint8_t group, uint8_t channel, uint32_t tempoIn10NanosecondsPerQN, uint64_t* result1,+    uint64_t* result2)+{+  cmidi2_ump_flex_data_complete_packet(+      group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+      CMIDI2_FLEX_DATA_STATUS_SET_TEMPO, tempoIn10NanosecondsPerQN, 0, 0, result1, result2);+}++static inline 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)+{+  cmidi2_ump_flex_data_complete_packet(+      group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+      CMIDI2_FLEX_DATA_STATUS_SET_TIME_SIGNATURE,+      (numerator << 24) + (denominator << 16) + (numberOf32thNotes << 8), 0, 0, result1, result2);+}++static inline 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)+{+  cmidi2_ump_flex_data_complete_packet(+      group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+      CMIDI2_FLEX_DATA_STATUS_SET_METRONOME,+      (clocksPerPrimaryClick << 24) + (barAccent1 << 16) + (barAccent2 << 8) + barAccent3,+      (subDivisionClicks1 << 24) + (subDivisionClicks2 << 16), 0, result1, result2);+}++static inline 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)+{+  cmidi2_ump_flex_data_complete_packet(+      group, addressing, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+      CMIDI2_FLEX_DATA_STATUS_SET_KEY_SIGNATURE, (sharpsFlats << 24) + (tonicNote << 16), 0, 0,+      result1, result2);+}++static inline 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,+    uint8_t alter4Degree, uint8_t bassSharpsFlats, uint8_t bassNote, uint8_t bassChordType,+    uint8_t bassAlter1Type, uint8_t bassAlter1Degree, uint8_t bassAlter2Type,+    uint8_t bassAlter2Degree, uint64_t* result1, uint64_t* result2)+{+  cmidi2_ump_flex_data_complete_packet(+      group, addressing, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+      CMIDI2_FLEX_DATA_STATUS_SET_CHORD_NAME,+      (sharpsFlats << 28) + (chordTonic << 24) + (chordType << 16) + (alter1Type << 12)+          + (alter1Degree << 8) + (alter2Type << 4) + alter2Degree,+      (alter3Type << 28) + (alter3Degree << 24) + (alter4Type << 20) + (alter4Degree << 16),+      (bassSharpsFlats << 28) + (bassNote << 24) + (bassChordType << 16) + (bassAlter1Type << 12)+          + (bassAlter1Degree << 8) + (bassAlter2Type << 4) + bassAlter2Degree,+      result1, result2);+}++// --------+// Strongly-typed(?) UMP.+// I kind of think those getters are overkill, so I would collect almost use `cmidi2_ump_get_xxx()`+// as those strongly typed functions, so that those who don't want them can safely ignore them.++typedef uint32_t cmidi2_ump;++static inline 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)+{+  dst[0] = value >> 32;+  dst[1] = value & 0xFFFFFFFF;+}++static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2)+{+  dst[0] = value1 >> 32;+  dst[1] = value1 & 0xFFFFFFFF;+  dst[2] = value2 >> 32;+  dst[3] = value2 & 0xFFFFFFFF;+}++static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at)+{+  ump += at / 4;+  switch (at % 4)+  {+    case 0:+      return (*ump & 0xFF000000) >> 24;+    case 1:+      return (*ump & 0xFF0000) >> 16;+    case 2:+      return (*ump & 0xFF00) >> 8;+    case 3:+      return *ump & 0xFF;+  }+  return 0; // This is unexpected.+}++static inline 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)+{+  switch (cmidi2_ump_get_message_type(ump))+  {+    case CMIDI2_MESSAGE_TYPE_UTILITY:+    case CMIDI2_MESSAGE_TYPE_SYSTEM:+    case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:+      return 4;+    case CMIDI2_MESSAGE_TYPE_SYSEX7:+    case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:+      return 8;+    case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:+      return 16;+  }+  return 0; // invalid+}++static inline 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)+{+  return (*ump >> 16) & 0xF0;+}++static inline 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)+{+  return *(ump + 1);+}++static inline 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)+{+  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)+{+  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)+{+  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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  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)+{+  return cmidi2_ump_get_channel(ump); // same bits+}++static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 3);+}+static inline 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)+{+  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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_32_to_64(ump);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 3);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 2);+}+static inline 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)+{+  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)+{+  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)+{+  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)+{+  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)+{+  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)+{+  return cmidi2_ump_get_32_to_64(ump);+}+static inline 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)+{+  return cmidi2_ump_get_byte_at(ump, 4);+}+static inline 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)+{+  return (cmidi2_ump_get_byte_at(ump, 7));+}+static inline 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)+{+  return cmidi2_ump_get_32_to_64(ump);+}+static inline 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)+{+  return cmidi2_ump_get_channel(ump); // same bits+}+static inline 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)+{+  return cmidi2_ump_get_channel(ump); // same bits+}+static inline 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)+{+  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)+{+  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)+{+  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)+{+  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)+{+  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)+{+  return (cmidi2_ump_get_byte_at(ump, 14) << 8) + cmidi2_ump_get_byte_at(ump, 15);+}++// --------+// Realtime-safe SysEx8 binary reader+//+// The primary end-user facing function is `cmidi2_ump_get_sysex8_data()`, but the+// function signature needs some explanation as it requires some complicated processing.+//+// There can be multiple SysEx8 "streams" that simultaneously run within a UMP stream+// (e.g. sysex8 start packet for stream #1, sysex8 continue packet for stream #1,+//  sysex8 start packet for stream #2, sysex8 continue packet for stream #1,+//  sysex8 continue packet for stream #2, sysex8 complete packet for stream #3,+//  sysex8 end packet for stream #2, sysex8 end packet for strean #1 ...).+//+// Those stream state must be preserved during UMP parsing. It is represented as+// `cmidi2_ump_binary_read_state` struct.+//+// There are handful of usage scenarios where we...+// - want to handle them all+// - want to handle only relevant ones+// - can control what we generate and determine that only one stream can appear so+//   the context stream is always obvious+//+// To handle them all, we need "stream selector". There is a function type+// `cmidi2_ump_stream_selector_func`, and a function pointer is passed to the entry+// point `cmidi2_ump_get_sysex8_data()` function.+//+// Another control point is where we want to finish parsing sysex8. Whenever a relevant+// stream is parsed, `cmidi2_ump_get_syex8_data()` calls "continuity checker" function+// which is typed as `cmidi2_ump_binary_read_continuity_checker_func`.+// If this function returns true, then it completes parsing, returning the number of+// the parsed UMPs (in uint32_t length).+//+// See `testType5Messages_sysex8_reader_writer()` testcase for the actual usage example.+//++enum cmidi2_ump_binary_reader_result_code+{+  CMIDI2_BINARY_READER_RESULT_INCOMPLETE = 0,+  CMIDI2_BINARY_READER_RESULT_COMPLETE = 1,+  CMIDI2_BINARY_READER_RESULT_NO_SPACE = 2,+};++typedef struct cmidi2_ump_binary_read_state+{+  void* context;+  uint8_t* data;+  size_t dataCapacity;+  size_t dataSize;+  bool continueOnCompletion;+  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)+{+  state->dataSize = 0;+  state->resultCode = CMIDI2_BINARY_READER_RESULT_INCOMPLETE;+}++static inline void cmidi2_ump_binary_read_state_init(+    cmidi2_ump_binary_read_state* state, void* context, uint8_t* dataBuffer, size_t dataCapacity,+    bool continueOnCompletion)+{+  state->context = context;+  state->data = dataBuffer;+  state->dataCapacity = dataCapacity;+  state->continueOnCompletion = continueOnCompletion;+  cmidi2_ump_binary_read_state_reset(state);+}++/// Binary stream selector for cmidi2_ump_get_sysex8_data() function.+/// If you do not support simultaneous streams, just return a reference to a fixed `cmidi2_ump_binary_read_state` instance.+/// If the targetSteramId does not indicate the streams the client handles, then it should return NULL.+/// Note that the resulting state must contain a non-null data and valid dataCapacity i.e. it must be already assigned.+/// Also note that the function implementation should not try to allocate a new instance of state+/// - otherwise it will break realtime safety.+typedef cmidi2_ump_binary_read_state* (*cmidi2_ump_stream_selector_func)(+    uint8_t targetStreamId, void* context);++/// Binary stream continuity checker for cmidi2_ump_get_sysex8_data() function.+/// If you do not support simultaneous streams, then it should reset stream state (dataSize etc.) when new stream starts.+/// Return true to continue parsing, otherwise return false. It is useful to determine whether it+/// should break or not when a stream is fully read (status code is 3 = CMIDI2_SYSEX_END).+/// `cmidi2_ump_get_sysex8_data()` can take NULL for this function pointer at `continuityChecker` argument,+/// then it falls back to the "default" behavior.+/// By default, it finishes parsing when a stream completed and `continueOnCompletion` was `false`.+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(+    uint8_t* dst, uint8_t srcHead, uint32_t* srcTail, size_t sizeInBytes)+{+  if (sizeInBytes == 0)+    return;++  // the first byte is at the lowest byte so it is always safe to copy as is.+  dst[0] = srcHead;+  uint8_t* d = dst + 1;+  uint32_t* s = srcTail;+  sizeInBytes--;++  // copy the rest+  size_t i = 0;+  while (i + 3 < sizeInBytes)+  {+    uint32_t i32 = *s;+    d[i++] = i32 >> 24;+    d[i++] = (i32 >> 16) & 0xFF;+    d[i++] = (i32 >> 8) & 0xFF;+    d[i++] = i32 & 0xFF;+    s++;+  }+  for (; i < sizeInBytes; i++)+    d[i] = s[i + 3 - i % 4];+}++/// 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(+    cmidi2_ump_stream_selector_func streamSelector, void* streamSelectorContext,+    cmidi2_ump_binary_read_continuity_checker_func continuityChecker, const cmidi2_ump* ump,+    const size_t umpCapacityInInt)+{++  cmidi2_ump *umpPtr = (cmidi2_ump*)ump, *umpEnd = (cmidi2_ump*)ump + umpCapacityInInt;+  for (; umpPtr < umpEnd; umpPtr += cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump))+  {+    if (cmidi2_ump_get_message_type(umpPtr) != CMIDI2_MESSAGE_TYPE_SYSEX8_MDS)+      continue;+    switch (cmidi2_ump_get_status_code(umpPtr))+    {+      case CMIDI2_SYSEX_IN_ONE_UMP:+      case CMIDI2_SYSEX_START:+      case CMIDI2_SYSEX_END:+      case CMIDI2_SYSEX_CONTINUE:+        break;+      default:+        continue; // the only expected value here is MDS (8 or 9)+    }++    cmidi2_ump_binary_read_state* state+        = streamSelector(cmidi2_ump_get_sysex8_stream_id(umpPtr), streamSelectorContext);+    if (state == NULL)+      continue;++    size_t copySize = cmidi2_ump_get_sysex8_num_bytes(umpPtr)+                      - 1; // SysEx8 size field contains the size byte itself, hence -1.+    if (state->dataSize + copySize >= state->dataCapacity)+    {+      state->resultCode = CMIDI2_BINARY_READER_RESULT_NO_SPACE;+      return umpPtr - ump;+    }++    if (cmidi2_util_is_platform_little_endian())+      // We need to swap data along with byte order for resulting data...+      cmidi2_internal_sysex8_copy_data_byte_swapping(+          state->data + state->dataSize, (*umpPtr) & 0xFF, umpPtr + 1, copySize);+    else+      memcpy(state->data + state->dataSize, ((uint8_t*)(void*)umpPtr) + 3, copySize);++    state->dataSize += copySize;++    if (continuityChecker != NULL)+      if (continuityChecker(state, umpPtr))+        return umpPtr + cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump)+               - ump; // "break here" is indicated++    // otherwise default continuity checker+    switch (cmidi2_ump_get_status_code(umpPtr))+    {+      case CMIDI2_SYSEX_IN_ONE_UMP:+      case CMIDI2_SYSEX_END:+        state->resultCode = CMIDI2_BINARY_READER_RESULT_COMPLETE;+        if (state->continueOnCompletion)+          return umpPtr + cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump)+                 - ump; // default "break here" condition.+        break;+    }+  }+  // finished parsing while no stream indicated "break here" for completion.+  return umpPtr - ump;+}++// --------+// sequence iterator++/* byte stream splitter */++static inline 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; \+       (iter) = (uint8_t*)cmidi2_ump_sequence_next(iter))++static inline 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; \+       (iter) = (uint8_t*)cmidi2_ump_sequence_next_le(iter))++static inline 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; \+       (iter) = (uint8_t*)cmidi2_ump_sequence_next_be(iter))++// --------+// MIDI CI support.++#define CMIDI2_CI_SUB_ID 0xD+#define CMIDI2_CI_SUB_ID_2_DISCOVERY_INQUIRY 0x70+#define CMIDI2_CI_SUB_ID_2_DISCOVERY_REPLY 0x71+#define CMIDI2_CI_SUB_ID_2_INVALIDATE_MUID 0x7E+#define CMIDI2_CI_SUB_ID_2_ACK 0x7F+#define CMIDI2_CI_SUB_ID_2_NAK 0x7F+#define CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_INQUIRY 0x10+#define CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_REPLY 0x11+#define CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL 0x12+#define CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_I2R 0x13+#define CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_R2I 0x14+#define CMIDI2_CI_SUB_ID_2_CONFIRM_NEW_PROTOCOL_ESTABLISHED 0x15+#define CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY 0x20+#define CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY_REPLY 0x21+#define CMIDI2_CI_SUB_ID_2_SET_PROFILE_ON 0x22+#define CMIDI2_CI_SUB_ID_2_SET_PROFILE_OFF 0x23+#define CMIDI2_CI_SUB_ID_2_PROFILE_ENABLED_REPORT 0x24+#define CMIDI2_CI_SUB_ID_2_PROFILE_DISABLED_REPORT 0x25+#define CMIDI2_CI_SUB_ID_2_PROFILE_SPECIFIC_DATA 0x2F+#define CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_INQUIRY 0x30+#define CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY 0x31+#define CMIDI2_CI_SUB_ID_2_PROPERTY_HAS_DATA 0x32+#define CMIDI2_CI_SUB_ID_2_PROPERTY_HAS_DATA_REPLY 0x33+#define CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA 0x34+#define CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA_REPLY 0x35+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA 0x36+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA_REPLY 0x37+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE 0x38+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE_REPLY 0x39+#define CMIDI2_CI_SUB_ID_2_PROPERTY_NOTIFY 0x3F+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES 0x40+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES_REPLY 0x41+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT 0x42+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_REPLY 0x43+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_END 0x44++#define CMIDI2_CI_PROTOCOL_NEGOTIATION_SUPPORTED 2+#define CMIDI2_CI_PROFILE_CONFIGURATION_SUPPORTED 4+#define CMIDI2_CI_PROPERTY_EXCHANGE_SUPPORTED 8++#define CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK 0x7F++typedef struct+{+  uint8_t type;+  uint8_t version;+  uint8_t extensions;+  uint8_t reserved1;+  uint8_t reserved2;+} cmidi2_ci_protocol_type_info;++typedef struct+{+  uint8_t fixed_7e; // 0x7E+  uint8_t bank;+  uint8_t number;+  uint8_t version;+  uint8_t level;+} 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)+{+  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)+{+  buf[0] = v & 0xFF;+  buf[1] = (v >> 8) & 0xFF;+  buf[2] = (v >> 16) & 0xFF;+  buf[3] = (v >> 24) & 0xFF;+}++static inline 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)+{+  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)+{+  buf[0] = v & 0x7F;+  buf[1] = (v >> 7) & 0x7F;+  buf[2] = (v >> 14) & 0x7F;+  buf[3] = (v >> 21) & 0x7F;+}++static inline void cmidi2_ci_message_common(+    uint8_t* buf, uint8_t destination, uint8_t sysexSubId2, uint8_t versionAndFormat,+    uint32_t sourceMUID, uint32_t destinationMUID)+{+  buf[0] = 0x7E;+  buf[1] = destination;+  buf[2] = CMIDI2_CI_SUB_ID;+  buf[3] = sysexSubId2;+  buf[4] = versionAndFormat;+  cmidi2_ci_direct_uint32_at(buf + 5, sourceMUID);+  cmidi2_ci_direct_uint32_at(buf + 9, destinationMUID);+}++// Discovery++static inline 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,+    uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId)+{+  cmidi2_ci_message_common(+      buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, sysexSubId2, versionAndFormat, sourceMUID,+      destinationMUID);+  cmidi2_ci_direct_uint32_at(+      buf + 13,+      deviceManufacturer3Bytes); // the last byte is extraneous, but will be overwritten next.+  cmidi2_ci_direct_uint16_at(buf + 16, deviceFamily);+  cmidi2_ci_direct_uint16_at(buf + 18, deviceFamilyModelNumber);+  // LAMESPEC: Software Revision Level does not mention in which endianness this field is stored.+  cmidi2_ci_direct_uint32_at(buf + 20, softwareRevisionLevel);+  buf[24] = ciCategorySupported;+  cmidi2_ci_direct_uint32_at(buf + 25, receivableMaxSysExSize);+  buf[29] = initiatorOutputPathId;+}++static inline 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)+{+  cmidi2_ci_discovery_common(+      buf, CMIDI2_CI_SUB_ID_2_DISCOVERY_INQUIRY, versionAndFormat, sourceMUID, 0x7F7F7F7F,+      deviceManufacturer, deviceFamily, deviceFamilyModelNumber, softwareRevisionLevel,+      ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId);+}++static inline 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,+    uint8_t initiatorOutputPathId, uint8_t functionBlockOr7Fh)+{+  cmidi2_ci_discovery_common(+      buf, CMIDI2_CI_SUB_ID_2_DISCOVERY_REPLY, versionAndFormat, sourceMUID, destinationMUID,+      deviceManufacturer, deviceFamily, deviceFamilyModelNumber, softwareRevisionLevel,+      ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId);+  buf[30] = functionBlockOr7Fh;+}++static inline void cmidi2_ci_discovery_invalidate_muid(+    uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t targetMUID)+{+  cmidi2_ci_message_common(+      buf, 0x7F, CMIDI2_CI_SUB_ID_2_INVALIDATE_MUID, versionAndFormat, sourceMUID, 0x7F7F7F7F);+  cmidi2_ci_direct_uint32_at(buf + 13, targetMUID);+}++static inline 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)+{+  cmidi2_ci_message_common(+      buf, deviceId, CMIDI2_CI_SUB_ID_2_ACK, versionAndFormat, sourceMUID, destinationMUID);+  buf[13] = originalTransactionSubID2Class;+  buf[14] = statusCode;+  buf[15] = statusData;+  memcpy(buf + 16, details5Bytes, 5);+  cmidi2_ci_direct_uint16_at(buf + 21, messageLength);+  memcpy(buf + 23, messageText, messageLength);+}++static inline 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,+    const char* messageText)+{+  cmidi2_ci_ack_nak_common(+      buf, deviceId, versionAndFormat, sourceMUID, destinationMUID, originalTransactionSubID2Class,+      ackStatusCode, ackStatusData, ackDetails5Bytes, messageLength, messageText);+}++static inline 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,+    const char* messageText)+{+  cmidi2_ci_ack_nak_common(+      buf, deviceId, versionAndFormat, sourceMUID, destinationMUID, originalTransactionSubID2Class,+      nakStatusCode, nakStatusData, nakDetails5Bytes, messageLength, messageText);+}++// 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)+{+  buf[0] = info.type;+  buf[1] = info.version;+  buf[2] = info.extensions;+  buf[3] = info.reserved1;+  buf[4] = info.reserved2;+}++static inline void cmidi2_ci_protocols(+    uint8_t* buf, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes)+{+  buf[0] = numSupportedProtocols;+  for (int i = 0; i < numSupportedProtocols; i++)+    cmidi2_ci_protocol_info(buf + 1 + i * 5, protocolTypes[i]);+}++static inline 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)+{+  cmidi2_ci_message_common(+      buf, 0x7F,+      isReply ? CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_REPLY+              : CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_INQUIRY,+      1, sourceMUID, destinationMUID);+  buf[13] = authorityLevel;+  cmidi2_ci_protocols(buf + 14, numSupportedProtocols, protocolTypes);+}++static inline void cmidi2_ci_protocol_set(+    uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel,+    cmidi2_ci_protocol_type_info newProtocolType)+{+  cmidi2_ci_message_common(+      buf, 0x7F, CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL, 1, sourceMUID, destinationMUID);+  buf[13] = authorityLevel;+  cmidi2_ci_protocol_info(buf + 14, newProtocolType);+}++static inline void cmidi2_ci_protocol_test(+    uint8_t* buf, bool isInitiatorToResponder, uint32_t sourceMUID, uint32_t destinationMUID,+    uint8_t authorityLevel, uint8_t* testData48Bytes)+{+  cmidi2_ci_message_common(+      buf, 0x7F,+      isInitiatorToResponder ? CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_I2R+                             : CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_R2I,+      1, sourceMUID, destinationMUID);+  buf[13] = authorityLevel;+  memcpy(buf + 14, testData48Bytes, 48);+}++static inline void cmidi2_ci_protocol_confirm_established(+    uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel)+{+  cmidi2_ci_message_common(+      buf, 0x7F, CMIDI2_CI_SUB_ID_2_CONFIRM_NEW_PROTOCOL_ESTABLISHED, 1, sourceMUID,+      destinationMUID);+  buf[13] = authorityLevel;+}++static inline 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)+             ? 0+             : buf[14];+}++// Profile Configuration++static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info)+{+  buf[0] = info.fixed_7e;+  buf[1] = info.bank;+  buf[2] = info.number;+  buf[3] = info.version;+  buf[4] = info.level;+}++static inline 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(+    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)+{+  cmidi2_ci_message_common(+      buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY_REPLY, 1, sourceMUID, destinationMUID);+  buf[13] = numEnabledProfiles;+  for (int i = 0; i < numEnabledProfiles; i++)+    cmidi2_ci_profile(buf + 14 + i * 5, enabledProfiles[i]);+  uint32_t pos = 14 + numEnabledProfiles * 5;+  buf[pos++] = numDisabledProfiles;+  for (int i = 0; i < numDisabledProfiles; i++)+    cmidi2_ci_profile(buf + pos + i * 5, disabledProfiles[i]);+}++static inline void cmidi2_ci_profile_set(+    uint8_t* buf, uint8_t destination, bool turnOn, uint32_t sourceMUID, uint32_t destinationMUID,+    cmidi2_profile_id profile)+{+  cmidi2_ci_message_common(+      buf, destination,+      turnOn ? CMIDI2_CI_SUB_ID_2_SET_PROFILE_ON : CMIDI2_CI_SUB_ID_2_SET_PROFILE_OFF, 1,+      sourceMUID, destinationMUID);+  cmidi2_ci_profile(buf + 13, profile);+}++static inline void cmidi2_ci_profile_report(+    uint8_t* buf, uint8_t source, bool isEnabledReport, uint32_t sourceMUID,+    cmidi2_profile_id profile)+{+  cmidi2_ci_message_common(+      buf, source,+      isEnabledReport ? CMIDI2_CI_SUB_ID_2_PROFILE_ENABLED_REPORT+                      : CMIDI2_CI_SUB_ID_2_PROFILE_DISABLED_REPORT,+      1, sourceMUID, 0x7F7F7F7F);+  cmidi2_ci_profile(buf + 13, profile);+}++static inline 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)+{+  cmidi2_ci_message_common(+      buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_SPECIFIC_DATA, 1, sourceMUID, destinationMUID);+  cmidi2_ci_profile(buf + 13, profile);+  cmidi2_ci_direct_uint32_at(buf + 18, dataSize);+  memcpy(buf + 22, data, dataSize);+}++// Property Exchange++static inline void cmidi2_ci_property_get_capabilities(+    uint8_t* buf, uint8_t destination, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID,+    uint8_t maxSupportedRequests)+{+  cmidi2_ci_message_common(+      buf, destination,+      isReply ? CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY+              : CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_INQUIRY,+      1, sourceMUID, destinationMUID);+  buf[13] = maxSupportedRequests;+}++// common to all of: has data & reply, get data & reply, set data & reply, subscribe & reply, notify+static inline 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)+{+  cmidi2_ci_message_common(buf, destination, messageTypeSubId2, 1, sourceMUID, destinationMUID);+  buf[13] = requestId;+  cmidi2_ci_direct_uint16_at(buf + 14, headerSize);+  memcpy(buf + 16, header, headerSize);+  cmidi2_ci_direct_uint16_at(buf + 16 + headerSize, numChunks);+  cmidi2_ci_direct_uint16_at(buf + 18 + headerSize, chunkIndex);+  cmidi2_ci_direct_uint16_at(buf + 20 + headerSize, dataSize);+  memcpy(buf + 22 + headerSize, data, dataSize);+}++static inline 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)+{+  cmidi2_ci_message_common(+      buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK,+      CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY, versionAndFormat, sourceMUID,+      destinationMUID);+  buf[13] = maxSupportedRequests;+  buf[14] = peMajorVersion;+  buf[15] = peMinorVersion;+}++static inline 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,+    const char* propertyData)+{+  cmidi2_ci_message_common(+      buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, subId2, versionAndFormat, sourceMUID,+      destinationMUID);+  buf[13] = requestId;+  buf[14] = headerSize % 0xFF;+  buf[15] = headerSize / 0xFF;+  memcpy(buf + 16, headerData, headerSize);+  cmidi2_ci_direct_uint16_at(buf + headerSize + 16, numChunksInMessage);+  cmidi2_ci_direct_uint16_at(buf + headerSize + 18, currentChunk);+  cmidi2_ci_direct_uint16_at(buf + headerSize + 20, propertyDataLength);+  if (propertyData)+    memcpy(buf + headerSize + 22, propertyData, propertyDataLength);+}++static inline 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)+{+  cmidi2_ci_property_data_common(+      buf, CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA, versionAndFormat, sourceMUID, destinationMUID,+      requestId, headerSize, headerData, 1, 1, 0, NULL);+}++static inline 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)+{+  cmidi2_ci_property_data_common(+      buf, CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA_REPLY, versionAndFormat, sourceMUID,+      destinationMUID, requestId, headerSize, headerData, numChunksInMessage, currentChunk,+      propertyDataLength, propertyData);+}++static inline 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)+{+  cmidi2_ci_property_data_common(+      buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA, versionAndFormat, sourceMUID, destinationMUID,+      requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,+      propertyData);+}++static inline 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)+{+  cmidi2_ci_property_data_common(+      buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA_REPLY, versionAndFormat, sourceMUID,+      destinationMUID, requestId, headerSize, headerData, 1, 1, 0, NULL);+}++static inline 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)+{+  cmidi2_ci_property_data_common(+      buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE, versionAndFormat, sourceMUID, destinationMUID,+      requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,+      propertyData);+}++static inline 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)+{+  cmidi2_ci_property_data_common(+      buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE_REPLY, versionAndFormat, sourceMUID,+      destinationMUID, requestId, headerSize, headerData, numChunksInMessage, currentChunk,+      propertyDataLength, propertyData);+}++static inline 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)+{+  cmidi2_ci_property_data_common(+      buf, CMIDI2_CI_SUB_ID_2_PROPERTY_NOTIFY, versionAndFormat, sourceMUID, destinationMUID,+      requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,+      propertyData);+}++// Process Inquiry++static inline void cmidi2_ci_process_get_capabilities(+    uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,+    uint32_t destinationMUID)+{+  (void)subId2;+  cmidi2_ci_message_common(+      buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES,+      versionAndFormat, sourceMUID, destinationMUID);+}++static inline 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)+{+  (void)subId2;+  cmidi2_ci_message_common(+      buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK,+      CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES_REPLY, versionAndFormat, sourceMUID,+      destinationMUID);+  buf[13] = processInquirySupportedFeatures;+}++static inline 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)+{+  (void)subId2;+  cmidi2_ci_message_common(+      buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES, versionAndFormat, sourceMUID,+      destinationMUID);+  buf[13] = messageDataControl;+  buf[14] = requestedSystemMessages;+  buf[15] = 0; // reserved+  buf[16] = requestedChannelControllerMessages;+  buf[17] = requestedNoteDataMessages;+}++static inline 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)+{+  cmidi2_ci_process_midi_report_common(+      buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT, versionAndFormat, sourceMUID,+      destinationMUID, messageDataControl, requestedSystemMessages,+      requestedChannelControllerMessages, requestedNoteDataMessages);+}++static inline 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)+{+  cmidi2_ci_process_midi_report_common(+      buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_REPLY, versionAndFormat,+      sourceMUID, destinationMUID, messageDataControl, requestedSystemMessages,+      requestedChannelControllerMessages, requestedNoteDataMessages);+}++static inline void cmidi2_ci_process_get_midi_report_end(+    uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,+    uint32_t destinationMUID)+{+  cmidi2_ci_message_common(+      buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_END, versionAndFormat, sourceMUID,+      destinationMUID);+}++// 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)+{+  uint8_t pos = 0;+  for (;; pos++)+  {+    bytes[pos] = value % 0x80;+    if (value >= 0x80)+    {+      value /= 0x80;+      bytes[pos] |= 0x80;+    }+    else+      return pos + 1;+  }+}++/** 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)+{+  for (uint8_t ret = 1;; ret++)+  {+    if (value >= 0x80)+      value /= 0x80;+    else+      return ret;+  }+  return 0;+}++/** 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)+{+  uint8_t* start = bytes;+  uint32_t value = 0;+  for (int digits = 0;; digits++)+  {+    if (bytes >= start + length)+      break;+    value += (0x7F & (*bytes)) << (digits * 7);+    if (*bytes < 0x80)+      break;+    bytes++;+  }+  bytes++;+  return value;+}++static inline 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:+      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;+    default:+      switch (bytes[0] & 0xF0)+      {+        case 0xC0:+        case 0xD0:+          bytes += 2;+          break;+        default:+          bytes += 3;+          break;+      }+      break;+  }+  return bytes - start;+}++// MIDI1 to UMP Translator++/* Should we define some structs like this to support conversion from MIDI1 UMP to MIDI2 UMP and complicate the API?+   I'm not sure if there are enough need, and it's better to avoid another cmidi2_convert_midi1_to_ump() (200-ish LoC).++typedef struct cmidi2_midi1_sequence_midi1_bytes {+    // input MIDI1 messages or SMF events.+    uint8_t *midi1;+    // size of the input stream to process in bytes+    size_t midi1_num_bytes;+    // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the MIDI1 stream.+    size_t midi1_proceeded_bytes;+} cmidi2_midi1_sequence_midi1_bytes;++typedef struct cmidi2_midi1_sequence_umps {+    // MIDI1 UMP stream.+    cmidi2_ump* ump;+    // size (capacity) of the output stream in bytes+    size_t ump_num_bytes;+    // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the UMP stream.+    size_t ump_proceeded_bytes;+} cmidi2_midi1_sequence_ump;+*/++enum cmidi2_translator_endianness+{+  CMIDI2_TRANSLATOR_DEFAULT_ENDIAN,+  CMIDI2_TRANSLATOR_BIG_ENDIAN,+  CMIDI2_TRANSLATOR_LITTLE_ENDIAN+};++// Conversion from MIDI1 message bytes or SMF event list to MIDI2 UMP stream+//+// The conversion requires some preserved context e.g. RPN/NRPN/DTE+//  MSB/LSB, so we take this struct.+typedef struct cmidi2_midi_conversion_context+{+  // When it is true, it means the input MIDI1 stream contains delta time.+  // TODO: implement support for it+  bool is_midi1_smf;+  // Context tempo as in SMF specification defines.+  // If the MIDI1 stream is SMF and non-SMPTE delta time is used, then we have to calculate+  // the actual delta time length in SMPTE (then to JR timestamp).+  // TODO: implement support for it+  int32_t tempo;+  // input MIDI1 messages or SMF events.+  uint8_t* midi1;+  // size of the input stream to process in bytes+  size_t midi1_num_bytes;+  // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the MIDI1 stream.+  size_t midi1_proceeded_bytes;+  // output UMP stream.+  cmidi2_ump* ump;+  // size (capacity) of the output stream in bytes+  size_t ump_num_bytes;+  // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the UMP stream.+  size_t ump_proceeded_bytes;+  // DTE conversion target.+  // cmidi2_convert_midi1_messages_to_ump() will return *_INVALID_RPN or *_INVALID_NRPN+  // for such invalid sequences, and to report that correctly we need to preserve CC status.+  // They are initialized to 0x8080 that implies both bank (MSB) and index (LSB) are invalid (> 0x7F).+  // When they are assigned valid values, then (context_[n]rpn & 0x8080) will become 0.+  // When cmidi2_convert_midi1_messages_to_ump() encountered DTE LSB, they are consumed+  // and reset to the initial value (0x8080).+  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+  // as the conversion terminator, but cmidi2 allows DTE LSB to come first,+  // if this flag is enabled.+  bool allow_reordered_dte;+  // Bank Select CC is preserved for the next program change.+  // The initial value is 0x8080, same as RPN/NRPN/DTE.+  // After program change is set, it is reset to the initial value.+  uint32_t context_bank;+  // Group can be specified.+  uint8_t group;+  // Destination protocol: can be MIDI1 UMP or MIDI2 UMP.+  enum cmidi2_ci_protocol_values midi_protocol;+  // Sysex conversion can be done to sysex8+  bool use_sysex8;+  // Determine whether delta time is skipped or not (inserted like SMF) in the output MIDI1 stream.+  bool skip_delta_time;+  // UMP serialization endianness+  int32_t ump_serialization_endianness;+} cmidi2_midi_conversion_context;++enum cmidi2_midi_conversion_result+{+  CMIDI2_CONVERSION_RESULT_OK = 0,+  CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE = 1,+  CMIDI2_CONVERSION_RESULT_INVALID_SYSEX = 0x10,+  CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE = 0x11,+  CMIDI2_CONVERSION_RESULT_INVALID_STATUS = 0x13,+  CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7 = 0x20,+  CMIDI2_CONVERSION_RESULT_INVALID_INPUT = 0x40,+};++static inline void+cmidi2_midi_conversion_context_initialize(cmidi2_midi_conversion_context* context)+{+  context->is_midi1_smf = false;+  context->tempo = 500000;+  context->midi1 = NULL;+  context->midi1_num_bytes = 0;+  context->midi1_proceeded_bytes = 0;+  context->ump = NULL;+  context->ump_num_bytes = 0;+  context->ump_proceeded_bytes = 0;+  context->context_rpn = 0x8080;+  context->context_nrpn = 0x8080;+  context->context_dte = 0x8080;+  context->context_bank = 0x8080;+  context->allow_reordered_dte = false;+  context->group = 0;+  context->midi_protocol = CMIDI2_PROTOCOL_TYPE_MIDI2;+  context->use_sysex8 = false;+  context->skip_delta_time = false;+  context->ump_serialization_endianness = CMIDI2_TRANSLATOR_DEFAULT_ENDIAN;+}++typedef struct cmidi2_convert_sysex_context+{+  cmidi2_midi_conversion_context* conversion_context;+  size_t dst_offset;+} cmidi2_convert_sysex_context;++static inline 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;+  return NULL;+}++static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list(+    uint64_t data1, uint64_t data2, size_t index, void* context)+{+  (void)index;+  cmidi2_convert_sysex_context* s8ctx = (cmidi2_convert_sysex_context*)context;+  s8ctx->conversion_context->ump[s8ctx->dst_offset] = data1 >> 32;+  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;+  return NULL;+}++static inline uint64_t+cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context, uint8_t channel)+{+  bool isRpn = (context->context_rpn & 0x8080) == 0;+  uint8_t msb = (isRpn ? context->context_rpn : context->context_nrpn) >> 8;+  uint8_t lsb = (isRpn ? context->context_rpn : context->context_nrpn) & 0xFF;+  int32_t data = (context->context_dte >> 8 << 25) + ((context->context_dte & 0x7F) << 18);+  // reset RPN/NRPN/DTE status to the initial values.+  context->context_rpn = 0x8080;+  context->context_nrpn = 0x8080;+  context->context_dte = 0x8080;+  return isRpn ? cmidi2_ump_midi2_rpn(context->group, channel, msb, lsb, data)+               : cmidi2_ump_midi2_nrpn(context->group, channel, msb, lsb, data);+}++static inline uint32_t cmidi2_internal_swap_endian(uint32_t v)+{+  return ((v & 0xFF) << 24) + (((v >> 8) & 0xFF) << 16) + (((v >> 16) & 0xFF) << 8)+         + ((v >> 24) & 0xFF);+}++/** converts MIDI1 bytestream which can contain deltaTime in SMF, to MIDI2 UMP stream.+ * The resulting stream is native endianness.+ */+static enum cmidi2_midi_conversion_result+cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context)+{+  uint8_t* dst = (uint8_t*)context->ump;+  size_t sLen = context->midi1_num_bytes;+  size_t dLen = context->ump_num_bytes;+  uint8_t* sIdx = (uint8_t*)&context->midi1_proceeded_bytes;+  uint8_t* dIdx = (uint8_t*)&context->ump_proceeded_bytes;++  while (*sIdx < sLen)+  {+    // FIXME: implement deltaTime to JR Timestamp conversion.++    if (context->midi1[*sIdx] == 0xF0)+    {+      // sysex+      uint8_t* f7+          = (uint8_t*)memchr(context->midi1 + *sIdx, 0xF7, context->midi1_num_bytes - *sIdx);+      if (f7 == NULL)+      {+        return CMIDI2_CONVERSION_RESULT_INVALID_SYSEX; // error+      }+      size_t sysexSize = f7 - context->midi1 - *sIdx - 1; // excluding 0xF7+      size_t numPackets = context->use_sysex8 ? cmidi2_ump_sysex8_get_num_packets(sysexSize)+                                              : cmidi2_ump_sysex7_get_num_packets(sysexSize);+      if (dLen - *dIdx < numPackets)+        return CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE;+      cmidi2_convert_sysex_context sysExCtx;+      sysExCtx.conversion_context = context;+      sysExCtx.dst_offset = *dIdx;+      if (context->use_sysex8)+      {+        // ignoring the return code as it never returns non-NULL... (size is already verified)+        cmidi2_ump_sysex8_process(+            context->group, context->midi1 + *sIdx, sysexSize, 0,+            cmidi2_internal_convert_add_midi1_sysex8_ump_to_list, &sysExCtx);+      }+      else+      {+        // ignoring the return code as it never returns non-NULL... (size is already verified)+        cmidi2_ump_sysex7_process(+            context->group, context->midi1 + *sIdx,+            cmidi2_internal_convert_add_midi1_sysex7_ump_to_list, &sysExCtx);+      }+      *sIdx += sysexSize + 1; // +1 for 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)+        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;+      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;+      }+      else+      {+        // generate MIDI2 UMPs+        uint64_t m2;+        const int8_t NO_ATTRIBUTE_TYPE = 0;+        const int16_t NO_ATTRIBUTE_DATA = 0;+        bool bankValid, bankMsbValid, bankLsbValid;+        bool skipEmitUmp = false;+        switch (context->midi1[*sIdx] & 0xF0)+        {+          case CMIDI2_STATUS_NOTE_OFF:+            m2 = cmidi2_ump_midi2_note_off(+                context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA);+            break;+          case CMIDI2_STATUS_NOTE_ON:+            m2 = cmidi2_ump_midi2_note_on(+                context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA);+            break;+          case CMIDI2_STATUS_PAF:+            m2 = cmidi2_ump_midi2_paf(context->group, channel, byte2, byte3 << 25);+            break;+          case CMIDI2_STATUS_CC:+            switch (byte2)+            {+              case CMIDI2_CC_RPN_MSB:+                context->context_rpn = (context->context_rpn & 0xFF) | (byte3 << 8);+                skipEmitUmp = true;+                break;+              case CMIDI2_CC_RPN_LSB:+                context->context_rpn = (context->context_rpn & 0xFF00) | byte3;+                skipEmitUmp = true;+                break;+              case CMIDI2_CC_NRPN_MSB:+                context->context_nrpn = (context->context_nrpn & 0xFF) | byte3 << 8;+                skipEmitUmp = true;+                break;+              case CMIDI2_CC_NRPN_LSB:+                context->context_nrpn = (context->context_nrpn & 0xFF00) | byte3;+                skipEmitUmp = true;+                break;+              case CMIDI2_CC_DTE_MSB:+                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);+                else+                  skipEmitUmp = true;++                break;+              case CMIDI2_CC_DTE_LSB:+                context->context_dte = (context->context_dte & 0xFF00) | byte3;++                if ((context->context_dte & 0x8000) && !context->allow_reordered_dte)+                  return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;+                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);++                break;+              case CMIDI2_CC_BANK_SELECT:+                context->context_bank = (context->context_bank & 0xFF) | (byte3 << 8);+                skipEmitUmp = true;+                break;+              case CMIDI2_CC_BANK_SELECT_LSB:+                context->context_bank = (context->context_bank & 0xFF00) | byte3;+                skipEmitUmp = true;+                break;+              default:+                m2 = cmidi2_ump_midi2_cc(context->group, channel, byte2, byte3 << 25);+                break;+            }+            break;+          case CMIDI2_STATUS_PROGRAM:+            bankMsbValid = (context->context_bank & 0x8000) == 0;+            bankLsbValid = (context->context_bank & 0x80) == 0;+            bankValid = bankMsbValid || bankLsbValid;+            m2 = cmidi2_ump_midi2_program(+                context->group, channel,+                bankValid ? CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID+                          : CMIDI2_PROGRAM_CHANGE_OPTION_NONE,+                byte2, bankMsbValid ? context->context_bank >> 8 : 0,+                bankLsbValid ? context->context_bank & 0x7F : 0);+            context->context_bank = 0x8080;+            break;+          case CMIDI2_STATUS_CAF:+            m2 = cmidi2_ump_midi2_caf(context->group, channel, byte2 << 25);+            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);+            break;+          default:+            return CMIDI2_CONVERSION_RESULT_INVALID_STATUS;+        }+        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;+        }+        *sIdx += len;+      }+    }+  }+  // incomplete UMP sequence will be reported as CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE,+  // so it is safe to judge that incomplete RPN/NRPN/DTE state at this state means invalid.+  if (context->context_rpn != 0x8080 || context->context_nrpn != 0x8080+      || context->context_dte != 0x8080)+    return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;++  return CMIDI2_CONVERSION_RESULT_OK;+}++// UMP to MIDI1 Translator++static int32_t cmidi2_internal_convert_jr_timestamp_to_timecode(+    int32_t deltaTime, cmidi2_midi_conversion_context* context)+{+  (void)context;+  // FIXME: implement+  return deltaTime;+}++static size_t cmidi2_internal_add_midi1_delta_time(+    uint8_t* dst, cmidi2_midi_conversion_context* context, int32_t deltaTime)+{+  if (!context || context->skip_delta_time)+    return 0;+  size_t* dIdx = (size_t*)&context->midi1_proceeded_bytes;+  int32_t len = cmidi2_midi1_get_7bit_encoded_int_length(deltaTime);+  cmidi2_midi1_write_7bit_encoded_int(dst, deltaTime);+  *dIdx += len;+  return len;+}++/// 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(+    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;+  size_t midiEventSize = 0;+  uint64_t sysex7U64;+  uint8_t sysex7NumBytesInUmp;++  uint8_t messageType = cmidi2_ump_get_message_type(ump);+  uint8_t statusCode = cmidi2_ump_get_status_code(ump); // may not apply, but won't break.++#define CMIDI2_INTERNAL_ADD_DELTA_TIME                                  \+  dst += cmidi2_internal_add_midi1_delta_time(dst, context, deltaTime); \+  dst[0] = statusCode | cmidi2_ump_get_channel(ump);++  switch (messageType)+  {+    case CMIDI2_MESSAGE_TYPE_SYSTEM:+      CMIDI2_INTERNAL_ADD_DELTA_TIME+      midiEventSize = 1;+      switch (statusCode)+      {+        case 0xF1:+        case 0xF3:+        case 0xF9:+          dst[1] = cmidi2_ump_get_system_message_byte2(ump);+          midiEventSize = 2;+          break;+      }+      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;+          break;+        default:+          dst[2] = cmidi2_ump_get_midi1_byte3(ump);+          break;+      }+      break;+    case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:+      // FIXME: convert MIDI2 to MIDI1 as long as possible+      switch (statusCode)+      {+        case CMIDI2_STATUS_RPN:+          CMIDI2_INTERNAL_ADD_DELTA_TIME+          midiEventSize = 12;+          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);+          dst[3] = dst[0]; // CC + channel+          dst[4] = CMIDI2_CC_RPN_LSB;+          dst[5] = cmidi2_ump_get_midi2_rpn_lsb(ump);+          dst[6] = dst[0]; // CC + channel+          dst[7] = CMIDI2_CC_DTE_MSB;+          dst[8] = (cmidi2_ump_get_midi2_rpn_data(ump) >> 25) & 0x7F;+          dst[9] = dst[0]; // CC + channel+          dst[10] = CMIDI2_CC_DTE_LSB;+          dst[11] = (cmidi2_ump_get_midi2_rpn_data(ump) >> 18) & 0x7F;+          break;+        case CMIDI2_STATUS_NRPN:+          CMIDI2_INTERNAL_ADD_DELTA_TIME+          midiEventSize = 12;+          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);+          dst[3] = dst[0]; // CC + channel+          dst[4] = CMIDI2_CC_NRPN_LSB;+          dst[5] = cmidi2_ump_get_midi2_nrpn_lsb(ump);+          dst[6] = dst[0]; // CC + channel+          dst[7] = CMIDI2_CC_DTE_MSB;+          dst[8] = (cmidi2_ump_get_midi2_nrpn_data(ump) >> 25) & 0x7F;+          dst[9] = dst[0]; // CC + channel+          dst[10] = CMIDI2_CC_DTE_LSB;+          dst[11] = (cmidi2_ump_get_midi2_nrpn_data(ump) >> 18) & 0x7F;+          break;+        case CMIDI2_STATUS_NOTE_OFF:+        case CMIDI2_STATUS_NOTE_ON:+          CMIDI2_INTERNAL_ADD_DELTA_TIME+          midiEventSize = 3;+          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;+          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;+          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;+          break;+        case CMIDI2_STATUS_PROGRAM:+          CMIDI2_INTERNAL_ADD_DELTA_TIME+          dst[0] = statusCode | cmidi2_ump_get_channel(ump);+          if (cmidi2_ump_get_midi2_program_options(ump) & CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID)+          {+            midiEventSize = 8;+            dst[6] = dst[0]; // copy+            dst[7] = cmidi2_ump_get_midi2_program_program(ump);+            dst[0] = (dst[6] & 0xF) + CMIDI2_STATUS_CC;+            dst[1] = 0; // Bank MSB+            dst[2] = cmidi2_ump_get_midi2_program_bank_msb(ump);+            dst[3] = (dst[6] & 0xF) + CMIDI2_STATUS_CC;+            dst[4] = 32; // Bank LSB+            dst[5] = cmidi2_ump_get_midi2_program_bank_lsb(ump);+          }+          else+          {+            midiEventSize = 2;+            dst[1] = cmidi2_ump_get_midi2_program_program(ump);+          }+          break;+        case CMIDI2_STATUS_CAF:+          CMIDI2_INTERNAL_ADD_DELTA_TIME+          midiEventSize = 2;+          dst[1] = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000;+          break;+        case CMIDI2_STATUS_PITCH_BEND:+          CMIDI2_INTERNAL_ADD_DELTA_TIME+          midiEventSize = 3;+          uint32_t pitchBendV1 = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000;+          dst[1] = pitchBendV1 % 0x80;+          dst[2] = pitchBendV1 / 0x80;+          break;+          // skip for other status bytes; we cannot support them.+      }+      break;+    case CMIDI2_MESSAGE_TYPE_SYSEX7:+      if (sysex7Buffer)+      {+        // sysex7 buffer is processed at cmidi2_convert_ump_to_midi1().+        midiEventSize = 0;+        sysex7U64 = cmidi2_ump_read_uint64_bytes(ump);+        sysex7NumBytesInUmp = cmidi2_ump_get_sysex7_num_bytes(ump);+        for (size_t i = 0; i < sysex7NumBytesInUmp; i++)+          sysex7Buffer[*sysex7BufferIndex + i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i);+        *sysex7BufferIndex += sysex7NumBytesInUmp;+      }+      else+      {+        // minimal implementation for single-byte sysex7+        CMIDI2_INTERNAL_ADD_DELTA_TIME+        midiEventSize = 1 + cmidi2_ump_get_sysex7_num_bytes(ump);+        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);+      }+      break;+    case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:+      // By the UMP specification they cannot be translated in Default Translation+      midiEventSize = 0;+      break;+  }+  return midiEventSize;+}++// Left for API backward compatibility.+static inline 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);+}++#define IS_JR_TIMESTAMP(ump)                                       \+  (cmidi2_ump_get_message_type(ump) == CMIDI2_MESSAGE_TYPE_UTILITY \+   && cmidi2_ump_get_status_code(ump) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)++static enum cmidi2_midi_conversion_result+cmidi2_convert_ump_to_midi1(cmidi2_midi_conversion_context* context)+{+  uint8_t* dst = (uint8_t*)context->midi1;+  size_t sLen = context->ump_num_bytes;+  size_t dLen = context->midi1_num_bytes;+  size_t* sIdx = (size_t*)&context->ump_proceeded_bytes;+  size_t* dIdx = (size_t*)&context->midi1_proceeded_bytes;+  uint8_t sysex7_buffer[1024];+  size_t sysex7_buffer_index = 0;++  while (*sIdx < sLen)+  {+    int32_t deltaTimeInJRTimestamp = 0;+    cmidi2_ump* ump;+    do+    {+      ump = (cmidi2_ump*)((uint8_t*)context->ump + *sIdx);+      if (IS_JR_TIMESTAMP(ump))+      {+        if (!context->skip_delta_time)+          deltaTimeInJRTimestamp += cmidi2_ump_get_jr_timestamp_timestamp(ump);+      }+      else+        break;+      *sIdx += 4; // 4 = sizeof JR Timestamp message+    } while (*sIdx < sLen);++    if (*sIdx >= sLen)+      break;++    int32_t deltaTime+        = cmidi2_internal_convert_jr_timestamp_to_timecode(deltaTimeInJRTimestamp, context);++    int32_t len = cmidi2_convert_single_ump_to_timed_midi1(+        dst + *dIdx, dLen - *dIdx, ump, deltaTime, context, sysex7_buffer, &sysex7_buffer_index);+    *dIdx += len;+    *sIdx += cmidi2_ump_get_num_bytes(*ump);++    if (cmidi2_ump_get_message_type(ump) == CMIDI2_MESSAGE_TYPE_SYSEX7)+    {+      switch (cmidi2_ump_get_status_code(ump))+      {+        case CMIDI2_SYSEX_END:+        case CMIDI2_SYSEX_IN_ONE_UMP:+          if (!context->skip_delta_time)+            cmidi2_internal_add_midi1_delta_time(dst + *dIdx, context, deltaTime);+          dst[*dIdx] = 0xF0;+          *dIdx += 1;+          memcpy(dst + *dIdx, sysex7_buffer, sysex7_buffer_index);+          *dIdx += sysex7_buffer_index;+          sysex7_buffer_index = 0;+          dst[*dIdx] = 0xF7;+          *dIdx += 1;+          break;+      }+    }+    deltaTime = 0;+  }++  return sysex7_buffer_index > 0 ? CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7+                                 : CMIDI2_CONVERSION_RESULT_OK;+}++// UMP forge (like LV2 Atom Forge, but flat and simpler)++typedef struct cmidi2_ump_forge+{+  cmidi2_ump* ump;+  size_t capacity;+  size_t offset;+} cmidi2_ump_forge;++static inline void+cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capacityInBytes)+{+  forge->ump = buffer;+  forge->capacity = capacityInBytes;+  forge->offset = 0;+}++static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump)+{+  int size = 4;+  if (forge->offset + size > forge->capacity)+    return false;+  uint8_t* p = (uint8_t*)forge->ump + forge->offset;+  cmidi2_ump_write32((cmidi2_ump*)p, ump);+  forge->offset += size;+  return true;+}++static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump)+{+  int size = 8;+  if (forge->offset + size > forge->capacity)+    return false;+  uint8_t* p = (uint8_t*)forge->ump + forge->offset;+  cmidi2_ump_write64((cmidi2_ump*)p, ump);+  forge->offset += size;+  return true;+}++static inline bool+cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t ump2)+{+  int size = 16;+  if (forge->offset + size > forge->capacity)+    return false;+  uint8_t* p = ((uint8_t*)forge->ump) + forge->offset;+  cmidi2_ump_write128((cmidi2_ump*)p, ump1, ump2);+  forge->offset += size;+  return true;+}++static inline 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)+    return false;+  memcpy((uint8_t*)forge->ump + forge->offset, ump, size);+  forge->offset += size;+  return true;+}++static inline bool+cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t size)+{+  if (forge->offset + size > forge->capacity)+    return false;+  memcpy((uint8_t*)forge->ump + forge->offset, ump, size);+  forge->offset += size;+  return true;+}++// UMP sequence merger++static inline 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)+{+  int32_t deltaTime = (timestamp1 <= timestamp2 ? timestamp1 : timestamp2) - *lastTimestamp;+  uint8_t jrTSSize = deltaTime / 0x10000 + (deltaTime % 0x10000 ? 1 : 0);+  if (*dIdx + jrTSSize * 4 > (int64_t)dstSize)+    return false;+  for (int32_t dt = deltaTime; dt > 0; dt -= 0x10000)+  {+    cmidi2_ump_write32(+        (cmidi2_ump*)((uint8_t*)dst + *dIdx),+        cmidi2_ump_jr_timestamp_direct(0, deltaTime > 0xFFFF ? 0xFFFF : deltaTime));+    *dIdx += 4;+  }+  *lastTimestamp += deltaTime;+  return true;+}++static inline size_t cmidi2_ump_merge_sequences(+    cmidi2_ump* dst, size_t dstCapacity, cmidi2_ump* seq1, size_t seq1Size, cmidi2_ump* seq2,+    size_t seq2Size)+{+  int32_t dIdx = 0, seq1Idx = 0, seq2Idx = 0;+  int32_t timestamp1 = 0, timestamp2 = 0, lastTimestamp = 0;+  while (true)+  {+    cmidi2_ump* s1 = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+    while (cmidi2_ump_get_message_type(s1) == CMIDI2_MESSAGE_TYPE_UTILITY+           && cmidi2_ump_get_status_code(s1) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)+    {+      timestamp1 += cmidi2_ump_get_jr_timestamp_timestamp(s1);+      seq1Idx += 4;+      if (seq1Idx >= (int64_t)seq1Size)+        break;+      s1 = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+    }+    cmidi2_ump* s2 = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+    while (cmidi2_ump_get_message_type(s2) == CMIDI2_MESSAGE_TYPE_UTILITY+           && cmidi2_ump_get_status_code(s2) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)+    {+      timestamp2 += cmidi2_ump_get_jr_timestamp_timestamp(s2);+      seq2Idx += 4;+      if (seq2Idx >= (int64_t)seq2Size)+        break;+      s2 = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+    }+    if (seq1Idx >= (int64_t)seq1Size || seq2Idx >= (int64_t)seq2Size)+      break;+    if (!cmidi2_internal_ump_merge_sequence_write_delta_time(+            timestamp1, timestamp2, &lastTimestamp, dst, &dIdx, dstCapacity))+      break;++    if (timestamp1 <= timestamp2)+    {+      cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+      uint8_t size = cmidi2_ump_get_message_size_bytes(sp);+      if (size == 0)+        return dIdx; // invalid bytes+      memcpy((uint8_t*)dst + dIdx, sp, size);+      seq1Idx += size;+      dIdx += size;+    }+    else+    {+      cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+      uint8_t size = cmidi2_ump_get_message_size_bytes(sp);+      if (size == 0)+        return dIdx; // invalid bytes+      memcpy((uint8_t*)dst + dIdx, sp, size);+      seq2Idx += size;+      dIdx += size;+    }+  }+  if (!cmidi2_internal_ump_merge_sequence_write_delta_time(+          timestamp1, timestamp2, &lastTimestamp, dst, &dIdx, dstCapacity))+    return dIdx;+  if (seq1Idx < (int64_t)seq1Size && dIdx + (int64_t)seq1Size - seq1Idx < (int64_t)dstCapacity)+  {+    cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+    memcpy((uint8_t*)dst + dIdx, sp, seq1Size - seq1Idx);+    dIdx += seq1Size - seq1Idx;+  }+  if (seq2Idx < (int64_t)seq2Size && dIdx + (int64_t)seq2Size - seq2Idx < (int64_t)dstCapacity)+  {+    cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+    memcpy((uint8_t*)dst + dIdx, sp, seq2Size - seq2Idx);+    dIdx += seq2Size - seq2Idx;+  }+  return dIdx;+}++#ifdef __cplusplus+}+#endif++#endif /* CMIDI2_H_INCLUDED */
+ vendor/libremidi/config.hpp view
@@ -0,0 +1,64 @@+#pragma once++#if defined(MSC_VER)+  #define NOMINMAX 1+  #define WIN32_LEAN_AND_MEAN+#endif++#if defined(LIBREMIDI_EXPORTS)+  #if defined(_MSC_VER)+    #define LIBREMIDI_EXPORT __declspec(dllexport)+  #elif defined(__GNUC__) || defined(__clang__)+    #define LIBREMIDI_EXPORT __attribute__((visibility("default")))+  #endif+#else+  #define LIBREMIDI_EXPORT+#endif++#define LIBREMIDI_VERSION "4.5.0"++#if defined(LIBREMIDI_USE_BOOST)+  #if !__has_include(<boost/container/small_vector.hpp>)+    #error \+        "Boost was used for building libremidi but is not found when using it. Add Boost to your include paths."+  #endif++  #if defined(LIBREMIDI_NO_BOOST)+    #error "Boost was used for building libremidi but LIBREMIDI_NO_BOOST is defined."+  #endif+#endif++#if __has_include(<boost/container/small_vector.hpp>) && !defined(LIBREMIDI_NO_BOOST)++  #if LIBREMIDI_SLIM_MESSAGE > 0+    #include <boost/container/static_vector.hpp>+namespace libremidi+{+using midi_bytes = boost::container::static_vector<unsigned char, LIBREMIDI_SLIM_MESSAGE>;+}+  #else+    #include <boost/container/small_vector.hpp>+namespace libremidi+{+static constexpr int small_vector_minimum_size+    = sizeof(boost::container::small_vector<unsigned char, 1>);+using midi_bytes = boost::container::small_vector<unsigned char, small_vector_minimum_size>;+}+  #endif+#else+  #include <vector>+namespace libremidi+{+using midi_bytes = std::vector<unsigned char>;+}+#endif++#if __has_include(<midi/universal_packet.h>) && defined(LIBREMIDI_USE_NI_MIDI2)+  #define LIBREMIDI_NI_MIDI2_COMPAT 1+#endif++#if defined(LIBREMIDI_HEADER_ONLY)+  #define LIBREMIDI_INLINE inline+#else+  #define LIBREMIDI_INLINE+#endif
+ vendor/libremidi/configurations.hpp view
@@ -0,0 +1,24 @@+#pragma once++#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw_ump/config.hpp>+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq_ump/config.hpp>+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/keyboard/config.hpp>+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winuwp/config.hpp>++namespace libremidi+{++struct dummy_configuration+{+};+}
+ vendor/libremidi/defaults.hpp view
@@ -0,0 +1,98 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/output_configuration.hpp>++#include <any>+#include <optional>++namespace libremidi+{+class midi_in;+class midi_out;+class observer;++//! Get the default midi in configuration object for a given API+LIBREMIDI_EXPORT+std::any 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);++//! Get the default observer configuration object for a given API+LIBREMIDI_EXPORT+std::any 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&);+//! Get a matching midi out configuration object for a given observer instance.+LIBREMIDI_EXPORT+std::any 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.+LIBREMIDI_EXPORT+std::optional<input_port> in_default_port(libremidi::API api) noexcept;++//! Get the default port for midi output (if any) in a given API.+//! Note that this will only return a hardware / physical port.+LIBREMIDI_EXPORT+std::optional<output_port> out_default_port(libremidi::API api) noexcept;++namespace midi1+{+//! Get a default configuration for creating a MIDI 1 input+LIBREMIDI_EXPORT+std::any in_default_configuration();++//! Get a default configuration for creating a MIDI 1 output+LIBREMIDI_EXPORT+std::any out_default_configuration();++//! Get a default configuration for creating a MIDI 1 observer+LIBREMIDI_EXPORT+std::any observer_default_configuration();++//! Get a default MIDI 1 input port for the default API of the system+inline std::optional<input_port> in_default_port() noexcept+{+  return libremidi::in_default_port(default_api());+}++//! Get a default MIDI 1 output port for the default API of the system+inline std::optional<output_port> out_default_port() noexcept+{+  return libremidi::out_default_port(default_api());+}+}++namespace midi2+{+//! Get a default configuration for creating a MIDI 2 input+LIBREMIDI_EXPORT+std::any in_default_configuration();++//! Get a default configuration for creating a MIDI 2 output+LIBREMIDI_EXPORT+std::any out_default_configuration();++//! Get a default configuration for creating a MIDI 2 observer+LIBREMIDI_EXPORT+std::any observer_default_configuration();++//! Get a default MIDI 2 input port for the default API of the system+inline std::optional<input_port> in_default_port() noexcept+{+  return libremidi::in_default_port(default_api());+}+//! Get a default MIDI 2 output port for the default API of the system+inline std::optional<output_port> out_default_port() noexcept+{+  return libremidi::out_default_port(default_api());+}+}+}
+ vendor/libremidi/detail/conversion.hpp view
@@ -0,0 +1,59 @@+#pragma once+// clang-format off+#include <libremidi/config.hpp>+// clang-format on++#include <libremidi/cmidi2.hpp>+#include <libremidi/error.hpp>+#include <libremidi/message.hpp>+#include <libremidi/ump.hpp>+namespace libremidi+{+struct midi1_to_midi2+{+  stdx::error+  convert(const unsigned char* message, std::size_t size, int64_t timestamp, auto on_ump)+  {+    context.midi1 = const_cast<unsigned char*>(message);+    context.midi1_num_bytes = size;+    context.midi1_proceeded_bytes = 0;+    context.ump = ump;+    context.ump_num_bytes = sizeof(ump);+    context.ump_proceeded_bytes = 0;++    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);+  }++  cmidi2_midi_conversion_context context = [] {+    cmidi2_midi_conversion_context tmp;+    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)+  {+    auto n+        = cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));+    if (n > 0)+      return on_midi(midi, n, timestamp);+    else+      return std::errc::no_buffer_space;+  }++  cmidi2_midi_conversion_context context = [] {+    cmidi2_midi_conversion_context tmp;+    cmidi2_midi_conversion_context_initialize(&tmp);+    return tmp;+  }();+  uint8_t midi[65536];+};++}
+ vendor/libremidi/detail/memory.hpp view
@@ -0,0 +1,40 @@+#pragma once++#include <memory>+#include <mutex>++namespace libremidi+{+template <auto func>+struct deleter+{+  template <typename U>+  void operator()(U* x) noexcept(noexcept(func(x)))+  {+    func(x);+  }+};++template <typename T, auto func>+using unique_handle = std::unique_ptr<T, deleter<func>>;++template <typename T>+std::shared_ptr<T> instance()+{+  static std::mutex mut;+  static std::weak_ptr<T> cache;++  std::lock_guard _{mut};++  if (auto ptr = cache.lock())+  {+    return ptr;+  }+  else+  {+    auto shared = std::make_shared<T>();+    cache = shared;+    return shared;+  }+}+}
+ vendor/libremidi/detail/midi_api.hpp view
@@ -0,0 +1,48 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>++#include <string_view>++namespace libremidi+{+class midi_api+{+public:+  midi_api() = default;+  virtual ~midi_api() = default;+  midi_api(const midi_api&) = delete;+  midi_api(midi_api&&) = delete;+  midi_api& operator=(const midi_api&) = delete;+  midi_api& operator=(midi_api&&) = delete;++  [[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0;++  [[nodiscard]] virtual stdx::error open_virtual_port(std::string_view)+  {+    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 close_port() = 0;++  stdx::error is_client_open() const noexcept { return client_open_; }+  bool is_port_open() const noexcept { return port_open_; }+  bool is_port_connected() const noexcept { return connected_; }++protected:+  friend class midi_in;+  friend class midi_out;+  stdx::error client_open_{std::errc::not_connected};+  bool port_open_{};+  bool connected_{};+};+}
+ vendor/libremidi/detail/midi_in.hpp view
@@ -0,0 +1,56 @@+#pragma once+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/error_handler.hpp>++namespace libremidi+{+class midi_in_api : public midi_api+{+public:+  midi_in_api() = default;+  ~midi_in_api() override = default;+  midi_in_api(const midi_in_api&) = delete;+  midi_in_api(midi_in_api&&) = delete;+  midi_in_api& operator=(const midi_in_api&) = delete;+  midi_in_api& operator=(midi_in_api&&) = delete;++  [[nodiscard]] virtual stdx::error+  open_port(const input_port& pt, std::string_view local_port_name)+      = 0;+  [[nodiscard]] virtual timestamp absolute_timestamp() const noexcept = 0;+};++namespace midi1+{+class in_api : public midi_in_api+{+public:+  using midi_in_api::midi_in_api;+};+}++namespace midi2+{+class in_api : public midi_in_api+{+public:+  using midi_in_api::midi_in_api;++  bool firstMessage{true};+};+}++template <typename T, typename Arg>+std::unique_ptr<midi_in_api> make(libremidi::input_configuration&& conf, Arg&& arg)+{+  return std::make_unique<T>(std::move(conf), std::move(arg));+}++template <typename T, typename Arg>+std::unique_ptr<midi_in_api> make(libremidi::ump_input_configuration&& conf, Arg&& arg)+{+  return std::make_unique<T>(std::move(conf), std::move(arg));+}+}
+ vendor/libremidi/detail/midi_out.hpp view
@@ -0,0 +1,89 @@+#pragma once+#include <libremidi/detail/conversion.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/error_handler.hpp>+#include <libremidi/output_configuration.hpp>++#include <string_view>++namespace libremidi+{++class midi_out_api : public midi_api+{+public:+  midi_out_api() = default;+  ~midi_out_api() override = default;+  midi_out_api(const midi_out_api&) = delete;+  midi_out_api(midi_out_api&&) = delete;+  midi_out_api& operator=(const midi_out_api&) = delete;+  midi_out_api& operator=(midi_out_api&&) = delete;++  [[nodiscard]] virtual stdx::error+  open_port(const output_port& pt, std::string_view local_port_name)+      = 0;++  [[nodiscard]] virtual int64_t current_time() const noexcept { return 0; }++  virtual stdx::error send_message(const unsigned char* message, std::size_t size) = 0;+  virtual stdx::error+  schedule_message(int64_t /*ts*/, const unsigned char* message, std::size_t size)+  {+    return send_message(message, size);+  }++  virtual stdx::error send_ump(const uint32_t* message, std::size_t size) = 0;+  virtual stdx::error schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t size)+  {+    return send_ump(ump, size);+  }+};++namespace midi1+{+class out_api : public midi_out_api+{+  friend struct midi_stream_decoder;++public:+  using midi_out_api::midi_out_api;++  stdx::error send_ump(const uint32_t* message, std::size_t size)+  {+    return converter.convert(+        message, size, 0, [this](const unsigned char* midi, std::size_t n, int64_t /* ts */) {+      return send_message(midi, n);+    });+  }++  midi2_to_midi1 converter;+};+}++namespace midi2+{+class out_api : public midi_out_api+{+  friend struct midi_stream_decoder;++public:+  using midi_out_api::midi_out_api;++  stdx::error send_message(const unsigned char* message, std::size_t size)+  {+    return converter.convert(+        message, size, 0, [this](const uint32_t* ump, std::size_t count, int64_t /* ts */) {+      return send_ump(ump, count);+    });+  }++  midi1_to_midi2 converter;+};+}++template <typename T, typename Arg>+std::unique_ptr<midi_out_api> make(libremidi::output_configuration&& conf, Arg&& arg)+{+  return std::make_unique<T>(std::move(conf), std::forward<Arg>(arg));+}+}
+ vendor/libremidi/detail/midi_stream_decoder.hpp view
@@ -0,0 +1,465 @@+#pragma once++#include <libremidi/detail/midi_in.hpp>++#include <libremidi/cmidi2.hpp>+#include <chrono>+#include <cstdint>+#include <cinttypes>+#include <span>++namespace libremidi+{+static inline int64_t system_ns() noexcept+{+  namespace clk = std::chrono;+  return clk::duration_cast<clk::nanoseconds>(clk::steady_clock::now().time_since_epoch()).count();+}++struct timestamp_backend_info+{+  // The API provides some kind of timestamping+  bool has_absolute_timestamps{};++  // The provided timestamping is equivalent or more precise than+  // e.g. clock_gettime(CLOCK_MONOTONIC)+  bool absolute_is_monotonic{};++  // The API can provide samples in a buffer cycle (only PipeWire and JACK so far)+  bool has_samples{};+};++template<typename Configuration>+struct input_state_machine_base+{+  const Configuration& configuration;++  explicit input_state_machine_base(const Configuration& conf)+      : configuration{conf}+  {+  }++  template <timestamp_backend_info info>+  int64_t timestamp(auto to_ns, int64_t samples)+  {+    switch (configuration.timestamps)+    {+      default:+      case timestamp_mode::NoTimestamp:+        return 0;++      case timestamp_mode::Relative: {+        int64_t time_ns;++        if constexpr (info.has_absolute_timestamps)+          time_ns = to_ns();+        else+          time_ns = system_ns();++        int64_t res;+        if (first_message)+        {+          first_message = false;+          res = 0;+        }+        else+        {+          res = time_ns - last_time_ns;+        }++        last_time_ns = time_ns;+        return res;+      }++      case timestamp_mode::Absolute:+        if constexpr (info.has_absolute_timestamps)+          return to_ns();+        else+          return system_ns();++      case timestamp_mode::SystemMonotonic:+        if constexpr (info.absolute_is_monotonic)+          return to_ns();+        else+          return system_ns();++      case timestamp_mode::AudioFrame:+        if constexpr (info.has_samples)+          return samples;+        else+          return 0;++      case timestamp_mode::Custom:+        return configuration.get_timestamp(to_ns());+    }+  }+  int64_t last_time_ns = 0;+  bool first_message = true;+};++namespace midi1+{+struct input_state_machine : input_state_machine_base<input_configuration>+{+  using input_state_machine_base::input_state_machine_base;++  void reset()+  {+    message.bytes.clear();+    message.timestamp = {};+    state = main;+  }++  bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept+  {+    return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));+  }++  // Function to process a byte stream which may contain multiple successive+  // MIDI events (CoreMIDI, ALSA Sequencer can work like this)+  void on_bytes_multi(std::span<const uint8_t> bytes, int64_t timestamp)+  {+    if (this->configuration.on_message)+      on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);+    if (this->configuration.on_raw_data)+      this->configuration.on_raw_data(bytes, timestamp);+  }++  // Function to process bytes corresponding to at most one midi event+  // e.g. a midi channel event or a single sysex+  void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)+  {+    if (this->configuration.on_message)+      on_bytes_segmented(this->configuration.on_message, bytes, timestamp);+    if (this->configuration.on_raw_data)+      this->configuration.on_raw_data(bytes, timestamp);+  }++private:+  void on_bytes_multi_segmented(+      const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)+  {+    int64_t nBytes = bytes.size();+    int64_t iByte = 0;++    const bool finished_sysex = has_finished_sysex(bytes);+    switch (state)+    {+      case in_sysex: {+        return on_continue_sysex(cb, bytes, finished_sysex);+      }+      case main: {+        while (iByte < nBytes)+        {+          int64_t size = 1;+          // We are expecting that the next byte in the packet is a status+          // byte.+          const auto status = bytes[iByte];+          if (!(status & 0x80))+            break;++          // Determine the number of bytes in the MIDI message.+          if (status < 0xC0)+            size = 3;+          else if (status < 0xE0)+            size = 2;+          else if (status < 0xF0)+            size = 3;+          else if (status == 0xF0)+          {+            if (configuration.ignore_sysex)+            {+              size = 0;+              iByte = nBytes;+            }+            else+            {+              size = nBytes - iByte;+            }++            if (bytes[nBytes - 1] != 0xF7)+            {+              // We know per CoreMIDI API there can't be anything else in this packet+              state = in_sysex;+              message.assign(bytes.begin(), bytes.begin() + size);+              message.timestamp = timestamp;+              return;+            }+          }+          else if (status == 0xF1)+          {+            // A MIDI time code message+            if (configuration.ignore_timing)+            {+              size = 0;+              iByte += 2;+            }+            else+            {+              size = 2;+            }+          }+          else if (status == 0xF2)+            size = 3;+          else if (status == 0xF3)+            size = 2;+          else if (status == 0xF8)+          {+            // A MIDI timing tick message+            if (configuration.ignore_timing)+            {+              size = 0;+              iByte += 1;+            }+            else+            {+              size = 1;+            }+          }+          else if (status == 0xFE)+          {+            // A MIDI active sensing message+            if (configuration.ignore_sensing)+            {+              size = 0;+              iByte += 1;+            }+            else+            {+              size = 1;+            }+          }+          else+          {+            // Remaining real-time messages+            size = 1;+          }++          // Now process the actual bytes of the message+          if (size > 0)+          {+            auto begin = bytes.begin() + iByte;+            message.assign(begin, begin + size);+            message.timestamp = timestamp;++            cb(std::move(message));+            message.clear();++            iByte += size;+          }+        }+      }+    }+  }++  void on_continue_sysex(+      const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)+  {+    if (finished_sysex)+      state = main;++    if (configuration.ignore_sysex)+    {+      return;+    }+    else+    {+      message.insert(message.end(), bytes.begin(), bytes.end());+      if (finished_sysex)+      {+        cb(std::move(message));+        message.clear();+      }+    }+    return;+  }++  void on_main(+      const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp,+      bool finished_sysex)+  {+    switch (bytes[0])+    {+      // SYSEX start+      case 0xF0: {+        if (!finished_sysex)+          state = in_sysex;++        if (!this->configuration.ignore_sysex)+        {+          message.assign(bytes.begin(), bytes.end());+          message.timestamp = timestamp;+          if (finished_sysex)+          {+            cb(std::move(message));+            message.clear();+          }+        }++        return;+      }++      case 0xF1:+      case 0xF8:+        if (this->configuration.ignore_timing)+          return;+        break;++      case 0xFE:+        if (this->configuration.ignore_sensing)+          return;+        break;++      default:+        break;+    }++    message.assign(bytes.begin(), bytes.end());+    message.timestamp = timestamp;++    cb(std::move(message));+    message.clear();+  }++  void+  on_bytes_segmented(const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)+  {+    if (bytes.empty())+      return;++    const bool finished_sysex = has_finished_sysex(bytes);+    switch (state)+    {+      case in_sysex:+        return on_continue_sysex(cb, bytes, finished_sysex);++      case main:+        return on_main(cb, bytes, timestamp, finished_sysex);+    }+  }++public:+  libremidi::message message;++private:+  enum+  {+    main,+    in_sysex+  } state{main};+};+}++namespace midi2+{+struct input_state_machine : input_state_machine_base<ump_input_configuration>+{+  using input_state_machine_base::input_state_machine_base;++public:+  void on_bytes_multi(std::span<const unsigned char> bytes, int64_t timestamp)+  {+    auto ptr = reinterpret_cast<const uint32_t*>(bytes.data());+    auto sz = bytes.size() / 4;+    return on_bytes_multi({ptr, sz}, timestamp);+  }++  void on_bytes_multi(std::span<const uint32_t> bytes, int64_t timestamp)+  {+    if (this->configuration.on_message)+      on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);+    if (this->configuration.on_raw_data)+      this->configuration.on_raw_data(bytes, timestamp);+  }++  void on_bytes(std::span<const uint32_t> bytes, int64_t timestamp)+  {+    if (this->configuration.on_message)+      on_bytes_segmented(this->configuration.on_message, bytes, timestamp);+    if (this->configuration.on_raw_data)+      this->configuration.on_raw_data(bytes, timestamp);+  }++private:+  // Function to process a byte stream which may contain multiple successive+  // MIDI events (CoreMIDI, ALSA Sequencer can work like this)+  void on_bytes_multi_segmented(+      const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)+  {+    auto count = bytes.size();+    auto ump_stream = bytes.data();+    while (count > 0)+    {+      // Handle NOOP (or padding)+      while (count > 0 && ump_stream[0] == 0)+      {+        count--;+        ump_stream++;+      }++      if (count == 0)+        break;++      const auto ump_uints = cmidi2_ump_get_num_bytes(ump_stream[0]) / 4;+      on_bytes_segmented(cb, {ump_stream, ump_stream + ump_uints}, timestamp);++      ump_stream += ump_uints;+      count -= ump_uints;+    }+  }++  // Function to process bytes corresponding to at most one midi event+  void+  on_bytes_segmented(const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)+  {+    // Filter according to message type+    switch(cmidi2_ump_get_message_type(bytes.data()))+    {+      case CMIDI2_MESSAGE_TYPE_UTILITY:+      {+        // All the utility messages are about timing+        if (this->configuration.ignore_timing)+          return;+        break;+      }++      case CMIDI2_MESSAGE_TYPE_SYSTEM:+      {+        if (this->configuration.ignore_timing)+        {+          auto status = cmidi2_ump_get_system_message_byte2(bytes.data());+          switch(status)+          {+            case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE:+            case CMIDI2_SYSTEM_STATUS_SONG_POSITION:+            case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK:+              return;+          }+        }++       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:+      {+        if (this->configuration.ignore_sysex)+          return;+        break;+      }+    }++    libremidi::ump msg;+    std::copy(bytes.begin(), bytes.end(), msg.data);+    msg.timestamp = timestamp;+    cb(std::move(msg));+  }+};+}+}
+ vendor/libremidi/detail/observer.hpp view
@@ -0,0 +1,27 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/error_handler.hpp>++#include <memory>+#include <vector>++namespace libremidi+{+class observer_api+{+public:+  virtual ~observer_api() = default;++  virtual libremidi::API get_current_api() const noexcept = 0;+  virtual std::vector<libremidi::input_port> get_input_ports() const noexcept = 0;+  virtual std::vector<libremidi::output_port> get_output_ports() const noexcept = 0;+};++template <typename T, typename Arg>+std::unique_ptr<observer_api> make(libremidi::observer_configuration&& conf, Arg&& arg)+{+  return std::make_unique<T>(std::move(conf), std::move(arg));+}+}
+ vendor/libremidi/detail/semaphore.hpp view
@@ -0,0 +1,24 @@+#pragma once++#include <chrono>+#include <semaphore>++struct semaphore_pair_lock+{+  std::binary_semaphore sem_cleanup{0};+  std::binary_semaphore sem_needpost{0};+  void prepare_release_client()+  {+    using namespace std::literals;++    // FIXME if jack is not running we can skip this+    this->sem_needpost.release();+    this->sem_cleanup.try_acquire_for(1s);+  }++  void check_client_released()+  {+    if (!this->sem_needpost.try_acquire())+      this->sem_cleanup.release();+  }+};
+ vendor/libremidi/detail/ump_stream.hpp view
@@ -0,0 +1,62 @@+#pragma once+#include <libremidi/cmidi2.hpp>+#include <libremidi/error.hpp>++#include <cinttypes>+#include <cstdint>++namespace libremidi+{+enum class segmentation_error+{+  no_error,+  need_space,+  other+};++/**+ * Utility function to segment an ump stream into individual messages.+ * Used to send a stream to APIs that work message-by-message.+ */+inline stdx::error+segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, auto realloc_func)+{+  while (count > 0)+  {+    // Handle NOOP (or padding)+    while (count > 0 && ump_stream[0] == 0)+    {+      count--;+      ump_stream++;+    }++    if (count == 0)+      break;++    const auto ump_bytes = cmidi2_ump_get_num_bytes(ump_stream[0]);++    // FIXME std::expected, propagate the error back to caller?+    switch (int err = static_cast<int>(write_func(ump_stream, ump_bytes)))+    {+      case 0:+        break;+      case -ENOMEM:+      case ENOMEM:+        // Try again if we didn't have enough space in the OS queue+        realloc_func();+        if (auto err = write_func(ump_stream, ump_bytes); err != std::errc{})+          return std::make_error_code(err);+        break;+      default:+        return from_errc(err);+    }++    const auto ump_uints = ump_bytes / 4;+    ump_stream += ump_uints;+    count -= ump_uints;+  }++  return stdx::error{};+}++}
+ vendor/libremidi/error.hpp view
@@ -0,0 +1,64 @@+#pragma once+#include <libremidi/config.hpp>++#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"+#pragma GCC diagnostic ignored "-Wunused-parameter"+#include <libremidi/system_error2.hpp>+#pragma GCC diagnostic pop++#include <functional>+#include <string_view>+#include <version>++#if __has_include(<source_location>) && (__cpp_lib_source_location >= 201907L)+  #include <source_location>+namespace libremidi { using source_location = std::source_location; }+#else+namespace libremidi {+struct source_location {+  static source_location current() noexcept { return {}; }+  int line() const noexcept { return -1; }+  int offset() const noexcept { return -1; }+  const char* function_name() const noexcept { return "(unknown)"; }+  const char* file_name() const noexcept { return "(unknown)"; }+};+}+#endif++namespace libremidi+{+inline auto from_errc(int ret) noexcept+{+  return static_cast<std::errc>(-ret);+}++/*! \brief Error callback function+    \param type Type of error.+    \param errorText Error description.++    Note that class behaviour is undefined after a critical error (not+    a warning) is reported.+ */+using midi_error_callback = std::function<void(std::string_view errorText, const source_location&)>;+using midi_warning_callback = std::function<void(std::string_view errorText, const source_location&)>;+}++#if !defined(LIBREMIDI_LOG)+  #if !defined(__LIBREMIDI_DEBUG__)+    #define LIBREMIDI_LOG(...) \+      do                       \+      {                        \+      } while (0)+  #else+    #include <iostream>+    #define LIBREMIDI_LOG(...)        \+      do                              \+      {                               \+        [](auto&&... args) {          \+          (std::cerr << ... << args); \+          std::cerr << std::endl;     \+        }(__VA_ARGS__);               \+      } while (0)+  #endif+#endif
+ vendor/libremidi/error_handler.hpp view
@@ -0,0 +1,63 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/output_configuration.hpp>++#include <string_view>++namespace libremidi+{+struct error_handler+{+  //! Error reporting function for libremidi classes.+  void error_impl(+      const midi_error_callback& callback, std::string_view errorString,+      const source_location& location) const+  {+    if (callback)+    {+      if (first_error)+        return;++      first_error = true;+      callback(errorString, location);+      first_error = false;+    }+    else+    {+      LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");+    }+  }++  //! Warning reporting function for libremidi classes.+  void warning_impl(+      const midi_warning_callback& callback, std::string_view errorString,+      const source_location& location) const+  {+    if (callback)+    {+      if (first_warning)+        return;++      first_warning = true;+      callback(errorString, location);+      first_warning = false;+    }+    else+    {+      LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");+    }+  }++  // To prevent infinite error loops+  mutable bool first_error{};+  mutable bool first_warning{};+};++// 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())+}
+ vendor/libremidi/input_configuration.hpp view
@@ -0,0 +1,127 @@+#pragma once+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>+#include <libremidi/message.hpp>+#include <libremidi/ump.hpp>++#include <functional>++namespace libremidi+{+//! Specify how timestamps are handled in the system+enum timestamp_mode+{+  //! No timestamping at all, all timestamps are zero+  NoTimestamp,++  //! In nanoseconds, timestamp is the time since the previous event (or zero)+  Relative,++  //! In nanoseconds, as per an arbitrary reference which may be provided by the host API,+  //! e.g. since the JACK cycle start, ALSA sequencer queue creation, through AudioHostTime on macOS.+  //! It offers the most precise ordering between events as it's the closest to the real timestamp of+  //! the event as provided by the host API.+  //! If the API does not provide any timing, it will be mapped to SystemMonotonic instead.+  Absolute,++  //! In nanoseconds, as per std::steady_clock::now() or equivalent (raw if possible).+  //! May be less precise than Absolute as timestamping is done within the library,+  //! but is more useful for system-wide synchronization.+  //! Note: depending on the backend, Absolute and SystemMonotonic may be the same.+  SystemMonotonic,++  //! For APIs which are based on audio process cycles such as JACK, timestamps will be in frames since+  //! the beginning of the current cycle's audio buffer+  AudioFrame,++  //! Will call the custom timestamping function provided by the user in the input configuration.+  Custom+};++using timestamp = int64_t;+using message_callback = std::function<void(message&& message)>;+using raw_callback = std::function<void(std::span<const uint8_t>, timestamp)>;+using timestamp_callback = std::function<timestamp(timestamp)>;+struct input_configuration+{+  //! Set a callback function to be invoked for incoming MIDI messages.+  //! Either this or on_raw_message must be set+  message_callback on_message{};++  //! Invoked for incoming MIDI bytes. No transformation, no filtering, no packetization,+  //! just the MIDI data straight from the source.+  raw_callback on_raw_data{};++  //! Set a custom callback function to be invoked for timestamping MIDI messages.+  //! Input: the API provided timestamp in nanoseconds, if available, for reference.+  //! (e.g. the same as "Absolute").+  //! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.+  timestamp_callback get_timestamp{};++  //! Set an error callback function to be invoked when an error has occured.+  /*!+    The callback function will be called whenever an error has occured. It is+    best to set the error callback function before opening a port.+  */+  midi_error_callback on_error{};+  midi_warning_callback on_warning{};++  //! Specify whether certain MIDI message types should be queued or ignored+  //! during input.+  /*!+    By default, MIDI timing and active sensing messages are ignored+    during message input because of their relative high data rates.+    MIDI sysex messages are ignored by default as well.  Variable+    values of "true" imply that the respective message type will be+    ignored.+  */+  uint32_t ignore_sysex : 1 = true;+  uint32_t ignore_timing : 1 = true;+  uint32_t ignore_sensing : 1 = true;++  //! Timestamp mode. See @libremidi::timestamp_mode+  uint32_t timestamps : 3 = timestamp_mode::Absolute;+};++using ump_callback = std::function<void(ump&&)>;+using raw_ump_callback = std::function<void(std::span<const uint32_t>, timestamp)>;+struct ump_input_configuration+{+  //! Set a callback function to be invoked for incoming UMP messages.+  //! Either this or on_raw_message must be set+  ump_callback on_message{};++  //! Invoked for incoming UMP bytes. No transformation, no filtering, no packetization,+  //! just the UMP data straight from the source.+  raw_ump_callback on_raw_data{};++  //! Set a custom callback function to be invoked for timestamping MIDI messages.+  //! Input: the API provided timestamp in nanoseconds, if available, for reference.+  //! (e.g. the same as "Absolute").+  //! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.+  timestamp_callback get_timestamp{};++  //! Set an error callback function to be invoked when an error has occured.+  /*!+    The callback function will be called whenever an error has occured. It is+    best to set the error callback function before opening a port.+  */+  midi_error_callback on_error{};+  midi_warning_callback on_warning{};++  //! Specify whether certain MIDI message types should be queued or ignored+  //! during input.+  /*!+    By default, MIDI timing and active sensing messages are ignored+    during message input because of their relative high data rates.+    MIDI sysex messages are ignored by default as well.  Variable+    values of "true" imply that the respective message type will be+    ignored.+  */+  uint32_t ignore_sysex : 1 = true;+  uint32_t ignore_timing : 1 = true;+  uint32_t ignore_sensing : 1 = true;++  uint32_t timestamps : 3 = timestamp_mode::Absolute;+};+}
+ vendor/libremidi/libremidi.hpp view
@@ -0,0 +1,279 @@+#pragma once+/* This software is based on the RtMidi and ModernMidi libraries.++  RtMidi WWW site: http://music.mcgill.ca/~gary/libremidi/++  RtMidi: realtime MIDI i/o C++ classes+  Copyright (c) 2003-2017 Gary P. Scavone++  Permission is hereby granted, free of charge, to any person+  obtaining a copy of this software and associated documentation files+  (the "Software"), to deal in the Software without restriction,+  including without limitation the rights to use, copy, modify, merge,+  publish, distribute, sublicense, and/or sell copies of the Software,+  and to permit persons to whom the Software is furnished to do so,+  subject to the following conditions:++  The above copyright notice and this permission notice shall be+  included in all copies or substantial portions of the Software.++  Any person wishing to distribute modifications to the Software is+  asked to send the modifications to the original developer so that+  they can be incorporated into the canonical version.  This is,+  however, not a binding provision of this license.++  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR+  ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF+  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.++ ------------++  ModernMidi Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++  Redistribution and use in source and binary forms, with or without+  modification, are permitted provided that the following conditions are met:++  * Redistributions of source code must retain the above copyright notice, this+    list of conditions and the following disclaimer.++  * Redistributions in binary form must reproduce the above copyright notice,+    this list of conditions and the following disclaimer in the documentation+    and/or other materials provided with the distribution.++  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+ POSSIBILITY OF SUCH DAMAGE.+*/++#include <libremidi/api.hpp>+#include <libremidi/defaults.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/message.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/output_configuration.hpp>++#if LIBREMIDI_NI_MIDI2_COMPAT+  #include <midi/sysex.h>+  #include <midi/universal_packet.h>+#endif+#include <any>++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+//! for a given API.+class LIBREMIDI_EXPORT observer+{+public:+  //! Open an observer instance with the given configuration.+  //!+  //! * api_conf can be an instance of observer_configuration,+  //!   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);+  observer(const observer&) = delete;+  observer(observer&& other) noexcept;+  observer& operator=(const observer&) = delete;+  observer& operator=(observer&& other) noexcept;+  ~observer();++  [[nodiscard]] libremidi::API get_current_api() const noexcept;++  //! Return identifiers for the available MIDI ports+  [[nodiscard]] std::vector<libremidi::input_port> get_input_ports() const noexcept;+  [[nodiscard]] std::vector<libremidi::output_port> get_output_ports() const noexcept;++private:+  std::unique_ptr<class observer_api> impl_;+};++//! Main class for receiving MIDI 1.0 and 2.0 messages.+class LIBREMIDI_EXPORT midi_in+{+public:+  //! Construct a midi_in object with the default MIDI 1 back-end for the platform+  explicit midi_in(const input_configuration& conf) noexcept;++  //! 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);++  //! Construct a midi_in object with the default MIDI 2 back-end for the platform+  explicit midi_in(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);++  midi_in(const midi_in&) = delete;+  midi_in(midi_in&& other) noexcept;+  midi_in& operator=(const midi_in&) = delete;+  midi_in& operator=(midi_in&& other) noexcept;+  ~midi_in();++  //! Returns the MIDI API specifier for the current instance of midi_in.+  [[nodiscard]] libremidi::API get_current_api() const noexcept;++  //! Open a MIDI input connection+  stdx::error+  open_port(const input_port& pt, std::string_view local_port_name = "libremidi input");++  //! Create a virtual input port, with optional name, to allow software+  //! connections.+  //!+  //! \param portName An optional name for the application port that is+  //!                 used to connect to portId can be specified.+  stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port");++  stdx::error set_port_name(std::string_view portName);++  //! Close an open MIDI connection (if one exists).+  stdx::error close_port();++  //! Returns true if a port has been opened successfully with open_port or open_virtual_port+  [[nodiscard]] bool is_port_open() const noexcept;++  //! Returns true if a port is connected to another port.+  //! Never true for virtual ports.+  [[nodiscard]] bool is_port_connected() const noexcept;++  //! Returns the current timestamp for absolute ticks.+  timestamp absolute_timestamp() const noexcept;++private:+  std::unique_ptr<class midi_in_api> impl_;+};++//! Main class for sending MIDI 1.0 and 2.0 messages.+class LIBREMIDI_EXPORT midi_out+{+public:+  //! Construct a midi_out object with the default back-end for the platform+  explicit midi_out(const output_configuration& conf = {}) noexcept;++  //! 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);++  midi_out(const midi_out&) = delete;+  midi_out(midi_out&& other) noexcept;+  midi_out& operator=(const midi_out&) = delete;+  midi_out& operator=(midi_out&& other) noexcept;+  ~midi_out();++  //! Returns the MIDI API specifier for the current instance of midi_out.+  [[nodiscard]] libremidi::API get_current_api() const noexcept;++  //! Open a MIDI output connection.+  stdx::error+  open_port(const output_port& pt, std::string_view local_port_name = "libremidi output") const;++  //! Close an open MIDI connection (if one exists).+  stdx::error close_port() const;++  //! Returns true if a port has been opened successfully with open_port or open_virtual_port+  [[nodiscard]] bool is_port_open() const noexcept;++  //! Returns true if a port is connected to another port.+  //! Never true for virtual ports.+  [[nodiscard]] bool is_port_connected() const noexcept;++  //! Create a virtual output port, with optional name, to allow software+  //! connections.+  //!+  //! \param portName An optional name for the application port that is+  //!                 used to connect to portId can be specified.+  stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port") const;++  stdx::error set_port_name(std::string_view portName) const;++  //! Immediately send a single message out an open MIDI output port.+  /*!+      An exception is thrown if an error occurs during output or an+      output connection was not previously established.+  */+  stdx::error send_message(const libremidi::message& message) const;++  //! Immediately send a single message to an open MIDI output port.+  stdx::error send_message(const unsigned char* message, size_t size) const;+  stdx::error send_message(std::span<const unsigned char>) const;+  stdx::error send_message(unsigned char b0) const;+  stdx::error send_message(unsigned char b0, unsigned char b1) const;+  stdx::error send_message(unsigned char b0, unsigned char b1, unsigned char b2) const;++  // Avoid silly mistakes:+  stdx::error send_message(auto* message) const noexcept = delete;+  stdx::error send_message(const auto* message) const noexcept = delete;++  //! Current time in the timestamp referential+  int64_t current_time();++  //! Try to schedule a message later in time if the underlying API supports it+  //! (currently not implemented anywhere)+  stdx::error schedule_message(int64_t timestamp, const unsigned char* message, size_t size) const;++  //! Immediately send a single UMP packet to an open MIDI output port.+  stdx::error send_ump(const uint32_t* message, size_t size) const;+  stdx::error send_ump(const libremidi::ump&) const;+  stdx::error send_ump(std::span<const uint32_t>) const;+  stdx::error send_ump(uint32_t b0) const;+  stdx::error send_ump(uint32_t b0, uint32_t b1) const;+  stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const;+  stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const;+  // Better compat with cmidi2+  stdx::error send_ump(int32_t b0) const;+  stdx::error send_ump(int64_t b01) const;+  stdx::error send_ump(uint64_t b01) const;++// Interop with ni-midi2+#if LIBREMIDI_NI_MIDI2_COMPAT+  stdx::error send_ump(const midi::universal_packet& pkt) const { send_ump(pkt.data, pkt.size()); }+  stdx::error send_ump(const midi::sysex7& msg, int group = 0)+  {+    midi::send_sysex7(msg, group, [&](const midi::sysex7_packet& x) { send_ump(x.data); });+  }+  stdx::error send_ump(const midi::sysex8& msg, int stream, int group = 0)+  {+    midi::send_sysex8(msg, stream, group, [&](const midi::sysex8_packet& x) { send_ump(x.data); });+  }+#endif++  //! Try to schedule an UMP packet later in time if the underlying API supports it+  //! (currently not implemented anywhere)+  stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const;++private:+  std::unique_ptr<class midi_out_api> impl_;+};+}++#if defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/libremidi.cpp>+  #include <libremidi/midi_in.cpp>+  #include <libremidi/midi_out.cpp>+  #include <libremidi/observer.cpp>++  #if defined(__EMSCRIPTEN__)+    #include <libremidi/backends/emscripten/midi_access.cpp>+    #include <libremidi/backends/emscripten/midi_in.cpp>+    #include <libremidi/backends/emscripten/midi_out.cpp>+    #include <libremidi/backends/emscripten/observer.cpp>+  #endif+#endif
+ vendor/libremidi/message.hpp view
@@ -0,0 +1,301 @@+#pragma once+#include <libremidi/config.hpp>++#include <algorithm>+#include <cstdint>+#include <span>+#include <vector>+#if defined(__cpp_exceptions)+  #include <stdexcept>+#endif++namespace libremidi+{+enum class message_type : uint8_t+{+  INVALID = 0x0,+  // Standard Message+  NOTE_OFF = 0x80,+  NOTE_ON = 0x90,+  POLY_PRESSURE = 0xA0,+  CONTROL_CHANGE = 0xB0,+  PROGRAM_CHANGE = 0xC0,+  AFTERTOUCH = 0xD0,+  PITCH_BEND = 0xE0,++  // System Common Messages+  SYSTEM_EXCLUSIVE = 0xF0,+  TIME_CODE = 0xF1,+  SONG_POS_POINTER = 0xF2,+  SONG_SELECT = 0xF3,+  RESERVED1 = 0xF4,+  RESERVED2 = 0xF5,+  TUNE_REQUEST = 0xF6,+  EOX = 0xF7,++  // System Realtime Messages+  TIME_CLOCK = 0xF8,+  RESERVED3 = 0xF9,+  START = 0xFA,+  CONTINUE = 0xFB,+  STOP = 0xFC,+  RESERVED4 = 0xFD,+  ACTIVE_SENSING = 0xFE,+  SYSTEM_RESET = 0xFF+};++enum class meta_event_type : uint8_t+{+  SEQUENCE_NUMBER = 0x00,+  TEXT = 0x01,+  COPYRIGHT = 0x02,+  TRACK_NAME = 0x03,+  INSTRUMENT = 0x04,+  LYRIC = 0x05,+  MARKER = 0x06,+  CUE = 0x07,+  PATCH_NAME = 0x08,+  DEVICE_NAME = 0x09,+  CHANNEL_PREFIX = 0x20,+  MIDI_PORT = 0x21,+  END_OF_TRACK = 0x2F,+  TEMPO_CHANGE = 0x51,+  SMPTE_OFFSET = 0x54,+  TIME_SIGNATURE = 0x58,+  KEY_SIGNATURE = 0x59,+  PROPRIETARY = 0x7F,+  UNKNOWN = 0xFF+};++struct message+{+  midi_bytes bytes;+  int64_t timestamp{};++  message() noexcept = default;+  operator std::span<const unsigned char>() const noexcept { return {bytes.data(), bytes.size()}; }++  message(const midi_bytes& src_bytes, int64_t src_timestamp) noexcept+      : bytes(src_bytes)+      , timestamp(src_timestamp)+  {+  }+  message(std::initializer_list<unsigned char> args) noexcept+      : bytes{args}+  {+  }++  template <typename... Args>+  auto assign(Args&&... args)+  {+    return bytes.assign(std::forward<Args>(args)...);+  }+  template <typename... Args>+  auto insert(Args&&... args)+  {+    return bytes.insert(std::forward<Args>(args)...);+  }++  auto size() const noexcept { return bytes.size(); }+  auto empty() const noexcept { return bytes.empty(); }++  auto clear() noexcept { bytes.clear(); }++  auto& operator[](midi_bytes::size_type i) const noexcept { return bytes[i]; }+  auto& operator[](midi_bytes::size_type i) noexcept { return bytes[i]; }++  auto& front() const { return bytes.front(); }+  auto& back() const { return bytes.back(); }+  auto& front() { return bytes.front(); }+  auto& back() { return bytes.back(); }++  auto begin() const noexcept { return bytes.begin(); }+  auto end() const noexcept { return bytes.end(); }+  auto begin() noexcept { return bytes.begin(); }+  auto end() noexcept { return bytes.end(); }+  auto cbegin() const noexcept { return bytes.cbegin(); }+  auto cend() const noexcept { return bytes.cend(); }+  auto cbegin() noexcept { return bytes.cbegin(); }+  auto cend() noexcept { return bytes.cend(); }+  auto rbegin() const noexcept { return bytes.rbegin(); }+  auto rend() const noexcept { return bytes.rend(); }+  auto rbegin() noexcept { return bytes.rbegin(); }+  auto rend() noexcept { return bytes.rend(); }++  bool uses_channel(int channel) const+  {+#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);+  }++  int get_channel() const noexcept+  {+    if ((bytes[0] & 0xF0) != 0xF0)+      return (bytes[0] & 0xF) + 1;+    return 0;+  }++  bool is_meta_event() const noexcept { return bytes[0] == 0xFF; }++  meta_event_type get_meta_event_type() const noexcept+  {+    if (!is_meta_event())+      return meta_event_type::UNKNOWN;+    return static_cast<meta_event_type>(bytes[1]);+  }++  message_type get_message_type() const noexcept+  {+    if (bytes[0] >= static_cast<uint8_t>(message_type::SYSTEM_EXCLUSIVE))+    {+      return static_cast<message_type>(bytes[0] & 0xFF);+    }+    else+    {+      return static_cast<message_type>(bytes[0] & 0xF0);+    }+  }++  bool is_note_on_or_off() const noexcept+  {+    const auto status = get_message_type();+    return (status == message_type::NOTE_ON) || (status == message_type::NOTE_OFF);+  }+};++struct channel_events+{+  static constexpr uint8_t clamp_channel(int channel) noexcept+  {+    channel--;+    if (channel < 0)+      channel = 0;+    else if (channel > 15)+      channel = 15;+    return static_cast<uint8_t>(channel);+  }++  static uint8_t make_command(const message_type type, const int channel) noexcept+  {+    return static_cast<uint8_t>(static_cast<uint8_t>(type) | clamp_channel(channel));+  }++  static message note_on(uint8_t channel, uint8_t note, uint8_t velocity) noexcept+  {+    return {make_command(message_type::NOTE_ON, channel), note, velocity};+  }++  static message note_off(uint8_t channel, uint8_t note, uint8_t velocity) noexcept+  {+    return {make_command(message_type::NOTE_OFF, channel), note, velocity};+  }++  static message control_change(uint8_t channel, uint8_t control, uint8_t value) noexcept+  {+    return {make_command(message_type::CONTROL_CHANGE, channel), control, value};+  }++  static message program_change(uint8_t channel, uint8_t value) noexcept+  {+    return {make_command(message_type::PROGRAM_CHANGE, channel), value};+  }++  static message pitch_bend(uint8_t channel, int value) noexcept+  {+    return {+        make_command(message_type::PITCH_BEND, channel), static_cast<unsigned char>(value & 0x7F),+        static_cast<uint8_t>((value >> 7) & 0x7F)};+  }++  static message pitch_bend(uint8_t channel, uint8_t lsb, uint8_t msb) noexcept+  {+    return {make_command(message_type::PITCH_BEND, channel), lsb, msb};+  }++  static message poly_pressure(uint8_t channel, uint8_t note, uint8_t value) noexcept+  {+    return {make_command(message_type::POLY_PRESSURE, channel), note, value};+  }++  static message aftertouch(uint8_t channel, uint8_t value) noexcept+  {+    return {make_command(message_type::AFTERTOUCH, channel), value};+  }+};++struct meta_events+{+  static message end_of_track() noexcept { return {0xFF, 0x2F, 0}; }++  static message channel(int channel) noexcept+  {+    return {0xff, 0x20, 0x01, static_cast<uint8_t>(std::clamp(0, 0xff, channel - 1))};+  }++  static message tempo(int mpqn) noexcept+  {+    return {+        0xff,+        81,+        3,+        static_cast<uint8_t>(mpqn >> 16),+        static_cast<uint8_t>(mpqn >> 8),+        static_cast<uint8_t>(mpqn)};+  }++  static message time_signature(+      int numerator, int denominator, int clocks_per_click = 1,+      int notated_32nd_notes_per_beat = 96)+  {+    int n = 1;+    int powTwo = 0;++    while (n < denominator)+    {+      n <<= 1;+      ++powTwo;+    }++    return {+        0xff,+        0x58,+        0x04,+        static_cast<uint8_t>(numerator),+        static_cast<uint8_t>(powTwo),+        static_cast<uint8_t>(clocks_per_click),+        static_cast<uint8_t>(notated_32nd_notes_per_beat)};+  }++  // Where key index goes from -7 (7 flats, C♭ Major) to +7 (7 sharps, C♯+  // Major)+  static message key_signature(int keyIndex, bool isMinor)+  {+#if defined(__cpp_exceptions)+    if (keyIndex < -7 || keyIndex > 7)+      throw std::range_error("out of range");+#endif+    return {+        0xff, 0x59, 0x02, static_cast<uint8_t>(keyIndex),+        isMinor ? static_cast<uint8_t>(1) : static_cast<uint8_t>(0)};+  }++  static message song_position(int positionInBeats) noexcept+  {+    return {+        0xf2, static_cast<uint8_t>(positionInBeats & 127),+        static_cast<uint8_t>((positionInBeats >> 7) & 127)};+  }+};++struct track_event+{+  int tick = 0;+  int track = 0;+  message m;+};++typedef std::vector<track_event> midi_track;+}
+ vendor/libremidi/observer_configuration.hpp view
@@ -0,0 +1,86 @@+#pragma once+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>++#include <compare>+#include <string>++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<client_handle>(-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<port_handle>(-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<void(const input_port&)>;+using output_port_callback = std::function<void(const output_port&)>;+struct observer_configuration+{+  midi_error_callback on_error{};+  midi_warning_callback on_warning{};++  input_port_callback input_added{};+  input_port_callback input_removed{};+  output_port_callback output_added{};+  output_port_callback output_removed{};++  // Observe hardware ports+  uint32_t track_hardware : 1 = true;++  // Observe software (virtual) ports if the API provides it+  uint32_t track_virtual : 1 = false;++  // Observe any port - some systems have other weird port types than hw / sw, this covers them+  uint32_t track_any : 1 = false;++  // Notify of the existing ports in the observer constructor+  uint32_t notify_in_constructor : 1 = true;++  bool has_callbacks() const noexcept+  {+    return input_added || input_removed || output_added || output_removed;+  }+};+}
+ vendor/libremidi/output_configuration.hpp view
@@ -0,0 +1,21 @@+#pragma once+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>+#include <libremidi/input_configuration.hpp>++namespace libremidi+{+struct output_configuration+{+  //! Set an error callback function to be invoked when an error has occured.+  /*!+    The callback function will be called whenever an error has occured. It is+    best to set the error callback function before opening a port.+  */+  midi_error_callback on_error{};+  midi_warning_callback on_warning{};++  //! Timestamp mode for the timestamps passed to schedule_message+  uint32_t timestamps : 3 = timestamp_mode::Absolute;+};+}
+ vendor/libremidi/reader.hpp view
@@ -0,0 +1,75 @@+/*+Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+*/++#pragma once++#include <libremidi/message.hpp>++namespace libremidi+{+/**+ * @brief reads Standard MIDI files (SMF).+ *+ * Usage:++ * ```+ * libremidi::reader r;+ * auto res = r.parse(midi_bytes, num_bytes);+ * ```+ */+class LIBREMIDI_EXPORT reader+{+public:+  enum parse_result+  {+    invalid,    //! Nothing could be parsed+    incomplete, //! Some of the data could be parsed, but not all: there may be missing events /+                //! tracks+    complete,   //! All the data could be parsed but not necessarily validated+    validated   //! The data could be parsed and conforms to SMF rules+  };+  explicit reader(bool useAbsolute = false);+  ~reader();++  parse_result parse(const uint8_t* data, std::size_t size) noexcept;+  parse_result parse(const std::vector<uint8_t>& buffer) noexcept;+  parse_result parse(std::span<uint8_t> buffer) noexcept;++  [[nodiscard]] double get_end_time() const noexcept;++  float ticksPerBeat{}; // precision (number of ticks distinguishable per second)+  float startingTempo{};+  int format{};++  std::vector<midi_track> tracks;++private:+  bool useAbsoluteTicks{};+};+}++#if defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/reader.cpp>+#endif
+ vendor/libremidi/shared_context.hpp view
@@ -0,0 +1,33 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>++#include <any>++namespace libremidi+{++class shared_context+{+public:+  shared_context() = default;+  virtual ~shared_context() = default;+  shared_context(const shared_context&) = delete;+  shared_context(shared_context&&) = delete;+  shared_context& operator=(const shared_context&) = delete;+  shared_context& operator=(shared_context&&) = delete;++  virtual void start_processing() = 0;+  virtual void stop_processing() = 0;+};++struct shared_configurations+{+  std::shared_ptr<shared_context> context;+  std::any observer, in, out;+};++LIBREMIDI_EXPORT+shared_configurations create_shared_context(libremidi::API api, std::string_view client_name);++}
+ vendor/libremidi/system_error2.hpp view
@@ -0,0 +1,3501 @@+#ifndef STDX_COMPILER_HPP+  #define STDX_COMPILER_HPP++  // Check compiler macros.  Note that Clang defines __GNUC__ and other GNU macros as well,+  // but GNU does not define Clang macros, so we must check for Clang first.++  #if defined(__llvm__) || defined(__clang__)++  // -------- LLVM/Clang++    #define STDX_CLANG_COMPILER 1++    #if defined(__cpp_variable_templates) && (__cplusplus >= 201703L)+      #define STDX_VARIABLE_TEMPLATES 1+    #endif++  #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__)++  // -------- GNU G++++    #include <version>++    #define STDX_GCC_COMPILER 1++    #if (__GNUC__ >= 5) && (__cplusplus >= 201703L)+      #define STDX_VARIABLE_TEMPLATES 1+    #endif++    #if ((__GNUC__ > 7) || ((__GNUC__ == 7) && (__GNUC_MINOR__ >= 1)))+      #define STDX_TRIVIALLY_MOVE_CONSTRUCTIBLE 1+    #endif++  #endif++  #if defined(STDX_GCC_COMPILER)+    #if (__GNUC__ == 7) && ((__GNUC_MINOR__ >= 1) && (__GNUC_MINOR__ <= 3))+      #define STDX_GCC7_WORKAROUND_CONSTEXPR+    #else+      #define STDX_GCC7_WORKAROUND_CONSTEXPR constexpr+    #endif+  #else+    #define STDX_GCC7_WORKAROUND_CONSTEXPR constexpr+  #endif++  // Add a legacy constexpr macro for cases where GCC < 5 incorrectly applies the const+  // qualifier to constexpr member functions or does not support relaxed constexpr functions+  //+  #if defined(STDX_GCC_COMPILER) && (__GNUC__ < 5)+    #define STDX_LEGACY_CONSTEXPR+  #else+    #define STDX_LEGACY_CONSTEXPR constexpr+  #endif++  #if defined(_MSC_VER) && (_MSC_VER >= 1910)+    #define STDX_MSVC_EMPTY_BASE_CLASSES __declspec(empty_bases)+  #else+    #define STDX_MSVC_EMPTY_BASE_CLASSES+  #endif++  #if defined(__cpp_impl_trivially_relocatable)+    #define STDX_TRIVIALLY_RELOCATABLE [[trivially_relocatable]]+  #else+    #define STDX_TRIVIALLY_RELOCATABLE+  #endif++#endif // STDX_COMPILER_HPP+++#ifndef STDX_TYPE_TRAITS_HPP+  #define STDX_TYPE_TRAITS_HPP++  #include <type_traits>++namespace stdx {++// Implementation of std::void_t for use with pre-C++17 compilers.+//+namespace detail {++template <class... T>+struct void_t_impl+{+  using type = void;+};++} // end namespace detail++template <class... T>+using void_t = typename detail::void_t_impl<T...>::type;++template <class T, class U = T>+struct dependent_type+{+  using type = U;+};++template <class T, class U = T>+using dependent_type_t = typename dependent_type<T, U>::type;++template <class... B>+struct disjunction : std::false_type+{ };++template <class B1>+struct disjunction<B1> : B1+{ };++template<class B1, class... Bn>+struct disjunction<B1, Bn...>+    :+      std::conditional_t<+          bool(B1::value),+          B1,+          disjunction<Bn...>+          >+{ };++struct sentinel_type+{+  static constexpr bool value = true;+  using type = void;+};++template <bool B>+struct bool_constant : std::integral_constant<bool, B>+{ };++// Implementation of std::remove_cvref for use with pre-C++20 compilers.+//+template <class T>+struct remove_cvref+{+  using type = std::remove_cv_t<std::remove_reference_t<T>>;+};++template <class T>+using remove_cvref_t = typename remove_cvref<T>::type;++  #if defined(STDX_GCC_COMPILER)+    // Support missing functionality on older compilers (<= gcc 4.7)+    //+    #if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 7)+    // Map the old incorrect type-trait names to the newer correct ones+template <class T>+using is_trivially_copyable = std::is_trivial<T>;++template <class T>+using is_trivially_copy_constructible = std::has_trivial_copy_constructor<T>;++template <class T>+using is_trivially_destructible = std::has_trivial_destructor<T>;+    #elif (__GNUC__ < 5)+template <class T>+using is_trivially_copyable = std::is_trivial<T>;++template <class T>+using is_trivially_copy_constructible = std::has_trivial_copy_constructor<T>;++template <class T>+using is_trivially_destructible = std::is_trivially_destructible<T>;+    #else+using std::is_trivially_destructible;+using std::is_trivially_copyable;+using std::is_trivially_copy_constructible;+    #endif+  #else+using std::is_trivially_destructible;+using std::is_trivially_copyable;+using std::is_trivially_copy_constructible;+  #endif++  #if defined(STDX_TRIVIALLY_MOVE_CONSTRUCTIBLE)+template <class T>+using is_trivially_move_constructible = std::is_trivially_move_constructible<T>;+  #else+template <class T>+using is_trivially_move_constructible = is_trivially_copyable<T>;+  #endif++  #if defined(__cpp_lib_trivially_relocatable)+using std::is_trivially_relocatable;+  #elif defined(__has_builtin)+    #if __has_builtin(__is_trivially_relocatable)+template <class T>+struct is_trivially_relocatable : std::bool_constant<__is_trivially_relocatable(T)> { };+      #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+    #else+template <class T>+struct is_trivially_relocatable : is_trivially_copyable<T> { };+      #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+    #endif+  #else+template <class T>+struct is_trivially_relocatable : is_trivially_copyable<T> { };+    #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+  #endif++  #if __cplusplus >= 201703L+    #define STDX_LEGACY_INLINE_CONSTEXPR inline constexpr+  #else+    #define STDX_LEGACY_INLINE_CONSTEXPR constexpr+  #endif++} // end namespace stdx++#endif++++#ifndef STDX_BIT_CAST_HPP+  #define STDX_BIT_CAST_HPP++  #include <cstdint>+  #include <cstring>+++namespace stdx {++namespace detail {++template <class To, class From>+using use_static_cast = bool_constant<+    ((std::is_integral<To>::value || std::is_enum<To>::value)+     && (std::is_integral<From>::value || std::is_enum<From>::value))+    || (std::is_same<To, From>::value && std::is_copy_constructible<To>::value)+    >;++template <class T>+using is_integral_ptr_t = bool_constant<+    std::is_same<T, std::intptr_t>::value+    || std::is_same<T, std::uintptr_t>::value+    >;++template <class To, class From>+using use_reinterpret_cast = bool_constant<+    !std::is_same<To, From>::value+    && ((+            std::is_pointer<To>::value+            && std::is_pointer<From>::value+            && std::is_convertible<From, To>::value+            )+        || (std::is_pointer<To>::value && is_integral_ptr_t<From>::value)+        || (std::is_pointer<From>::value && is_integral_ptr_t<To>::value)+        )+    >;++  #if defined(STDX_GCC_COMPILER)+template <class To, class From>+using use_union_type_punning = bool_constant<+    !use_static_cast<To, From>::value+    && !use_reinterpret_cast<To, From>::value+    && !std::is_array<To>::value+    && !std::is_array<From>::value+    >;++template <class To, class From>+union bit_cast_union+{+  From from;+  To to;+};+  #else+template <class To, class From>+using use_union_type_punning = std::false_type;+  #endif++template <class To, class From>+using can_bit_cast = bool_constant<+    (sizeof(To) == sizeof(From))+    && is_trivially_copyable<From>::value+    && is_trivially_copyable<To>::value+    >;++} // end namespace detail++template <+    class To,+    class From,+    class = std::enable_if_t<+        detail::can_bit_cast<To, From>::value+        && detail::use_static_cast<To, From>::value+        >+    >+constexpr To bit_cast(const From& from) noexcept+{+  return static_cast<To>(from);+}++template <+    class To,+    class From,+    class = std::enable_if_t<+        detail::can_bit_cast<To, From>::value+        && detail::use_reinterpret_cast<To, From>::value+        >,+    int = 0+    >+constexpr To bit_cast(const From& from) noexcept+{+  return reinterpret_cast<To>(from);+}++  #if defined(STDX_GCC_COMPILER) // GCC allows union type punning+template <+    class To,+    class From,+    class = std::enable_if_t<+        detail::can_bit_cast<To, From>::value+        && detail::use_union_type_punning<To, From>::value+        >,+    class = void+    >+constexpr To bit_cast(const From& from) noexcept+{+  return detail::bit_cast_union<To, From>{from}.to;+}+  #elif defined(STDX_CLANG_COMPILER)+    #if __has_builtin(__builtin_bit_cast)+template <+    class To,+    class From,+    class = std::enable_if_t<+        detail::can_bit_cast<To, From>::value+        && !detail::use_static_cast<To, From>::value+        && !detail::use_reinterpret_cast<To, From>::value+        >,+    class = void+    >+constexpr To bit_cast(const From& from) noexcept+{+  return __builtin_bit_cast(To, from);+}+    #else+template <+    class To,+    class From,+    class = std::enable_if_t<+        detail::can_bit_cast<To, From>::value+        && !detail::use_static_cast<To, From>::value+        && !detail::use_reinterpret_cast<To, From>::value+        >,+    class = void+    >+To bit_cast(const From& from) noexcept+{+  To to;+  std::memcpy(&to, &from, sizeof(To));+  return to;+}+    #endif+  #endif // STDX_CLANG_COMPILER++template <class To, class From>+struct is_bit_castable+    :+      bool_constant<+          (sizeof(To) == sizeof(From))+          && is_trivially_copyable<From>::value+          && is_trivially_copyable<To>::value+          >+{ };++} // end namespace stdx++#endif++++#ifndef STDX_INTRUSIVE_POINTER_HPP+  #define STDX_INTRUSIVE_POINTER_HPP+++  #include <cstdint>+  #include <atomic>+  #include <memory>++namespace stdx {++struct default_intrusive_reference_count;+class default_intrusive_reference_control;++template <+    class T,+    class RefCountAccessor = default_intrusive_reference_count,+    class Deleter = std::default_delete<T>,+    class Pointer = T*+    >+class intrusive_ptr;++using ref_count_t = std::size_t;++struct enable_reference_count+{+protected:++  constexpr enable_reference_count() noexcept : m_reference_count(1)+  { }++public:++  std::atomic<ref_count_t>& shared_reference_count() noexcept+  {+    return m_reference_count;+  }++private:++  std::atomic<ref_count_t> m_reference_count;+};++struct default_intrusive_reference_count+{+  template <class Pointer>+  std::atomic<ref_count_t>& operator()(Pointer p) const noexcept+  {+    return p->shared_reference_count();+  }+};++namespace detail {++template <class Pointer>+struct pointer_wrapper+{+  constexpr pointer_wrapper() noexcept : shared_object(nullptr)+  { }++  constexpr explicit pointer_wrapper(Pointer p) noexcept : shared_object(p)+  { }++  constexpr pointer_wrapper(const pointer_wrapper& p) noexcept = default;++  pointer_wrapper(pointer_wrapper&& p) noexcept+      : shared_object(p.shared_object)+  {+    p.shared_object = nullptr;+  }++  pointer_wrapper& operator = (const pointer_wrapper&) noexcept = default;++  pointer_wrapper& operator = (pointer_wrapper&& p) noexcept+  {+    shared_object = p.shared_object;+    p.shared_object = nullptr;+    return *this;+  }++  void assign(Pointer ptr) noexcept+  {+    shared_object = ptr;+  }++  Pointer shared_object;+};++template <+    class T,+    class RefCountAccessor,+    class Deleter,+    class Pointer,+    class PointerImplementation+    >+class intrusive_ptr_base+{+protected:++  using pointer = Pointer;+  using count_type = ref_count_t;++  template <class, class, class, class>+  friend class reference_count_base;++  constexpr intrusive_ptr_base() = default;++  constexpr intrusive_ptr_base(pointer p) noexcept+      : m_impl(p)+  { }++  template <class RefCountAccessorForwardingReference>+  constexpr explicit intrusive_ptr_base(+      RefCountAccessorForwardingReference&& f,+      std::enable_if_t<+          std::is_constructible<+              RefCountAccessor,+              RefCountAccessorForwardingReference&&+              >::value+          >* = nullptr+      )+      : m_impl(std::forward<RefCountAccessorForwardingReference>(f))+  { }++  template <class RefCountAccessorForwardingReference>+  explicit intrusive_ptr_base(+      pointer ptr,+      RefCountAccessorForwardingReference&& f,+      std::enable_if_t<+          std::is_constructible<+              RefCountAccessor,+              RefCountAccessorForwardingReference&&+              >::value+          >* = nullptr+      )+      : m_impl(ptr, std::forward<RefCountAccessorForwardingReference>(f))+  { }++  template <+      class RefCountAccessorForwardingReference,+      class DeleterForwardingReference+      >+  constexpr explicit intrusive_ptr_base(+      RefCountAccessorForwardingReference&& f,+      DeleterForwardingReference&& d,+      typename std::enable_if<+          std::is_constructible<+              RefCountAccessor,+              RefCountAccessorForwardingReference&&+              >::value+          && std::is_constructible<+              Deleter,+              DeleterForwardingReference&&+              >::value+          >::type* = nullptr+      )+      :+      m_impl(+          std::forward<RefCountAccessorForwardingReference>(f),+          std::forward<DeleterForwardingReference>(d)+          )+  { }++  template <+      class RefCountAccessorForwardingReference,+      class DeleterForwardingReference+      >+  explicit intrusive_ptr_base(+      pointer ptr,+      RefCountAccessorForwardingReference&& f,+      DeleterForwardingReference&& d,+      std::enable_if_t<+          std::is_constructible<+              RefCountAccessor,+              RefCountAccessorForwardingReference&&+              >::value+          && std::is_constructible<+              Deleter,+              DeleterForwardingReference&&+              >::value+          >* = nullptr+      )+      :+      m_impl(+          ptr,+          std::forward<RefCountAccessorForwardingReference>(f),+          std::forward<DeleterForwardingReference>(d)+          )+  { }++  void assign(pointer ptr) noexcept+  {+    m_impl.assign(ptr);+  }++  template <+      class RefCountAccessorForwardingReference,+      class DeleterForwardingReference+      >+  void assign(+      pointer ptr,+      RefCountAccessorForwardingReference&& f,+      DeleterForwardingReference&& d+      )+  {+    m_impl.assign(+        ptr,+        std::forward<RefCountAccessorForwardingReference>(f),+        std::forward<DeleterForwardingReference>(d)+        );+  }++  void swap(intrusive_ptr_base& other)+  {+    m_impl.swap(other.m_impl);+  }++  struct STDX_MSVC_EMPTY_BASE_CLASSES impl+      :+        PointerImplementation,+        RefCountAccessor,+        Deleter+  {+    constexpr impl() = default;++    explicit impl(pointer ptr) noexcept : PointerImplementation(ptr)+    { }++    template <+        class RefCountAccess,+        class = std::enable_if_t<+            std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+            >+        >+    constexpr explicit impl(RefCountAccess&& f)+        : RefCountAccessor(std::forward<RefCountAccess>(f))+    { }++    template <+        class RefCountAccess,+        class = std::enable_if_t<+            std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+            >+        >+    impl(pointer ptr, RefCountAccess&& f)+        :+        PointerImplementation(ptr),+        RefCountAccessor(std::forward<RefCountAccessor>(f))+    { }++    template <+        class RefCountAccess,+        class D,+        class = std::enable_if_t<+            std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+            && std::is_constructible<Deleter, D&&>::value+            >+        >+    constexpr impl(RefCountAccess&& f, D&& d)+        :+        RefCountAccessor(std::forward<RefCountAccess>(f)),+        Deleter(std::forward<D>(d))+    { }++    template <+        class RefCountAccess,+        class D,+        class = std::enable_if_t<+            std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+            && std::is_constructible<Deleter, D&&>::value+            >+        >+    impl(pointer ptr, RefCountAccess&& f, D&& d)+        :+        PointerImplementation(ptr),+        RefCountAccessor(std::forward<RefCountAccess>(f)),+        Deleter(std::forward<D>(d))+    { }++    impl(const impl&) = default;+    impl& operator = (const impl&) = default;+    impl(impl&&) = default;+    impl& operator = (impl&&) = default;++    Deleter& get_deleter() noexcept+    {+      return static_cast<Deleter&>(*this);+    }++    const Deleter& get_deleter() const noexcept+    {+      return static_cast<const Deleter&>(*this);+    }++    void assign(pointer ptr) noexcept+    {+      static_cast<PointerImplementation&>(*this).assign(ptr);+    }++    void assign(std::nullptr_t) noexcept+    {+      static_cast<PointerImplementation&>(*this).assign(nullptr);+    }++    void swap(impl& other)+    {+      std::swap(+          static_cast<PointerImplementation&>(*this),+          static_cast<PointerImplementation&>(other)+          );++      std::swap(static_cast<RefCountAccessor&>(*this), static_cast<RefCountAccessor&>(other));+      std::swap(+          static_cast<Deleter&>(this->get_deleter()),+          static_cast<Deleter&>(other.get_deleter())+          );+    }+  };++  impl m_impl;++  void increment_shared_reference_count(+      std::memory_order order = std::memory_order_relaxed+      ) const noexcept+  {+    if (ptr()) ref_count_func()(ptr()).fetch_add(1, order);+  }++  void decrement_shared_reference_count() noexcept+  {+    if (ptr())+    {+      if (ref_count_func()(ptr()).fetch_sub(1, std::memory_order_release) == 1)+      {+        std::atomic_thread_fence(std::memory_order_acquire);+        invoke_deleter(ptr());+      }+    }+  }++  // ----- accessors and modifiers++  pointer& ptr() noexcept+  {+    return static_cast<PointerImplementation&>(m_impl).shared_object;+  }++  constexpr pointer ptr() const noexcept+  {+    return static_cast<const PointerImplementation&>(m_impl).shared_object;+  }++  RefCountAccessor& ref_count_func() noexcept+  {+    return static_cast<RefCountAccessor&>(m_impl);+  }++  const RefCountAccessor& ref_count_func() const noexcept+  {+    return static_cast<const RefCountAccessor&>(m_impl);+  }++  Deleter& deleter() noexcept+  {+    return m_impl.get_deleter();+  }++  const Deleter& deleter() const noexcept+  {+    return m_impl.get_deleter();+  }++  intrusive_ptr<+      T,+      RefCountAccessor,+      Deleter,+      Pointer+      > make_intrusive_pointer(pointer p) const noexcept+  {+    return intrusive_ptr<T, RefCountAccessor, Deleter, Pointer>{+        p,+        ref_count_func(),+        deleter()+    };+  }++private:++  void invoke_deleter(pointer p)+  {+    m_impl.get_deleter()(p);+  }++  void invoke_deleter(pointer p) const+  {+    m_impl.get_deleter()(p);+  }++  template <class WeakReferenceCountDescriptor>+  void invoke_deleter(pointer p, WeakReferenceCountDescriptor* d)+  {+    maybe_delete_shared_object(p, m_impl.get_deleter(), d);+  }++  template <class WeakReferenceCountDescriptor>+  void invoke_deleter(pointer p, WeakReferenceCountDescriptor* d) const+  {+    maybe_delete_shared_object(p, m_impl.get_deleter(), d);+  }+};++} // end namespace detail++template <+    class T,+    class RefCountAccessor,+    class Deleter,+    class Pointer+    >+class STDX_TRIVIALLY_RELOCATABLE intrusive_ptr+    :+      public detail::intrusive_ptr_base<+          T,+          RefCountAccessor,+          Deleter,+          Pointer,+          detail::pointer_wrapper<Pointer>+          >+{+  using base_type = detail::intrusive_ptr_base<+      T,+      RefCountAccessor,+      Deleter,+      Pointer,+      detail::pointer_wrapper<Pointer>+      >;++public:++  using pointer = Pointer;+  using element_type = T;+  using ref_count_accessor = RefCountAccessor;+  using deleter_type = Deleter;+  using count_type = typename base_type::count_type;++  constexpr intrusive_ptr() noexcept : base_type()+  { }++  constexpr intrusive_ptr(std::nullptr_t) noexcept : base_type()+  { }++  template <class RefCountAccess>+  constexpr intrusive_ptr(std::nullptr_t, RefCountAccess&& f)+      : base_type(std::forward<RefCountAccess>(f))+  { }++  template <class RefCountAccess, class D>+  constexpr intrusive_ptr(std::nullptr_t, RefCountAccess&& f,	D&& d)+      : base_type(std::forward<RefCountAccess>(f), std::forward<D>(d))+  { }++  constexpr explicit intrusive_ptr(Pointer ptr) noexcept+      : base_type(ptr)+  {+    // reference count must initially be >= 1+  }++  template <class RefCountAccess>+  intrusive_ptr(Pointer ptr, RefCountAccess&& f) noexcept+      : base_type(ptr, std::forward<RefCountAccess>(f))+  {+    // reference count must initially be >= 1+  }++  template <class RefCountAccess, class D>+  intrusive_ptr(Pointer ptr, RefCountAccess&& f, D&& d) noexcept+      :+      base_type(+          ptr,+          std::forward<RefCountAccess>(f),+          std::forward<D>(d)+          )+  {+    // reference count must initially be >= 1+  }++  // Copy constructor+  //+  intrusive_ptr(const intrusive_ptr& rhs) noexcept+      : base_type(rhs)+  {+    this->increment_shared_reference_count();+  }++  // Converting copy-constructor+  //+  template <+      class Y,+      class Ptr,+      class = std::enable_if_t<std::is_convertible<Ptr, pointer>::value>+      >+  intrusive_ptr(const intrusive_ptr<Y, RefCountAccessor, Deleter, Ptr>& rhs) noexcept+      :+      base_type(+          rhs.get(),+          rhs.ref_count_func(),+          rhs.get_deleter()+          )+  {+    this->increment_shared_reference_count();+  }++  // Move constructor+  //+  intrusive_ptr(intrusive_ptr&& rhs) noexcept+      : base_type(std::move(rhs))+  { }++  // Copy assignment+  //+  intrusive_ptr& operator = (const intrusive_ptr& rhs) noexcept+  {+    rhs.increment_shared_reference_count();+    this->decrement_shared_reference_count();++    static_cast<base_type&>(*this) = static_cast<const base_type&>(rhs);+    return *this;+  }++  // Move assignment+  //+  intrusive_ptr& operator = (intrusive_ptr&& rhs) noexcept+  {+    if (this != std::addressof(rhs))+    {+      this->decrement_shared_reference_count();+      static_cast<base_type&>(*this) = std::move(static_cast<base_type&>(rhs));+    }++    return *this;+  }++  ~intrusive_ptr() noexcept+  {+    this->decrement_shared_reference_count();+  }++  void reset() noexcept+  {+    this->decrement_shared_reference_count();+    this->assign(nullptr);+  }++  void reset(std::nullptr_t) noexcept+  {+    reset();+  }++  void reset(Pointer ptr) noexcept+  {+    if (this->ptr() != ptr)+    {+      this->decrement_shared_reference_count();+      this->assign(ptr);+      this->increment_shared_reference_count();+    }+  }++  void swap(intrusive_ptr& other) noexcept+  {+    if (this->get() != other.get())+    {+      base_type::swap(other);+    }+  }++  pointer get() const noexcept+  {+    return this->ptr();+  }++  element_type& operator * () const noexcept+  {+    return *this->get();+  }++  pointer operator -> () const noexcept+  {+    return this->get();+  }++  count_type use_count() const noexcept+  {+    return this->ref_count_func()(get()).load(std::memory_order_acquire);+  }++  explicit operator bool() const noexcept+  {+    return static_cast<bool>(this->get());+  }++  Deleter get_deleter() noexcept+  {+    return this->deleter();+  }++  const Deleter& get_deleter() const noexcept+  {+    return this->deleter();+  }++  RefCountAccessor ref_count_access() noexcept+  {+    return this->ref_count_func();+  }++  const RefCountAccessor& ref_count_access() const noexcept+  {+    return this->ref_count_func();+  }++private:++  template <class Y, class G, class D, class P>+  friend class intrusive_ptr;+};++// -------------- Global equality operators+//+template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator == (+    const intrusive_ptr<T, G1, D1, P1>& lhs,+    const intrusive_ptr<U, G2, D2, P2>& rhs+    ) noexcept+{+  return lhs.get() == rhs.get();+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator != (+    const intrusive_ptr<T, G1, D1, P1>& lhs,+    const intrusive_ptr<U, G2, D2, P2>& rhs+    ) noexcept+{+  return !(lhs == rhs);+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator < (+    const intrusive_ptr<T, G1, D1, P1>& lhs,+    const intrusive_ptr<U, G2, D2, P2>& rhs+    ) noexcept+{+  using pointer1 = typename intrusive_ptr<T, G1, D1, P1>::pointer;+  using pointer2 = typename intrusive_ptr<U, G2, D2, P2>::pointer;+  using common_type = typename std::common_type<pointer1, pointer2>::type;++  return std::less<common_type>{}(lhs.get(), rhs.get());+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator > (+    const intrusive_ptr<T, G1, D1, P1>& lhs,+    const intrusive_ptr<U, G2, D2, P2>& rhs+    ) noexcept+{+  return rhs < lhs;+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator <= (+    const intrusive_ptr<T, G1, D1, P1>& lhs,+    const intrusive_ptr<U, G2, D2, P2>& rhs+    ) noexcept+{+  return !(rhs < lhs);+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator >= (+    const intrusive_ptr<T, G1, D1, P1>& lhs,+    const intrusive_ptr<U, G2, D2, P2>& rhs+    ) noexcept+{+  return !(lhs < rhs);+}++template <class T, class G1, class D1, class P1>+bool operator == (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+  return !lhs;+}++template <class T, class G1, class D1, class P1>+bool operator == (std::nullptr_t, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+  return !rhs;+}++template <class T, class G1, class D1, class P1>+bool operator != (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+  return bool(lhs);+}++template <class T, class G1, class D1, class P1>+bool operator != (std::nullptr_t, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+  return bool(rhs);+}++template <class T, class G1, class D1, class P1>+bool operator < (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+  using pointer = typename intrusive_ptr<T, G1, D1, P1>::pointer;+  return std::less<pointer>{}(lhs.get(), nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator < (std::nullptr_t, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+  using pointer = typename intrusive_ptr<T, G1, D1, P1>::pointer;+  return std::less<pointer>{}(nullptr, rhs.get());+}++template <class T, class G1, class D1, class P1>+bool operator > (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+  return (nullptr < lhs);+}++template <class T, class G1, class D1, class P1>+bool operator > (std::nullptr_t lhs, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+  return (rhs < nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator <= (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t rhs) noexcept+{+  return !(nullptr < lhs);+}++template <class T, class G1, class D1, class P1>+bool operator <= (std::nullptr_t lhs, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+  return !(rhs < nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator >= (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t rhs) noexcept+{+  return !(lhs < nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator >= (std::nullptr_t lhs, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+  return !(nullptr < rhs);+}++template <class T, class G, class D, class P>+void swap(intrusive_ptr<T, G, D, P>& lhs, intrusive_ptr<T, G, D, P>& rhs) noexcept+{+  lhs.swap(rhs);+}++  #ifdef STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+template <class Y, class G, class D, class P>+struct is_trivially_relocatable<intrusive_ptr<Y,G,D,P>> : std::true_type+{ };+  #endif++} // end namespace stdx++#endif // include guard++++#ifndef STDX_STRING_REF_HPP+  #define STDX_STRING_REF_HPP++  #include <cstring>+  #include <cstddef>+  #include <atomic>++namespace stdx {++class string_ref;++namespace detail {++constexpr const char* cstring_null_scan(const char* s) noexcept+{+  return *s ? cstring_null_scan(s + 1) : s;+}++} // end namespace detail++class string_ref+{+protected:++  struct state_type;++public:++  using value_type = const char;+  using size_type = std::size_t;+  using pointer = const char*;+  using const_pointer = const char*;+  using iterator = const char*;+  using const_iterator = const char*;++  struct resource_management+  {+    using copy_constructor = state_type(*)(const string_ref&);+    using move_constructor = state_type(*)(string_ref&&);+    using destructor = void(*)(string_ref&);++    constexpr resource_management() noexcept+        : copy{nullptr}, move{nullptr}, destroy{nullptr}+    { }++    constexpr resource_management(+        copy_constructor cctor,+        move_constructor mctor,+        destructor dtor+        ) noexcept+        : copy{cctor}, move{mctor}, destroy{dtor}+    { }++    copy_constructor copy;+    move_constructor move;+    destructor destroy;+  };++  constexpr string_ref() noexcept : m_begin(nullptr), m_end(nullptr), context{}+  { }++  constexpr string_ref(const char* beg) noexcept+      : m_begin(beg), m_end(detail::cstring_null_scan(beg)), context{}+  { }++  constexpr string_ref(const char* beg, const char* e) noexcept+      : m_begin(beg), m_end(e), context{}+  { }++  constexpr string_ref(const char* beg, resource_management rm) noexcept+      :+      m_begin(beg),+      m_end(detail::cstring_null_scan(beg)),+      m_resource_management(rm),+      context{}+  { }++  constexpr string_ref(const char* beg, const char* e, resource_management rm) noexcept+      : m_begin(beg), m_end(e), m_resource_management(rm), context{}+  { }++  constexpr string_ref(+      const char* beg,+      const char* e,+      resource_management rm,+      void* ctx+      ) noexcept+      : m_begin(beg), m_end(e), m_resource_management(rm), context{ctx}+  { }++  STDX_GCC7_WORKAROUND_CONSTEXPR string_ref(const string_ref& s)+      : string_ref{s.m_resource_management.copy ? s.m_resource_management.copy(s) : s.state()}+  { }++  STDX_GCC7_WORKAROUND_CONSTEXPR string_ref(string_ref&& s)+      :+      string_ref{+          s.m_resource_management.move ?+              s.m_resource_management.move(std::move(s)) : s.state()+      }+  { }++  string_ref& operator = (const string_ref& s)+  {+    string_ref tmp = s;+    *this = std::move(tmp);+    return *this;+  }++  string_ref& operator = (string_ref&& s)+  {+    if (this != &s)+    {+      if (m_resource_management.destroy) m_resource_management.destroy(*this);++      // This is legal because of the common initial sequence and the fact+      // that any type erased object must be trivially relocatable.+      *this = string_ref_state_union{std::move(s)}.state;+    }++    return *this;+  }++  ~string_ref() noexcept+  {+    if (m_resource_management.destroy) m_resource_management.destroy(*this);+  }++  bool empty() const noexcept { return m_begin == m_end; }++  size_type size() const noexcept { return m_end - m_begin; }++  const_pointer data() const noexcept { return m_begin; }++  iterator begin() noexcept { return m_begin; }++  iterator end() noexcept { return m_end; }++  const_iterator begin() const noexcept { return m_begin; }++  const_iterator end() const noexcept { return m_end; }++  const_iterator cbegin() const noexcept { return m_begin; }++  const_iterator cend() const noexcept { return m_end; }++protected:++  struct state_type+  {+    pointer m_begin;+    pointer m_end;+    resource_management m_resource_management;+    void* context;+  };++  state_type state() const noexcept+  {+    return state_type{m_begin, m_end, m_resource_management, context};+  }++  constexpr explicit string_ref(const state_type& s) noexcept+      :+      m_begin(s.m_begin),+      m_end(s.m_end),+      m_resource_management(s.m_resource_management),+      context(s.context)+  { }++  void clear() noexcept+  {+    m_begin = nullptr;+    m_end = nullptr;+  }++  template <class StringRef>+  union string_ref_state_union_type+  {+    explicit string_ref_state_union_type(StringRef&& s) : str(std::move(s))+    { }++    ~string_ref_state_union_type() noexcept {}++    StringRef str;+    state_type state;+  };++  using string_ref_state_union = string_ref_state_union_type<string_ref>;++  void operator = (const state_type& s) noexcept+  {+    m_begin = s.m_begin;+    m_end = s.m_end;+    m_resource_management = s.m_resource_management;+    context = s.context;+  }++  pointer m_begin;+  pointer m_end;+  resource_management m_resource_management;+  void* context;+};++inline bool operator == (const string_ref& lhs, const string_ref& rhs) noexcept+{+  return (lhs.size() == rhs.size()) && (std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0);+}++inline bool operator != (const string_ref& lhs, const string_ref& rhs) noexcept+{+  return !(lhs == rhs);+}++inline bool operator < (const string_ref& lhs, const string_ref& rhs) noexcept+{+  const std::size_t sz = (lhs.size() < rhs.size()) ? lhs.size() : rhs.size();+  int result = std::memcmp(lhs.data(), rhs.data(), sz);+  if (result == 0) return lhs.size() < rhs.size();+  return result < 0;+}++inline bool operator > (const string_ref& lhs, const string_ref& rhs) noexcept+{+  return rhs < lhs;+}++inline bool operator <= (const string_ref& lhs, const string_ref& rhs) noexcept+{+  return !(lhs > rhs);+}++inline bool operator >= (const string_ref& lhs, const string_ref& rhs) noexcept+{+  return !(lhs < rhs);+}++// Reference-counted allocated string+//+class shared_string_ref : public string_ref+{+  struct string_arena_base+  {+    mutable std::atomic<std::size_t> ref_count;+    std::size_t length;+  };++  struct string_arena : string_arena_base+  {+    constexpr explicit string_arena(std::size_t length) noexcept+        : string_arena_base{{1}, length}+    { }++    constexpr static std::size_t header_size() noexcept+    {+      return sizeof(string_arena);+    }++    char* data() noexcept+    {+      return reinterpret_cast<char*>(this) + header_size();+    }++    const char* data() const noexcept+    {+      return reinterpret_cast<const char*>(this) + header_size();+    }++    const char* begin() const noexcept { return data(); }++    const char* end() const noexcept { return data() + length; }+  };++  string_ref allocate_string_ref(const char* s, std::size_t length)+  {+    const std::size_t arena_size = string_arena::header_size() + length;+    char* buf = static_cast<char*>(::operator new(arena_size));+    string_arena* a = new (buf) string_arena{length};+    std::memcpy(a->data(), s, length);+    return shared_string_ref{a};+  }++  explicit shared_string_ref(string_arena* a) noexcept+      :+      string_ref{+          a->begin(),+          a->end(),+          string_ref::resource_management{&copy_construct, &move_construct, &destroy},+          a+      }+  { }++  const string_arena_base* get_arena() const noexcept+  {+    return static_cast<string_arena_base*>(this->context);+  }++  string_arena_base* get_arena() noexcept+  {+    return static_cast<string_arena_base*>(this->context);+  }++  static string_ref::state_type copy_construct(const string_ref& base) noexcept+  {+    const shared_string_ref& s = static_cast<const shared_string_ref&>(base);+    const string_arena_base* a = s.get_arena();+    if (a) a->ref_count.fetch_add(1, std::memory_order_relaxed);+    return s.state();+  }++  static string_ref::state_type move_construct(string_ref&& base) noexcept+  {+    shared_string_ref& s = static_cast<shared_string_ref&>(base);+    auto st = s.state();+    s.context = nullptr;+    s.clear();+    return st;+  }++  static void destroy(string_ref& base) noexcept+  {+    shared_string_ref& s = static_cast<shared_string_ref&>(base);+    string_arena* a = static_cast<string_arena*>(s.get_arena());+    if (a && (a->ref_count.fetch_sub(1, std::memory_order_release) == 1))+    {+      std::atomic_thread_fence(std::memory_order_acquire);+      ::operator delete(a);+    }+  }++  template <class Allocator>+  struct allocated_string_arena : string_arena_base+  {+    constexpr allocated_string_arena(const Allocator& alloc, std::size_t length) noexcept+        : string_arena_base{{1}, length}, allocator(alloc)+    { }++    constexpr static std::size_t header_size() noexcept+    {+      return sizeof(allocated_string_arena);+    }++    char* data() noexcept+    {+      return reinterpret_cast<char*>(this) + header_size();+    }++    const char* data() const noexcept+    {+      return reinterpret_cast<const char*>(this) + header_size();+    }++    const char* begin() const noexcept { return data(); }++    const char* end() const noexcept { return data() + length; }++    std::size_t allocated_size() const noexcept { return header_size() + length; }++    Allocator allocator;+  };++  template <class Allocator>+  explicit shared_string_ref(allocated_string_arena<Allocator>* a) noexcept+      :+      string_ref{+          a->begin(),+          a->end(),+          string_ref::resource_management{+              &copy_construct,+              &move_construct,+              &allocator_destroy<Allocator>+          },+          a+      }+  { }++  template <class Allocator>+  string_ref allocate_string_ref(+      const Allocator& allocator,+      const char* s,+      std::size_t length+      )+  {+    using allocator_type = typename std::allocator_traits<+        Allocator+        >::template rebind_alloc<char>;+    using arena_type = allocated_string_arena<allocator_type>;++    allocator_type alloc{allocator};+    const std::size_t arena_size = arena_type::header_size() + length;+    char* buf = alloc.allocate(arena_size);+    arena_type* a = new (buf) arena_type{alloc, length};+    std::memcpy(a->data(), s, length);+    return shared_string_ref{a};+  }++  template <class Allocator>+  static void allocator_destroy(string_ref& base) noexcept+  {+    using arena_type = allocated_string_arena<Allocator>;++    shared_string_ref& s = static_cast<shared_string_ref&>(base);+    arena_type* a = static_cast<arena_type*>(s.get_arena());+    if (a && (a->ref_count.fetch_sub(1, std::memory_order_release) == 1))+    {+      std::atomic_thread_fence(std::memory_order_acquire);++      Allocator alloc = std::move(a->allocator);+      const std::size_t allocated_size = a->allocated_size();+      a->~arena_type();+      alloc.deallocate(reinterpret_cast<char*>(a), allocated_size);+    }+  }++public:++  shared_string_ref(const char* beg)+      : string_ref{allocate_string_ref(beg, detail::cstring_null_scan(beg) - beg)}+  { }++  shared_string_ref(const char* beg, const char* end)+      : string_ref{allocate_string_ref(beg, end - beg)}+  { }++  template <class Allocator>+  shared_string_ref(const Allocator& alloc, const char* beg)+      : string_ref{allocate_string_ref(alloc, beg, detail::cstring_null_scan(beg) - beg)}+  { }++  template <class Allocator>+  shared_string_ref(const Allocator& alloc, const char* beg, const char* end)+      : string_ref{allocate_string_ref(alloc, beg, end - beg)}+  { }++  std::size_t use_count() const noexcept+  {+    const string_arena_base* a = get_arena();+    return a ? a->ref_count.load(std::memory_order_acquire) : 0;+  }+};++} // end namespace stdx++#endif++++#ifndef STDX_LAUNDER_HPP+  #define STDX_LAUNDER_HPP++  #include <new>+++namespace stdx {++  #if __cplusplus >= 201703L+    #if defined(__cpp_lib_launder)+      #define STDX_HAVE_NATIVE_LAUNDER 1+using std::launder;+    #elif defined(STDX_CLANG_COMPILER)+      #if __has_builtin(__builtin_launder)+        #define STDX_HAVE_NATIVE_LAUNDER 1+template <class T>+constexpr T* launder(T* p) noexcept+{+  return __builtin_launder(p);+}+      #endif+    #endif+  #endif++  #if !defined(STDX_HAVE_NATIVE_LAUNDER)+template <class T>+constexpr T* launder(T* p) noexcept+{+  return p;+}+  #endif++} // end namespace stdx++#endif++++#ifndef STDX_ERROR_HPP+  #define STDX_ERROR_HPP++  #include <exception>+  #include <stdexcept>+  #include <system_error>+  #include <memory>+  #include <cassert>+++namespace stdx {++class error;++namespace detail {++template <class... Args>+struct error_ctor_args;++} // end namespace detail++} // end namespace stdx++namespace stdx_adl {++namespace detail {++template <class Args, class Enable = void>+struct can_use_adl_to_call_make_error : std::false_type+{ };++inline void make_error() noexcept { }++template <class... Args>+struct can_use_adl_to_call_make_error<+    stdx::detail::error_ctor_args<Args...>,+    std::enable_if_t<+        std::is_same<+            decltype(make_error(std::declval<Args>()...)),+            stdx::error+            >::value+        >+    >+    : std::true_type+{ };++template <class... Args>+constexpr auto construct_error_from_adl(Args&&... args) noexcept(+    noexcept(make_error(std::forward<Args>(args)...))+    )+{+  return make_error(std::forward<Args>(args)...);+}+}++} // end namespace stdx_adl++namespace stdx {++enum class dynamic_exception_errc+{+  runtime_error = 1,+  domain_error,+  invalid_argument,+  length_error,+  out_of_range,+  logic_error,+  range_error,+  overflow_error,+  underflow_error,+  bad_alloc,+  bad_array_new_length,+  bad_optional_access,+  bad_typeid,+  bad_any_cast,+  bad_cast,+  bad_weak_ptr,+  bad_function_call,+  bad_exception,+  bad_variant_access,+  unspecified_exception+};++std::error_code error_code_from_exception(std::exception_ptr eptr) noexcept;++// -------------------- error_traits+//+template <class E>+struct error_traits+{ };++namespace detail {++template <class E, class Enable = void>+struct is_convertible_from_exception_using_traits : std::false_type+{ };++template <class E>+struct is_convertible_from_exception_using_traits<+    E,+    std::enable_if_t<+        std::is_convertible<+            decltype(error_traits<E>::from_exception(std::declval<std::exception_ptr>())),+            E+            >::value+        >+    >+    : std::true_type+{ };++template <class E, class Enable = void>+struct is_convertible_to_exception_using_traits : std::false_type+{ };++template <class E>+struct is_convertible_to_exception_using_traits<+    E,+    std::enable_if_t<+        std::is_convertible<+            decltype(error_traits<E>::to_exception(std::declval<E>())),+            std::exception_ptr+            >::value+        >+    >+    : std::true_type+{ };++template <class E>+E from_exception_impl(std::exception_ptr e, std::is_convertible<std::exception_ptr, E>) noexcept+{+  return e;+}++template <class E>+E from_exception_impl(+    std::exception_ptr e,+    is_convertible_from_exception_using_traits<E>+    ) noexcept+{+  return error_traits<E>::from_exception(std::move(e));+}++void from_exception_impl(std::exception_ptr, sentinel_type) = delete;++template <class E, class G>+std::exception_ptr to_exception_impl(E&& e, std::is_convertible<G, std::exception_ptr>) noexcept+{+  return std::forward<E>(e);+}++template <class E, class G>+std::exception_ptr to_exception_impl(+    E&& e,+    is_convertible_to_exception_using_traits<G>+    ) noexcept+{+  return error_traits<E>::to_exception(std::forward<E>(e));+}++template <class E>+void to_exception_impl(const E&, sentinel_type) = delete;++} // end namespace detail++template <class E>+E from_exception(std::exception_ptr e) noexcept+{+  return detail::from_exception_impl<E>(+      std::move(e),+      disjunction<+          std::is_convertible<std::exception_ptr, E>,+          detail::is_convertible_from_exception_using_traits<E>,+          sentinel_type+          >{}+      );+}++template <class E>+std::exception_ptr to_exception(E&& e) noexcept+{+  return detail::to_exception_impl(+      std::forward<E>(e),+      disjunction<+          std::is_convertible<std::decay_t<E>, std::exception_ptr>,+          detail::is_convertible_to_exception_using_traits<std::decay_t<E>>,+          sentinel_type+          >{}+      );+}++struct error_domain_id+{+  constexpr error_domain_id(std::uint64_t l, std::uint64_t h) noexcept+      : lo(l), hi(h)+  { }++private:++  friend constexpr bool operator == (const error_domain_id&, const error_domain_id&) noexcept;++  std::uint64_t lo;+  std::uint64_t hi;+};++constexpr bool operator == (const error_domain_id& lhs, const error_domain_id& rhs) noexcept+{+  return (lhs.lo == rhs.lo) && (lhs.hi == rhs.hi);+}++constexpr bool operator != (const error_domain_id& lhs, const error_domain_id& rhs) noexcept+{+  return !(lhs == rhs);+}++template <class T = void>+struct error_value;++namespace detail {++template <class T>+struct is_error_value : std::false_type+{ };++template <class T>+struct is_error_value<error_value<T>> : std::true_type+{ };++} // end namespace detail++struct error_resource_management+{+  using copy_constructor = error_value<void>(*)(const error&);+  using move_constructor = error_value<void>(*)(error&&);+  using destructor = void(*)(error&);++  constexpr error_resource_management() noexcept+      : copy{nullptr}, move{nullptr}, destroy{nullptr}+  { }++  constexpr error_resource_management(+      copy_constructor cctor,+      move_constructor mctor,+      destructor dtor+      ) noexcept+      : copy(cctor), move(mctor), destroy(dtor)+  { }++  copy_constructor copy;+  move_constructor move;+  destructor destroy;+};++class error_domain+{+public:++  virtual string_ref name() const noexcept = 0;+  virtual bool equivalent(const error& lhs, const error& rhs) const noexcept = 0;+  virtual string_ref message(const error&) const noexcept = 0;++  virtual void throw_exception(const error& e) const;++  friend class error;+  friend constexpr bool operator == (const error_domain&, const error_domain&) noexcept;+  friend constexpr bool operator != (const error_domain&, const error_domain&) noexcept;++  constexpr error_domain_id id() const noexcept+  {+    return m_id;+  }++protected:++  constexpr explicit error_domain(error_domain_id id) noexcept+      :+      m_id{id},+      m_resource_management{}+  { }++  constexpr error_domain(error_domain_id id, error_resource_management erm) noexcept+      :+      m_id{id},+      m_resource_management{erm}+  { }++  error_domain(const error_domain &) = default;+  error_domain(error_domain &&) = default;+  error_domain &operator = (const error_domain &) = default;+  error_domain &operator = (error_domain&&) = default;+  ~error_domain() = default;++  template <+      class E = error,+      class = std::enable_if_t<+          std::is_convertible<const E&, error>::value+          >+      >+  constexpr dependent_type_t<E, error_value<>> copy(const E& e) const+  {+    return m_resource_management.copy ? m_resource_management.copy(e) : error_value<>{e.m_value};+  }++  template <+      class E = error,+      class = std::enable_if_t<+          std::is_rvalue_reference<E&&>::value+          && std::is_convertible<E&&, error>::value+          >+      >+  constexpr dependent_type_t<remove_cvref_t<E>, error_value<>> move(E&& e) const+  {+    return m_resource_management.move ?+               m_resource_management.move(static_cast<E&&>(e)) : error_value<>{e.m_value};+  }++  void destroy(error& e) const noexcept+  {+    if (m_resource_management.destroy) m_resource_management.destroy(e);+  }++private:++  error_domain_id m_id;+  error_resource_management m_resource_management;+};++constexpr bool operator == (const error_domain& lhs, const error_domain& rhs) noexcept+{+  return lhs.id() == rhs.id();+}++constexpr bool operator != (const error_domain& lhs, const error_domain& rhs) noexcept+{+  return lhs.id() != rhs.id();+}++namespace detail {++template <class ErasedType, class T>+struct error_type_is_erasable+    :+      bool_constant<+          is_trivially_relocatable<T>::value+          && (sizeof(T) <= sizeof(ErasedType))+          && (alignof(T) <= alignof(ErasedType))+          >+{ };++template <class ErasedType>+struct error_type_is_erasable<ErasedType, void> : std::false_type+{ };++template <class ErasedType>+struct error_type_is_erasable<ErasedType, const void> : std::false_type+{ };++template <class T>+using can_use_static_cast = bool_constant<+    std::is_integral<T>::value+    || std::is_enum<T>::value+    >;++struct erased_error+{+  using integral_type = std::intptr_t;+  using storage_type = std::aligned_storage_t<sizeof(integral_type), alignof(integral_type)>;++  constexpr erased_error() noexcept : code{}+  { }++  template <+      class T,+      class = std::enable_if_t<+          error_type_is_erasable<integral_type, T>::value+          && can_use_static_cast<T>::value+          >+      >+  constexpr erased_error(T value) noexcept+      : code{static_cast<integral_type>(value)}+  { }++  template <+      class T,+      class = std::enable_if_t<+          error_type_is_erasable<integral_type, T>::value+          && !can_use_static_cast<T>::value+          && is_bit_castable<T, integral_type>::value+          >,+      class = void+      >+  constexpr erased_error(T value) noexcept+      : code{bit_cast<integral_type>(value)}+  { }++  template <+      class T,+      class = std::enable_if_t<+          error_type_is_erasable<integral_type, T>::value+          && !can_use_static_cast<T>::value+          && !is_bit_castable<T, integral_type>::value+          >,+      int = 0+      >+  erased_error(T value) noexcept(std::is_nothrow_move_constructible<T>::value)+  {+    new (&storage) T(std::move(value));+  }++  union+  {+    integral_type code;+    storage_type storage;+  };+};++template <+    class T,+    class = std::enable_if_t<+        error_type_is_erasable<erased_error::integral_type, T>::value+        && can_use_static_cast<T>::value+        >+    >+constexpr T error_cast_impl(erased_error e) noexcept+{+  return static_cast<T>(e.code);+}++template <+    class T,+    class = std::enable_if_t<+        error_type_is_erasable<erased_error::integral_type, T>::value+        && !can_use_static_cast<T>::value+        && is_bit_castable<erased_error::integral_type, T>::value+        >,+    class = void+    >+constexpr T error_cast_impl(erased_error e) noexcept+{+  return bit_cast<T>(e.code);+}++template <+    class T,+    class = std::enable_if_t<+        error_type_is_erasable<erased_error::integral_type, T>::value+        && !can_use_static_cast<T>::value+        && !is_bit_castable<erased_error::integral_type, T>::value+        >+    >+constexpr T error_cast_impl(erased_error&& e) noexcept(+    std::is_nothrow_move_constructible<T>::value+    )+{+  return std::move(*stdx::launder(reinterpret_cast<T*>(&e.storage)));+}++template <+    class T,+    class = std::enable_if_t<+        error_type_is_erasable<erased_error::integral_type, T>::value+        && !can_use_static_cast<T>::value+        && !is_bit_castable<erased_error::integral_type, T>::value+        >+    >+constexpr T error_cast_impl(const erased_error& e) noexcept(+    std::is_nothrow_copy_constructible<T>::value+    )+{+  return *stdx::launder(reinterpret_cast<const T*>(&e.storage));+}++} // end namespace detail++template <class T>+struct error_value+{+  using value_type = T;++  constexpr error_value(const T& v) noexcept(+      std::is_nothrow_copy_constructible<T>::value+      )+      : m_value(v)+  { }++  constexpr error_value(T&& v) noexcept(+      std::is_nothrow_move_constructible<T>::value+      )+      : m_value(std::move(v))+  { }++  constexpr const T& value() const & noexcept+  {+    return m_value;+  }++  STDX_LEGACY_CONSTEXPR T& value() & noexcept+  {+    return m_value;+  }++  constexpr const T&& value() const && noexcept+  {+    return static_cast<const T&&>(m_value);+  }++  STDX_LEGACY_CONSTEXPR T&& value() && noexcept+  {+    return static_cast<T&&>(m_value);+  }++  T m_value;+};++template <>+struct error_value<void>+{+  template <+      class T,+      class = std::enable_if_t<+          !std::is_same<remove_cvref_t<T>, error_value>::value+          && !detail::is_error_value<remove_cvref_t<T>>::value+          && std::is_constructible<detail::erased_error, T&&>::value+          >+      >+  constexpr error_value(T&& v) noexcept(+      std::is_nothrow_constructible<detail::erased_error, T&&>::value+      )+      : m_value(std::forward<T>(v))+  { }++  template <+      class T,+      class = std::enable_if_t<+          std::is_constructible<detail::erased_error, const T&>::value+          >+      >+  constexpr error_value(const error_value<T>& v) noexcept(+      std::is_nothrow_constructible<detail::erased_error, const T&>::value+      )+      : error_value(v.value())+  { }++  template <+      class T,+      class = std::enable_if_t<+          std::is_constructible<detail::erased_error, T&&>::value+          >+      >+  constexpr error_value(error_value<T>&& v) noexcept(+      std::is_nothrow_constructible<detail::erased_error, T&&>::value+      )+      : error_value(std::move(v.value()))+  { }++  friend class error;++private:++  detail::erased_error m_value;+};++class error;++namespace detail {++struct error_copy_construct_t {};+struct error_move_construct_t {};++template <class... Args>+struct error_ctor_args {};++template <class Args, class DecayedArgs>+struct can_construct_error_from_adl+{ };++template <class... Args, class... DecayedArgs>+struct can_construct_error_from_adl<error_ctor_args<Args...>, error_ctor_args<DecayedArgs...>>+    : stdx_adl::detail::can_use_adl_to_call_make_error<error_ctor_args<Args...>>+{ };++template <>+struct can_construct_error_from_adl<error_ctor_args<>, error_ctor_args<>> : std::false_type+{ };++template <class Error>+struct can_construct_error_from_adl<error_ctor_args<Error>, error_ctor_args<error>>+    : std::false_type+{ };++template <class E, class D, class T, class ErrorDomain>+struct can_construct_error_from_adl<+    error_ctor_args<E, D>,+    error_ctor_args<error_value<T>, ErrorDomain>+    >+    : std::false_type+{ };++template <class CC, class V, class ED, class ErrorDomain>+struct can_construct_error_from_adl<+    error_ctor_args<CC, V, ED>,+    error_ctor_args<error_copy_construct_t, error_value<>, ErrorDomain>+    >+    : std::false_type+{ };++template <class MC, class V, class ED, class ErrorDomain>+struct can_construct_error_from_adl<+    error_ctor_args<MC, V, ED>,+    error_ctor_args<error_move_construct_t, error_value<>, ErrorDomain>+    >+    : std::false_type+{ };++template <class E, class Enable = void>+struct is_convertible_to_error_using_traits : std::false_type+{ };++template <>+struct is_convertible_to_error_using_traits<error_ctor_args<error>>+    : std::false_type+{ };++template <class E>+struct is_convertible_to_error_using_traits<+    error_ctor_args<E>,+    std::enable_if_t<+        std::is_same<+            decltype(error_traits<E>::to_error(std::declval<E>())),+            error+            >::value+        >+    >+    : std::true_type+{ };++template <class E, class... Args>+constexpr auto construct_error_impl(+    is_convertible_to_error_using_traits<error_ctor_args<E>>,+    Args&&... args+    ) noexcept(noexcept(error_traits<E>::to_error(std::declval<Args&&>()...)))+{+  return error_traits<E>::to_error(std::forward<Args>(args)...);+}++template <class A1, class A2, class... Args>+constexpr auto construct_error_impl(+    can_construct_error_from_adl<A1, A2>,+    Args&&... args+    ) noexcept(noexcept(stdx_adl::detail::construct_error_from_adl(std::declval<Args&&>()...)))+{+  return stdx_adl::detail::construct_error_from_adl(std::forward<Args>(args)...);+}++struct cannot_construct_error+{+  static constexpr bool value = false;+  using type = void;+};++template <class... Args>+void construct_error_impl(cannot_construct_error, Args&&...) = delete;++template <class Args, class DecayedArgs>+using construct_error_disjunction_impl_t = disjunction<+    is_convertible_to_error_using_traits<DecayedArgs>,+    can_construct_error_from_adl<Args, DecayedArgs>,+    cannot_construct_error+    >;++template <class... Args>+using construct_error_disjunction_t = construct_error_disjunction_impl_t<+    error_ctor_args<Args...>,+    error_ctor_args<remove_cvref_t<Args>...>+    >;++struct error_move_access;+struct error_ref_access;+struct error_cref_access;++} // end namespace detail++// Generic domain for std::errc codes+//+class generic_error_domain : public error_domain+{+public:++  constexpr generic_error_domain() noexcept+      : error_domain{{0x574ce0d940b64a2bULL, 0xa7c4438dd858c9cfULL}}+  { }++  virtual string_ref name() const noexcept override+  {+    return "generic domain";+  }++  virtual bool equivalent(const error& lhs, const error& rhs) const noexcept override;++  virtual string_ref message(const error&) const noexcept override;+};++STDX_LEGACY_INLINE_CONSTEXPR generic_error_domain generic_domain {};++class STDX_TRIVIALLY_RELOCATABLE error+{+  using erased_type = detail::erased_error;++  constexpr error(detail::error_copy_construct_t, error_value<> v, const error_domain* d) noexcept+      : m_domain(d), m_value(v.m_value)+  { }++  constexpr error(detail::error_move_construct_t, error_value<> v, const error_domain* d) noexcept+      : m_domain(d), m_value(std::move(v.m_value))+  { }++public:++  constexpr error() noexcept : m_domain(&generic_domain), m_value{}+  { }++  constexpr error(const error& e)+      : error(detail::error_copy_construct_t{}, e.m_domain->copy(e), e.m_domain)+  { }++  constexpr error(error&& e)+      : error(detail::error_move_construct_t{}, e.m_domain->move(std::move(e)), e.m_domain)+  { }++  template <+      class T,+      class = std::enable_if_t<+          detail::error_type_is_erasable<erased_type, T>::value+          >+      >+  constexpr error(const error_value<T>& v, const error_domain& d) noexcept+      : m_domain(&d), m_value(v.value())+  { }++  template <+      class T,+      class = std::enable_if_t<+          detail::error_type_is_erasable<erased_type, T>::value+          >+      >+  constexpr error(error_value<T>&& v, const error_domain& d) noexcept+      : m_domain(&d), m_value(static_cast<T&&>(v.value()))+  { }++  constexpr error(error_value<> v, const error_domain& d) noexcept+      : m_domain(&d), m_value(v.m_value)+  { }++  template <+      class A,+      class... Args,+      class = std::enable_if_t<+          detail::construct_error_disjunction_t<A&&, Args&&...>::value+          >+      >+  constexpr error(A&& a, Args&&... args) noexcept(+      noexcept(+          detail::construct_error_impl(+              std::declval<detail::construct_error_disjunction_t<A&&, Args&&...>>(),+              std::forward<A>(a),+              std::forward<Args>(args)...+              )+          )+      )+      :+      error(+          detail::construct_error_impl(+              detail::construct_error_disjunction_t<A&&, Args&&...>{},+              std::forward<A>(a),+              std::forward<Args>(args)...+              )+          )+  { }++  error& operator = (const error& e)+  {+    error_value<> v = e.domain().copy(e);+    domain().destroy(*this);+    m_domain = e.m_domain;+    m_value = v.m_value;+    return *this;+  }++  error& operator = (error&& e) noexcept+  {+    if (this != &e)+    {+      error_value<> v = e.domain().move(std::move(e));+      domain().destroy(*this);+      m_domain = e.m_domain;+      m_value = v.m_value;+    }++    return *this;+  }++  ~error() noexcept+  {+    m_domain->destroy(*this);+  }++  bool is_set() const noexcept { return (*this != stdx::error{}); }++  const error_domain& domain() const noexcept+  {+    return *m_domain;+  }++  string_ref message() const noexcept+  {+    return domain().message(*this);+  }++  [[noreturn]] void throw_exception() const+  {+    domain().throw_exception(*this);+    abort();+  }++  friend class error_domain;+  friend struct detail::error_move_access;+  friend struct detail::error_ref_access;+  friend struct detail::error_cref_access;+  friend inline bool operator==(const error& lhs, const error& rhs) noexcept;+  friend inline bool operator!=(const error& lhs, const error& rhs) noexcept;++private:++  const error_domain* m_domain;+  erased_type m_value;+};++inline bool operator == (const error& lhs, const error& rhs) noexcept+{+  if (lhs.domain().equivalent(lhs, rhs)) return true;+  if (rhs.domain().equivalent(rhs, lhs)) return true;+  return false;+}++inline bool operator != (const error& lhs, const error& rhs) noexcept+{+  return !(lhs == rhs);+}++namespace detail {++struct error_move_access+{+  constexpr explicit error_move_access(error&& e) noexcept : m_value(&e.m_value)+  { }++  STDX_LEGACY_CONSTEXPR detail::erased_error&& rvalue_ref() noexcept+  {+    return std::move(*m_value);+  }++  detail::erased_error* m_value;+};++struct error_ref_access+{+  constexpr explicit error_ref_access(error& e) noexcept : m_ptr(&e.m_value)+  { }++  detail::erased_error& ref() noexcept { return *m_ptr; }++  detail::erased_error* m_ptr;+};++struct error_cref_access+{+  constexpr explicit error_cref_access(const error& e) noexcept : m_ptr(&e.m_value)+  { }++  constexpr const detail::erased_error& ref() const noexcept { return *m_ptr; }++  const detail::erased_error* m_ptr;+};++} // end namespace detail++template <+    class T,+    class = void_t<+        decltype(detail::error_cast_impl<T>(std::declval<const detail::erased_error&>()))+        >+    >+constexpr T error_cast(const error& e) noexcept(+    noexcept(detail::error_cast_impl<T>(std::declval<const detail::erased_error&>()))+    )+{+  return detail::error_cast_impl<T>(detail::error_cref_access{e}.ref());+}++template <+    class T,+    class = void_t<decltype(detail::error_cast_impl<T>(std::declval<detail::erased_error>()))>+    >+constexpr T error_cast(error&& e) noexcept(+    noexcept(detail::error_cast_impl<T>(std::declval<detail::erased_error>()))+    )+{+  return detail::error_cast_impl<T>(detail::error_move_access{std::move(e)}.rvalue_ref());+}++namespace detail {++struct default_error_constructors+{+  template <class T>+  static error_value<> copy_constructor(const error& e) noexcept(+      std::is_nothrow_copy_constructible<T>::value+      && std::is_nothrow_move_constructible<T>::value+      )+  {+    T value = error_cast<T>(e);+    return error_value<>{std::move(value)};+  }++  template <class T>+  static error_value<> move_constructor(error&& e) noexcept(+      std::is_nothrow_move_constructible<T>::value+      )+  {+    return error_value<>{error_cast<T>(std::move(e))};+  }++  template <class T>+  static void destructor(error& e) noexcept+  {+    detail::erased_error& value = error_ref_access{e}.ref();+    stdx::launder(reinterpret_cast<T*>(&value.storage))->~T();+  }++  template <class T>+  constexpr static error_resource_management::copy_constructor copy() noexcept+  {+    return is_trivially_copy_constructible<T>::value ?+               nullptr : &copy_constructor<T>;+  }++  template <class T>+  constexpr static error_resource_management::move_constructor move() noexcept+  {+    return is_trivially_move_constructible<T>::value ?+               nullptr : &move_constructor<T>;+  }++  template <class T>+  constexpr static error_resource_management::destructor destroy() noexcept+  {+    return is_trivially_destructible<T>::value ?+               nullptr : &destructor<T>;+  }+};++} // end namespace detail++template <class T>+struct default_error_resource_management_t : error_resource_management+{+  constexpr default_error_resource_management_t() noexcept+      :+      error_resource_management{+          detail::default_error_constructors::copy<T>(),+          detail::default_error_constructors::move<T>(),+          detail::default_error_constructors::destroy<T>()+      }+  {}+};++  #if defined(STDX_VARIABLE_TEMPLATES)+template <class T>+inline constexpr default_error_resource_management_t<T> default_error_resource_management {};+  #endif++template <>+struct error_traits<std::errc>+{+  static std::exception_ptr to_exception(std::errc ec) noexcept+  {+    return std::make_exception_ptr(std::make_error_code(ec));+  }++  static error to_error(std::errc ec) noexcept+  {+    return error{error_value<std::errc>{ec}, generic_domain};+  }+};++namespace detail {++struct error_code_wrapper : enable_reference_count+{+  explicit error_code_wrapper(std::error_code ec) noexcept : code(ec)+  { }++  std::error_code code;+};++} // end namespace detail++// Error domain mapping to std::error_code+//+class error_code_error_domain : public error_domain+{+  using internal_value_type = intrusive_ptr<detail::error_code_wrapper>;++  friend struct error_traits<std::error_code>;++public:++  constexpr error_code_error_domain() noexcept+      :+      error_domain{+          {0x84e99cdcecae4443ULL, 0x9050179b713fd2afULL},+          default_error_resource_management_t<internal_value_type>{}+      }+  { }++  virtual string_ref name() const noexcept override+  {+    return "std::error_code error domain";+  }++  virtual bool equivalent(const error& lhs, const error& rhs) const noexcept override;++  virtual string_ref message(const error& e) const noexcept override;++  [[noreturn]] virtual void throw_exception(const error& e) const override;+};++STDX_LEGACY_INLINE_CONSTEXPR error_code_error_domain error_code_domain {};++template <>+struct error_traits<std::error_code>+{+  static std::error_code from_exception(std::exception_ptr e) noexcept+  {+    return error_code_from_exception(std::move(e));+  }++  static std::exception_ptr to_exception(std::error_code ec) noexcept+  {+    return std::make_exception_ptr(std::system_error{ec});+  }++  static error to_error(std::error_code ec) noexcept;+};++namespace detail {++template <class Ptr, bool = (sizeof(Ptr) <= sizeof(std::intptr_t))>+struct exception_ptr_wrapper_impl+{+  struct control_block : enable_reference_count+  {+    explicit control_block(Ptr p) noexcept : ptr_(std::move(p))+    { }++    Ptr ptr_;+  };++  explicit exception_ptr_wrapper_impl(Ptr p) : ptr{new control_block{std::move(p)}}+  { }++  Ptr get() noexcept { return ptr ? ptr->ptr_ : Ptr{}; }++  intrusive_ptr<control_block> ptr;+};++template <class Ptr>+struct exception_ptr_wrapper_impl<Ptr, true>+{+  explicit exception_ptr_wrapper_impl(Ptr p) : ptr{std::move(p)}+  { }++  Ptr get() noexcept { return ptr; }++  Ptr ptr;+};++using exception_ptr_wrapper = exception_ptr_wrapper_impl<std::exception_ptr>;++static_assert(sizeof(exception_ptr_wrapper) == sizeof(std::intptr_t), "Internal library error");++} // end namespace detail++  #ifdef STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+template <>+struct is_trivially_relocatable<detail::exception_ptr_wrapper> : std::true_type+{ };+  #endif++// Error domain mapping to std::exception_ptr+//+class dynamic_exception_error_domain : public error_domain+{+public:++  constexpr dynamic_exception_error_domain() noexcept+      :+      error_domain{+          {0x3c223c0aa3cf45e5ULL, 0x80dac24345cfb9fcULL},+          default_error_resource_management_t<detail::exception_ptr_wrapper>{}+      }+  { }++  virtual string_ref name() const noexcept override+  {+    return "dynamic exception domain";+  }++  virtual bool equivalent(const error& lhs, const error& rhs) const noexcept override;++  virtual string_ref message(const error&) const noexcept override;++  [[noreturn]] virtual void throw_exception(const error& e) const override+  {+    assert(e.domain() == *this);+    std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());+  }+};++STDX_LEGACY_INLINE_CONSTEXPR dynamic_exception_error_domain dynamic_exception_domain {};++// Error domain mapping to dynamic_exception_errc+//+class dynamic_exception_code_error_domain : public error_domain+{+public:++  constexpr dynamic_exception_code_error_domain() noexcept+      : error_domain{{0xa242506c26484677ULL, 0x82365303df25e338ULL}}+  { }++  virtual string_ref name() const noexcept override+  {+    return "dynamic exception code domain";+  }++  virtual bool equivalent(const error& lhs, const error& rhs) const noexcept override;++  virtual string_ref message(const error&) const noexcept override;+};++STDX_LEGACY_INLINE_CONSTEXPR dynamic_exception_code_error_domain dynamic_exception_code_domain {};++inline error make_error(dynamic_exception_errc code) noexcept+{+  return error{error_value<dynamic_exception_errc>{code}, dynamic_exception_code_domain};+}++struct thrown_dynamic_exception : std::exception+{+  explicit thrown_dynamic_exception(stdx::error e) noexcept : m_error(e)+  { }++  stdx::error error() const noexcept+  {+    return m_error;+  }++private:++  stdx::error m_error;+};++template <>+struct error_traits<std::exception_ptr>+{+  static std::exception_ptr from_exception(std::exception_ptr e) noexcept+  {+    return e;+  }++  static std::exception_ptr to_exception(std::exception_ptr e) noexcept+  {+    return e;+  }++  static error to_error(std::exception_ptr e) noexcept+  {+    return error{+        error_value<detail::exception_ptr_wrapper>{detail::exception_ptr_wrapper{e}},+        dynamic_exception_domain+    };+  }+};++} // end namespace stdx++namespace std {++template<>+struct is_error_code_enum<stdx::dynamic_exception_errc> : std::true_type+{ };++} // end namespace std++#endif++++#if __cplusplus >= 201703L+  #include <any>+  #include <variant>+  #include <optional>+#endif++#include <functional>++namespace stdx {++namespace {++inline const char* dynamic_exception_errc_str(unsigned ev) noexcept+{+  constexpr const char* msg[] =+      {+          "Success",+          "std::runtime_error",+          "std::domain_error",+          "std::invalid_argument",+          "std::length_error",+          "std::out_of_range",+          "std::logic_error",+          "std::range_error",+          "std::overflow_error",+          "std::underflow_error",+          "std::bad_alloc",+          "std::bad_array_new_length",+          "std::bad_optional_access",+          "std::bad_typeid",+          "std::bad_any_cast",+          "std::bad_cast",+          "std::bad_weak_ptr",+          "std::bad_function_call",+          "std::bad_exception",+          "std::bad_variant_access",+          "unspecified dynamic exception"+      };++  assert(ev < (sizeof(msg) / sizeof(const char*)));+  return msg[ev];+}++class dynamic_exception_error_category : public std::error_category+{+public:++  const char* name() const noexcept override+  {+    return "dynamic_exception";+  }++  std::string message(int code) const override+  {+    return dynamic_exception_errc_str(code);+  }++  bool equivalent(int code, const std::error_condition& cond) const noexcept override+  {+    switch (static_cast<dynamic_exception_errc>(code))+    {+      case dynamic_exception_errc::domain_error:+        return (cond == std::errc::argument_out_of_domain);+      case dynamic_exception_errc::invalid_argument:+        return (cond == std::errc::invalid_argument);+      case dynamic_exception_errc::length_error:+        return (cond == std::errc::value_too_large);+      case dynamic_exception_errc::out_of_range:+      case dynamic_exception_errc::range_error:+      case dynamic_exception_errc::underflow_error:+        return (cond == std::errc::result_out_of_range);+      case dynamic_exception_errc::overflow_error:+        return (cond == std::errc::value_too_large);+      case dynamic_exception_errc::bad_alloc:+      case dynamic_exception_errc::bad_array_new_length:+        return (cond == std::errc::not_enough_memory);+      default:;+    }+    return false;+  }+};++inline const std::error_category& dynamic_exception_category() noexcept+{+  static const dynamic_exception_error_category dynamic_exception_error_category_instance;+  return dynamic_exception_error_category_instance;+}++} // end anonymous namespace++inline std::error_code make_error_code(dynamic_exception_errc code) noexcept+{+  return std::error_code{static_cast<int>(code), dynamic_exception_category()};+}++inline std::error_code error_code_from_exception(std::exception_ptr eptr) noexcept+{+  if (!eptr) return make_error_code(dynamic_exception_errc::bad_exception);++  try+  {+    std::rethrow_exception(eptr);+  }+  catch (const std::domain_error&)+  {+    return make_error_code(dynamic_exception_errc::domain_error);+  }+  catch (const std::invalid_argument&)+  {+    return make_error_code(dynamic_exception_errc::invalid_argument);+  }+  catch (const std::length_error&)+  {+    return make_error_code(dynamic_exception_errc::length_error);+  }+  catch (const std::out_of_range&)+  {+    return make_error_code(dynamic_exception_errc::out_of_range);+  }+  catch (const std::logic_error&)+  {+    return make_error_code(dynamic_exception_errc::logic_error);+  }+  catch (const std::range_error&)+  {+    return make_error_code(dynamic_exception_errc::range_error);+  }+  catch (const std::overflow_error&)+  {+    return make_error_code(dynamic_exception_errc::overflow_error);+  }+  catch (const std::underflow_error&)+  {+    return make_error_code(dynamic_exception_errc::underflow_error);+  }+  catch (const std::system_error& e)+  {+    return e.code();+  }+  catch (const std::runtime_error&)+  {+    return make_error_code(dynamic_exception_errc::runtime_error);+  }+  catch (const std::bad_array_new_length&)+  {+    return make_error_code(dynamic_exception_errc::bad_array_new_length);+  }+  catch (const std::bad_alloc&)+  {+    return make_error_code(dynamic_exception_errc::bad_alloc);+  }+  catch (const std::bad_typeid&)+  {+    return make_error_code(dynamic_exception_errc::bad_typeid);+  }+#if __cplusplus >= 201703L+  catch (const std::bad_optional_access&)+  {+    return make_error_code(dynamic_exception_errc::bad_optional_access);+  }+  catch (const std::bad_any_cast&)+  {+    return make_error_code(dynamic_exception_errc::bad_any_cast);+  }+  catch (const std::bad_variant_access&)+  {+    return make_error_code(dynamic_exception_errc::bad_variant_access);+  }+#endif+  catch (const std::bad_cast&)+  {+    return make_error_code(dynamic_exception_errc::bad_cast);+  }+  catch (const std::bad_weak_ptr&)+  {+    return make_error_code(dynamic_exception_errc::bad_weak_ptr);+  }+  catch (const std::bad_function_call&)+  {+    return make_error_code(dynamic_exception_errc::bad_function_call);+  }+  catch (const std::bad_exception&)+  {+    return make_error_code(dynamic_exception_errc::bad_exception);+  }+  catch (...)+  { }++  return make_error_code(dynamic_exception_errc::unspecified_exception);+}++inline error error_from_exception(std::exception_ptr eptr) noexcept+{+  if (!eptr) return make_error(dynamic_exception_errc::bad_exception);++  try+  {+    std::rethrow_exception(eptr);+  }+  catch (const std::domain_error&)+  {+    return make_error(dynamic_exception_errc::domain_error);+  }+  catch (const std::invalid_argument&)+  {+    return make_error(dynamic_exception_errc::invalid_argument);+  }+  catch (const std::length_error&)+  {+    return make_error(dynamic_exception_errc::length_error);+  }+  catch (const std::out_of_range&)+  {+    return make_error(dynamic_exception_errc::out_of_range);+  }+  catch (const std::logic_error&)+  {+    return make_error(dynamic_exception_errc::logic_error);+  }+  catch (const std::range_error&)+  {+    return make_error(dynamic_exception_errc::range_error);+  }+  catch (const std::overflow_error&)+  {+    return make_error(dynamic_exception_errc::overflow_error);+  }+  catch (const std::underflow_error&)+  {+    return make_error(dynamic_exception_errc::underflow_error);+  }+  catch (const std::system_error& e)+  {+    return error{e.code()};+  }+  catch (const std::runtime_error&)+  {+    return make_error(dynamic_exception_errc::runtime_error);+  }+  catch (const std::bad_array_new_length&)+  {+    return make_error(dynamic_exception_errc::bad_array_new_length);+  }+  catch (const std::bad_alloc&)+  {+    return make_error(dynamic_exception_errc::bad_alloc);+  }+  catch (const std::bad_typeid&)+  {+    return make_error(dynamic_exception_errc::bad_typeid);+  }+#if __cplusplus >= 201703L+  catch (const std::bad_optional_access&)+  {+    return make_error(dynamic_exception_errc::bad_optional_access);+  }+  catch (const std::bad_any_cast&)+  {+    return make_error(dynamic_exception_errc::bad_any_cast);+  }+  catch (const std::bad_variant_access&)+  {+    return make_error(dynamic_exception_errc::bad_variant_access);+  }+#endif+  catch (const std::bad_cast&)+  {+    return make_error(dynamic_exception_errc::bad_cast);+  }+  catch (const std::bad_weak_ptr&)+  {+    return make_error(dynamic_exception_errc::bad_weak_ptr);+  }+  catch (const std::bad_function_call&)+  {+    return make_error(dynamic_exception_errc::bad_function_call);+  }+  catch (const std::bad_exception&)+  {+    return make_error(dynamic_exception_errc::bad_exception);+  }+  catch (...)+  { }++  return make_error(dynamic_exception_errc::unspecified_exception);+}++// ---------- ErrorDomain (abstract base class)+//+inline void error_domain::throw_exception(const error& e) const+{+  throw thrown_dynamic_exception{e};+}++// ---------- GenericErrorDomain+//+inline bool generic_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept+{+  assert(lhs.domain() == *this);+  if (lhs.domain() == rhs.domain())+  {+    return error_cast<std::errc>(lhs) == error_cast<std::errc>(rhs);+  }++  return false;+}++namespace {++string_ref generic_error_code_message(std::errc code) noexcept+{+  switch (code)+  {+    case std::errc::address_family_not_supported:+      return "Address family not supported by protocol";+    case std::errc::address_in_use:+      return "Address already in use";+    case std::errc::address_not_available:+      return "Cannot assign requested address";+    case std::errc::already_connected:+      return "Transport endpoint is already connected";+    case std::errc::argument_list_too_long:+      return "Argument list too long";+    case std::errc::argument_out_of_domain:+      return "Numerical argument out of domain";+    case std::errc::bad_address:+      return "Bad address";+    case std::errc::bad_file_descriptor:+      return "Bad file descriptor";+    case std::errc::bad_message:+      return "Bad message";+    case std::errc::broken_pipe:+      return "Broken pipe";+    case std::errc::connection_aborted:+      return "Software caused connection abort";+    case std::errc::connection_already_in_progress:+      return "Operation already in progress";+    case std::errc::connection_refused:+      return "Connection refused";+    case std::errc::connection_reset:+      return "Connection reset by peer";+    case std::errc::cross_device_link:+      return "Invalid cross-device link";+    case std::errc::destination_address_required:+      return "Destination address required";+    case std::errc::device_or_resource_busy:+      return "Device or resource busy";+    case std::errc::directory_not_empty:+      return "Directory not empty";+    case std::errc::executable_format_error:+      return "Exec format error";+    case std::errc::file_exists:+      return "File exists";+    case std::errc::file_too_large:+      return "File too large";+    case std::errc::filename_too_long:+      return "File name too long";+    case std::errc::function_not_supported:+      return "Function not implemented";+    case std::errc::host_unreachable:+      return "No route to host";+    case std::errc::identifier_removed:+      return "Identifier removed";+    case std::errc::illegal_byte_sequence:+      return "Invalid or incomplete multibyte or wide character";+    case std::errc::inappropriate_io_control_operation:+      return "Inappropriate ioctl for device";+    case std::errc::interrupted:+      return "Interrupted system call";+    case std::errc::invalid_argument:+      return "Invalid argument";+    case std::errc::invalid_seek:+      return "Illegal seek";+    case std::errc::io_error:+      return "Input/output error";+    case std::errc::is_a_directory:+      return "Is a directory";+    case std::errc::message_size:+      return "Message too long";+    case std::errc::network_down:+      return "Network is down";+    case std::errc::network_reset:+      return "Network dropped connection on reset";+    case std::errc::network_unreachable:+      return "Network is unreachable";+    case std::errc::no_buffer_space:+      return "No buffer space available";+    case std::errc::no_child_process:+      return "No child processes";+    case std::errc::no_link:+      return "Link has been severed";+    case std::errc::no_lock_available:+      return "No locks available";+    case std::errc::no_message:+      return "No message of desired type";+    case std::errc::no_protocol_option:+      return "Protocol not available";+    case std::errc::no_space_on_device:+      return "No space left on device";+    case std::errc::no_stream_resources:+      return "Out of streams resources";+    case std::errc::no_such_device_or_address:+      return "No such device or address";+    case std::errc::no_such_device:+      return "No such device";+    case std::errc::no_such_file_or_directory:+      return "No such file or directory";+    case std::errc::no_such_process:+      return "No such process";+    case std::errc::not_a_directory:+      return "Not a directory";+    case std::errc::not_a_socket:+      return "Socket operation on non-socket";+    case std::errc::not_a_stream:+      return "Device not a stream";+    case std::errc::not_connected:+      return "Transport endpoint is not connected";+    case std::errc::not_enough_memory:+      return "Cannot allocate memory";+#if ENOTSUP != EOPNOTSUPP+    case std::errc::not_supported:+      return "Operation not supported";+#endif+    case std::errc::operation_canceled:+      return "Operation canceled";+    case std::errc::operation_in_progress:+      return "Operation now in progress";+    case std::errc::operation_not_permitted:+      return "Operation not permitted";+    case std::errc::operation_not_supported:+      return "Operation not supported";+#if EAGAIN != EWOULDBLOCK+    case std::errc::operation_would_block:+      return "Resource temporarily unavailable";+#endif+    case std::errc::owner_dead:+      return "Owner died";+    case std::errc::permission_denied:+      return "Permission denied";+    case std::errc::protocol_error:+      return "Protocol error";+    case std::errc::protocol_not_supported:+      return "Protocol not supported";+    case std::errc::read_only_file_system:+      return "Read-only file system";+    case std::errc::resource_deadlock_would_occur:+      return "Resource deadlock avoided";+    case std::errc::resource_unavailable_try_again:+      return "Resource temporarily unavailable";+    case std::errc::result_out_of_range:+      return "Numerical result out of range";+    case std::errc::state_not_recoverable:+      return "State not recoverable";+    case std::errc::stream_timeout:+      return "Timer expired";+    case std::errc::text_file_busy:+      return "Text file busy";+    case std::errc::timed_out:+      return "Connection timed out";+    case std::errc::too_many_files_open_in_system:+      return "Too many open files in system";+    case std::errc::too_many_files_open:+      return "Too many open files";+    case std::errc::too_many_links:+      return "Too many links";+    case std::errc::too_many_symbolic_link_levels:+      return "Too many levels of symbolic links";+    case std::errc::value_too_large:+      return "Value too large for defined data type";+    case std::errc::wrong_protocol_type:+      return "Protocol wrong type for socket";+    default:+      return "Unspecified error";+  }+}++} // end anonymous namespace++inline string_ref generic_error_domain::message(const error& e) const noexcept+{+  assert(e.domain() == *this);+  return generic_error_code_message(error_cast<std::errc>(e));+}++// ---------- ErrorCodeErrorDomain+//+inline string_ref error_code_error_domain::message(const error& e) const noexcept+{+  assert(e.domain() == *this);++  auto ptr = error_cast<internal_value_type>(e);+  if (ptr)+  {+    std::string msg = ptr->code.message();+    return shared_string_ref{msg.c_str(), msg.c_str() + msg.size()};+  }++  return string_ref{"Bad error code"};+}++inline void error_code_error_domain::throw_exception(const error& e) const+{+  assert(e.domain() == *this);++  std::error_code code;+  auto ptr = error_cast<internal_value_type>(e);+  if (ptr) code = ptr->code;+  throw std::system_error{code};+}++inline bool error_code_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept+{+  assert(lhs.domain() == *this);++  if (lhs.domain() == rhs.domain())+  {+    auto ptr1 = error_cast<internal_value_type>(lhs);+    auto ptr2 = error_cast<internal_value_type>(rhs);+    if (ptr1 && ptr2) return ptr1->code == ptr2->code.default_error_condition();+    return false;+  }++  if (rhs.domain() == generic_domain)+  {+    auto ptr1 = error_cast<internal_value_type>(lhs);+    if (ptr1) return ptr1->code == error_cast<std::errc>(rhs);+  }++  return false;+}++inline stdx::error error_traits<std::error_code>::to_error(std::error_code ec) noexcept+{+  using internal_value_type = error_code_error_domain::internal_value_type;++  if (ec.category() == std::generic_category())+  {+    return error{+        error_value<std::errc>{static_cast<std::errc>(ec.default_error_condition().value())},+        generic_domain+    };+  }++  return error{+      error_value<internal_value_type>{internal_value_type{new detail::error_code_wrapper{ec}}},+      error_code_domain+  };+}++// ---------- DynamicExceptionErrorDomain+//+inline string_ref dynamic_exception_error_domain::message(const error& e) const noexcept+{+  assert(e.domain() == *this);++  std::exception_ptr eptr = error_cast<detail::exception_ptr_wrapper>(e).get();++  try+  {+    std::rethrow_exception(eptr);+  }+  catch (const std::exception& ex)+  {+    return shared_string_ref{ex.what()};+  }+  catch (...) {}++  return string_ref{"Unknown dynamic exception"};+}++namespace {++std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept+{+  switch (code)+  {+    case dynamic_exception_errc::domain_error:+      return std::errc::argument_out_of_domain;+    case dynamic_exception_errc::invalid_argument:+      return std::errc::invalid_argument;+    case dynamic_exception_errc::length_error:+      return std::errc::value_too_large;+    case dynamic_exception_errc::out_of_range:+    case dynamic_exception_errc::range_error:+    case dynamic_exception_errc::underflow_error:+      return std::errc::result_out_of_range;+    case dynamic_exception_errc::overflow_error:+      return std::errc::value_too_large;+    case dynamic_exception_errc::bad_alloc:+    case dynamic_exception_errc::bad_array_new_length:+      return std::errc::not_enough_memory;+    default:;+  }+  return std::errc{};+}++} // end anonymous namespace++inline bool dynamic_exception_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept+{+  assert(lhs.domain() == *this);++  std::exception_ptr eptr = error_cast<detail::exception_ptr_wrapper>(lhs).get();++  if (rhs.domain() == *this)+  {+    std::exception_ptr eptr2 = error_cast<detail::exception_ptr_wrapper>(rhs).get();+    if (eptr == eptr2) return true;++    error e1 = error_from_exception(eptr);+    error e2 = error_from_exception(eptr2);+    return e1.domain().equivalent(e1, e2);+  }+  else if (rhs.domain() == error_code_domain)+  {+    std::error_code ec = error_code_from_exception(eptr);+    return error_code_domain.equivalent(rhs, error{ec});+  }++  error e = error_from_exception(eptr);+  return e.domain().equivalent(e, rhs);+}++// ---------- DynamicExceptionCodeErrorDomain+//+inline bool dynamic_exception_code_error_domain::equivalent(+    const error& lhs,+    const error& rhs+    ) const noexcept+{+  assert(lhs.domain() == *this);++  const dynamic_exception_errc code = error_cast<dynamic_exception_errc>(lhs);++  if (rhs.domain() == *this)+  {+    return code == error_cast<dynamic_exception_errc>(rhs);+  }+  else if (rhs.domain() == error_code_domain)+  {+    return error_code_domain.equivalent(rhs, make_error_code(code));+  }+  else if (rhs.domain() == generic_domain)+  {+    std::errc generic_code = dynamic_exception_code_to_generic_code(code);+    return generic_domain.equivalent(rhs, generic_code);+  }++  return false;+}++inline string_ref dynamic_exception_code_error_domain::message(const error& e) const noexcept+{+  assert(e.domain() == *this);+  return string_ref{+      dynamic_exception_errc_str(static_cast<unsigned>(error_cast<dynamic_exception_errc>(e)))+  };+}++} // end namespace stdx+++
+ vendor/libremidi/ump.hpp view
@@ -0,0 +1,113 @@+#pragma once+#include <libremidi/config.hpp>++#include <span>++#if LIBREMIDI_NI_MIDI2_COMPAT+  #include <midi/universal_packet.h>+#endif++namespace libremidi+{+struct ump+{+  alignas(4) uint32_t data[4] = {};+  int64_t timestamp{};++  constexpr ump() noexcept = default;+  constexpr ~ump() = default;++  explicit constexpr ump(uint32_t b0) noexcept+      : data{b0, 0, 0, 0}+  {+  }+  constexpr ump(uint32_t b0, uint32_t b1) noexcept+      : data{b0, b1, 0, 0}+  {+  }+  constexpr ump(uint32_t b0, uint32_t b1, uint32_t b2) noexcept+      : data{b0, b1, b2, 0}+  {+  }+  constexpr ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) noexcept+      : data{b0, b1, b2, b3}+  {+  }++  // Compatibility with ni-midi2:+#if LIBREMIDI_NI_MIDI2_COMPAT+  constexpr operator midi::universal_packet() const noexcept+  {+    return {data[0], data[1], data[2], data[3]};+  }+  explicit constexpr ump(midi::universal_packet b) noexcept+      : data{b.data[0], b.data[1], b.data[2], b.data[3]}+  {+  }+  constexpr ump& operator=(midi::universal_packet b) noexcept+  {+    data[0] = b.data[0];+    data[1] = b.data[1];+    data[2] = b.data[2];+    data[3] = b.data[3];+    return *this;+  }+#endif++  // Compatibility with cmidi2:+  operator uint32_t*() & noexcept { return data; }+  operator const uint32_t*() const& noexcept { return data; }+  operator uint32_t*() && noexcept = delete;+  operator const uint32_t*() const&& noexcept = delete;++  constexpr std::size_t size() const noexcept+  {+    // Imported from cmidi2+    enum midi2_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,+    };++    switch (((data[0] & 0xF0000000) >> 28) & 0xF)+    {+      case UTILITY:+      case SYSTEM:+      case MIDI_1_CHANNEL:+        return 1;+      case MIDI_2_CHANNEL:+      case SYSEX7:+        return 2;+      case SYSEX8_MDS:+        return 4;+      default:+        return 0;+    }+  }++  constexpr void clear() noexcept+  {+    data[0] = 0;+    data[1] = 0;+    data[2] = 0;+    data[3] = 0;+  }++  constexpr auto& operator[](int i) const noexcept { return data[i]; }+  constexpr auto& operator[](int i) noexcept { return data[i]; }++  constexpr auto begin() const noexcept { return data; }+  constexpr auto end() const noexcept { return data + size(); }+  constexpr auto begin() noexcept { return data; }+  constexpr auto end() noexcept { return data + size(); }+  constexpr auto cbegin() const noexcept { return data; }+  constexpr auto cend() const noexcept { return data + size(); }+  constexpr auto cbegin() noexcept { return data; }+  constexpr auto cend() noexcept { return data + size(); }+};+}
+ vendor/libremidi/writer.hpp view
@@ -0,0 +1,52 @@+/*+Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+*/++#pragma once+#include <libremidi/message.hpp>++#include <cstdint>+#include <iosfwd>+#include <vector>++namespace libremidi+{+struct LIBREMIDI_EXPORT writer+{+public:+  uint16_t ticksPerQuarterNote{120};+  std::vector<midi_track> tracks;++  void add_event(int tick, int track, const message& m);+  void add_event(int track, const track_event& m);++  void add_track();++  void write(std::ostream& out) const;+};+}++#if defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/writer.cpp>+#endif
+ vendor/readerwriterqueue.h view
@@ -0,0 +1,979 @@+// ©2013-2020 Cameron Desrochers.+// Distributed under the simplified BSD license (see the license file that+// should have come with this header).++#pragma once++#include "atomicops.h"+#include <new>+#include <type_traits>+#include <utility>+#include <cassert>+#include <stdexcept>+#include <new>+#include <cstdint>+#include <cstdlib>		// For malloc/free/abort & size_t+#include <memory>+#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012+#include <chrono>+#endif+++// A lock-free queue for a single-consumer, single-producer architecture.+// The queue is also wait-free in the common path (except if more memory+// needs to be allocated, in which case malloc is called).+// Allocates memory sparingly, and only once if the original maximum size+// estimate is never exceeded.+// Tested on x86/x64 processors, but semantics should be correct for all+// architectures (given the right implementations in atomicops.h), provided+// that aligned integer and pointer accesses are naturally atomic.+// Note that there should only be one consumer thread and producer thread;+// Switching roles of the threads, or using multiple consecutive threads for+// one role, is not safe unless properly synchronized.+// Using the queue exclusively from one thread is fine, though a bit silly.++#ifndef MOODYCAMEL_CACHE_LINE_SIZE+#define MOODYCAMEL_CACHE_LINE_SIZE 64+#endif++#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED+#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__))+#define MOODYCAMEL_EXCEPTIONS_ENABLED+#endif+#endif++#ifndef MOODYCAMEL_HAS_EMPLACE+#if !defined(_MSC_VER) || _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013+#define MOODYCAMEL_HAS_EMPLACE    1+#endif+#endif++#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE+#if defined (__APPLE__) && defined (__MACH__) && __cplusplus >= 201703L+// This is required to find out what deployment target we are using+#include <AvailabilityMacros.h>+#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || !defined(MAC_OS_X_VERSION_10_14) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14+// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we can't support over-alignment in this case+#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE+#endif+#endif+#endif++#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE+#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE)+#endif++#ifdef AE_VCPP+#pragma warning(push)+#pragma warning(disable: 4324)	// structure was padded due to __declspec(align())+#pragma warning(disable: 4820)	// padding was added+#pragma warning(disable: 4127)	// conditional expression is constant+#endif++namespace moodycamel {++template<typename T, size_t MAX_BLOCK_SIZE = 512>+class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue+{+	// Design: Based on a queue-of-queues. The low-level queues are just+	// circular buffers with front and tail indices indicating where the+	// next element to dequeue is and where the next element can be enqueued,+	// respectively. Each low-level queue is called a "block". Each block+	// wastes exactly one element's worth of space to keep the design simple+	// (if front == tail then the queue is empty, and can't be full).+	// The high-level queue is a circular linked list of blocks; again there+	// is a front and tail, but this time they are pointers to the blocks.+	// The front block is where the next element to be dequeued is, provided+	// the block is not empty. The back block is where elements are to be+	// enqueued, provided the block is not full.+	// The producer thread owns all the tail indices/pointers. The consumer+	// thread owns all the front indices/pointers. Both threads read each+	// other's variables, but only the owning thread updates them. E.g. After+	// the consumer reads the producer's tail, the tail may change before the+	// consumer is done dequeuing an object, but the consumer knows the tail+	// will never go backwards, only forwards.+	// If there is no room to enqueue an object, an additional block (of+	// equal size to the last block) is added. Blocks are never removed.++public:+	typedef T value_type;++	// Constructs a queue that can hold at least `size` elements without further+	// allocations. If more than MAX_BLOCK_SIZE elements are requested,+	// then several blocks of MAX_BLOCK_SIZE each are reserved (including+	// at least one extra buffer block).+	AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15)+#ifndef NDEBUG+		: enqueuing(false)+		,dequeuing(false)+#endif+	{+		assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && "MAX_BLOCK_SIZE must be a power of 2");+		assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2");+		+		Block* firstBlock = nullptr;+		+		largestBlockSize = ceilToPow2(size + 1);		// We need a spare slot to fit size elements in the block+		if (largestBlockSize > MAX_BLOCK_SIZE * 2) {+			// We need a spare block in case the producer is writing to a different block the consumer is reading from, and+			// wants to enqueue the maximum number of elements. We also need a spare element in each block to avoid the ambiguity+			// between front == tail meaning "empty" and "full".+			// So the effective number of slots that are guaranteed to be usable at any time is the block size - 1 times the+			// number of blocks - 1. Solving for size and applying a ceiling to the division gives us (after simplifying):+			size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1);+			largestBlockSize = MAX_BLOCK_SIZE;+			Block* lastBlock = nullptr;+			for (size_t i = 0; i != initialBlockCount; ++i) {+				auto block = make_block(largestBlockSize);+				if (block == nullptr) {+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED+					throw std::bad_alloc();+#else+					abort();+#endif+				}+				if (firstBlock == nullptr) {+					firstBlock = block;+				}+				else {+					lastBlock->next = block;+				}+				lastBlock = block;+				block->next = firstBlock;+			}+		}+		else {+			firstBlock = make_block(largestBlockSize);+			if (firstBlock == nullptr) {+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED+				throw std::bad_alloc();+#else+				abort();+#endif+			}+			firstBlock->next = firstBlock;+		}+		frontBlock = firstBlock;+		tailBlock = firstBlock;+		+		// Make sure the reader/writer threads will have the initialized memory setup above:+		fence(memory_order_sync);+	}++	// Note: The queue should not be accessed concurrently while it's+	// being moved. It's up to the user to synchronize this.+	AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other)+		: frontBlock(other.frontBlock.load()),+		tailBlock(other.tailBlock.load()),+		largestBlockSize(other.largestBlockSize)+#ifndef NDEBUG+		,enqueuing(false)+		,dequeuing(false)+#endif+	{+		other.largestBlockSize = 32;+		Block* b = other.make_block(other.largestBlockSize);+		if (b == nullptr) {+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED+			throw std::bad_alloc();+#else+			abort();+#endif+		}+		b->next = b;+		other.frontBlock = b;+		other.tailBlock = b;+	}++	// Note: The queue should not be accessed concurrently while it's+	// being moved. It's up to the user to synchronize this.+	ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN+	{+		Block* b = frontBlock.load();+		frontBlock = other.frontBlock.load();+		other.frontBlock = b;+		b = tailBlock.load();+		tailBlock = other.tailBlock.load();+		other.tailBlock = b;+		std::swap(largestBlockSize, other.largestBlockSize);+		return *this;+	}++	// Note: The queue should not be accessed concurrently while it's+	// being deleted. It's up to the user to synchronize this.+	AE_NO_TSAN ~ReaderWriterQueue()+	{+		// Make sure we get the latest version of all variables from other CPUs:+		fence(memory_order_sync);++		// Destroy any remaining objects in queue and free memory+		Block* frontBlock_ = frontBlock;+		Block* block = frontBlock_;+		do {+			Block* nextBlock = block->next;+			size_t blockFront = block->front;+			size_t blockTail = block->tail;++			for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) {+				auto element = reinterpret_cast<T*>(block->data + i * sizeof(T));+				element->~T();+				(void)element;+			}+			+			auto rawBlock = block->rawThis;+			block->~Block();+			std::free(rawBlock);+			block = nextBlock;+		} while (block != frontBlock_);+	}+++	// Enqueues a copy of element if there is room in the queue.+	// Returns true if the element was enqueued, false otherwise.+	// Does not allocate memory.+	AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN+	{+		return inner_enqueue<CannotAlloc>(element);+	}++	// Enqueues a moved copy of element if there is room in the queue.+	// Returns true if the element was enqueued, false otherwise.+	// Does not allocate memory.+	AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN+	{+		return inner_enqueue<CannotAlloc>(std::forward<T>(element));+	}++#if MOODYCAMEL_HAS_EMPLACE+	// Like try_enqueue() but with emplace semantics (i.e. construct-in-place).+	template<typename... Args>+	AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN+	{+		return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...);+	}+#endif++	// Enqueues a copy of element on the queue.+	// Allocates an additional block of memory if needed.+	// Only fails (returns false) if memory allocation fails.+	AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN+	{+		return inner_enqueue<CanAlloc>(element);+	}++	// Enqueues a moved copy of element on the queue.+	// Allocates an additional block of memory if needed.+	// Only fails (returns false) if memory allocation fails.+	AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN+	{+		return inner_enqueue<CanAlloc>(std::forward<T>(element));+	}++#if MOODYCAMEL_HAS_EMPLACE+	// Like enqueue() but with emplace semantics (i.e. construct-in-place).+	template<typename... Args>+	AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN+	{+		return inner_enqueue<CanAlloc>(std::forward<Args>(args)...);+	}+#endif++	// Attempts to dequeue an element; if the queue is empty,+	// returns false instead. If the queue has at least one element,+	// moves front to result using operator=, then returns true.+	template<typename U>+	bool try_dequeue(U& result) AE_NO_TSAN+	{+#ifndef NDEBUG+		ReentrantGuard guard(this->dequeuing);+#endif++		// High-level pseudocode:+		// Remember where the tail block is+		// If the front block has an element in it, dequeue it+		// Else+		//     If front block was the tail block when we entered the function, return false+		//     Else advance to next block and dequeue the item there++		// Note that we have to use the value of the tail block from before we check if the front+		// block is full or not, in case the front block is empty and then, before we check if the+		// tail block is at the front block or not, the producer fills up the front block *and+		// moves on*, which would make us skip a filled block. Seems unlikely, but was consistently+		// reproducible in practice.+		// In order to avoid overhead in the common case, though, we do a double-checked pattern+		// where we have the fast path if the front block is not empty, then read the tail block,+		// then re-read the front block and check if it's not empty again, then check if the tail+		// block has advanced.+		+		Block* frontBlock_ = frontBlock.load();+		size_t blockTail = frontBlock_->localTail;+		size_t blockFront = frontBlock_->front.load();+		+		if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {+			fence(memory_order_acquire);+			+		non_empty_front_block:+			// Front block not empty, dequeue from here+			auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));+			result = std::move(*element);+			element->~T();++			blockFront = (blockFront + 1) & frontBlock_->sizeMask;++			fence(memory_order_release);+			frontBlock_->front = blockFront;+		}+		else if (frontBlock_ != tailBlock.load()) {+			fence(memory_order_acquire);++			frontBlock_ = frontBlock.load();+			blockTail = frontBlock_->localTail = frontBlock_->tail.load();+			blockFront = frontBlock_->front.load();+			fence(memory_order_acquire);+			+			if (blockFront != blockTail) {+				// Oh look, the front block isn't empty after all+				goto non_empty_front_block;+			}+			+			// Front block is empty but there's another block ahead, advance to it+			Block* nextBlock = frontBlock_->next;+			// Don't need an acquire fence here since next can only ever be set on the tailBlock,+			// and we're not the tailBlock, and we did an acquire earlier after reading tailBlock which+			// ensures next is up-to-date on this CPU in case we recently were at tailBlock.++			size_t nextBlockFront = nextBlock->front.load();+			size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();+			fence(memory_order_acquire);++			// Since the tailBlock is only ever advanced after being written to,+			// we know there's for sure an element to dequeue on it+			assert(nextBlockFront != nextBlockTail);+			AE_UNUSED(nextBlockTail);++			// We're done with this block, let the producer use it if it needs+			fence(memory_order_release);		// Expose possibly pending changes to frontBlock->front from last dequeue+			frontBlock = frontBlock_ = nextBlock;++			compiler_fence(memory_order_release);	// Not strictly needed++			auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));+			+			result = std::move(*element);+			element->~T();++			nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;+			+			fence(memory_order_release);+			frontBlock_->front = nextBlockFront;+		}+		else {+			// No elements in current block and no other block to advance to+			return false;+		}++		return true;+	}+++	// Returns a pointer to the front element in the queue (the one that+	// would be removed next by a call to `try_dequeue` or `pop`). If the+	// queue appears empty at the time the method is called, nullptr is+	// returned instead.+	// Must be called only from the consumer thread.+	T* peek() const AE_NO_TSAN+	{+#ifndef NDEBUG+		ReentrantGuard guard(this->dequeuing);+#endif+		// See try_dequeue() for reasoning++		Block* frontBlock_ = frontBlock.load();+		size_t blockTail = frontBlock_->localTail;+		size_t blockFront = frontBlock_->front.load();+		+		if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {+			fence(memory_order_acquire);+		non_empty_front_block:+			return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));+		}+		else if (frontBlock_ != tailBlock.load()) {+			fence(memory_order_acquire);+			frontBlock_ = frontBlock.load();+			blockTail = frontBlock_->localTail = frontBlock_->tail.load();+			blockFront = frontBlock_->front.load();+			fence(memory_order_acquire);+			+			if (blockFront != blockTail) {+				goto non_empty_front_block;+			}+			+			Block* nextBlock = frontBlock_->next;+			+			size_t nextBlockFront = nextBlock->front.load();+			fence(memory_order_acquire);++			assert(nextBlockFront != nextBlock->tail.load());+			return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T));+		}+		+		return nullptr;+	}+	+	// Removes the front element from the queue, if any, without returning it.+	// Returns true on success, or false if the queue appeared empty at the time+	// `pop` was called.+	bool pop() AE_NO_TSAN+	{+#ifndef NDEBUG+		ReentrantGuard guard(this->dequeuing);+#endif+		// See try_dequeue() for reasoning+		+		Block* frontBlock_ = frontBlock.load();+		size_t blockTail = frontBlock_->localTail;+		size_t blockFront = frontBlock_->front.load();+		+		if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {+			fence(memory_order_acquire);+			+		non_empty_front_block:+			auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));+			element->~T();++			blockFront = (blockFront + 1) & frontBlock_->sizeMask;++			fence(memory_order_release);+			frontBlock_->front = blockFront;+		}+		else if (frontBlock_ != tailBlock.load()) {+			fence(memory_order_acquire);+			frontBlock_ = frontBlock.load();+			blockTail = frontBlock_->localTail = frontBlock_->tail.load();+			blockFront = frontBlock_->front.load();+			fence(memory_order_acquire);+			+			if (blockFront != blockTail) {+				goto non_empty_front_block;+			}+			+			// Front block is empty but there's another block ahead, advance to it+			Block* nextBlock = frontBlock_->next;+			+			size_t nextBlockFront = nextBlock->front.load();+			size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();+			fence(memory_order_acquire);++			assert(nextBlockFront != nextBlockTail);+			AE_UNUSED(nextBlockTail);++			fence(memory_order_release);+			frontBlock = frontBlock_ = nextBlock;++			compiler_fence(memory_order_release);++			auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));+			element->~T();++			nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;+			+			fence(memory_order_release);+			frontBlock_->front = nextBlockFront;+		}+		else {+			// No elements in current block and no other block to advance to+			return false;+		}++		return true;+	}+	+	// Returns the approximate number of items currently in the queue.+	// Safe to call from both the producer and consumer threads.+	inline size_t size_approx() const AE_NO_TSAN+	{+		size_t result = 0;+		Block* frontBlock_ = frontBlock.load();+		Block* block = frontBlock_;+		do {+			fence(memory_order_acquire);+			size_t blockFront = block->front.load();+			size_t blockTail = block->tail.load();+			result += (blockTail - blockFront) & block->sizeMask;+			block = block->next.load();+		} while (block != frontBlock_);+		return result;+	}++	// Returns the total number of items that could be enqueued without incurring+	// an allocation when this queue is empty.+	// Safe to call from both the producer and consumer threads.+	//+	// NOTE: The actual capacity during usage may be different depending on the consumer.+	//       If the consumer is removing elements concurrently, the producer cannot add to+	//       the block the consumer is removing from until it's completely empty, except in+	//       the case where the producer was writing to the same block the consumer was+	//       reading from the whole time.+	inline size_t max_capacity() const {+		size_t result = 0;+		Block* frontBlock_ = frontBlock.load();+		Block* block = frontBlock_;+		do {+			fence(memory_order_acquire);+			result += block->sizeMask;+			block = block->next.load();+		} while (block != frontBlock_);+		return result;+	}+++private:+	enum AllocationMode { CanAlloc, CannotAlloc };++#if MOODYCAMEL_HAS_EMPLACE+	template<AllocationMode canAlloc, typename... Args>+	bool inner_enqueue(Args&&... args) AE_NO_TSAN+#else+	template<AllocationMode canAlloc, typename U>+	bool inner_enqueue(U&& element) AE_NO_TSAN+#endif+	{+#ifndef NDEBUG+		ReentrantGuard guard(this->enqueuing);+#endif++		// High-level pseudocode (assuming we're allowed to alloc a new block):+		// If room in tail block, add to tail+		// Else check next block+		//     If next block is not the head block, enqueue on next block+		//     Else create a new block and enqueue there+		//     Advance tail to the block we just enqueued to++		Block* tailBlock_ = tailBlock.load();+		size_t blockFront = tailBlock_->localFront;+		size_t blockTail = tailBlock_->tail.load();++		size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask;+		if (nextBlockTail != blockFront || nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) {+			fence(memory_order_acquire);+			// This block has room for at least one more element+			char* location = tailBlock_->data + blockTail * sizeof(T);+#if MOODYCAMEL_HAS_EMPLACE+			new (location) T(std::forward<Args>(args)...);+#else+			new (location) T(std::forward<U>(element));+#endif++			fence(memory_order_release);+			tailBlock_->tail = nextBlockTail;+		}+		else {+			fence(memory_order_acquire);+			if (tailBlock_->next.load() != frontBlock) {+				// Note that the reason we can't advance to the frontBlock and start adding new entries there+				// is because if we did, then dequeue would stay in that block, eventually reading the new values,+				// instead of advancing to the next full block (whose values were enqueued first and so should be+				// consumed first).++				fence(memory_order_acquire);		// Ensure we get latest writes if we got the latest frontBlock++				// tailBlock is full, but there's a free block ahead, use it+				Block* tailBlockNext = tailBlock_->next.load();+				size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load();+				nextBlockTail = tailBlockNext->tail.load();+				fence(memory_order_acquire);++				// This block must be empty since it's not the head block and we+				// go through the blocks in a circle+				assert(nextBlockFront == nextBlockTail);+				tailBlockNext->localFront = nextBlockFront;++				char* location = tailBlockNext->data + nextBlockTail * sizeof(T);+#if MOODYCAMEL_HAS_EMPLACE+				new (location) T(std::forward<Args>(args)...);+#else+				new (location) T(std::forward<U>(element));+#endif++				tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask;++				fence(memory_order_release);+				tailBlock = tailBlockNext;+			}+			else if (canAlloc == CanAlloc) {+				// tailBlock is full and there's no free block ahead; create a new block+				auto newBlockSize = largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2;+				auto newBlock = make_block(newBlockSize);+				if (newBlock == nullptr) {+					// Could not allocate a block!+					return false;+				}+				largestBlockSize = newBlockSize;++#if MOODYCAMEL_HAS_EMPLACE+				new (newBlock->data) T(std::forward<Args>(args)...);+#else+				new (newBlock->data) T(std::forward<U>(element));+#endif+				assert(newBlock->front == 0);+				newBlock->tail = newBlock->localTail = 1;++				newBlock->next = tailBlock_->next.load();+				tailBlock_->next = newBlock;++				// Might be possible for the dequeue thread to see the new tailBlock->next+				// *without* seeing the new tailBlock value, but this is OK since it can't+				// advance to the next block until tailBlock is set anyway (because the only+				// case where it could try to read the next is if it's already at the tailBlock,+				// and it won't advance past tailBlock in any circumstance).++				fence(memory_order_release);+				tailBlock = newBlock;+			}+			else if (canAlloc == CannotAlloc) {+				// Would have had to allocate a new block to enqueue, but not allowed+				return false;+			}+			else {+				assert(false && "Should be unreachable code");+				return false;+			}+		}++		return true;+	}+++	// Disable copying+	ReaderWriterQueue(ReaderWriterQueue const&) {  }++	// Disable assignment+	ReaderWriterQueue& operator=(ReaderWriterQueue const&) {  }+++	AE_FORCEINLINE static size_t ceilToPow2(size_t x)+	{+		// From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2+		--x;+		x |= x >> 1;+		x |= x >> 2;+		x |= x >> 4;+		for (size_t i = 1; i < sizeof(size_t); i <<= 1) {+			x |= x >> (i << 3);+		}+		++x;+		return x;+	}+	+	template<typename U>+	static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN+	{+		const std::size_t alignment = std::alignment_of<U>::value;+		return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;+	}+private:+#ifndef NDEBUG+	struct ReentrantGuard+	{+		AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection)+			: inSection(_inSection)+		{+			assert(!inSection && "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one thread at a time may hold the producer or consumer role)");+			inSection = true;+		}++		AE_NO_TSAN ~ReentrantGuard() { inSection = false; }++	private:+		ReentrantGuard& operator=(ReentrantGuard const&);++	private:+		weak_atomic<bool>& inSection;+	};+#endif++	struct Block+	{+		// Avoid false-sharing by putting highly contended variables on their own cache lines+		weak_atomic<size_t> front;	// (Atomic) Elements are read from here+		size_t localTail;			// An uncontended shadow copy of tail, owned by the consumer+		+		char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - sizeof(size_t)];+		weak_atomic<size_t> tail;	// (Atomic) Elements are enqueued here+		size_t localFront;+		+		char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - sizeof(size_t)];	// next isn't very contended, but we don't want it on the same cache line as tail (which is)+		weak_atomic<Block*> next;	// (Atomic)+		+		char* data;		// Contents (on heap) are aligned to T's alignment++		const size_t sizeMask;+++		// size must be a power of two (and greater than 0)+		AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data)+			: front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), sizeMask(_size - 1), rawThis(_rawThis)+		{+		}++	private:+		// C4512 - Assignment operator could not be generated+		Block& operator=(Block const&);++	public:+		char* rawThis;+	};+	+	+	static Block* make_block(size_t capacity) AE_NO_TSAN+	{+		// Allocate enough memory for the block itself, as well as all the elements it will contain+		auto size = sizeof(Block) + std::alignment_of<Block>::value - 1;+		size += sizeof(T) * capacity + std::alignment_of<T>::value - 1;+		auto newBlockRaw = static_cast<char*>(std::malloc(size));+		if (newBlockRaw == nullptr) {+			return nullptr;+		}+		+		auto newBlockAligned = align_for<Block>(newBlockRaw);+		auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block));+		return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData);+	}++private:+	weak_atomic<Block*> frontBlock;		// (Atomic) Elements are dequeued from this block+	+	char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)];+	weak_atomic<Block*> tailBlock;		// (Atomic) Elements are enqueued to this block++	size_t largestBlockSize;++#ifndef NDEBUG+	weak_atomic<bool> enqueuing;+	mutable weak_atomic<bool> dequeuing;+#endif+};++// Like ReaderWriterQueue, but also providees blocking operations+template<typename T, size_t MAX_BLOCK_SIZE = 512>+class BlockingReaderWriterQueue+{+private:+	typedef ::moodycamel::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue;+	+public:+	explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN+		: inner(size), sema(new spsc_sema::LightweightSemaphore())+	{ }++	BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN+		: inner(std::move(other.inner)), sema(std::move(other.sema))+	{ }++	BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN+	{+		std::swap(sema, other.sema);+		std::swap(inner, other.inner);+		return *this;+	}+++	// Enqueues a copy of element if there is room in the queue.+	// Returns true if the element was enqueued, false otherwise.+	// Does not allocate memory.+	AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN+	{+		if (inner.try_enqueue(element)) {+			sema->signal();+			return true;+		}+		return false;+	}++	// Enqueues a moved copy of element if there is room in the queue.+	// Returns true if the element was enqueued, false otherwise.+	// Does not allocate memory.+	AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN+	{+		if (inner.try_enqueue(std::forward<T>(element))) {+			sema->signal();+			return true;+		}+		return false;+	}++#if MOODYCAMEL_HAS_EMPLACE+	// Like try_enqueue() but with emplace semantics (i.e. construct-in-place).+	template<typename... Args>+	AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN+	{+		if (inner.try_emplace(std::forward<Args>(args)...)) {+			sema->signal();+			return true;+		}+		return false;+	}+#endif+++	// Enqueues a copy of element on the queue.+	// Allocates an additional block of memory if needed.+	// Only fails (returns false) if memory allocation fails.+	AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN+	{+		if (inner.enqueue(element)) {+			sema->signal();+			return true;+		}+		return false;+	}++	// Enqueues a moved copy of element on the queue.+	// Allocates an additional block of memory if needed.+	// Only fails (returns false) if memory allocation fails.+	AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN+	{+		if (inner.enqueue(std::forward<T>(element))) {+			sema->signal();+			return true;+		}+		return false;+	}++#if MOODYCAMEL_HAS_EMPLACE+	// Like enqueue() but with emplace semantics (i.e. construct-in-place).+	template<typename... Args>+	AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN+	{+		if (inner.emplace(std::forward<Args>(args)...)) {+			sema->signal();+			return true;+		}+		return false;+	}+#endif+++	// Attempts to dequeue an element; if the queue is empty,+	// returns false instead. If the queue has at least one element,+	// moves front to result using operator=, then returns true.+	template<typename U>+	bool try_dequeue(U& result) AE_NO_TSAN+	{+		if (sema->tryWait()) {+			bool success = inner.try_dequeue(result);+			assert(success);+			AE_UNUSED(success);+			return true;+		}+		return false;+	}+	+	+	// Attempts to dequeue an element; if the queue is empty,+	// waits until an element is available, then dequeues it.+	template<typename U>+	void wait_dequeue(U& result) AE_NO_TSAN+	{+		while (!sema->wait());+		bool success = inner.try_dequeue(result);+		AE_UNUSED(result);+		assert(success);+		AE_UNUSED(success);+	}+++	// Attempts to dequeue an element; if the queue is empty,+	// waits until an element is available up to the specified timeout,+	// then dequeues it and returns true, or returns false if the timeout+	// expires before an element can be dequeued.+	// Using a negative timeout indicates an indefinite timeout,+	// and is thus functionally equivalent to calling wait_dequeue.+	template<typename U>+	bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN+	{+		if (!sema->wait(timeout_usecs)) {+			return false;+		}+		bool success = inner.try_dequeue(result);+		AE_UNUSED(result);+		assert(success);+		AE_UNUSED(success);+		return true;+	}+++#if __cplusplus > 199711L || _MSC_VER >= 1700+	// Attempts to dequeue an element; if the queue is empty,+	// waits until an element is available up to the specified timeout,+	// then dequeues it and returns true, or returns false if the timeout+	// expires before an element can be dequeued.+	// Using a negative timeout indicates an indefinite timeout,+	// and is thus functionally equivalent to calling wait_dequeue.+	template<typename U, typename Rep, typename Period>+	inline bool wait_dequeue_timed(U& result, std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN+	{+        return wait_dequeue_timed(result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());+	}+#endif+++	// Returns a pointer to the front element in the queue (the one that+	// would be removed next by a call to `try_dequeue` or `pop`). If the+	// queue appears empty at the time the method is called, nullptr is+	// returned instead.+	// Must be called only from the consumer thread.+	AE_FORCEINLINE T* peek() const AE_NO_TSAN+	{+		return inner.peek();+	}+	+	// Removes the front element from the queue, if any, without returning it.+	// Returns true on success, or false if the queue appeared empty at the time+	// `pop` was called.+	AE_FORCEINLINE bool pop() AE_NO_TSAN+	{+		if (sema->tryWait()) {+			bool result = inner.pop();+			assert(result);+			AE_UNUSED(result);+			return true;+		}+		return false;+	}+	+	// Returns the approximate number of items currently in the queue.+	// Safe to call from both the producer and consumer threads.+	AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN+	{+		return sema->availableApprox();+	}++	// Returns the total number of items that could be enqueued without incurring+	// an allocation when this queue is empty.+	// Safe to call from both the producer and consumer threads.+	//+	// NOTE: The actual capacity during usage may be different depending on the consumer.+	//       If the consumer is removing elements concurrently, the producer cannot add to+	//       the block the consumer is removing from until it's completely empty, except in+	//       the case where the producer was writing to the same block the consumer was+	//       reading from the whole time.+	AE_FORCEINLINE size_t max_capacity() const {+		return inner.max_capacity();+	}++private:+	// Disable copying & assignment+	BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) {  }+	BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) {  }+	+private:+	ReaderWriterQueue inner;+	std::unique_ptr<spsc_sema::LightweightSemaphore> sema;+};++}    // end namespace moodycamel++#ifdef AE_VCPP+#pragma warning(pop)+#endif