diff --git a/libremidi.cabal b/libremidi.cabal
--- a/libremidi.cabal
+++ b/libremidi.cabal
@@ -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
diff --git a/vendor/atomicops.h b/vendor/atomicops.h
new file mode 100644
--- /dev/null
+++ b/vendor/atomicops.h
@@ -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
diff --git a/vendor/libremidi/api.hpp b/vendor/libremidi/api.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/api.hpp
@@ -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
+}
+}
+}
diff --git a/vendor/libremidi/backends.hpp b/vendor/libremidi/backends.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends.hpp
@@ -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);
+}
+}
+}
diff --git a/vendor/libremidi/backends/alsa_raw.hpp b/vendor/libremidi/backends/alsa_raw.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw.hpp
@@ -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;
+  }
+};
+}
diff --git a/vendor/libremidi/backends/alsa_raw/config.hpp b/vendor/libremidi/backends/alsa_raw/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw/config.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/alsa_raw/error_domain.hpp b/vendor/libremidi/backends/alsa_raw/error_domain.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw/error_domain.hpp
diff --git a/vendor/libremidi/backends/alsa_raw/helpers.hpp b/vendor/libremidi/backends/alsa_raw/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw/helpers.hpp
@@ -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);
+      }
+    }
+  }
+};
+}
+}
diff --git a/vendor/libremidi/backends/alsa_raw/midi_in.hpp b/vendor/libremidi/backends/alsa_raw/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw/midi_in.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/backends/alsa_raw/midi_out.hpp b/vendor/libremidi/backends/alsa_raw/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw/midi_out.hpp
@@ -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_{};
+};
+}
diff --git a/vendor/libremidi/backends/alsa_raw/observer.hpp b/vendor/libremidi/backends/alsa_raw/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw/observer.hpp
@@ -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; }
+};
+}
diff --git a/vendor/libremidi/backends/alsa_raw_ump.hpp b/vendor/libremidi/backends/alsa_raw_ump.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw_ump.hpp
@@ -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;
+  }
+};
+}
diff --git a/vendor/libremidi/backends/alsa_raw_ump/config.hpp b/vendor/libremidi/backends/alsa_raw_ump/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw_ump/config.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/alsa_raw_ump/helpers.hpp b/vendor/libremidi/backends/alsa_raw_ump/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw_ump/helpers.hpp
@@ -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));
+  }
+};
+
+}
diff --git a/vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp b/vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp b/vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp
@@ -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_{};
+};
+
+}
diff --git a/vendor/libremidi/backends/alsa_raw_ump/observer.hpp b/vendor/libremidi/backends/alsa_raw_ump/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_raw_ump/observer.hpp
@@ -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; }
+};
+
+}
diff --git a/vendor/libremidi/backends/alsa_seq.hpp b/vendor/libremidi/backends/alsa_seq.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq.hpp
@@ -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;
+  }
+};
+
+}
diff --git a/vendor/libremidi/backends/alsa_seq/config.hpp b/vendor/libremidi/backends/alsa_seq/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq/config.hpp
@@ -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;
+};
+
+}
diff --git a/vendor/libremidi/backends/alsa_seq/helpers.hpp b/vendor/libremidi/backends/alsa_seq/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq/helpers.hpp
@@ -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;
+    }
+  }
+};
+}
diff --git a/vendor/libremidi/backends/alsa_seq/midi_in.hpp b/vendor/libremidi/backends/alsa_seq/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq/midi_in.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/backends/alsa_seq/midi_out.hpp b/vendor/libremidi/backends/alsa_seq/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq/midi_out.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/alsa_seq/observer.hpp b/vendor/libremidi/backends/alsa_seq/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq/observer.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/backends/alsa_seq/shared_handler.hpp b/vendor/libremidi/backends/alsa_seq/shared_handler.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq/shared_handler.hpp
@@ -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
diff --git a/vendor/libremidi/backends/alsa_seq_ump.hpp b/vendor/libremidi/backends/alsa_seq_ump.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq_ump.hpp
@@ -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;
+  }
+};
+}
diff --git a/vendor/libremidi/backends/alsa_seq_ump/config.hpp b/vendor/libremidi/backends/alsa_seq_ump/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq_ump/config.hpp
@@ -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;
+};
+
+}
diff --git a/vendor/libremidi/backends/alsa_seq_ump/helpers.hpp b/vendor/libremidi/backends/alsa_seq_ump/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq_ump/helpers.hpp
diff --git a/vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp b/vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp
@@ -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{};
+  }
+};
+}
diff --git a/vendor/libremidi/backends/coremidi.hpp b/vendor/libremidi/backends/coremidi.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi.hpp
@@ -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; }
+};
+}
diff --git a/vendor/libremidi/backends/coremidi/config.hpp b/vendor/libremidi/backends/coremidi/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi/config.hpp
@@ -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{};
+};
+
+}
diff --git a/vendor/libremidi/backends/coremidi/error_domain.hpp b/vendor/libremidi/backends/coremidi/error_domain.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi/error_domain.hpp
@@ -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};
+}
+}
diff --git a/vendor/libremidi/backends/coremidi/helpers.hpp b/vendor/libremidi/backends/coremidi/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi/helpers.hpp
@@ -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{};
+  }
+};
+
+}
diff --git a/vendor/libremidi/backends/coremidi/midi_in.hpp b/vendor/libremidi/backends/coremidi/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi/midi_in.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/coremidi/midi_out.hpp b/vendor/libremidi/backends/coremidi/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi/midi_out.hpp
@@ -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{};
+};
+}
diff --git a/vendor/libremidi/backends/coremidi/observer.hpp b/vendor/libremidi/backends/coremidi/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi/observer.hpp
@@ -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{};
+};
+}
diff --git a/vendor/libremidi/backends/coremidi_ump.hpp b/vendor/libremidi/backends/coremidi_ump.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi_ump.hpp
@@ -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? */ }
+};
+}
diff --git a/vendor/libremidi/backends/coremidi_ump/config.hpp b/vendor/libremidi/backends/coremidi_ump/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi_ump/config.hpp
@@ -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{};
+};
+}
diff --git a/vendor/libremidi/backends/coremidi_ump/helpers.hpp b/vendor/libremidi/backends/coremidi_ump/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi_ump/helpers.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include <libremidi/backends/coremidi/helpers.hpp>
diff --git a/vendor/libremidi/backends/coremidi_ump/midi_in.hpp b/vendor/libremidi/backends/coremidi_ump/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi_ump/midi_in.hpp
@@ -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};
+};
+
+}
diff --git a/vendor/libremidi/backends/coremidi_ump/midi_out.hpp b/vendor/libremidi/backends/coremidi_ump/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi_ump/midi_out.hpp
@@ -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];
+};
+}
diff --git a/vendor/libremidi/backends/coremidi_ump/observer.hpp b/vendor/libremidi/backends/coremidi_ump/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/coremidi_ump/observer.hpp
@@ -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; }
+};
+
+}
diff --git a/vendor/libremidi/backends/dummy.hpp b/vendor/libremidi/backends/dummy.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/dummy.hpp
@@ -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; }
+};
+}
diff --git a/vendor/libremidi/backends/emscripten.hpp b/vendor/libremidi/backends/emscripten.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/emscripten.hpp
@@ -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
diff --git a/vendor/libremidi/backends/emscripten/config.hpp b/vendor/libremidi/backends/emscripten/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/emscripten/config.hpp
@@ -0,0 +1,17 @@
+#pragma once
+#include <libremidi/config.hpp>
+
+namespace libremidi
+{
+
+struct emscripten_input_configuration
+{
+};
+struct emscripten_output_configuration
+{
+};
+struct emscripten_observer_configuration
+{
+};
+
+}
diff --git a/vendor/libremidi/backends/emscripten/helpers.hpp b/vendor/libremidi/backends/emscripten/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/emscripten/helpers.hpp
@@ -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{};
+};
+}
+}
diff --git a/vendor/libremidi/backends/emscripten/midi_access.hpp b/vendor/libremidi/backends/emscripten/midi_access.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/emscripten/midi_access.hpp
@@ -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;
+};
+
+}
+}
diff --git a/vendor/libremidi/backends/emscripten/midi_in.hpp b/vendor/libremidi/backends/emscripten/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/emscripten/midi_in.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/emscripten/midi_out.hpp b/vendor/libremidi/backends/emscripten/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/emscripten/midi_out.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/emscripten/observer.hpp b/vendor/libremidi/backends/emscripten/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/emscripten/observer.hpp
@@ -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;
+};
+}
diff --git a/vendor/libremidi/backends/jack.hpp b/vendor/libremidi/backends/jack.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack.hpp
@@ -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
+  }
+};
+}
diff --git a/vendor/libremidi/backends/jack/config.hpp b/vendor/libremidi/backends/jack/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack/config.hpp
@@ -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{};
+};
+
+}
diff --git a/vendor/libremidi/backends/jack/error_domain.hpp b/vendor/libremidi/backends/jack/error_domain.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack/error_domain.hpp
@@ -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};
+}
+}
diff --git a/vendor/libremidi/backends/jack/helpers.hpp b/vendor/libremidi/backends/jack/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack/helpers.hpp
@@ -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);
+  }
+};
+}
diff --git a/vendor/libremidi/backends/jack/midi_in.hpp b/vendor/libremidi/backends/jack/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack/midi_in.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/jack/midi_out.hpp b/vendor/libremidi/backends/jack/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack/midi_out.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/backends/jack/observer.hpp b/vendor/libremidi/backends/jack/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack/observer.hpp
@@ -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;
+};
+}
diff --git a/vendor/libremidi/backends/jack/shared_handler.hpp b/vendor/libremidi/backends/jack/shared_handler.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/jack/shared_handler.hpp
@@ -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
diff --git a/vendor/libremidi/backends/keyboard.hpp b/vendor/libremidi/backends/keyboard.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/keyboard.hpp
@@ -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; }
+};
+}
diff --git a/vendor/libremidi/backends/keyboard/config.hpp b/vendor/libremidi/backends/keyboard/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/keyboard/config.hpp
@@ -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
+};
+
+}
diff --git a/vendor/libremidi/backends/keyboard/midi_in.hpp b/vendor/libremidi/backends/keyboard/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/keyboard/midi_in.hpp
@@ -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;
+};
+}
diff --git a/vendor/libremidi/backends/linux/alsa.hpp b/vendor/libremidi/backends/linux/alsa.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/linux/alsa.hpp
@@ -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
+}
diff --git a/vendor/libremidi/backends/linux/dylib_loader.hpp b/vendor/libremidi/backends/linux/dylib_loader.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/linux/dylib_loader.hpp
@@ -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
diff --git a/vendor/libremidi/backends/linux/helpers.hpp b/vendor/libremidi/backends/linux/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/linux/helpers.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/linux/pipewire.hpp b/vendor/libremidi/backends/linux/pipewire.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/linux/pipewire.hpp
@@ -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
+}
diff --git a/vendor/libremidi/backends/linux/udev.hpp b/vendor/libremidi/backends/linux/udev.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/linux/udev.hpp
@@ -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{};
+};
+
+}
diff --git a/vendor/libremidi/backends/net/config.hpp b/vendor/libremidi/backends/net/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/net/config.hpp
@@ -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{};
+};
+
+}
diff --git a/vendor/libremidi/backends/net/helpers.hpp b/vendor/libremidi/backends/net/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/net/helpers.hpp
@@ -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{};
+};
+}
diff --git a/vendor/libremidi/backends/net/midi_in.hpp b/vendor/libremidi/backends/net/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/net/midi_in.hpp
@@ -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];
+};
+}
diff --git a/vendor/libremidi/backends/net/midi_out.hpp b/vendor/libremidi/backends/net/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/net/midi_out.hpp
@@ -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;
+};
+
+}
diff --git a/vendor/libremidi/backends/net/observer.hpp b/vendor/libremidi/backends/net/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/net/observer.hpp
@@ -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;
+}
diff --git a/vendor/libremidi/backends/network.hpp b/vendor/libremidi/backends/network.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/network.hpp
@@ -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; }
+};
+}
diff --git a/vendor/libremidi/backends/network_ump.hpp b/vendor/libremidi/backends/network_ump.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/network_ump.hpp
@@ -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; }
+};
+}
diff --git a/vendor/libremidi/backends/pipewire.hpp b/vendor/libremidi/backends/pipewire.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire.hpp
@@ -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;
+  }
+};
+}
diff --git a/vendor/libremidi/backends/pipewire/config.hpp b/vendor/libremidi/backends/pipewire/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire/config.hpp
@@ -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{};
+};
+
+}
diff --git a/vendor/libremidi/backends/pipewire/context.hpp b/vendor/libremidi/backends/pipewire/context.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire/context.hpp
@@ -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
diff --git a/vendor/libremidi/backends/pipewire/helpers.hpp b/vendor/libremidi/backends/pipewire/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire/helpers.hpp
@@ -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;
+  }
+};
+}
diff --git a/vendor/libremidi/backends/pipewire/midi_in.hpp b/vendor/libremidi/backends/pipewire/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire/midi_in.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/pipewire/midi_out.hpp b/vendor/libremidi/backends/pipewire/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire/midi_out.hpp
@@ -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;
+};
+}
diff --git a/vendor/libremidi/backends/pipewire/observer.hpp b/vendor/libremidi/backends/pipewire/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire/observer.hpp
@@ -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;
+};
+}
diff --git a/vendor/libremidi/backends/pipewire/shared_handler.hpp b/vendor/libremidi/backends/pipewire/shared_handler.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/pipewire/shared_handler.hpp
@@ -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
diff --git a/vendor/libremidi/backends/winmidi.hpp b/vendor/libremidi/backends/winmidi.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmidi.hpp
@@ -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
diff --git a/vendor/libremidi/backends/winmidi/config.hpp b/vendor/libremidi/backends/winmidi/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmidi/config.hpp
@@ -0,0 +1,19 @@
+#pragma once
+#include <libremidi/config.hpp>
+
+namespace libremidi::winmidi
+{
+
+struct input_configuration
+{
+};
+
+struct output_configuration
+{
+};
+
+struct observer_configuration
+{
+};
+
+}
diff --git a/vendor/libremidi/backends/winmidi/helpers.hpp b/vendor/libremidi/backends/winmidi/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmidi/helpers.hpp
@@ -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};
+}
+}
diff --git a/vendor/libremidi/backends/winmidi/midi_in.hpp b/vendor/libremidi/backends/winmidi/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmidi/midi_in.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/winmidi/midi_out.hpp b/vendor/libremidi/backends/winmidi/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmidi/midi_out.hpp
@@ -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};
+};
+
+}
diff --git a/vendor/libremidi/backends/winmidi/observer.hpp b/vendor/libremidi/backends/winmidi/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmidi/observer.hpp
@@ -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;
+};
+
+}
diff --git a/vendor/libremidi/backends/winmm.hpp b/vendor/libremidi/backends/winmm.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmm.hpp
@@ -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; }
+};
+}
diff --git a/vendor/libremidi/backends/winmm/config.hpp b/vendor/libremidi/backends/winmm/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmm/config.hpp
@@ -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;
+};
+
+}
diff --git a/vendor/libremidi/backends/winmm/error_domain.hpp b/vendor/libremidi/backends/winmm/error_domain.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmm/error_domain.hpp
@@ -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};
+}
+
+}
diff --git a/vendor/libremidi/backends/winmm/helpers.hpp b/vendor/libremidi/backends/winmm/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmm/helpers.hpp
@@ -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);
+}
+
+}
diff --git a/vendor/libremidi/backends/winmm/midi_in.hpp b/vendor/libremidi/backends/winmm/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmm/midi_in.hpp
@@ -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};
+};
+
+}
diff --git a/vendor/libremidi/backends/winmm/midi_out.hpp b/vendor/libremidi/backends/winmm/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmm/midi_out.hpp
@@ -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;
+};
+
+}
diff --git a/vendor/libremidi/backends/winmm/observer.hpp b/vendor/libremidi/backends/winmm/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winmm/observer.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/backends/winuwp.hpp b/vendor/libremidi/backends/winuwp.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winuwp.hpp
@@ -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
diff --git a/vendor/libremidi/backends/winuwp/config.hpp b/vendor/libremidi/backends/winuwp/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winuwp/config.hpp
@@ -0,0 +1,19 @@
+#pragma once
+#include <libremidi/config.hpp>
+
+namespace libremidi
+{
+
+struct winuwp_input_configuration
+{
+};
+
+struct winuwp_output_configuration
+{
+};
+
+struct winuwp_observer_configuration
+{
+};
+
+}
diff --git a/vendor/libremidi/backends/winuwp/helpers.hpp b/vendor/libremidi/backends/winuwp/helpers.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winuwp/helpers.hpp
@@ -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();
+}
+}
+}
diff --git a/vendor/libremidi/backends/winuwp/midi_in.hpp b/vendor/libremidi/backends/winuwp/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winuwp/midi_in.hpp
@@ -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};
+};
+}
diff --git a/vendor/libremidi/backends/winuwp/midi_out.hpp b/vendor/libremidi/backends/winuwp/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winuwp/midi_out.hpp
@@ -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};
+};
+
+}
diff --git a/vendor/libremidi/backends/winuwp/observer.hpp b/vendor/libremidi/backends/winuwp/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/backends/winuwp/observer.hpp
@@ -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};
+};
+
+}
diff --git a/vendor/libremidi/client.hpp b/vendor/libremidi/client.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/client.hpp
@@ -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
diff --git a/vendor/libremidi/cmidi2.hpp b/vendor/libremidi/cmidi2.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/cmidi2.hpp
@@ -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 */
diff --git a/vendor/libremidi/config.hpp b/vendor/libremidi/config.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/config.hpp
@@ -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
diff --git a/vendor/libremidi/configurations.hpp b/vendor/libremidi/configurations.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/configurations.hpp
@@ -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
+{
+};
+}
diff --git a/vendor/libremidi/defaults.hpp b/vendor/libremidi/defaults.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/defaults.hpp
@@ -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());
+}
+}
+}
diff --git a/vendor/libremidi/detail/conversion.hpp b/vendor/libremidi/detail/conversion.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/conversion.hpp
@@ -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];
+};
+
+}
diff --git a/vendor/libremidi/detail/memory.hpp b/vendor/libremidi/detail/memory.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/memory.hpp
@@ -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;
+  }
+}
+}
diff --git a/vendor/libremidi/detail/midi_api.hpp b/vendor/libremidi/detail/midi_api.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/midi_api.hpp
@@ -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_{};
+};
+}
diff --git a/vendor/libremidi/detail/midi_in.hpp b/vendor/libremidi/detail/midi_in.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/midi_in.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/detail/midi_out.hpp b/vendor/libremidi/detail/midi_out.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/midi_out.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/detail/midi_stream_decoder.hpp b/vendor/libremidi/detail/midi_stream_decoder.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/midi_stream_decoder.hpp
@@ -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));
+  }
+};
+}
+}
diff --git a/vendor/libremidi/detail/observer.hpp b/vendor/libremidi/detail/observer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/observer.hpp
@@ -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));
+}
+}
diff --git a/vendor/libremidi/detail/semaphore.hpp b/vendor/libremidi/detail/semaphore.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/semaphore.hpp
@@ -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();
+  }
+};
diff --git a/vendor/libremidi/detail/ump_stream.hpp b/vendor/libremidi/detail/ump_stream.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/detail/ump_stream.hpp
@@ -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{};
+}
+
+}
diff --git a/vendor/libremidi/error.hpp b/vendor/libremidi/error.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/error.hpp
@@ -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
diff --git a/vendor/libremidi/error_handler.hpp b/vendor/libremidi/error_handler.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/error_handler.hpp
@@ -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())
+}
diff --git a/vendor/libremidi/input_configuration.hpp b/vendor/libremidi/input_configuration.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/input_configuration.hpp
@@ -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;
+};
+}
diff --git a/vendor/libremidi/libremidi.hpp b/vendor/libremidi/libremidi.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/libremidi.hpp
@@ -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
diff --git a/vendor/libremidi/message.hpp b/vendor/libremidi/message.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/message.hpp
@@ -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;
+}
diff --git a/vendor/libremidi/observer_configuration.hpp b/vendor/libremidi/observer_configuration.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/observer_configuration.hpp
@@ -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;
+  }
+};
+}
diff --git a/vendor/libremidi/output_configuration.hpp b/vendor/libremidi/output_configuration.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/output_configuration.hpp
@@ -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;
+};
+}
diff --git a/vendor/libremidi/reader.hpp b/vendor/libremidi/reader.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/reader.hpp
@@ -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
diff --git a/vendor/libremidi/shared_context.hpp b/vendor/libremidi/shared_context.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/shared_context.hpp
@@ -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);
+
+}
diff --git a/vendor/libremidi/system_error2.hpp b/vendor/libremidi/system_error2.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/system_error2.hpp
@@ -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
+
+
+
diff --git a/vendor/libremidi/ump.hpp b/vendor/libremidi/ump.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/ump.hpp
@@ -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(); }
+};
+}
diff --git a/vendor/libremidi/writer.hpp b/vendor/libremidi/writer.hpp
new file mode 100644
--- /dev/null
+++ b/vendor/libremidi/writer.hpp
@@ -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
diff --git a/vendor/readerwriterqueue.h b/vendor/readerwriterqueue.h
new file mode 100644
--- /dev/null
+++ b/vendor/readerwriterqueue.h
@@ -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
