libremidi 0.3.0 → 0.4.0
raw patch · 126 files changed
+24259/−1 lines, 126 files
Files
- libremidi.cabal +376/−1
- vendor/atomicops.h +772/−0
- vendor/libremidi/api.hpp +95/−0
- vendor/libremidi/backends.hpp +250/−0
- vendor/libremidi/backends/alsa_raw.hpp +31/−0
- vendor/libremidi/backends/alsa_raw/config.hpp +80/−0
- vendor/libremidi/backends/alsa_raw/error_domain.hpp +0/−0
- vendor/libremidi/backends/alsa_raw/helpers.hpp +296/−0
- vendor/libremidi/backends/alsa_raw/midi_in.hpp +356/−0
- vendor/libremidi/backends/alsa_raw/midi_out.hpp +169/−0
- vendor/libremidi/backends/alsa_raw/observer.hpp +240/−0
- vendor/libremidi/backends/alsa_raw_ump.hpp +30/−0
- vendor/libremidi/backends/alsa_raw_ump/config.hpp +25/−0
- vendor/libremidi/backends/alsa_raw_ump/helpers.hpp +66/−0
- vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp +349/−0
- vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp +94/−0
- vendor/libremidi/backends/alsa_raw_ump/observer.hpp +14/−0
- vendor/libremidi/backends/alsa_seq.hpp +42/−0
- vendor/libremidi/backends/alsa_seq/config.hpp +65/−0
- vendor/libremidi/backends/alsa_seq/helpers.hpp +308/−0
- vendor/libremidi/backends/alsa_seq/midi_in.hpp +549/−0
- vendor/libremidi/backends/alsa_seq/midi_out.hpp +179/−0
- vendor/libremidi/backends/alsa_seq/observer.hpp +355/−0
- vendor/libremidi/backends/alsa_seq/shared_handler.hpp +197/−0
- vendor/libremidi/backends/alsa_seq_ump.hpp +62/−0
- vendor/libremidi/backends/alsa_seq_ump/config.hpp +55/−0
- vendor/libremidi/backends/alsa_seq_ump/helpers.hpp +0/−0
- vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp +144/−0
- vendor/libremidi/backends/coremidi.hpp +22/−0
- vendor/libremidi/backends/coremidi/config.hpp +42/−0
- vendor/libremidi/backends/coremidi/error_domain.hpp +72/−0
- vendor/libremidi/backends/coremidi/helpers.hpp +351/−0
- vendor/libremidi/backends/coremidi/midi_in.hpp +154/−0
- vendor/libremidi/backends/coremidi/midi_out.hpp +178/−0
- vendor/libremidi/backends/coremidi/observer.hpp +175/−0
- vendor/libremidi/backends/coremidi_ump.hpp +22/−0
- vendor/libremidi/backends/coremidi_ump/config.hpp +23/−0
- vendor/libremidi/backends/coremidi_ump/helpers.hpp +2/−0
- vendor/libremidi/backends/coremidi_ump/midi_in.hpp +152/−0
- vendor/libremidi/backends/coremidi_ump/midi_out.hpp +159/−0
- vendor/libremidi/backends/coremidi_ump/observer.hpp +20/−0
- vendor/libremidi/backends/dummy.hpp +105/−0
- vendor/libremidi/backends/emscripten.hpp +30/−0
- vendor/libremidi/backends/emscripten/config.hpp +17/−0
- vendor/libremidi/backends/emscripten/helpers.hpp +17/−0
- vendor/libremidi/backends/emscripten/midi_access.hpp +303/−0
- vendor/libremidi/backends/emscripten/midi_in.hpp +38/−0
- vendor/libremidi/backends/emscripten/midi_out.hpp +33/−0
- vendor/libremidi/backends/emscripten/observer.hpp +37/−0
- vendor/libremidi/backends/jack.hpp +37/−0
- vendor/libremidi/backends/jack/config.hpp +49/−0
- vendor/libremidi/backends/jack/error_domain.hpp +78/−0
- vendor/libremidi/backends/jack/helpers.hpp +241/−0
- vendor/libremidi/backends/jack/midi_in.hpp +117/−0
- vendor/libremidi/backends/jack/midi_out.hpp +244/−0
- vendor/libremidi/backends/jack/observer.hpp +224/−0
- vendor/libremidi/backends/jack/shared_handler.hpp +153/−0
- vendor/libremidi/backends/keyboard.hpp +21/−0
- vendor/libremidi/backends/keyboard/config.hpp +92/−0
- vendor/libremidi/backends/keyboard/midi_in.hpp +112/−0
- vendor/libremidi/backends/linux/alsa.hpp +459/−0
- vendor/libremidi/backends/linux/dylib_loader.hpp +85/−0
- vendor/libremidi/backends/linux/helpers.hpp +77/−0
- vendor/libremidi/backends/linux/pipewire.hpp +169/−0
- vendor/libremidi/backends/linux/udev.hpp +92/−0
- vendor/libremidi/backends/net/config.hpp +86/−0
- vendor/libremidi/backends/net/helpers.hpp +91/−0
- vendor/libremidi/backends/net/midi_in.hpp +440/−0
- vendor/libremidi/backends/net/midi_out.hpp +288/−0
- vendor/libremidi/backends/net/observer.hpp +14/−0
- vendor/libremidi/backends/network.hpp +24/−0
- vendor/libremidi/backends/network_ump.hpp +23/−0
- vendor/libremidi/backends/pipewire.hpp +30/−0
- vendor/libremidi/backends/pipewire/config.hpp +53/−0
- vendor/libremidi/backends/pipewire/context.hpp +612/−0
- vendor/libremidi/backends/pipewire/helpers.hpp +473/−0
- vendor/libremidi/backends/pipewire/midi_in.hpp +131/−0
- vendor/libremidi/backends/pipewire/midi_out.hpp +188/−0
- vendor/libremidi/backends/pipewire/observer.hpp +85/−0
- vendor/libremidi/backends/pipewire/shared_handler.hpp +153/−0
- vendor/libremidi/backends/winmidi.hpp +22/−0
- vendor/libremidi/backends/winmidi/config.hpp +19/−0
- vendor/libremidi/backends/winmidi/helpers.hpp +60/−0
- vendor/libremidi/backends/winmidi/midi_in.hpp +97/−0
- vendor/libremidi/backends/winmidi/midi_out.hpp +89/−0
- vendor/libremidi/backends/winmidi/observer.hpp +148/−0
- vendor/libremidi/backends/winmm.hpp +36/−0
- vendor/libremidi/backends/winmm/config.hpp +31/−0
- vendor/libremidi/backends/winmm/error_domain.hpp +70/−0
- vendor/libremidi/backends/winmm/helpers.hpp +74/−0
- vendor/libremidi/backends/winmm/midi_in.hpp +292/−0
- vendor/libremidi/backends/winmm/midi_out.hpp +164/−0
- vendor/libremidi/backends/winmm/observer.hpp +271/−0
- vendor/libremidi/backends/winuwp.hpp +22/−0
- vendor/libremidi/backends/winuwp/config.hpp +19/−0
- vendor/libremidi/backends/winuwp/helpers.hpp +63/−0
- vendor/libremidi/backends/winuwp/midi_in.hpp +97/−0
- vendor/libremidi/backends/winuwp/midi_out.hpp +77/−0
- vendor/libremidi/backends/winuwp/observer.hpp +296/−0
- vendor/libremidi/client.hpp +204/−0
- vendor/libremidi/cmidi2.hpp +3266/−0
- vendor/libremidi/config.hpp +64/−0
- vendor/libremidi/configurations.hpp +24/−0
- vendor/libremidi/defaults.hpp +98/−0
- vendor/libremidi/detail/conversion.hpp +59/−0
- vendor/libremidi/detail/memory.hpp +40/−0
- vendor/libremidi/detail/midi_api.hpp +48/−0
- vendor/libremidi/detail/midi_in.hpp +56/−0
- vendor/libremidi/detail/midi_out.hpp +89/−0
- vendor/libremidi/detail/midi_stream_decoder.hpp +465/−0
- vendor/libremidi/detail/observer.hpp +27/−0
- vendor/libremidi/detail/semaphore.hpp +24/−0
- vendor/libremidi/detail/ump_stream.hpp +62/−0
- vendor/libremidi/error.hpp +64/−0
- vendor/libremidi/error_handler.hpp +63/−0
- vendor/libremidi/input_configuration.hpp +127/−0
- vendor/libremidi/libremidi.hpp +279/−0
- vendor/libremidi/message.hpp +301/−0
- vendor/libremidi/observer_configuration.hpp +86/−0
- vendor/libremidi/output_configuration.hpp +21/−0
- vendor/libremidi/reader.hpp +75/−0
- vendor/libremidi/shared_context.hpp +33/−0
- vendor/libremidi/system_error2.hpp +3501/−0
- vendor/libremidi/ump.hpp +113/−0
- vendor/libremidi/writer.hpp +52/−0
- vendor/readerwriterqueue.h +979/−0
libremidi.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: libremidi-version: 0.3.0+version: 0.4.0 synopsis: libremidi bindings for haskell description: Please see the README on GitHub at <https://github.com/ejconlon/libremidi-haskell#readme> homepage: https://github.com/ejconlon/libremidi-haskell#readme@@ -67,8 +67,133 @@ include-dirs: vendor install-includes:+ vendor/atomicops.h vendor/libremidi/api-c.h vendor/libremidi/libremidi-c.h+ vendor/readerwriterqueue.h+ vendor/libremidi/api.hpp+ vendor/libremidi/backends.hpp+ vendor/libremidi/backends/alsa_raw.hpp+ vendor/libremidi/backends/alsa_raw/config.hpp+ vendor/libremidi/backends/alsa_raw/error_domain.hpp+ vendor/libremidi/backends/alsa_raw/helpers.hpp+ vendor/libremidi/backends/alsa_raw/midi_in.hpp+ vendor/libremidi/backends/alsa_raw/midi_out.hpp+ vendor/libremidi/backends/alsa_raw/observer.hpp+ vendor/libremidi/backends/alsa_raw_ump.hpp+ vendor/libremidi/backends/alsa_raw_ump/config.hpp+ vendor/libremidi/backends/alsa_raw_ump/helpers.hpp+ vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp+ vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp+ vendor/libremidi/backends/alsa_raw_ump/observer.hpp+ vendor/libremidi/backends/alsa_seq.hpp+ vendor/libremidi/backends/alsa_seq/config.hpp+ vendor/libremidi/backends/alsa_seq/helpers.hpp+ vendor/libremidi/backends/alsa_seq/midi_in.hpp+ vendor/libremidi/backends/alsa_seq/midi_out.hpp+ vendor/libremidi/backends/alsa_seq/observer.hpp+ vendor/libremidi/backends/alsa_seq/shared_handler.hpp+ vendor/libremidi/backends/alsa_seq_ump.hpp+ vendor/libremidi/backends/alsa_seq_ump/config.hpp+ vendor/libremidi/backends/alsa_seq_ump/helpers.hpp+ vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp+ vendor/libremidi/backends/coremidi.hpp+ vendor/libremidi/backends/coremidi/config.hpp+ vendor/libremidi/backends/coremidi/error_domain.hpp+ vendor/libremidi/backends/coremidi/helpers.hpp+ vendor/libremidi/backends/coremidi/midi_in.hpp+ vendor/libremidi/backends/coremidi/midi_out.hpp+ vendor/libremidi/backends/coremidi/observer.hpp+ vendor/libremidi/backends/coremidi_ump.hpp+ vendor/libremidi/backends/coremidi_ump/config.hpp+ vendor/libremidi/backends/coremidi_ump/helpers.hpp+ vendor/libremidi/backends/coremidi_ump/midi_in.hpp+ vendor/libremidi/backends/coremidi_ump/midi_out.hpp+ vendor/libremidi/backends/coremidi_ump/observer.hpp+ vendor/libremidi/backends/dummy.hpp+ vendor/libremidi/backends/emscripten.hpp+ vendor/libremidi/backends/emscripten/config.hpp+ vendor/libremidi/backends/emscripten/helpers.hpp+ vendor/libremidi/backends/emscripten/midi_access.hpp+ vendor/libremidi/backends/emscripten/midi_in.hpp+ vendor/libremidi/backends/emscripten/midi_out.hpp+ vendor/libremidi/backends/emscripten/observer.hpp+ vendor/libremidi/backends/jack.hpp+ vendor/libremidi/backends/jack/config.hpp+ vendor/libremidi/backends/jack/error_domain.hpp+ vendor/libremidi/backends/jack/helpers.hpp+ vendor/libremidi/backends/jack/midi_in.hpp+ vendor/libremidi/backends/jack/midi_out.hpp+ vendor/libremidi/backends/jack/observer.hpp+ vendor/libremidi/backends/jack/shared_handler.hpp+ vendor/libremidi/backends/keyboard.hpp+ vendor/libremidi/backends/keyboard/config.hpp+ vendor/libremidi/backends/keyboard/midi_in.hpp+ vendor/libremidi/backends/linux/alsa.hpp+ vendor/libremidi/backends/linux/dylib_loader.hpp+ vendor/libremidi/backends/linux/helpers.hpp+ vendor/libremidi/backends/linux/pipewire.hpp+ vendor/libremidi/backends/linux/udev.hpp+ vendor/libremidi/backends/net/config.hpp+ vendor/libremidi/backends/net/helpers.hpp+ vendor/libremidi/backends/net/midi_in.hpp+ vendor/libremidi/backends/net/midi_out.hpp+ vendor/libremidi/backends/net/observer.hpp+ vendor/libremidi/backends/network.hpp+ vendor/libremidi/backends/network_ump.hpp+ vendor/libremidi/backends/pipewire.hpp+ vendor/libremidi/backends/pipewire/config.hpp+ vendor/libremidi/backends/pipewire/context.hpp+ vendor/libremidi/backends/pipewire/helpers.hpp+ vendor/libremidi/backends/pipewire/midi_in.hpp+ vendor/libremidi/backends/pipewire/midi_out.hpp+ vendor/libremidi/backends/pipewire/observer.hpp+ vendor/libremidi/backends/pipewire/shared_handler.hpp+ vendor/libremidi/backends/winmidi.hpp+ vendor/libremidi/backends/winmidi/config.hpp+ vendor/libremidi/backends/winmidi/helpers.hpp+ vendor/libremidi/backends/winmidi/midi_in.hpp+ vendor/libremidi/backends/winmidi/midi_out.hpp+ vendor/libremidi/backends/winmidi/observer.hpp+ vendor/libremidi/backends/winmm.hpp+ vendor/libremidi/backends/winmm/config.hpp+ vendor/libremidi/backends/winmm/error_domain.hpp+ vendor/libremidi/backends/winmm/helpers.hpp+ vendor/libremidi/backends/winmm/midi_in.hpp+ vendor/libremidi/backends/winmm/midi_out.hpp+ vendor/libremidi/backends/winmm/observer.hpp+ vendor/libremidi/backends/winuwp.hpp+ vendor/libremidi/backends/winuwp/config.hpp+ vendor/libremidi/backends/winuwp/helpers.hpp+ vendor/libremidi/backends/winuwp/midi_in.hpp+ vendor/libremidi/backends/winuwp/midi_out.hpp+ vendor/libremidi/backends/winuwp/observer.hpp+ vendor/libremidi/client.hpp+ vendor/libremidi/cmidi2.hpp+ vendor/libremidi/config.hpp+ vendor/libremidi/configurations.hpp+ vendor/libremidi/defaults.hpp+ vendor/libremidi/detail/conversion.hpp+ vendor/libremidi/detail/memory.hpp+ vendor/libremidi/detail/midi_api.hpp+ vendor/libremidi/detail/midi_in.hpp+ vendor/libremidi/detail/midi_out.hpp+ vendor/libremidi/detail/midi_stream_decoder.hpp+ vendor/libremidi/detail/observer.hpp+ vendor/libremidi/detail/semaphore.hpp+ vendor/libremidi/detail/ump_stream.hpp+ vendor/libremidi/error.hpp+ vendor/libremidi/error_handler.hpp+ vendor/libremidi/input_configuration.hpp+ vendor/libremidi/libremidi.hpp+ vendor/libremidi/message.hpp+ vendor/libremidi/observer_configuration.hpp+ vendor/libremidi/output_configuration.hpp+ vendor/libremidi/reader.hpp+ vendor/libremidi/shared_context.hpp+ vendor/libremidi/system_error2.hpp+ vendor/libremidi/ump.hpp+ vendor/libremidi/writer.hpp cxx-sources: vendor/libremidi/client.cpp vendor/libremidi/libremidi-c.cpp@@ -141,8 +266,133 @@ TypeFamilies ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N install-includes:+ vendor/atomicops.h vendor/libremidi/api-c.h vendor/libremidi/libremidi-c.h+ vendor/readerwriterqueue.h+ vendor/libremidi/api.hpp+ vendor/libremidi/backends.hpp+ vendor/libremidi/backends/alsa_raw.hpp+ vendor/libremidi/backends/alsa_raw/config.hpp+ vendor/libremidi/backends/alsa_raw/error_domain.hpp+ vendor/libremidi/backends/alsa_raw/helpers.hpp+ vendor/libremidi/backends/alsa_raw/midi_in.hpp+ vendor/libremidi/backends/alsa_raw/midi_out.hpp+ vendor/libremidi/backends/alsa_raw/observer.hpp+ vendor/libremidi/backends/alsa_raw_ump.hpp+ vendor/libremidi/backends/alsa_raw_ump/config.hpp+ vendor/libremidi/backends/alsa_raw_ump/helpers.hpp+ vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp+ vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp+ vendor/libremidi/backends/alsa_raw_ump/observer.hpp+ vendor/libremidi/backends/alsa_seq.hpp+ vendor/libremidi/backends/alsa_seq/config.hpp+ vendor/libremidi/backends/alsa_seq/helpers.hpp+ vendor/libremidi/backends/alsa_seq/midi_in.hpp+ vendor/libremidi/backends/alsa_seq/midi_out.hpp+ vendor/libremidi/backends/alsa_seq/observer.hpp+ vendor/libremidi/backends/alsa_seq/shared_handler.hpp+ vendor/libremidi/backends/alsa_seq_ump.hpp+ vendor/libremidi/backends/alsa_seq_ump/config.hpp+ vendor/libremidi/backends/alsa_seq_ump/helpers.hpp+ vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp+ vendor/libremidi/backends/coremidi.hpp+ vendor/libremidi/backends/coremidi/config.hpp+ vendor/libremidi/backends/coremidi/error_domain.hpp+ vendor/libremidi/backends/coremidi/helpers.hpp+ vendor/libremidi/backends/coremidi/midi_in.hpp+ vendor/libremidi/backends/coremidi/midi_out.hpp+ vendor/libremidi/backends/coremidi/observer.hpp+ vendor/libremidi/backends/coremidi_ump.hpp+ vendor/libremidi/backends/coremidi_ump/config.hpp+ vendor/libremidi/backends/coremidi_ump/helpers.hpp+ vendor/libremidi/backends/coremidi_ump/midi_in.hpp+ vendor/libremidi/backends/coremidi_ump/midi_out.hpp+ vendor/libremidi/backends/coremidi_ump/observer.hpp+ vendor/libremidi/backends/dummy.hpp+ vendor/libremidi/backends/emscripten.hpp+ vendor/libremidi/backends/emscripten/config.hpp+ vendor/libremidi/backends/emscripten/helpers.hpp+ vendor/libremidi/backends/emscripten/midi_access.hpp+ vendor/libremidi/backends/emscripten/midi_in.hpp+ vendor/libremidi/backends/emscripten/midi_out.hpp+ vendor/libremidi/backends/emscripten/observer.hpp+ vendor/libremidi/backends/jack.hpp+ vendor/libremidi/backends/jack/config.hpp+ vendor/libremidi/backends/jack/error_domain.hpp+ vendor/libremidi/backends/jack/helpers.hpp+ vendor/libremidi/backends/jack/midi_in.hpp+ vendor/libremidi/backends/jack/midi_out.hpp+ vendor/libremidi/backends/jack/observer.hpp+ vendor/libremidi/backends/jack/shared_handler.hpp+ vendor/libremidi/backends/keyboard.hpp+ vendor/libremidi/backends/keyboard/config.hpp+ vendor/libremidi/backends/keyboard/midi_in.hpp+ vendor/libremidi/backends/linux/alsa.hpp+ vendor/libremidi/backends/linux/dylib_loader.hpp+ vendor/libremidi/backends/linux/helpers.hpp+ vendor/libremidi/backends/linux/pipewire.hpp+ vendor/libremidi/backends/linux/udev.hpp+ vendor/libremidi/backends/net/config.hpp+ vendor/libremidi/backends/net/helpers.hpp+ vendor/libremidi/backends/net/midi_in.hpp+ vendor/libremidi/backends/net/midi_out.hpp+ vendor/libremidi/backends/net/observer.hpp+ vendor/libremidi/backends/network.hpp+ vendor/libremidi/backends/network_ump.hpp+ vendor/libremidi/backends/pipewire.hpp+ vendor/libremidi/backends/pipewire/config.hpp+ vendor/libremidi/backends/pipewire/context.hpp+ vendor/libremidi/backends/pipewire/helpers.hpp+ vendor/libremidi/backends/pipewire/midi_in.hpp+ vendor/libremidi/backends/pipewire/midi_out.hpp+ vendor/libremidi/backends/pipewire/observer.hpp+ vendor/libremidi/backends/pipewire/shared_handler.hpp+ vendor/libremidi/backends/winmidi.hpp+ vendor/libremidi/backends/winmidi/config.hpp+ vendor/libremidi/backends/winmidi/helpers.hpp+ vendor/libremidi/backends/winmidi/midi_in.hpp+ vendor/libremidi/backends/winmidi/midi_out.hpp+ vendor/libremidi/backends/winmidi/observer.hpp+ vendor/libremidi/backends/winmm.hpp+ vendor/libremidi/backends/winmm/config.hpp+ vendor/libremidi/backends/winmm/error_domain.hpp+ vendor/libremidi/backends/winmm/helpers.hpp+ vendor/libremidi/backends/winmm/midi_in.hpp+ vendor/libremidi/backends/winmm/midi_out.hpp+ vendor/libremidi/backends/winmm/observer.hpp+ vendor/libremidi/backends/winuwp.hpp+ vendor/libremidi/backends/winuwp/config.hpp+ vendor/libremidi/backends/winuwp/helpers.hpp+ vendor/libremidi/backends/winuwp/midi_in.hpp+ vendor/libremidi/backends/winuwp/midi_out.hpp+ vendor/libremidi/backends/winuwp/observer.hpp+ vendor/libremidi/client.hpp+ vendor/libremidi/cmidi2.hpp+ vendor/libremidi/config.hpp+ vendor/libremidi/configurations.hpp+ vendor/libremidi/defaults.hpp+ vendor/libremidi/detail/conversion.hpp+ vendor/libremidi/detail/memory.hpp+ vendor/libremidi/detail/midi_api.hpp+ vendor/libremidi/detail/midi_in.hpp+ vendor/libremidi/detail/midi_out.hpp+ vendor/libremidi/detail/midi_stream_decoder.hpp+ vendor/libremidi/detail/observer.hpp+ vendor/libremidi/detail/semaphore.hpp+ vendor/libremidi/detail/ump_stream.hpp+ vendor/libremidi/error.hpp+ vendor/libremidi/error_handler.hpp+ vendor/libremidi/input_configuration.hpp+ vendor/libremidi/libremidi.hpp+ vendor/libremidi/message.hpp+ vendor/libremidi/observer_configuration.hpp+ vendor/libremidi/output_configuration.hpp+ vendor/libremidi/reader.hpp+ vendor/libremidi/shared_context.hpp+ vendor/libremidi/system_error2.hpp+ vendor/libremidi/ump.hpp+ vendor/libremidi/writer.hpp build-depends: base >=4.12 && <5 , containers >=0.6.7 && <0.7@@ -194,8 +444,133 @@ TypeFamilies ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N install-includes:+ vendor/atomicops.h vendor/libremidi/api-c.h vendor/libremidi/libremidi-c.h+ vendor/readerwriterqueue.h+ vendor/libremidi/api.hpp+ vendor/libremidi/backends.hpp+ vendor/libremidi/backends/alsa_raw.hpp+ vendor/libremidi/backends/alsa_raw/config.hpp+ vendor/libremidi/backends/alsa_raw/error_domain.hpp+ vendor/libremidi/backends/alsa_raw/helpers.hpp+ vendor/libremidi/backends/alsa_raw/midi_in.hpp+ vendor/libremidi/backends/alsa_raw/midi_out.hpp+ vendor/libremidi/backends/alsa_raw/observer.hpp+ vendor/libremidi/backends/alsa_raw_ump.hpp+ vendor/libremidi/backends/alsa_raw_ump/config.hpp+ vendor/libremidi/backends/alsa_raw_ump/helpers.hpp+ vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp+ vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp+ vendor/libremidi/backends/alsa_raw_ump/observer.hpp+ vendor/libremidi/backends/alsa_seq.hpp+ vendor/libremidi/backends/alsa_seq/config.hpp+ vendor/libremidi/backends/alsa_seq/helpers.hpp+ vendor/libremidi/backends/alsa_seq/midi_in.hpp+ vendor/libremidi/backends/alsa_seq/midi_out.hpp+ vendor/libremidi/backends/alsa_seq/observer.hpp+ vendor/libremidi/backends/alsa_seq/shared_handler.hpp+ vendor/libremidi/backends/alsa_seq_ump.hpp+ vendor/libremidi/backends/alsa_seq_ump/config.hpp+ vendor/libremidi/backends/alsa_seq_ump/helpers.hpp+ vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp+ vendor/libremidi/backends/coremidi.hpp+ vendor/libremidi/backends/coremidi/config.hpp+ vendor/libremidi/backends/coremidi/error_domain.hpp+ vendor/libremidi/backends/coremidi/helpers.hpp+ vendor/libremidi/backends/coremidi/midi_in.hpp+ vendor/libremidi/backends/coremidi/midi_out.hpp+ vendor/libremidi/backends/coremidi/observer.hpp+ vendor/libremidi/backends/coremidi_ump.hpp+ vendor/libremidi/backends/coremidi_ump/config.hpp+ vendor/libremidi/backends/coremidi_ump/helpers.hpp+ vendor/libremidi/backends/coremidi_ump/midi_in.hpp+ vendor/libremidi/backends/coremidi_ump/midi_out.hpp+ vendor/libremidi/backends/coremidi_ump/observer.hpp+ vendor/libremidi/backends/dummy.hpp+ vendor/libremidi/backends/emscripten.hpp+ vendor/libremidi/backends/emscripten/config.hpp+ vendor/libremidi/backends/emscripten/helpers.hpp+ vendor/libremidi/backends/emscripten/midi_access.hpp+ vendor/libremidi/backends/emscripten/midi_in.hpp+ vendor/libremidi/backends/emscripten/midi_out.hpp+ vendor/libremidi/backends/emscripten/observer.hpp+ vendor/libremidi/backends/jack.hpp+ vendor/libremidi/backends/jack/config.hpp+ vendor/libremidi/backends/jack/error_domain.hpp+ vendor/libremidi/backends/jack/helpers.hpp+ vendor/libremidi/backends/jack/midi_in.hpp+ vendor/libremidi/backends/jack/midi_out.hpp+ vendor/libremidi/backends/jack/observer.hpp+ vendor/libremidi/backends/jack/shared_handler.hpp+ vendor/libremidi/backends/keyboard.hpp+ vendor/libremidi/backends/keyboard/config.hpp+ vendor/libremidi/backends/keyboard/midi_in.hpp+ vendor/libremidi/backends/linux/alsa.hpp+ vendor/libremidi/backends/linux/dylib_loader.hpp+ vendor/libremidi/backends/linux/helpers.hpp+ vendor/libremidi/backends/linux/pipewire.hpp+ vendor/libremidi/backends/linux/udev.hpp+ vendor/libremidi/backends/net/config.hpp+ vendor/libremidi/backends/net/helpers.hpp+ vendor/libremidi/backends/net/midi_in.hpp+ vendor/libremidi/backends/net/midi_out.hpp+ vendor/libremidi/backends/net/observer.hpp+ vendor/libremidi/backends/network.hpp+ vendor/libremidi/backends/network_ump.hpp+ vendor/libremidi/backends/pipewire.hpp+ vendor/libremidi/backends/pipewire/config.hpp+ vendor/libremidi/backends/pipewire/context.hpp+ vendor/libremidi/backends/pipewire/helpers.hpp+ vendor/libremidi/backends/pipewire/midi_in.hpp+ vendor/libremidi/backends/pipewire/midi_out.hpp+ vendor/libremidi/backends/pipewire/observer.hpp+ vendor/libremidi/backends/pipewire/shared_handler.hpp+ vendor/libremidi/backends/winmidi.hpp+ vendor/libremidi/backends/winmidi/config.hpp+ vendor/libremidi/backends/winmidi/helpers.hpp+ vendor/libremidi/backends/winmidi/midi_in.hpp+ vendor/libremidi/backends/winmidi/midi_out.hpp+ vendor/libremidi/backends/winmidi/observer.hpp+ vendor/libremidi/backends/winmm.hpp+ vendor/libremidi/backends/winmm/config.hpp+ vendor/libremidi/backends/winmm/error_domain.hpp+ vendor/libremidi/backends/winmm/helpers.hpp+ vendor/libremidi/backends/winmm/midi_in.hpp+ vendor/libremidi/backends/winmm/midi_out.hpp+ vendor/libremidi/backends/winmm/observer.hpp+ vendor/libremidi/backends/winuwp.hpp+ vendor/libremidi/backends/winuwp/config.hpp+ vendor/libremidi/backends/winuwp/helpers.hpp+ vendor/libremidi/backends/winuwp/midi_in.hpp+ vendor/libremidi/backends/winuwp/midi_out.hpp+ vendor/libremidi/backends/winuwp/observer.hpp+ vendor/libremidi/client.hpp+ vendor/libremidi/cmidi2.hpp+ vendor/libremidi/config.hpp+ vendor/libremidi/configurations.hpp+ vendor/libremidi/defaults.hpp+ vendor/libremidi/detail/conversion.hpp+ vendor/libremidi/detail/memory.hpp+ vendor/libremidi/detail/midi_api.hpp+ vendor/libremidi/detail/midi_in.hpp+ vendor/libremidi/detail/midi_out.hpp+ vendor/libremidi/detail/midi_stream_decoder.hpp+ vendor/libremidi/detail/observer.hpp+ vendor/libremidi/detail/semaphore.hpp+ vendor/libremidi/detail/ump_stream.hpp+ vendor/libremidi/error.hpp+ vendor/libremidi/error_handler.hpp+ vendor/libremidi/input_configuration.hpp+ vendor/libremidi/libremidi.hpp+ vendor/libremidi/message.hpp+ vendor/libremidi/observer_configuration.hpp+ vendor/libremidi/output_configuration.hpp+ vendor/libremidi/reader.hpp+ vendor/libremidi/shared_context.hpp+ vendor/libremidi/system_error2.hpp+ vendor/libremidi/ump.hpp+ vendor/libremidi/writer.hpp build-depends: base >=4.12 && <5 , containers >=0.6.7 && <0.7
+ vendor/atomicops.h view
@@ -0,0 +1,772 @@+// ©2013-2016 Cameron Desrochers.+// Distributed under the simplified BSD license (see the license file that+// should have come with this header).+// Uses Jeff Preshing's semaphore implementation (under the terms of its+// separate zlib license, embedded below).++#pragma once++// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) implementation+// of low-level memory barriers, plus a few semi-portable utility macros (for inlining and alignment).+// Also has a basic atomic type (limited to hardware-supported atomics with no memory ordering guarantees).+// Uses the AE_* prefix for macros (historical reasons), and the "moodycamel" namespace for symbols.++#include <cerrno>+#include <cassert>+#include <type_traits>+#include <cerrno>+#include <cstdint>+#include <ctime>++// Platform detection+#if defined(__INTEL_COMPILER)+#define AE_ICC+#elif defined(_MSC_VER)+#define AE_VCPP+#elif defined(__GNUC__)+#define AE_GCC+#endif++#if defined(_M_IA64) || defined(__ia64__)+#define AE_ARCH_IA64+#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__)+#define AE_ARCH_X64+#elif defined(_M_IX86) || defined(__i386__)+#define AE_ARCH_X86+#elif defined(_M_PPC) || defined(__powerpc__)+#define AE_ARCH_PPC+#else+#define AE_ARCH_UNKNOWN+#endif+++// AE_UNUSED+#define AE_UNUSED(x) ((void)x)++// AE_NO_TSAN/AE_TSAN_ANNOTATE_*+// For GCC+#if defined(__SANITIZE_THREAD__)+#define AE_TSAN_IS_ENABLED+#endif+// For clang+#if defined(__has_feature)+#if __has_feature(thread_sanitizer) && !defined(AE_TSAN_IS_ENABLED)+#define AE_TSAN_IS_ENABLED+#endif+#endif++#ifdef AE_TSAN_IS_ENABLED+#if __cplusplus >= 201703L // inline variables require C++17+namespace moodycamel { inline int ae_tsan_global; }+#define AE_TSAN_ANNOTATE_RELEASE() AnnotateHappensBefore(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))+#define AE_TSAN_ANNOTATE_ACQUIRE() AnnotateHappensAfter(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))+extern "C" void AnnotateHappensBefore(const char*, int, void*);+extern "C" void AnnotateHappensAfter(const char*, int, void*);+#else // when we can't work with tsan, attempt to disable its warnings+#define AE_NO_TSAN __attribute__((no_sanitize("thread")))+#endif+#endif++#ifndef AE_NO_TSAN+#define AE_NO_TSAN+#endif++#ifndef AE_TSAN_ANNOTATE_RELEASE+#define AE_TSAN_ANNOTATE_RELEASE()+#define AE_TSAN_ANNOTATE_ACQUIRE()+#endif+++// AE_FORCEINLINE+#if defined(AE_VCPP) || defined(AE_ICC)+#define AE_FORCEINLINE __forceinline+#elif defined(AE_GCC)+//#define AE_FORCEINLINE __attribute__((always_inline)) +#define AE_FORCEINLINE inline+#else+#define AE_FORCEINLINE inline+#endif+++// AE_ALIGN+#if defined(AE_VCPP) || defined(AE_ICC)+#define AE_ALIGN(x) __declspec(align(x))+#elif defined(AE_GCC)+#define AE_ALIGN(x) __attribute__((aligned(x)))+#else+// Assume GCC compliant syntax...+#define AE_ALIGN(x) __attribute__((aligned(x)))+#endif+++// Portable atomic fences implemented below:++namespace moodycamel {++enum memory_order {+ memory_order_relaxed,+ memory_order_acquire,+ memory_order_release,+ memory_order_acq_rel,+ memory_order_seq_cst,++ // memory_order_sync: Forces a full sync:+ // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad+ memory_order_sync = memory_order_seq_cst+};++} // end namespace moodycamel++#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || (defined(AE_ICC) && __INTEL_COMPILER < 1600)+// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences++#include <intrin.h>++#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)+#define AeFullSync _mm_mfence+#define AeLiteSync _mm_mfence+#elif defined(AE_ARCH_IA64)+#define AeFullSync __mf+#define AeLiteSync __mf+#elif defined(AE_ARCH_PPC)+#include <ppcintrinsics.h>+#define AeFullSync __sync+#define AeLiteSync __lwsync+#endif+++#ifdef AE_VCPP+#pragma warning(push)+#pragma warning(disable: 4365) // Disable erroneous 'conversion from long to unsigned int, signed/unsigned mismatch' error when using `assert`+#ifdef __cplusplus_cli+#pragma managed(push, off)+#endif+#endif++namespace moodycamel {++AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN+{+ switch (order) {+ case memory_order_relaxed: break;+ case memory_order_acquire: _ReadBarrier(); break;+ case memory_order_release: _WriteBarrier(); break;+ case memory_order_acq_rel: _ReadWriteBarrier(); break;+ case memory_order_seq_cst: _ReadWriteBarrier(); break;+ default: assert(false);+ }+}++// x86/x64 have a strong memory model -- all loads and stores have+// acquire and release semantics automatically (so only need compiler+// barriers for those).+#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64)+AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN+{+ switch (order) {+ case memory_order_relaxed: break;+ case memory_order_acquire: _ReadBarrier(); break;+ case memory_order_release: _WriteBarrier(); break;+ case memory_order_acq_rel: _ReadWriteBarrier(); break;+ case memory_order_seq_cst:+ _ReadWriteBarrier();+ AeFullSync();+ _ReadWriteBarrier();+ break;+ default: assert(false);+ }+}+#else+AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN+{+ // Non-specialized arch, use heavier memory barriers everywhere just in case :-(+ switch (order) {+ case memory_order_relaxed:+ break;+ case memory_order_acquire:+ _ReadBarrier();+ AeLiteSync();+ _ReadBarrier();+ break;+ case memory_order_release:+ _WriteBarrier();+ AeLiteSync();+ _WriteBarrier();+ break;+ case memory_order_acq_rel:+ _ReadWriteBarrier();+ AeLiteSync();+ _ReadWriteBarrier();+ break;+ case memory_order_seq_cst:+ _ReadWriteBarrier();+ AeFullSync();+ _ReadWriteBarrier();+ break;+ default: assert(false);+ }+}+#endif+} // end namespace moodycamel+#else+// Use standard library of atomics+#include <atomic>++namespace moodycamel {++AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN+{+ switch (order) {+ case memory_order_relaxed: break;+ case memory_order_acquire: std::atomic_signal_fence(std::memory_order_acquire); break;+ case memory_order_release: std::atomic_signal_fence(std::memory_order_release); break;+ case memory_order_acq_rel: std::atomic_signal_fence(std::memory_order_acq_rel); break;+ case memory_order_seq_cst: std::atomic_signal_fence(std::memory_order_seq_cst); break;+ default: assert(false);+ }+}++AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN+{+ switch (order) {+ case memory_order_relaxed: break;+ case memory_order_acquire: AE_TSAN_ANNOTATE_ACQUIRE(); std::atomic_thread_fence(std::memory_order_acquire); break;+ case memory_order_release: AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_release); break;+ case memory_order_acq_rel: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_acq_rel); break;+ case memory_order_seq_cst: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_seq_cst); break;+ default: assert(false);+ }+}++} // end namespace moodycamel++#endif+++#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli))+#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+#endif++#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+#include <atomic>+#endif+#include <utility>++// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY:+// Provides basic support for atomic variables -- no memory ordering guarantees are provided.+// The guarantee of atomicity is only made for types that already have atomic load and store guarantees+// at the hardware level -- on most platforms this generally means aligned pointers and integers (only).+namespace moodycamel {+template<typename T>+class weak_atomic+{+public:+ AE_NO_TSAN weak_atomic() : value() { }+#ifdef AE_VCPP+#pragma warning(push)+#pragma warning(disable: 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning+#endif+ template<typename U> AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) { }+#ifdef __cplusplus_cli+ // Work around bug with universal reference/nullptr combination that only appears when /clr is on+ AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) { }+#endif+ AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) { }+ AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) { }+#ifdef AE_VCPP+#pragma warning(pop)+#endif++ AE_FORCEINLINE operator T() const AE_NO_TSAN { return load(); }++ +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+ template<typename U> AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward<U>(x); return *this; }+ AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { value = other.value; return *this; }+ + AE_FORCEINLINE T load() const AE_NO_TSAN { return value; }+ + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN+ {+#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)+ if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);+#if defined(_M_AMD64)+ else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);+#endif+#else+#error Unsupported platform+#endif+ assert(false && "T must be either a 32 or 64 bit type");+ return value;+ }+ + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN+ {+#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)+ if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);+#if defined(_M_AMD64)+ else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);+#endif+#else+#error Unsupported platform+#endif+ assert(false && "T must be either a 32 or 64 bit type");+ return value;+ }+#else+ template<typename U>+ AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN+ {+ value.store(std::forward<U>(x), std::memory_order_relaxed);+ return *this;+ }+ + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN+ {+ value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed);+ return *this;+ }++ AE_FORCEINLINE T load() const AE_NO_TSAN { return value.load(std::memory_order_relaxed); }+ + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN+ {+ return value.fetch_add(increment, std::memory_order_acquire);+ }+ + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN+ {+ return value.fetch_add(increment, std::memory_order_release);+ }+#endif+ ++private:+#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC+ // No std::atomic support, but still need to circumvent compiler optimizations.+ // `volatile` will make memory access slow, but is guaranteed to be reliable.+ volatile T value;+#else+ std::atomic<T> value;+#endif+};++} // end namespace moodycamel++++// Portable single-producer, single-consumer semaphore below:++#if defined(_WIN32)+// Avoid including windows.h in a header; we only need a handful of+// items, so we'll redeclare them here (this is relatively safe since+// the API generally has to remain stable between Windows versions).+// I know this is an ugly hack but it still beats polluting the global+// namespace with thousands of generic names or adding a .cpp for nothing.+extern "C" {+ struct _SECURITY_ATTRIBUTES;+ __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);+ __declspec(dllimport) int __stdcall CloseHandle(void* hObject);+ __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);+ __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);+}+#elif defined(__MACH__)+#include <mach/mach.h>+#elif defined(__unix__)+#include <semaphore.h>+#elif defined(FREERTOS)+#include <FreeRTOS.h>+#include <semphr.h>+#include <task.h>+#endif++namespace moodycamel+{+ // Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's+ // portable + lightweight semaphore implementations, originally from+ // https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h+ // LICENSE:+ // Copyright (c) 2015 Jeff Preshing+ //+ // This software is provided 'as-is', without any express or implied+ // warranty. In no event will the authors be held liable for any damages+ // arising from the use of this software.+ //+ // Permission is granted to anyone to use this software for any purpose,+ // including commercial applications, and to alter it and redistribute it+ // freely, subject to the following restrictions:+ //+ // 1. The origin of this software must not be misrepresented; you must not+ // claim that you wrote the original software. If you use this software+ // in a product, an acknowledgement in the product documentation would be+ // appreciated but is not required.+ // 2. Altered source versions must be plainly marked as such, and must not be+ // misrepresented as being the original software.+ // 3. This notice may not be removed or altered from any source distribution.+ namespace spsc_sema+ {+#if defined(_WIN32)+ class Semaphore+ {+ private:+ void* m_hSema;+ + Semaphore(const Semaphore& other);+ Semaphore& operator=(const Semaphore& other);++ public:+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema()+ {+ assert(initialCount >= 0);+ const long maxLong = 0x7fffffff;+ m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);+ assert(m_hSema);+ }++ AE_NO_TSAN ~Semaphore()+ {+ CloseHandle(m_hSema);+ }++ bool wait() AE_NO_TSAN+ {+ const unsigned long infinite = 0xffffffff;+ return WaitForSingleObject(m_hSema, infinite) == 0;+ }++ bool try_wait() AE_NO_TSAN+ {+ return WaitForSingleObject(m_hSema, 0) == 0;+ }++ bool timed_wait(std::uint64_t usecs) AE_NO_TSAN+ {+ return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;+ }++ void signal(int count = 1) AE_NO_TSAN+ {+ while (!ReleaseSemaphore(m_hSema, count, nullptr));+ }+ };+#elif defined(__MACH__)+ //---------------------------------------------------------+ // Semaphore (Apple iOS and OSX)+ // Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html+ //---------------------------------------------------------+ class Semaphore+ {+ private:+ semaphore_t m_sema;++ Semaphore(const Semaphore& other);+ Semaphore& operator=(const Semaphore& other);++ public:+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()+ {+ assert(initialCount >= 0);+ kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);+ assert(rc == KERN_SUCCESS);+ AE_UNUSED(rc);+ }++ AE_NO_TSAN ~Semaphore()+ {+ semaphore_destroy(mach_task_self(), m_sema);+ }++ bool wait() AE_NO_TSAN+ {+ return semaphore_wait(m_sema) == KERN_SUCCESS;+ }++ bool try_wait() AE_NO_TSAN+ {+ return timed_wait(0);+ }++ bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN+ {+ mach_timespec_t ts;+ ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);+ ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);++ // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html+ kern_return_t rc = semaphore_timedwait(m_sema, ts);+ return rc == KERN_SUCCESS;+ }++ void signal() AE_NO_TSAN+ {+ while (semaphore_signal(m_sema) != KERN_SUCCESS);+ }++ void signal(int count) AE_NO_TSAN+ {+ while (count-- > 0)+ {+ while (semaphore_signal(m_sema) != KERN_SUCCESS);+ }+ }+ };+#elif defined(__unix__)+ //---------------------------------------------------------+ // Semaphore (POSIX, Linux)+ //---------------------------------------------------------+ class Semaphore+ {+ private:+ sem_t m_sema;++ Semaphore(const Semaphore& other);+ Semaphore& operator=(const Semaphore& other);++ public:+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()+ {+ assert(initialCount >= 0);+ int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));+ assert(rc == 0);+ AE_UNUSED(rc);+ }++ AE_NO_TSAN ~Semaphore()+ {+ sem_destroy(&m_sema);+ }++ bool wait() AE_NO_TSAN+ {+ // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error+ int rc;+ do+ {+ rc = sem_wait(&m_sema);+ }+ while (rc == -1 && errno == EINTR);+ return rc == 0;+ }++ bool try_wait() AE_NO_TSAN+ {+ int rc;+ do {+ rc = sem_trywait(&m_sema);+ } while (rc == -1 && errno == EINTR);+ return rc == 0;+ }++ bool timed_wait(std::uint64_t usecs) AE_NO_TSAN+ {+ struct timespec ts;+ const int usecs_in_1_sec = 1000000;+ const int nsecs_in_1_sec = 1000000000;+ clock_gettime(CLOCK_REALTIME, &ts);+ ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec);+ ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000;+ // sem_timedwait bombs if you have more than 1e9 in tv_nsec+ // so we have to clean things up before passing it in+ if (ts.tv_nsec >= nsecs_in_1_sec) {+ ts.tv_nsec -= nsecs_in_1_sec;+ ++ts.tv_sec;+ }++ int rc;+ do {+ rc = sem_timedwait(&m_sema, &ts);+ } while (rc == -1 && errno == EINTR);+ return rc == 0;+ }++ void signal() AE_NO_TSAN+ {+ while (sem_post(&m_sema) == -1);+ }++ void signal(int count) AE_NO_TSAN+ {+ while (count-- > 0)+ {+ while (sem_post(&m_sema) == -1);+ }+ }+ };+#elif defined(FREERTOS)+ //---------------------------------------------------------+ // Semaphore (FreeRTOS)+ //---------------------------------------------------------+ class Semaphore+ {+ private:+ SemaphoreHandle_t m_sema;++ Semaphore(const Semaphore& other);+ Semaphore& operator=(const Semaphore& other);++ public:+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()+ {+ assert(initialCount >= 0);+ m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull), static_cast<UBaseType_t>(initialCount));+ assert(m_sema);+ }++ AE_NO_TSAN ~Semaphore()+ {+ vSemaphoreDelete(m_sema);+ }++ bool wait() AE_NO_TSAN+ {+ return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE;+ }++ bool try_wait() AE_NO_TSAN+ {+ // Note: In an ISR context, if this causes a task to unblock,+ // the caller won't know about it+ if (xPortIsInsideInterrupt())+ return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE;+ return xSemaphoreTake(m_sema, 0) == pdTRUE;+ }++ bool timed_wait(std::uint64_t usecs) AE_NO_TSAN+ {+ std::uint64_t msecs = usecs / 1000;+ TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS);+ if (ticks == 0)+ return try_wait();+ return xSemaphoreTake(m_sema, ticks) == pdTRUE;+ }++ void signal() AE_NO_TSAN+ {+ // Note: In an ISR context, if this causes a task to unblock,+ // the caller won't know about it+ BaseType_t rc;+ if (xPortIsInsideInterrupt())+ rc = xSemaphoreGiveFromISR(m_sema, NULL);+ else+ rc = xSemaphoreGive(m_sema);+ assert(rc == pdTRUE);+ AE_UNUSED(rc);+ }++ void signal(int count) AE_NO_TSAN+ {+ while (count-- > 0)+ signal();+ }+ };+#else+#error Unsupported platform! (No semaphore wrapper available)+#endif++ //---------------------------------------------------------+ // LightweightSemaphore+ //---------------------------------------------------------+ class LightweightSemaphore+ {+ public:+ typedef std::make_signed<std::size_t>::type ssize_t;+ + private:+ weak_atomic<ssize_t> m_count;+ Semaphore m_sema;++ bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN+ {+ ssize_t oldCount;+ // Is there a better way to set the initial spin count?+ // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,+ // as threads start hitting the kernel semaphore.+ int spin = 1024;+ while (--spin >= 0)+ {+ if (m_count.load() > 0)+ {+ m_count.fetch_add_acquire(-1);+ return true;+ }+ compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop.+ }+ oldCount = m_count.fetch_add_acquire(-1);+ if (oldCount > 0)+ return true;+ if (timeout_usecs < 0)+ {+ if (m_sema.wait())+ return true;+ }+ if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs)))+ return true;+ // At this point, we've timed out waiting for the semaphore, but the+ // count is still decremented indicating we may still be waiting on+ // it. So we have to re-adjust the count, but only if the semaphore+ // wasn't signaled enough times for us too since then. If it was, we+ // need to release the semaphore too.+ while (true)+ {+ oldCount = m_count.fetch_add_release(1);+ if (oldCount < 0)+ return false; // successfully restored things to the way they were+ // Oh, the producer thread just signaled the semaphore after all. Try again:+ oldCount = m_count.fetch_add_acquire(-1);+ if (oldCount > 0 && m_sema.try_wait())+ return true;+ }+ }++ public:+ AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema()+ {+ assert(initialCount >= 0);+ }++ bool tryWait() AE_NO_TSAN+ {+ if (m_count.load() > 0)+ {+ m_count.fetch_add_acquire(-1);+ return true;+ }+ return false;+ }++ bool wait() AE_NO_TSAN+ {+ return tryWait() || waitWithPartialSpinning();+ }++ bool wait(std::int64_t timeout_usecs) AE_NO_TSAN+ {+ return tryWait() || waitWithPartialSpinning(timeout_usecs);+ }++ void signal(ssize_t count = 1) AE_NO_TSAN+ {+ assert(count >= 0);+ ssize_t oldCount = m_count.fetch_add_release(count);+ assert(oldCount >= -1);+ if (oldCount < 0)+ {+ m_sema.signal(1);+ }+ }+ + std::size_t availableApprox() const AE_NO_TSAN+ {+ ssize_t count = m_count.load();+ return count > 0 ? static_cast<std::size_t>(count) : 0;+ }+ };+ } // end namespace spsc_sema+} // end namespace moodycamel++#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))+#pragma warning(pop)+#ifdef __cplusplus_cli+#pragma managed(pop)+#endif+#endif
+ vendor/libremidi/api.hpp view
@@ -0,0 +1,95 @@+#pragma once+#include <libremidi/api-c.h>+#include <libremidi/config.hpp>++#include <any>+#include <string_view>+#include <vector>++namespace libremidi+{+//! MIDI API specifier arguments.+//! To get information on which feature is supported by each back-end, check their backend file+//! in e.g. backends/winmm.hpp, etc.+using API = libremidi_api;++/**+ * \brief A function to determine the available compiled MIDI 1.0 APIs.++ The values returned in the std::vector can be compared against+ the enumerated list values. Note that there can be more than one+ API compiled for certain operating systems.+*/+LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;++/**+ * \brief A function to determine the available compiled MIDI 2.0 APIs.++ The values returned in the std::vector can be compared against+ the enumerated list values. Note that there can be more than one+ API compiled for certain operating systems.+*/+LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;++LIBREMIDI_EXPORT+libremidi::API midi_api(const std::any& conf);++//! A static function to determine the current version.+LIBREMIDI_EXPORT std::string_view get_version() noexcept;++//! Map from and to API names+LIBREMIDI_EXPORT std::string_view get_api_name(libremidi::API api);+//! Map from and to API names+LIBREMIDI_EXPORT std::string_view get_api_display_name(libremidi::API api);+//! Look-up an API through its name+LIBREMIDI_EXPORT libremidi::API get_compiled_api_by_name(std::string_view api);++inline constexpr bool is_midi1(libremidi::API api)+{+ return (static_cast<int>(api) >= 0x1 && static_cast<int>(api) < 0x1000)+ || api == libremidi::API::DUMMY;+}++inline constexpr bool is_midi2(libremidi::API api)+{+ return static_cast<int>(api) >= 0x1000;+}++namespace midi1+{+//! Returns the default MIDI 1.0 backend to use for the target OS.+inline constexpr libremidi::API default_api() noexcept+{+#if defined(__APPLE__)+ return API::COREMIDI;+#elif defined(_WIN32)+ return API::WINDOWS_MM;+#elif defined(__linux__)+ return API::ALSA_SEQ;+#elif defined(__emscripten__)+ return API::EMSCRIPTEN_WEBMIDI;+#else+ return API::DUMMY;+#endif+}+}++namespace midi2+{+//! Returns the default MIDI 2.0 backend to use for the target OS.+inline constexpr libremidi::API default_api() noexcept+{+#if defined(__APPLE__)+ return API::COREMIDI_UMP;+#elif defined(_WIN32)+ return API::WINDOWS_MIDI_SERVICES;+#elif defined(__linux__)+ return API::ALSA_SEQ_UMP;+#elif defined(__emscripten__)+ return API::DUMMY;+#else+ return API::DUMMY;+#endif+}+}+}
+ vendor/libremidi/backends.hpp view
@@ -0,0 +1,250 @@+#pragma once+#include <libremidi/config.hpp>++#include <any>+#include <tuple>++#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)+ #if defined(LIBREMIDI_JACK)+ #undef LIBREMIDI_JACK+ #endif+#endif+#if !defined(LIBREMIDI_ALSA) && !defined(LIBREMIDI_JACK) && !defined(LIBREMIDI_COREMIDI) \+ && !defined(LIBREMIDI_WINMM)+ #define LIBREMIDI_DUMMY+#endif++#if defined(LIBREMIDI_ALSA)+ #include <libremidi/backends/alsa_seq.hpp>+ #include <libremidi/backends/linux/alsa.hpp>++ #if LIBREMIDI_ALSA_HAS_RAMWIDI+ #include <libremidi/backends/alsa_raw.hpp>+ #endif++ #if LIBREMIDI_ALSA_HAS_UMP+ #include <libremidi/backends/alsa_raw_ump.hpp>+ #include <libremidi/backends/alsa_seq_ump.hpp>+ #endif+#endif++#if defined(LIBREMIDI_JACK)+ #include <libremidi/backends/jack.hpp>+#endif++#if defined(LIBREMIDI_PIPEWIRE)+ #include <libremidi/backends/pipewire.hpp>+#endif++#if defined(LIBREMIDI_COREMIDI)+ #include <libremidi/backends/coremidi.hpp>+ #include <libremidi/backends/coremidi_ump.hpp>+#endif++#if defined(LIBREMIDI_WINMM)+ #include <libremidi/backends/winmm.hpp>+#endif++#if defined(LIBREMIDI_WINUWP)+ #include <libremidi/backends/winuwp.hpp>+#endif++#if defined(LIBREMIDI_WINMIDI)+ #include <libremidi/backends/winmidi.hpp>+#endif++#if defined(LIBREMIDI_EMSCRIPTEN)+ #include <libremidi/backends/emscripten.hpp>+#endif++#include <libremidi/backends/dummy.hpp>++#if defined(LIBREMIDI_KEYBOARD)+ #include <libremidi/backends/keyboard.hpp>+#endif++#if defined(LIBREMIDI_NETWORK)+ #include <libremidi/backends/network.hpp>+ #include <libremidi/backends/network_ump.hpp>+#endif++namespace libremidi+{+// The order here will control the order of the API search in+// the constructor.+template <typename unused, typename... Args>+constexpr auto make_tl(unused, Args...)+{+ return std::tuple<Args...>{};+}++namespace midi1+{+static constexpr auto available_backends = make_tl(+ 0+#if defined(LIBREMIDI_ALSA)+ ,+ alsa_seq::backend{}+ #if LIBREMIDI_ALSA_HAS_RAMWIDI+ ,+ alsa_raw::backend{}+ #endif+#endif+#if defined(LIBREMIDI_COREMIDI)+ ,+ core_backend{}+#endif+#if defined(LIBREMIDI_WINMM)+ ,+ winmm_backend{}+#endif+#if defined(LIBREMIDI_WINUWP)+ ,+ winuwp_backend{}+#endif+#if defined(LIBREMIDI_EMSCRIPTEN)+ ,+ emscripten_backend{}+#endif+#if defined(LIBREMIDI_JACK)+ ,+ jack_backend{}+#endif+#if defined(LIBREMIDI_PIPEWIRE)+ ,+ pipewire::backend{}+#endif+#if defined(LIBREMIDI_KEYBOARD)+ ,+ kbd_backend{}+#endif+#if defined(LIBREMIDI_NETWORK)+ ,+ net::backend{}+#endif+ ,+ dummy_backend{});++// There should always be at least one back-end.+static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);++template <typename F>+auto for_all_backends(F&& f)+{+ std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends);+}++template <typename F>+auto for_backend(libremidi::API api, F&& f)+{+ static constexpr auto is_api = [](auto& backend, libremidi::API api) {+ return backend.available() && backend.API == api;+ };+ std::apply([&](auto&&... b) { ((is_api(b, api) && (f(b), true)) || ...); }, available_backends);+}+}++namespace midi2+{+static constexpr auto available_backends = make_tl(+ 0+#if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP+ ,+ alsa_seq_ump::backend{}, alsa_raw_ump::backend{}+#endif+#if defined(LIBREMIDI_COREMIDI)+ ,+ coremidi_ump::backend{}+#endif+#if defined(LIBREMIDI_WINMIDI)+ ,+ winmidi::backend{}+#endif+#if defined(LIBREMIDI_NETWORK)+ ,+ net_ump::backend{}+#endif+ ,+ dummy_backend{});++// There should always be at least one back-end.+static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);++template <typename F>+auto for_all_backends(F&& f)+{+ std::apply([&](auto&&... x) { (f(x), ...); }, available_backends);+}++template <typename F>+auto for_backend(libremidi::API api, F&& f)+{+ static constexpr auto is_api+ = [](auto& backend, libremidi::API api) { return backend.API == api; };+ std::apply([&](auto&&... b) { ((is_api(b, api) && (f(b), true)) || ...); }, available_backends);+}+}++namespace midi_any+{++template <typename F>+auto for_all_backends(F&& f)+{+ midi1::for_all_backends(f);+ midi2::for_all_backends(f);+}++template <typename F>+auto for_backend(libremidi::API api, F&& f)+{+ midi1::for_backend(api, f);+ midi2::for_backend(api, f);+}++void for_input_configuration(auto f, std::any& api_conf)+{+ auto from_api = [&]<typename T>(T& /*backend*/) mutable {+ if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))+ {+ f(*conf);+ return true;+ }+ return false;+ };+ if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))+ return;+ std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);+}++void for_output_configuration(auto f, std::any& api_conf)+{+ auto from_api = [&]<typename T>(T& /*backend*/) mutable {+ if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))+ {+ f(*conf);+ return true;+ }+ return false;+ };+ if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))+ return;+ std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);+}++void for_observer_configuration(auto f, std::any& api_conf)+{+ auto from_api = [&]<typename T>(T& /*backend*/) mutable {+ if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))+ {+ f(*conf);+ return true;+ }+ return false;+ };+ if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))+ return;+ std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);+}+}+}
+ vendor/libremidi/backends/alsa_raw.hpp view
@@ -0,0 +1,31 @@+#pragma once+#include <libremidi/backends/alsa_raw/midi_in.hpp>+#include <libremidi/backends/alsa_raw/midi_out.hpp>+#include <libremidi/backends/alsa_raw/observer.hpp>++// Credits: greatly inspired from+// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c+// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c+// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>++namespace libremidi::alsa_raw+{+struct backend+{+ using midi_in = midi_in_impl;+ using midi_out = midi_out_impl;+ using midi_observer = observer_impl;+ using midi_in_configuration = alsa_raw_input_configuration;+ using midi_out_configuration = alsa_raw_output_configuration;+ using midi_observer_configuration = alsa_raw_observer_configuration;+ static const constexpr auto API = libremidi::API::ALSA_RAW;+ static const constexpr auto name = "alsa_raw";+ static const constexpr auto display_name = "ALSA (raw)";++ static inline bool available() noexcept+ {+ static const libasound& snd = libasound::instance();+ return snd.available && snd.rawmidi.available;+ }+};+}
+ vendor/libremidi/backends/alsa_raw/config.hpp view
@@ -0,0 +1,80 @@+#pragma once+#include <libremidi/config.hpp>++#include <chrono>+#include <functional>+#include <optional>+#include <span>+#include <thread>++#if __has_include(<poll.h>)+ #include <poll.h>+namespace libremidi+{+using poll_descriptors = pollfd;+}+#else+namespace libremidi+{+struct poll_descriptors+{+ int fd;+ short int events;+ short int revents;+};+}+#endif++namespace libremidi+{++/**+ * Used to determine how large sent messages will be chunked.+ */+struct LIBREMIDI_EXPORT chunking_parameters+{+ std::chrono::milliseconds interval{};+ int32_t size{};++ /**+ * @brief Will be called by the chunking code to allow the API user to wait.+ *+ * By default just calls sleep.+ * Arguments are: the time that must be waited, the bytes currently written.+ * Return false if you want to abort the transfer, and true otherwise.+ */+ std::function<bool(std::chrono::microseconds, int64_t)> wait = chunking_parameters::default_wait;++ static bool default_wait(std::chrono::microseconds time_to_wait, int64_t /*written_bytes*/)+ {+ std::this_thread::sleep_for(time_to_wait);+ return true;+ }+};++struct manual_poll_parameters+{+ std::span<poll_descriptors> fds;+ std::function<int64_t(std::span<poll_descriptors> fds)> callback;+};++struct alsa_raw_input_configuration+{+ std::function<bool(const manual_poll_parameters&)> manual_poll;+ std::chrono::milliseconds poll_period{2};+};++struct alsa_raw_output_configuration+{+ /**+ * For large messages, chunk their content and wait.+ * Setting a null optional will disable chunking.+ */+ std::optional<chunking_parameters> chunking;+};++struct alsa_raw_observer_configuration+{+ std::chrono::milliseconds poll_period{100};+};+}
+ vendor/libremidi/backends/alsa_raw/error_domain.hpp view
+ vendor/libremidi/backends/alsa_raw/helpers.hpp view
@@ -0,0 +1,296 @@+#pragma once+#include <libremidi/backends/linux/alsa.hpp>+#include <libremidi/config.hpp>+#include <libremidi/detail/observer.hpp>++#include <functional>+#include <string>+#include <string_view>+#include <vector>++// Credits: greatly inspired from+// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c+// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c+// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>++namespace libremidi+{+namespace+{+struct alsa_raw_port_id+{+ int card{}, dev{}, port{};+ std::string to_string() const noexcept+ {+ return "hw:" + std::to_string(card) + "," + std::to_string(dev) + "," + std::to_string(port);+ }+};+inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept+{+ return (uint64_t(id.card) << 32) + (uint64_t(id.dev) << 16) + id.port;+}+inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept+{+ alsa_raw_port_id ret;+ ret.card = (p & 0x00'00'FF'FF'00'00'00'00) >> 32;+ ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16;+ ret.port = (p & 0x00'00'00'00'00'00'FF'FF);+ return ret;+}+static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102);+static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7);+static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3);+}++namespace alsa_raw+{+struct alsa_raw_port_info+{+ std::string device;+ std::string card_name;+ std::string device_name;+ std::string subdevice_name;+ int card{}, dev{}, sub{};++ std::string pretty_name() const+ {+ return device + ": " + card_name + " : " + device_name + " : " + subdevice_name;+ }++ bool operator==(const alsa_raw_port_info& other) const noexcept = default;+};++struct enumerator;+struct snd_ctl_wrapper+{+ const libasound& snd;+ snd_ctl_t* ctl{};+ inline snd_ctl_wrapper(enumerator& self, const char* name);++ ~snd_ctl_wrapper()+ {+ if (ctl)+ {+ snd.ctl.close(ctl);+ }+ }++ snd_ctl_t& operator*() const noexcept { return *ctl; }+ snd_ctl_t* operator->() const noexcept { return ctl; }+ operator snd_ctl_t*() const noexcept { return ctl; }+};+struct enumerator+{+ const libasound& snd = libasound::instance();+ const error_handler& handler;+ const observer_configuration& configuration;+ std::vector<alsa_raw_port_info> inputs;+ std::vector<alsa_raw_port_info> outputs;++ explicit enumerator(const auto& self)+ : handler{self}+ , configuration{self.configuration}+ {++ }++ // 1: is an input / output+ // 0: isn't an input / output+ // < 0: error+ int is(snd_rawmidi_stream_t stream, snd_ctl_t* ctl, int card, int device, int sub)+ {+ snd_rawmidi_info_t* info;++ snd_rawmidi_info_alloca(&info);+ snd.rawmidi.info_set_device(info, device);+ snd.rawmidi.info_set_subdevice(info, sub);+ snd.rawmidi.info_set_stream(info, stream);++ const int status = snd.ctl.rawmidi.info(ctl, info);+ if (status == 0)+ {+ return 1;+ }+ else if (status < 0 && status != -ENXIO)+ {+ handler.libremidi_handle_error(+ configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub)+ + " : " + snd.strerror(status));+ return status;+ }+ else+ {+ return 0;+ }+ }++ int is_input(snd_ctl_t* ctl, int card, int device, int sub)+ {+ return is(SND_RAWMIDI_STREAM_INPUT, ctl, card, device, sub);+ }++ int is_output(snd_ctl_t* ctl, int card, int device, int sub)+ {+ return is(SND_RAWMIDI_STREAM_OUTPUT, ctl, card, device, sub);+ }++ std::string get_card_name(int card)+ {+ char* card_name{};+ snd.card.get_name(card, &card_name);++ std::string str = card_name;+ free(card_name);+ return str;+ }++ static std::string device_identifier(int card, int device, int sub)+ {+ std::string s;+ s.reserve(12);+ s += "hw:";+ s += std::to_string(card);+ s += ",";+ s += std::to_string(device);+ s += ",";+ s += std::to_string(sub);+ return s;+ }++ stdx::error enumerate_cards()+ {+ using namespace std::literals;+ int card = -1;++ int status = snd.card.next(&card);+ if (status < 0)+ {+ handler.libremidi_handle_error(+ configuration, "Cannot determine card number: "s + snd.strerror(status));+ return from_errc(status);+ }++ if (card < 0)+ {+ handler.libremidi_handle_error(configuration, "No sound cards found");+ return std::errc::no_such_device;+ }++ while (card >= 0)+ {+ enumerate_devices(card);++ if ((status = snd.card.next(&card)) < 0)+ {+ handler.libremidi_handle_error(+ configuration, "cannot determine card number: "s + snd.strerror(status));+ return std::errc::no_such_device;+ }+ }+ return stdx::error{};+ }++ virtual void enumerate_devices(int card) = 0;+};++inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)+ : snd{self.snd}+{+ using namespace std::literals;+ int status = snd.ctl.open(&ctl, name, 0);+ if (status < 0)+ {+ self.handler.libremidi_handle_error(+ self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status));+ }+}++struct midi1_enumerator : enumerator+{+ using alsa_raw::enumerator::enumerator;++ void enumerate_devices(int card) override+ {+ using namespace std::literals;+ char name[128];++ sprintf(name, "hw:%d", card);++ // Open card.+ snd_ctl_wrapper ctl{*this, name};+ if (!ctl)+ return;++ // Enumerate devices.+ int device = -1;+ do+ {+ const int status = snd.ctl.rawmidi.next_device(ctl, &device);+ if (device == -1)+ return;++ if (status < 0)+ {+ handler.libremidi_handle_error(+ configuration, "Cannot determine device number: "s + snd.strerror(status));+ break;+ }++ if (device >= 0)+ enumerate_subdevices(ctl, card, device);++ } while (device >= 0);+ }++ void enumerate_subdevices(snd_ctl_t* ctl, int card, int device)+ {+ snd_rawmidi_info_t* info;+ snd_rawmidi_info_alloca(&info);+ snd.rawmidi.info_set_device(info, device);++ snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);+ snd.ctl.rawmidi.info(ctl, info);+ const int subs_in = snd.rawmidi.info_get_subdevices_count(info);++ snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);+ snd.ctl.rawmidi.info(ctl, info);+ const int subs_out = snd.rawmidi.info_get_subdevices_count(info);++ alsa_raw_port_info d;+ d.card = card;+ d.dev = device;+ d.card_name = get_card_name(card);+ d.device_name = snd.rawmidi.info_get_name(info);++ auto read_subdevice_info = [&](int sub) {+ snd.rawmidi.info_set_subdevice(info, sub);+ snd.ctl.rawmidi.info(ctl, info);++ d.device = device_identifier(card, device, sub);+ d.subdevice_name = snd.rawmidi.info_get_subdevice_name(info);+ d.sub = sub;+ };++ if (subs_in > 0)+ {+ snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);+ for (int sub = 0; sub < subs_in; sub++)+ {+ read_subdevice_info(sub);+ inputs.push_back(d);+ }+ }++ if (subs_out > 0)+ {+ snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);+ for (int sub = 0; sub < subs_out; sub++)+ {+ read_subdevice_info(sub);+ outputs.push_back(d);+ }+ }+ }+};+}+}
+ vendor/libremidi/backends/alsa_raw/midi_in.hpp view
@@ -0,0 +1,356 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <alsa/asoundlib.h>++#include <chrono>+#include <thread>++namespace libremidi::alsa_raw+{+class midi_in_impl+ : public midi1::in_api+ , public error_handler+{+public:+ struct+ : input_configuration+ , alsa_raw_input_configuration+ {+ } configuration;++ const libasound& snd = libasound::instance();++ explicit midi_in_impl(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ fds_.reserve(4);+ }++ ~midi_in_impl() override { }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }++ // Must be a string such as: "hw:2,4,1"+ [[nodiscard]] stdx::error do_init_port(const char* portname)+ {+ constexpr int mode = SND_RAWMIDI_NONBLOCK;+ if (const int err = snd.rawmidi.open(&midiport_, nullptr, portname, mode); err < 0)+ {+ libremidi_handle_error(this->configuration, "cannot open device.");+ return from_errc(err);+ }++ snd_rawmidi_params_t* params{};+ snd_rawmidi_params_alloca(¶ms);++ if (const int err = snd.rawmidi.params_current(midiport_, params); err < 0)+ return from_errc(err);+ if (const int err = snd.rawmidi.params_set_no_active_sensing(midiport_, params, 1); err < 0)+ return from_errc(err);+#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD+ if (configuration.timestamps == timestamp_mode::NoTimestamp)+ {+ if (const int err+ = snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_STANDARD);+ err < 0)+ return from_errc(err);+ if (const int err+ = snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_NONE);+ err < 0)+ return from_errc(err);+ }+ else+ {+ if (const int err+ = snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_TSTAMP);+ err < 0)+ return from_errc(err);+ if (const int err+ = snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_MONOTONIC);+ err < 0)+ return from_errc(err);+ }+#endif++ if (const int err = snd.rawmidi.params(midiport_, params); err < 0)+ return from_errc(err);++ return init_pollfd();+ }++ [[nodiscard]] stdx::error init_port(const port_information& p)+ {+ return do_init_port(raw_from_port_handle(p.port).to_string().c_str());+ }++ [[nodiscard]] stdx::error init_pollfd()+ {+ const int num_fds = snd.rawmidi.poll_descriptors_count(this->midiport_);++ this->fds_.clear();+ this->fds_.resize(num_fds);++ int ret = snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), num_fds);+ if (ret < 0)+ return from_errc(ret);+ return stdx::error{};+ }++ ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)+ {+ // Read events+ if (fds.empty())+ {+ return (this->*parse_func)();+ }+ else+ {+ unsigned short res{};+ const int err = snd.rawmidi.poll_descriptors_revents(+ this->midiport_, fds.data(), static_cast<unsigned int>(fds.size()), &res);+ if (err < 0)+ return err;++ // Did we encounter an error during polling+ if (res & (POLLERR | POLLHUP))+ return -EIO;++ // Is there data to read+ if (res & POLLIN)+ return (this->*parse_func)();+ }++ return 0;+ }++ ssize_t read_input_buffer()+ {+ static const constexpr int nbytes = 1024;+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = false,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ unsigned char bytes[nbytes];++ ssize_t err = 0;+ // err is the amount of bytes read+ while ((err = snd.rawmidi.read(this->midiport_, bytes, nbytes)) > 0)+ {+ const auto to_ns = [this] { return absolute_timestamp(); };+ m_processing.on_bytes(+ {bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }+ return err;+ }++#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD+ ssize_t read_input_buffer_with_timestamps()+ {+ static constexpr int nbytes = 1024;+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = true,+ .has_samples = false,+ };++ unsigned char bytes[nbytes];+ struct timespec ts;++ ssize_t err = 0;+ // err is the amount of bytes read+ while ((err = snd.rawmidi.tread(this->midiport_, &ts, bytes, nbytes)) > 0)+ {+ const auto to_ns = [ts] {+ return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+ };+ m_processing.on_bytes(+ {bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }+ return err;+ }+#else+ ssize_t read_input_buffer_with_timestamps() { return read_input_buffer(); }+#endif++ stdx::error close_port() override+ {+ if (midiport_)+ snd.rawmidi.close(midiport_);+ midiport_ = nullptr;+ return stdx::error{};+ }++ timestamp absolute_timestamp() const noexcept final override { return system_ns(); }++ snd_rawmidi_t* midiport_{};+ std::vector<pollfd> fds_;+ midi1::input_state_machine m_processing{this->configuration};+};++class midi_in_alsa_raw_threaded : public midi_in_impl+{+public:+ midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)+ : midi_in_impl{std::move(conf), std::move(apiconf)}+ {+ if (this->termination_event < 0)+ {+ libremidi_handle_error(this->configuration, "error creating eventfd.");+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_in_alsa_raw_threaded() override+ {+ // Close a connection if it exists.+ this->midi_in_alsa_raw_threaded::close_port();++ client_open_ = std::errc::not_connected;+ }++private:+ void run_thread(auto parse_func)+ {+ fds_.push_back(this->termination_event);+ const auto period+ = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+ .count();++ for (;;)+ {+ // Poll+ ssize_t err = poll(fds_.data(), fds_.size(), period);+ if (err == -EAGAIN)+ continue;+ else if (err < 0)+ return;+ else if (termination_event.ready(fds_.back()))+ break;++ err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});+ if (err == -EAGAIN)+ continue;+ else if (err < 0)+ return;+ }+ }++ [[nodiscard]] stdx::error start_thread()+ {+ try+ {+ if (configuration.timestamps == timestamp_mode::NoTimestamp)+ {+ this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};+ }+ else+ {+ this->thread_ = std::thread{+ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};+ }+ return stdx::error{};+ }+ catch (const std::system_error& e)+ {+ using namespace std::literals;++ libremidi_handle_error(+ this->configuration,+ "error starting MIDI input thread: "s + e.what());+ return e.code();+ }+ return stdx::error{};+ }++ stdx::error open_port(const input_port& port, std::string_view /*name*/) override+ {+ if (auto err = midi_in_impl::init_port(port); err != stdx::error{})+ return err;+ if (auto err = start_thread(); err != stdx::error{})+ return err;+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ termination_event.notify();+ if (thread_.joinable())+ thread_.join();+ termination_event.consume(); // Reset to zero++ return midi_in_impl::close_port();+ }++ std::thread thread_;+ eventfd_notifier termination_event{};+};++class midi_in_alsa_raw_manual : public midi_in_impl+{+public:+ midi_in_alsa_raw_manual(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)+ : midi_in_impl{std::move(conf), std::move(apiconf)}+ {+ client_open_ = stdx::error{};+ }++ ~midi_in_alsa_raw_manual()+ {+ // Close a connection if it exists.+ this->close_port();++ client_open_ = std::errc::not_connected;+ }++private:+ void send_poll_callback()+ {+ if (configuration.timestamps == timestamp_mode::NoTimestamp)+ {+ configuration.manual_poll(manual_poll_parameters{+ .fds = {this->fds_.data(), this->fds_.size()},+ .callback = [this](std::span<pollfd> fds) {+ return do_read_events(&midi_in_impl::read_input_buffer, fds);+ }});+ }+ else+ {+ configuration.manual_poll(manual_poll_parameters{+ .fds = {this->fds_.data(), this->fds_.size()},+ .callback = [this](std::span<pollfd> fds) {+ return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);+ }});+ }+ }++ stdx::error open_port(const input_port& p, std::string_view /*name*/) override+ {+ if (auto err = midi_in_impl::init_port(p); err != stdx::error{})+ return err;+ send_poll_callback();+ return stdx::error{};+ }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_in_api> make<alsa_raw::midi_in_impl>(+ libremidi::input_configuration&& conf, libremidi::alsa_raw_input_configuration&& api)+{+ if (api.manual_poll)+ return std::make_unique<alsa_raw::midi_in_alsa_raw_manual>(std::move(conf), std::move(api));+ else+ return std::make_unique<alsa_raw::midi_in_alsa_raw_threaded>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/alsa_raw/midi_out.hpp view
@@ -0,0 +1,169 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <alsa/asoundlib.h>++#include <atomic>+#include <thread>++namespace libremidi::alsa_raw+{+class midi_out_impl final+ : public midi1::out_api+ , public error_handler+{+public:+ struct+ : output_configuration+ , alsa_raw_output_configuration+ {+ } configuration;++ const libasound& snd = libasound::instance();++ midi_out_impl(output_configuration&& conf, alsa_raw_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ client_open_ = stdx::error{};+ }++ ~midi_out_impl() override+ {+ // Close a connection if it exists.+ midi_out_impl::close_port();+ client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }++ stdx::error connect_port(const char* portname)+ {+ constexpr int mode = SND_RAWMIDI_SYNC;+ int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);+ if (status < 0)+ {+ libremidi_handle_error(+ this->configuration, "cannot open device.");+ return from_errc(status);+ }+ return stdx::error{};+ }++ stdx::error open_port(const output_port& p, std::string_view) override+ {+ return connect_port(raw_from_port_handle(p.port).to_string().c_str());+ }++ stdx::error close_port() override+ {+ if (midiport_)+ snd.rawmidi.close(midiport_);+ midiport_ = nullptr;++ return stdx::error{};+ }++ stdx::error send_message(const unsigned char* message, size_t size) override+ {+ if (!midiport_)+ libremidi_handle_error(+ this->configuration,+ "trying to send a message without an open "+ "port.");++ if (!this->configuration.chunking)+ {+ return write(message, size);+ }+ else+ {+ return write_chunked(message, size);+ }+ }++ stdx::error write(const unsigned char* message, size_t size)+ {+ if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0)+ {+ libremidi_handle_error(+ this->configuration, "cannot write message.");+ return from_errc(err);+ }++ return stdx::error{};+ }++ std::size_t get_chunk_size() const noexcept+ {+ snd_rawmidi_params_t* param;+ snd_rawmidi_params_alloca(¶m);+ snd.rawmidi.params_current(midiport_, param);++ std::size_t buffer_size = snd.rawmidi.params_get_buffer_size(param);+ return std::min(buffer_size, (std::size_t)configuration.chunking->size);+ }++ std::size_t get_available_bytes_to_write() const noexcept+ {+ snd_rawmidi_status_t* st{};+ snd_rawmidi_status_alloca(&st);+ snd.rawmidi.status(midiport_, st);++ return snd.rawmidi.status_get_avail(st);+ }++ // inspired from ALSA amidi.c source code+ stdx::error write_chunked(const unsigned char* const begin, size_t size)+ {+ const unsigned char* data = begin;+ const unsigned char* end = begin + size;++ const std::size_t chunk_size = std::min(get_chunk_size(), size);++ // Send the first buffer+ std::size_t len = chunk_size;++ if (auto err = write(data, len); err != stdx::error{})+ return err;++ data += len;++ while (data < end)+ {+ // Wait for the buffer to have some space available+ const std::size_t written_bytes = data - begin;+ std::size_t available{};+ while ((available = get_available_bytes_to_write()) < chunk_size)+ {+ if (!configuration.chunking->wait(+ std::chrono::microseconds((chunk_size - available) * 320), written_bytes))+ return std::errc::protocol_error;+ };++ if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes))+ return std::errc::protocol_error;++ // Write more data+ len = end - data;++ // Maybe until the end of the sysex+ if (const auto sysex_end = static_cast<const unsigned char*>(memchr(data, 0xf7, len)))+ len = sysex_end - data + 1;++ if (len > chunk_size)+ len = chunk_size;++ if (auto err = write(data, len); err != stdx::error{})+ return err;++ data += len;+ }++ return stdx::error{};+ }++ snd_rawmidi_t* midiport_{};+};+}
+ vendor/libremidi/backends/alsa_raw/observer.hpp view
@@ -0,0 +1,240 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/backends/dummy.hpp>++#if LIBREMIDI_HAS_UDEV+ #include <libremidi/backends/linux/helpers.hpp>+ #include <libremidi/backends/linux/udev.hpp>+ #include <libremidi/detail/observer.hpp>++namespace libremidi::alsa_raw+{+template <typename Enumerator>+class observer_impl_base+ : public observer_api+ , public error_handler+{+public:+ struct+ : observer_configuration+ , alsa_raw_observer_configuration+ {+ } configuration;++ const libasound& snd = libasound::instance();++ explicit observer_impl_base(+ observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (!configuration.has_callbacks())+ return;++ fds[0] = this->udev;+ fds[1] = termination_event;+ fds[2] = timer_fd;++ // Set-up initial state+ if (configuration.notify_in_constructor)+ this->check_devices();++ // Start thread+ thread = std::thread{[this] { this->run(); }};+ }++ ~observer_impl_base()+ {+ termination_event.notify();++ if (thread.joinable())+ thread.join();+ }++ std::vector<libremidi::input_port> get_input_ports() const noexcept override+ {+ std::vector<libremidi::input_port> ret;+ Enumerator new_devs{*this};++ new_devs.enumerate_cards();+ for (auto& d : new_devs.inputs)+ {+ ret.push_back(to_port_info<true>(d));+ }+ return ret;+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept override+ {+ std::vector<libremidi::output_port> ret;+ Enumerator new_devs{*this};++ new_devs.enumerate_cards();+ for (auto& d : new_devs.outputs)+ {+ ret.push_back(to_port_info<false>(d));+ }+ return ret;+ }++private:+ void run()+ {+ for (;;)+ {+ if (int err = poll(fds, 3, -1); err < 0)+ {+ if (err == -EAGAIN)+ continue;+ else+ return;+ }++ // Check udev+ if (fds[0].revents & POLLIN)+ {+ udev_device* dev = udev.udev.monitor_receive_device(udev.monitor);+ if (!dev)+ continue;++ std::string_view act = udev.udev.device_get_action(dev);+ std::string_view ss = udev.udev.device_get_subsystem(dev);+ if (!act.empty() && ss == "snd_seq")+ {+ if (act == "add" || act == "remove")+ {+ // Check every 100 milliseconds for ten seconds+ this->timer_fd.restart(configuration.poll_period.count());+ timer_check_counts = 100;+ }+ }++ udev.udev.device_unref(dev);++ fds[0].revents = 0;+ }++ // Check eventfd+ if (fds[1].revents & POLLIN)+ {+ break;+ }++ // Check timer+ if (fds[2].revents & POLLIN)+ {+ if (this->timer_check_counts-- <= 0)+ this->timer_fd.cancel();+ fds[2].revents = 0;++ check_devices();+ }+ }+ }++ template <bool Input>+ auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept+ -> std::conditional_t<Input, input_port, output_port>+ {+ return {+ {.client = 0,+ .port = raw_to_port_handle({p.card, p.dev, p.sub}),+ .manufacturer = p.card_name,+ .device_name = p.device_name,+ .port_name = p.subdevice_name,+ .display_name = p.subdevice_name}};+ }++ void check_devices()+ {+ Enumerator new_devs{*this};++ new_devs.enumerate_cards();++ for (auto& in_prev : current_inputs)+ {+ if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);+ it == new_devs.inputs.end())+ {+ if (auto& cb = this->configuration.input_removed)+ {+ cb(to_port_info<true>(in_prev));+ }+ }+ }++ for (auto& in_next : new_devs.inputs)+ {+ if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next);+ it == current_inputs.end())+ {+ if (auto& cb = this->configuration.input_added)+ {+ cb(to_port_info<true>(in_next));+ }+ }+ }++ for (auto& out_prev : current_outputs)+ {+ if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);+ it == new_devs.outputs.end())+ {+ if (auto& cb = this->configuration.output_removed)+ {+ cb(to_port_info<false>(out_prev));+ }+ }+ }++ for (auto& out_next : new_devs.outputs)+ {+ if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next);+ it == current_outputs.end())+ {+ if (auto& cb = this->configuration.output_added)+ {+ cb(to_port_info<false>(out_next));+ }+ }+ }+ current_inputs = std::move(new_devs.inputs);+ current_outputs = std::move(new_devs.outputs);+ }++ udev_helper udev{};+ eventfd_notifier termination_event{};+ timerfd_timer timer_fd{};+ int timer_check_counts = 0;+ std::thread thread;+ std::vector<alsa_raw_port_info> current_inputs;+ std::vector<alsa_raw_port_info> current_outputs;++ pollfd fds[3]{};+};+}+#else+ #include <libremidi/backends/dummy.hpp>+namespace libremidi::alsa_raw+{+template <typename Enumerator>+struct observer_impl_base : observer_dummy+{+ explicit observer_impl_base(+ [[maybe_unused]] observer_configuration&& conf,+ [[maybe_unused]] alsa_raw_observer_configuration&& apiconf)+ : observer_dummy{dummy_configuration{}, dummy_configuration{}}+ {+ }+};+}+#endif++namespace libremidi::alsa_raw+{+struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>+{+ using alsa_raw::observer_impl_base<midi1_enumerator>::observer_impl_base;+ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }+};+}
+ vendor/libremidi/backends/alsa_raw_ump.hpp view
@@ -0,0 +1,30 @@+#pragma once+#include <libremidi/backends/alsa_raw/observer.hpp>+#include <libremidi/backends/alsa_raw_ump/midi_in.hpp>+#include <libremidi/backends/alsa_raw_ump/midi_out.hpp>+#include <libremidi/backends/alsa_raw_ump/observer.hpp>+#include <libremidi/backends/dummy.hpp>++namespace libremidi::alsa_raw_ump+{+struct backend+{+ using midi_in = midi_in_impl;+ using midi_out = midi_out_impl;+ using midi_observer = observer_impl;+ using midi_in_configuration = alsa_raw_ump::input_configuration;+ using midi_out_configuration = alsa_raw_ump::output_configuration;+ struct midi_observer_configuration : alsa_raw_observer_configuration+ {+ };+ static const constexpr auto API = libremidi::API::ALSA_RAW_UMP;+ static const constexpr auto name = "alsa_raw_ump";+ static const constexpr auto display_name = "ALSA (raw UMP)";++ static inline bool available() noexcept+ {+ static const libasound& snd = libasound::instance();+ return snd.available && snd.rawmidi.available && snd.ump.available;+ }+};+}
+ vendor/libremidi/backends/alsa_raw_ump/config.hpp view
@@ -0,0 +1,25 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>++namespace libremidi::alsa_raw_ump+{+struct input_configuration+{+ std::function<bool(const manual_poll_parameters&)> manual_poll;+ std::chrono::milliseconds poll_period{2};+};++struct output_configuration+{+ /**+ * For large messages, chunk their content and wait.+ * Setting a null optional will disable chunking.+ */+ std::optional<chunking_parameters> chunking;+};++struct observer_configuration+{+ std::chrono::milliseconds poll_period{100};+};+}
+ vendor/libremidi/backends/alsa_raw_ump/helpers.hpp view
@@ -0,0 +1,66 @@+#pragma once+#include <libremidi/backends/alsa_raw/helpers.hpp>+#include <libremidi/config.hpp>+#include <libremidi/detail/observer.hpp>++namespace libremidi::alsa_raw_ump+{+struct midi2_enumerator : alsa_raw::enumerator+{+ using alsa_raw::enumerator::enumerator;+ void enumerate_devices(int card) override+ {+ using namespace std::literals;+ char name[128];++ sprintf(name, "hw:%d", card);++ // Open card.+ alsa_raw::snd_ctl_wrapper ctl{*this, name};+ if (!ctl)+ return;++ // Enumerate devices.+ int device = -1;+ do+ {+ const int status = snd.ctl.ump.next_device(ctl, &device);+ if (device == -1)+ return;++ if (status < 0)+ {+ handler.libremidi_handle_error(+ configuration, "Cannot determine device number: "s + snd.strerror(status));+ break;+ }++ if (device >= 0)+ {+ enumerate_blocks(ctl, card, device);+ enumerate_endpoints(ctl, card, device);+ }++ } while (device >= 0);+ }++ void enumerate_endpoints(snd_ctl_t* ctl, [[maybe_unused]] int card, [[maybe_unused]] int device)+ {+ snd_ump_endpoint_info_t* info{};+ snd_ump_endpoint_info_alloca(&info);+ snd.ctl.ump.endpoint_info(ctl, info);++ fprintf(stderr, "UMP endpoint: %s", snd.ump.endpoint_info_get_name(info));+ }++ void enumerate_blocks(snd_ctl_t* ctl, [[maybe_unused]] int card, [[maybe_unused]] int device)+ {+ snd_ump_block_info_t* info{};+ snd_ump_block_info_alloca(&info);+ snd.ctl.ump.block_info(ctl, info);++ fprintf(stderr, "UMP block: %s", snd.ump.block_info_get_name(info));+ }+};++}
+ vendor/libremidi/backends/alsa_raw_ump/midi_in.hpp view
@@ -0,0 +1,349 @@+#pragma once+#include <libremidi/backends/alsa_raw_ump/config.hpp>+#include <libremidi/backends/alsa_raw_ump/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <alsa/asoundlib.h>++#include <atomic>+#include <thread>++namespace libremidi::alsa_raw_ump+{+class midi_in_impl+ : public midi2::in_api+ , public error_handler+{+public:+ struct+ : libremidi::ump_input_configuration+ , alsa_raw_ump::input_configuration+ {+ } configuration;++ const libasound& snd = libasound::instance();++ explicit midi_in_impl(+ libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ fds_.reserve(4);+ assert(snd.ump.available);+ }++ ~midi_in_impl() override { }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }++ // Must be a string such as: "hw:2,4,1"+ [[nodiscard]] stdx::error do_init_port(const char* portname)+ {+ constexpr int mode = 0;+ SND_RAWMIDI_NONBLOCK; // fixme+ if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0)+ {+ libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");+ return from_errc(err);+ }++ snd_rawmidi_params_t* params{};+ snd_rawmidi_params_alloca(¶ms);++ auto rawmidi = snd.ump.rawmidi(midiport_);++ if (int err = snd.ump.rawmidi_params_current(midiport_, params); err < 0)+ return from_errc(err);+ if (int err = snd.rawmidi.params_set_no_active_sensing(rawmidi, params, 1); err < 0)+ return from_errc(err);++ if (configuration.timestamps == timestamp_mode::NoTimestamp)+ {+ if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_STANDARD);+ err < 0)+ return from_errc(err);+ if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_NONE);+ err < 0)+ return from_errc(err);+ }+ else+ {+ if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_TSTAMP);+ err < 0)+ return from_errc(err);+ if (int err+ = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_MONOTONIC);+ err < 0)+ return from_errc(err);+ }++ if (int err = snd.ump.rawmidi_params(midiport_, params); err < 0)+ return from_errc(err);++ return init_pollfd();+ }++ [[nodiscard]] stdx::error init_port(const port_information& p)+ {+ return do_init_port(raw_from_port_handle(p.port).to_string().c_str());+ }++ [[nodiscard]] stdx::error init_pollfd()+ {+ int num_fds = snd.ump.poll_descriptors_count(this->midiport_);++ this->fds_.clear();+ this->fds_.resize(num_fds);++ int ret = snd.ump.poll_descriptors(this->midiport_, fds_.data(), num_fds);+ if (ret < 0)+ return from_errc(ret);+ return stdx::error{};+ }++ ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)+ {+ // Read events+ if (fds.empty())+ {+ return (this->*parse_func)();+ }+ else+ {+ unsigned short res{};+ ssize_t err = snd.ump.poll_descriptors_revents(+ this->midiport_, fds.data(), static_cast<unsigned int>(fds.size()), &res);+ if (err < 0)+ return err;++ // Did we encounter an error during polling+ if (res & (POLLERR | POLLHUP))+ return -EIO;++ // Is there data to read+ if (res & POLLIN)+ return (this->*parse_func)();+ }++ return 0;+ }++ ssize_t read_input_buffer()+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = false,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };+ static const constexpr int nwords = 64;+ uint32_t words[nwords];++ ssize_t err = 0;+ while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0)+ {+ const auto to_ns = [this] { return absolute_timestamp(); };+ m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }+ return err;+ }++ ssize_t read_input_buffer_with_timestamps()+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = true,+ .has_samples = false,+ };++ static const constexpr int nwords = 64;+ uint32_t words[nwords];+ struct timespec ts;++ ssize_t err = 0;+ while ((err = snd.ump.tread(this->midiport_, &ts, words, nwords * 4)) > 0)+ {+ const auto to_ns = [ts] {+ return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+ };+ m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }+ return err;+ }++ stdx::error close_port() override+ {+ if (midiport_)+ snd.ump.close(midiport_);+ midiport_ = nullptr;++ return stdx::error{};+ }++ timestamp absolute_timestamp() const noexcept override { return system_ns(); }++ snd_ump_t* midiport_{};+ std::vector<pollfd> fds_;+ midi2::input_state_machine m_processing{this->configuration};+};++class midi_in_impl_threaded : public midi_in_impl+{+public:+ midi_in_impl_threaded(+ libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)+ : midi_in_impl{std::move(conf), std::move(apiconf)}+ {+ if (this->termination_event < 0)+ {+ libremidi_handle_error(this->configuration, "error creating eventfd.");+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_in_impl_threaded()+ {+ // Close a connection if it exists.+ this->close_port();+ client_open_ = std::errc::not_connected;+ }++private:+ void run_thread(auto parse_func)+ {+ fds_.push_back(this->termination_event);+ const auto period+ = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+ .count();++ for (;;)+ {+ // Poll+ ssize_t err = poll(fds_.data(), fds_.size(), period);+ if (err == -EAGAIN)+ continue;+ else if (err < 0)+ return;+ else if (termination_event.ready(fds_.back()))+ break;++ err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});+ if (err == -EAGAIN)+ continue;+ else if (err < 0)+ return;+ }+ }++ [[nodiscard]] stdx::error start_thread()+ {+ try+ {+ if (configuration.timestamps == timestamp_mode::NoTimestamp)+ {+ this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};+ }+ else+ {+ this->thread_ = std::thread{+ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};+ }+ return stdx::error{};+ }+ catch (const std::system_error& e)+ {+ using namespace std::literals;++ libremidi_handle_error(+ this->configuration,+ "error starting MIDI input thread: "s + e.what());+ return e.code();+ }+ return stdx::error{};+ }++ stdx::error open_port(const input_port& port, [[maybe_unused]] std::string_view name) override+ {+ if (auto err = midi_in_impl::init_port(port); err != stdx::error{})+ return err;+ if (auto err = start_thread(); err != stdx::error{})+ return err;+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ termination_event.notify();+ if (thread_.joinable())+ thread_.join();+ termination_event.consume(); // Reset to zero++ return midi_in_impl::close_port();+ }++ std::thread thread_;+ eventfd_notifier termination_event{};+};++class midi_in_impl_manual : public midi_in_impl+{+public:+ midi_in_impl_manual(ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)+ : midi_in_impl{std::move(conf), std::move(apiconf)}+ {+ client_open_ = stdx::error{};+ }++ ~midi_in_impl_manual()+ {+ // Close a connection if it exists.+ this->close_port();++ client_open_ = std::errc::not_connected;+ }++private:+ void send_poll_callback()+ {+ if (configuration.timestamps == timestamp_mode::NoTimestamp)+ {+ configuration.manual_poll(manual_poll_parameters{+ .fds = {this->fds_.data(), this->fds_.size()},+ .callback = [this](std::span<pollfd> fds) {+ return do_read_events(&midi_in_impl::read_input_buffer, fds);+ }});+ }+ else+ {+ configuration.manual_poll(manual_poll_parameters{+ .fds = {this->fds_.data(), this->fds_.size()},+ .callback = [this](std::span<pollfd> fds) {+ return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);+ }});+ }+ }++ stdx::error open_port(const input_port& p, [[maybe_unused]] std::string_view name) override+ {+ if (auto err = midi_in_impl::init_port(p); err != stdx::error{})+ return err;+ send_poll_callback();+ return stdx::error{};+ }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_in_api> make<alsa_raw_ump::midi_in_impl>(+ libremidi::ump_input_configuration&& conf, libremidi::alsa_raw_ump::input_configuration&& api)+{+ if (api.manual_poll)+ return std::make_unique<alsa_raw_ump::midi_in_impl_manual>(std::move(conf), std::move(api));+ else+ return std::make_unique<alsa_raw_ump::midi_in_impl_threaded>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/alsa_raw_ump/midi_out.hpp view
@@ -0,0 +1,94 @@+#pragma once+#include <libremidi/backends/alsa_raw_ump/config.hpp>+#include <libremidi/backends/alsa_raw_ump/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <alsa/asoundlib.h>++#include <atomic>+#include <thread>++namespace libremidi::alsa_raw_ump+{+class midi_out_impl final+ : public midi2::out_api+ , public error_handler+{+public:+ struct+ : libremidi::output_configuration+ , libremidi::alsa_raw_ump::output_configuration+ {+ } configuration;++ const libasound& snd = libasound::instance();++ midi_out_impl(+ libremidi::output_configuration&& conf,+ libremidi::alsa_raw_ump::output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ assert(snd.ump.available);+ client_open_ = stdx::error{};+ }++ ~midi_out_impl() override+ {+ // Close a connection if it exists.+ midi_out_impl::close_port();+ client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }++ stdx::error connect_port(const char* portname)+ {+ constexpr int mode = SND_RAWMIDI_SYNC;+ int ret = snd.ump.open(NULL, &midiport_, portname, mode);+ if (ret < 0)+ {+ libremidi_handle_error(this->configuration, "cannot open device.");+ return from_errc(ret);+ }+ return stdx::error{};+ }++ stdx::error open_port(const output_port& p, std::string_view) override+ {+ return connect_port(raw_from_port_handle(p.port).to_string().c_str());+ }++ stdx::error close_port() override+ {+ if (midiport_)+ snd.ump.close(midiport_);+ midiport_ = nullptr;+ return stdx::error{};+ }++ stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override+ {+ if (!midiport_)+ libremidi_handle_error(+ this->configuration,+ "trying to send a message without an open "+ "port.");++ return write(ump_stream, count * sizeof(uint32_t));+ }++ stdx::error write(const uint32_t* ump_stream, size_t bytes)+ {+ if (auto err = snd.ump.write(midiport_, ump_stream, bytes); err < 0)+ {+ libremidi_handle_error(this->configuration, "cannot write message.");+ return from_errc(err);+ }++ return stdx::error{};+ }++ snd_ump_t* midiport_{};+};++}
+ vendor/libremidi/backends/alsa_raw_ump/observer.hpp view
@@ -0,0 +1,14 @@+#pragma once+#include <libremidi/backends/alsa_raw/observer.hpp>+#include <libremidi/backends/alsa_raw_ump/helpers.hpp>++namespace libremidi::alsa_raw_ump+{++class observer_impl : public alsa_raw::observer_impl_base<midi2_enumerator>+{+ using alsa_raw::observer_impl_base<midi2_enumerator>::observer_impl_base;+ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }+};++}
+ vendor/libremidi/backends/alsa_seq.hpp view
@@ -0,0 +1,42 @@+#pragma once+//*********************************************************************//+// API: LINUX ALSA SEQUENCER+//*********************************************************************//++// API information found at:+// - http://www.alsa-project.org/documentation.php#Library++// The ALSA Sequencer API is based on the use of a callback function for+// MIDI input.+//+// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer+// time stamps and other assorted fixes!!!++#include <libremidi/backends/alsa_seq/midi_in.hpp>+#include <libremidi/backends/alsa_seq/midi_out.hpp>+#include <libremidi/backends/alsa_seq/observer.hpp>++namespace libremidi::alsa_seq+{++struct backend+{+ using midi_in+ = alsa_seq::midi_in_impl<libremidi::input_configuration, alsa_seq::input_configuration>;+ using midi_out = alsa_seq::midi_out_impl;+ using midi_observer = alsa_seq::observer_impl<alsa_seq::observer_configuration>;+ using midi_in_configuration = alsa_seq::input_configuration;+ using midi_out_configuration = alsa_seq::output_configuration;+ using midi_observer_configuration = alsa_seq::observer_configuration;+ static const constexpr auto API = libremidi::API::ALSA_SEQ;+ static const constexpr auto name = "alsa_seq";+ static const constexpr auto display_name = "ALSA (sequencer)";++ static inline bool available() noexcept+ {+ static const libasound& snd = libasound::instance();+ return snd.available && snd.seq.available;+ }+};++}
+ vendor/libremidi/backends/alsa_seq/config.hpp view
@@ -0,0 +1,65 @@+#pragma once+#include <libremidi/backends/alsa_raw/config.hpp>++#if __has_include(<alsa/asoundlib.h>)+ #include <alsa/asoundlib.h>+#else+extern "C" {+typedef struct _snd_seq snd_seq_t;+typedef struct snd_seq_event snd_seq_event_t;+typedef struct snd_seq_addr+{+ unsigned char client;+ unsigned char port;+} snd_seq_addr_t;+}+#endif++namespace libremidi::alsa_seq+{++// Possible parameters:+// - Direct / non-direct output+// - Timer source used (high resolution, etc)+// - Timestamping+// - Tempo, ppq?++struct poll_parameters+{+ snd_seq_addr_t addr{};+ std::function<int(const snd_seq_event_t&)> callback;+};++struct input_configuration+{+ using poll_parameters_type = poll_parameters;++ std::string client_name = "libremidi client";+ snd_seq_t* context{};+ std::function<bool(const poll_parameters&)> manual_poll;+ std::function<bool(snd_seq_addr_t)> stop_poll;+ std::chrono::milliseconds poll_period{2};++ static constexpr int midi_version = 1;+};++struct output_configuration+{+ std::string client_name = "libremidi client";+ snd_seq_t* context{};++ static constexpr int midi_version = 1;+};++struct observer_configuration+{+ std::string client_name = "libremidi client";+ snd_seq_t* context{};+ std::function<bool(const poll_parameters&)> manual_poll;+ std::function<bool(snd_seq_addr_t)> stop_poll;+ std::chrono::milliseconds poll_period{100};++ static constexpr int midi_version = 1;+};++}
+ vendor/libremidi/backends/alsa_seq/helpers.hpp view
@@ -0,0 +1,308 @@+#pragma once+#include <libremidi/backends/linux/alsa.hpp>+#include <libremidi/config.hpp>+#include <libremidi/detail/observer.hpp>++#include <sys/time.h>++#include <cstring>+#include <optional>+#include <string>++namespace libremidi::alsa_seq+{+struct event_handle+{+ const libasound& snd;+ snd_seq_event_t* ev{};++ explicit event_handle(const libasound& snd) noexcept+ : snd{snd}+ {+ }++ explicit event_handle(const libasound& snd, snd_seq_event_t* ev) noexcept+ : snd{snd}+ , ev{ev}+ {+ }++ event_handle(const event_handle&) noexcept = delete;+ event_handle& operator=(const event_handle&) noexcept = delete;+ event_handle(event_handle&&) noexcept = delete;+ event_handle& operator=(event_handle&&) noexcept = delete;++ void reset(snd_seq_event_t* new_ev) noexcept+ {+ if (ev)+ snd.seq.free_event(ev);+ ev = new_ev;+ }++ ~event_handle() { snd.seq.free_event(ev); }+};++namespace+{+inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept+{+ return (client << 32) + port;+}++inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept+{+ int client = p >> 32;+ int port = p & 0xFFFFFFFF;+ return {client, port};+}++inline void for_all_ports(+ const libasound& snd, snd_seq_t* seq,+ std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)> func)+{+ snd_seq_client_info_t* cinfo{};+ snd_seq_client_info_alloca(&cinfo);+ snd_seq_port_info_t* pinfo{};+ snd_seq_port_info_alloca(&pinfo);++ snd.seq.client_info_set_client(cinfo, -1);+ while (snd.seq.query_next_client(seq, cinfo) >= 0)+ {+ int client = snd.seq.client_info_get_client(cinfo);+ if (client == 0)+ continue;++ // Reset query info+ snd.seq.port_info_set_client(pinfo, client);+ snd.seq.port_info_set_port(pinfo, -1);+ while (snd.seq.query_next_port(seq, pinfo) >= 0)+ {+ func(*cinfo, *pinfo);+ }+ }+}++// This function is used to count or get the pinfo structure for a given port+// number.+inline unsigned int iterate_port_info(+ const libasound& snd, snd_seq_t* seq, snd_seq_port_info_t* pinfo, unsigned int type,+ int portNumber)+{+ snd_seq_client_info_t* cinfo{};+ int count = 0;+ snd_seq_client_info_alloca(&cinfo);++ snd.seq.client_info_set_client(cinfo, -1);+ while (snd.seq.query_next_client(seq, cinfo) >= 0)+ {+ const int client = snd.seq.client_info_get_client(cinfo);+ if (client == 0)+ continue;++ // Reset query info+ snd.seq.port_info_set_client(pinfo, client);+ snd.seq.port_info_set_port(pinfo, -1);+ while (snd.seq.query_next_port(seq, pinfo) >= 0)+ {+ const unsigned int atyp = snd.seq.port_info_get_type(pinfo);+ if (((atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC) == 0) && ((atyp & SND_SEQ_PORT_TYPE_SYNTH) == 0)+ && ((atyp & SND_SEQ_PORT_TYPE_APPLICATION) == 0))+ continue;++ const unsigned int caps = snd.seq.port_info_get_capability(pinfo);+ if ((caps & type) != type)+ continue;+ if ((caps & SND_SEQ_PORT_CAP_NO_EXPORT) != 0)+ continue;+ if (count == portNumber)+ return 1;+ ++count;+ }+ }++ // If a negative portNumber was used, return the port count.+ if (portNumber < 0)+ return count;+ return 0;+}+}++// A structure to hold variables related to the ALSA API+// implementation.+struct alsa_data+{+ const libasound& snd = libasound::instance();+ snd_seq_t* seq{};+ int vport{-1};+ snd_seq_addr_t vaddr{};++ snd_seq_port_subscribe_t* subscription{};+ snd_midi_event_t* coder{};++ [[nodiscard]] int init_client(auto& configuration)+ {+ // Initialize or use the snd_seq client+ if (configuration.context)+ {+ seq = configuration.context;+ return 0;+ }+ else+ {+ // Set up the ALSA sequencer client.+ int ret = snd.seq.open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);+ if (ret < 0)+ return ret;++ // Set client name.+ if (!configuration.client_name.empty())+ snd.seq.set_client_name(seq, configuration.client_name.data());++#if __has_include(<alsa/ump.h>)+ if (snd.seq.ump.set_client_midi_version)+ {+ switch (configuration.midi_version)+ {+ case 1:+ snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI);+ break;+ case 2:+ snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0);+ break;+ }+ }+#endif++ return 0;+ }+ }++ stdx::error set_client_name(std::string_view clientName)+ {+ int ret = snd.seq.set_client_name(seq, clientName.data());+ return from_errc(ret);+ }++ stdx::error set_port_name(std::string_view portName)+ {+ snd_seq_port_info_t* pinfo;+ snd_seq_port_info_alloca(&pinfo);+ snd.seq.get_port_info(seq, vport, pinfo);+ snd.seq.port_info_set_name(pinfo, portName.data());+ int ret = snd.seq.set_port_info(seq, vport, pinfo);+ return from_errc(ret);+ }++ unsigned int get_port_count(int caps) const+ {+ snd_seq_port_info_t* pinfo;+ snd_seq_port_info_alloca(&pinfo);++ return alsa_seq::iterate_port_info(snd, seq, pinfo, caps, -1);+ }++ std::optional<snd_seq_addr_t> get_port_info(const port_information& portNumber)+ {+ auto [client, port] = alsa_seq::seq_from_port_handle(portNumber.port);+ // FIXME check that the {client, port} pair actually exists+ // snd_seq_port_info_t* src_pinfo{};+ // snd_seq_port_info_alloca(&src_pinfo);+ // snd.seq.port_info_set_client(src_pinfo, client);+ // snd.seq.port_info_set_port(src_pinfo, port);++ // {+ // self.libremidi_handle_error(+ // self.configuration,+ // "invalid 'portNumber' argument: " + std::to_string(portNumber));+ // return {};+ // }+ snd_seq_addr_t addr;+ addr.client = client;+ addr.port = port;+ return addr;+ }++ [[nodiscard]] int create_port(+ auto& /*self*/, std::string_view portName, unsigned int caps, unsigned int type,+ std::optional<int> queue)+ {+ if (this->vport < 0)+ {+ snd_seq_port_info_t* pinfo{};+ snd_seq_port_info_alloca(&pinfo);++ snd.seq.port_info_set_name(pinfo, portName.data());+ snd.seq.port_info_set_client(pinfo, 0);+ snd.seq.port_info_set_port(pinfo, 0);+ snd.seq.port_info_set_capability(pinfo, caps);+ snd.seq.port_info_set_type(pinfo, type);++ if (type & SND_SEQ_PORT_TYPE_MIDI_GENERIC)+ {+ snd.seq.port_info_set_midi_channels(pinfo, 16);+ }++ if (queue)+ {+ snd.seq.port_info_set_timestamping(pinfo, 1);+ snd.seq.port_info_set_timestamp_real(pinfo, 1);+ snd.seq.port_info_set_timestamp_queue(pinfo, *queue);+ }++ if (int err = snd.seq.create_port(this->seq, pinfo); err < 0)+ return err;++ this->vport = snd.seq.port_info_get_port(pinfo);+ if (int err = snd.seq.get_port_info(this->seq, this->vport, pinfo); err < 0)+ return err;++ if (auto addr = snd.seq.port_info_get_addr(pinfo))+ this->vaddr = *addr;+ else+ return -1;++ return this->vport;+ }+ return 0;+ }++ int create_connection(auto& self, snd_seq_addr_t sender, snd_seq_addr_t receiver, bool realtime)+ {+ // Create the connection between ports+ // Make subscription+ if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0)+ {+ self.libremidi_handle_error(+ self.configuration, "ALSA error allocation port subscription.");+ return err;+ }++ snd.seq.port_subscribe_set_sender(this->subscription, &sender);+ snd.seq.port_subscribe_set_dest(this->subscription, &receiver);++ if (realtime)+ {+ snd.seq.port_subscribe_set_time_update(this->subscription, 1);+ snd.seq.port_subscribe_set_time_real(this->subscription, 1);+ }++ if (int err = snd.seq.subscribe_port(this->seq, this->subscription); err != 0)+ {+ snd.seq.port_subscribe_free(this->subscription);+ this->subscription = nullptr;+ return err;+ }+ return 0;+ }++ void unsubscribe()+ {+ if (this->subscription)+ {+ snd.seq.unsubscribe_port(this->seq, this->subscription);+ snd.seq.port_subscribe_free(this->subscription);+ this->subscription = nullptr;+ }+ }+};+}
+ vendor/libremidi/backends/alsa_seq/midi_in.hpp view
@@ -0,0 +1,549 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi::alsa_seq+{+struct dummy_processing+{+ explicit dummy_processing(auto&&...) { }+};++template <typename ConfigurationImpl>+using midi_in_base+ = std::conditional_t<ConfigurationImpl::midi_version == 1, midi1::in_api, midi2::in_api>;+template <typename ConfigurationImpl>+using midi_in_processing = std::conditional_t<+ ConfigurationImpl::midi_version == 1, midi1::input_state_machine, midi2::input_state_machine>;++template <typename ConfigurationBase, typename ConfigurationImpl>+class midi_in_impl+ : public midi_in_base<ConfigurationImpl>+ , protected alsa_data+ , public error_handler+{+public:+ struct+ : ConfigurationBase+ , ConfigurationImpl+ {+ } configuration;+ midi_in_processing<ConfigurationImpl> m_processing{this->configuration};++ bool require_timestamps() const noexcept+ {+ switch (configuration.timestamps)+ {+ case timestamp_mode::NoTimestamp:+ case timestamp_mode::SystemMonotonic:+ case timestamp_mode::AudioFrame:+ return false;+ case timestamp_mode::Absolute:+ case timestamp_mode::Relative:+ case timestamp_mode::Custom:+ return true;+ }+ return true;+ }++ explicit midi_in_impl(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)+ : midi_in_base<ConfigurationImpl>{}+ , configuration{std::move(conf), std::move(apiconf)}+ {+ if (init_client(configuration) < 0)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating ALSA sequencer client "+ "object.");+ return;+ }++ // Create the input queue+ if (require_timestamps())+ {+ this->queue_id = snd.seq.alloc_queue(seq);+ // Set arbitrary tempo (mm=100) and resolution (240)+ snd_seq_queue_tempo_t* qtempo{};+ snd_seq_queue_tempo_alloca(&qtempo);+ snd.seq.queue_tempo_set_tempo(qtempo, 600000);+ snd.seq.queue_tempo_set_ppq(qtempo, 240);+ snd.seq.set_queue_tempo(this->seq, this->queue_id, qtempo);+ snd.seq.drain_output(this->seq);+ }++ // Create the event -> midi encoder+ {+ int result = snd.midi.event_new(0, &coder);+ if (result < 0)+ {+ libremidi_handle_error(this->configuration, "error during snd_midi_event_new.");+ return;+ }+ snd.midi.event_init(coder);+ snd.midi.event_no_status(coder, 1);+ }+ }++ ~midi_in_impl() override+ {+ // Cleanup.+ if (this->vport >= 0)+ snd.seq.delete_port(this->seq, this->vport);++ if (require_timestamps())+ snd.seq.free_queue(this->seq, this->queue_id);++ snd.midi.event_free(coder);++ // Close if we do not have an user-provided client object+ if (!configuration.context)+ snd.seq.close(this->seq);+ }++ libremidi::API get_current_api() const noexcept override+ {+ if constexpr (ConfigurationImpl::midi_version == 1)+ return libremidi::API::ALSA_SEQ;+ else+ return libremidi::API::ALSA_SEQ_UMP;+ }++ [[nodiscard]] int create_port(std::string_view portName)+ {+ return alsa_data::create_port(+ *this, portName, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,+ SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION,+ require_timestamps() ? std::optional<int>{this->queue_id} : std::nullopt);+ }++ void start_queue()+ {+ if (require_timestamps())+ {+ snd.seq.control_queue(this->seq, this->queue_id, SND_SEQ_EVENT_START, 0, nullptr);+ this->queue_creation_time = std::chrono::steady_clock::now();+ snd.seq.drain_output(this->seq);+ }+ }++ void stop_queue()+ {+ if (require_timestamps())+ {+ snd.seq.control_queue(this->seq, this->queue_id, SND_SEQ_EVENT_STOP, 0, nullptr);+ snd.seq.drain_output(this->seq);+ }+ }++ int connect_port(snd_seq_addr_t sender)+ {+ snd_seq_addr_t receiver{};+ receiver.client = snd.seq.client_id(this->seq);+ receiver.port = this->vport;++ return create_connection(*this, sender, receiver, false);+ }++ std::optional<snd_seq_addr_t> to_address(const port_information& p)+ {+ return alsa_data::get_port_info(p);+ }++ int init_port(std::optional<snd_seq_addr_t> source, std::string_view portName)+ {+ this->close_port();++ if (!source)+ return -1;++ if (int ret = create_port(portName); ret < 0)+ {+ libremidi_handle_error(configuration, "ALSA error creating port.");+ return ret;+ }++ if (int ret = connect_port(*source); ret < 0)+ {+ libremidi_handle_error(configuration, "ALSA error making port connection.");+ return ret;+ }++ start_queue();++ return 0;+ }++ int init_virtual_port(std::string_view portName)+ {+ this->close_port();++ if (int ret = create_port(portName); ret < 0)+ return ret;++ start_queue();+ return 0;+ }++ stdx::error close_port() override+ {+ unsubscribe();+ stop_queue();+ return stdx::error{};+ }++ stdx::error set_client_name(std::string_view clientName) override+ {+ return alsa_data::set_client_name(clientName);+ }++ stdx::error set_port_name(std::string_view portName) override+ {+ return alsa_data::set_port_name(portName);+ }++ timestamp absolute_timestamp() const noexcept override+ {+ return std::chrono::duration_cast<std::chrono::nanoseconds>(+ std::chrono::steady_clock::now() - this->queue_creation_time)+ .count();+ }++ int process_event(const snd_seq_event_t& ev)+ {+ if constexpr (ConfigurationImpl::midi_version == 1)+ {+ switch (ev.type)+ {+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:+ return 0;+ case SND_SEQ_EVENT_SYSEX: {+ if (configuration.ignore_sysex)+ return 0;+ else if (ev.data.ext.len > decoding_buffer.size())+ decoding_buffer.resize(ev.data.ext.len);+ break;+ }+ }++ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ const auto to_ns = [ts = ev.time.time] {+ return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+ };+ auto buf = decoding_buffer.data();+ auto buf_space = decoding_buffer.size();++ // FIXME according to the doc snd_midi_event_decode can apparently return multiple events????+ const auto avail = snd.midi.event_decode(coder, buf, buf_space, &ev);+ if (avail > 0)+ {+ m_processing.on_bytes(+ {buf, buf + avail}, m_processing.template timestamp<timestamp_info>(to_ns, 0));+ return 0;+ }+ else+ {+ return avail;+ }+ }+ return 0;+ }++ int process_events()+ {+ if constexpr (ConfigurationImpl::midi_version == 1)+ {+ snd_seq_event_t* ev{};+ event_handle handle{snd};+ int result = 0;+ if ((result = snd.seq.event_input(seq, &ev)) > 0)+ {+ handle.reset(ev);+ if (int err = process_event(*ev); err < 0)+ {+ return err;+ }+ }+ return result;+ }+ else+ {+ return 0;+ }+ }++#if __has_include(<alsa/ump.h>)+ int process_ump_event(const snd_seq_ump_event_t& ev)+ {+ // Filter the message types before any decoding+ switch (ev.type)+ {+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:+ return 0;++ case SND_SEQ_EVENT_QFRAME: // MIDI time code+ case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick+ case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick+ if (configuration.ignore_timing)+ return 0;+ break;++ case SND_SEQ_EVENT_SENSING: // Active sensing+ if (configuration.ignore_sensing)+ return 0;+ break;++ case SND_SEQ_EVENT_SYSEX: {+ if (configuration.ignore_sysex)+ return 0;+ break;+ }+ }++ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };+ const auto to_ns = [ts = ev.time.time] {+ return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);+ };++ m_processing.on_bytes_multi(+ {ev.ump, ev.ump + 4}, m_processing.template timestamp<timestamp_info>(to_ns, 0));+ return 0;+ }++ int process_ump_events()+ {+ snd_seq_ump_event_t* ev{};+ event_handle handle{snd};+ int result = 0;+ while ((result = snd.seq.ump.event_input(seq, &ev)) > 0)+ {+ handle.reset((snd_seq_event_t*)ev);+ if (int err = process_ump_event(*ev); err < 0)+ return err;+ }+ return result;+ }+#endif++ int queue_id{}; // an input queue is needed to get timestamped events++ // Only needed for midi 1+ std::vector<unsigned char> decoding_buffer = std::vector<unsigned char>(4096);+ std::chrono::steady_clock::time_point queue_creation_time;+};++template <typename ConfigurationBase, typename ConfigurationImpl>+class midi_in_alsa_threaded : public midi_in_impl<ConfigurationBase, ConfigurationImpl>+{+public:+ midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)+ : midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}+ {+ if (this->termination_event < 0)+ {+ this->libremidi_handle_error(this->configuration, "error creating eventfd.");+ return;+ }++ this->client_open_ = stdx::error{};+ }++ ~midi_in_alsa_threaded()+ {+ this->close_port();+ this->client_open_ = std::errc::not_connected;+ }++private:+ stdx::error open_port(const input_port& pt, std::string_view local_port_name) override+ {+ if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)+ return from_errc(err);++ return this->start_thread();+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ if (int err = this->init_virtual_port(portName); err < 0)+ return from_errc(err);++ return this->start_thread();+ }++ stdx::error close_port() override+ {+ // FIXME shouldn't we always close the thread+ auto err = midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();++ stop_thread();++ return err;+ }++ [[nodiscard]] stdx::error start_thread()+ {+ try+ {+ this->thread = std::thread([this] { thread_handler(); });+ return stdx::error{};+ }+ catch (const std::system_error& e)+ {+ using namespace std::literals;+ this->unsubscribe();++ this->libremidi_handle_error(+ this->configuration, "error starting MIDI input thread: "s + e.what());+ return e.code();+ }+ }++ stdx::error stop_thread()+ {+ termination_event.notify();++ if (this->thread.joinable())+ this->thread.join();++ termination_event.consume();+ return stdx::error{};+ }++ void thread_handler()+ {+ int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1;+ auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd));+ poll_fds[0] = this->termination_event;+ alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);++ const auto period+ = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+ .count();+ for (;;)+ {+ if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)+ {+ // No data pending+ if (poll(poll_fds, poll_fd_count, period) >= 0)+ {+ // We got our stop-thread signal+ if (termination_event.ready(poll_fds[0]))+ {+ break;+ }+ }+ continue;+ }++ int res{};+ if constexpr (ConfigurationImpl::midi_version == 1)+ {+ res = this->process_events();+ }+#if __has_include(<alsa/ump.h>)+ else if constexpr (ConfigurationImpl::midi_version == 2)+ {+ res = this->process_ump_events();+ }+#endif++ if (res < 0)+ LIBREMIDI_LOG("MIDI input error: ", this->snd.strerror(res));+ }+ }++ std::thread thread{};+ eventfd_notifier termination_event{};+};++template <typename ConfigurationBase, typename ConfigurationImpl>+class midi_in_alsa_manual : public midi_in_impl<ConfigurationBase, ConfigurationImpl>+{+public:+ midi_in_alsa_manual(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)+ : midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}+ {+ assert(this->configuration.manual_poll);+ assert(this->configuration.stop_poll);+ }++ [[nodiscard]] int init_callback()+ {+ using poll_params = typename ConfigurationImpl::poll_parameters_type;+ this->configuration.manual_poll(+ poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) {+ if constexpr (ConfigurationImpl::midi_version == 1)+ return this->process_event(ev);+#if __has_include(<alsa/ump.h>)+ else+ return this->process_ump_event(ev);+#endif+ }});+ return 0;+ }++ ~midi_in_alsa_manual() { this->close_port(); }++ stdx::error open_port(const input_port& pt, std::string_view local_port_name) override+ {+ if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)+ return from_errc(err);++ if (int err = init_callback(); err < 0)+ return from_errc(err);++ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view name) override+ {+ if (int err = this->init_virtual_port(name); err < 0)+ return from_errc(err);++ if (int err = init_callback(); err < 0)+ return from_errc(err);++ return stdx::error{};+ }++ stdx::error close_port() override+ {+ this->configuration.stop_poll(this->vaddr);++ return midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();+ }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_in_api>+make<alsa_seq::midi_in_impl<libremidi::input_configuration, alsa_seq::input_configuration>>(+ libremidi::input_configuration&& conf, libremidi::alsa_seq::input_configuration&& api)+{+ if (api.manual_poll)+ return std::make_unique<alsa_seq::midi_in_alsa_manual<+ libremidi::input_configuration, alsa_seq::input_configuration>>(+ std::move(conf), std::move(api));+ else+ return std::make_unique<alsa_seq::midi_in_alsa_threaded<+ libremidi::input_configuration, alsa_seq::input_configuration>>(+ std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/alsa_seq/midi_out.hpp view
@@ -0,0 +1,179 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi::alsa_seq+{++class midi_out_impl final+ : public midi1::out_api+ , private alsa_data+ , public error_handler+{+public:+ struct+ : libremidi::output_configuration+ , alsa_seq::output_configuration+ {+ } configuration;++ midi_out_impl(libremidi::output_configuration&& conf, alsa_seq::output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (init_client(configuration) < 0)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating ALSA sequencer client "+ "object.");+ return;+ }++ if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)+ {+ libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");+ return;+ }+ snd.midi.event_init(this->coder);++ this->client_open_ = stdx::error{};+ }++ ~midi_out_impl() override+ {+ // Close a connection if it exists.+ midi_out_impl::close_port();++ // Cleanup.+ if (this->vport >= 0)+ snd.seq.delete_port(this->seq, this->vport);+ if (this->coder)+ snd.midi.event_free(this->coder);++ if (!configuration.context)+ snd.seq.close(this->seq);++ client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }++ [[nodiscard]] int create_port(std::string_view portName)+ {+ return alsa_data::create_port(+ *this, portName, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,+ SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);+ }++ stdx::error open_port(const output_port& p, std::string_view portName) override+ {+ unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);+ if (nSrc < 1)+ {+ libremidi_handle_error(this->configuration, "no MIDI output sources found!");+ return make_error_code(std::errc::no_such_device);+ }++ auto sink = get_port_info(p);+ if (!sink)+ return std::errc::invalid_argument;++ if (int err = create_port(portName); err < 0)+ {+ libremidi_handle_error(configuration, "ALSA error creating port.");+ return from_errc(err);+ }++ snd_seq_addr_t source{+ .client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};+ if (int err = create_connection(*this, source, *sink, true); err < 0)+ {+ libremidi_handle_error(configuration, "ALSA error making port connection.");+ return from_errc(err);+ }++ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ if (int err = create_port(portName); err < 0)+ return from_errc(err);+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ unsubscribe();+ return stdx::error{};+ }++ stdx::error set_client_name(std::string_view clientName) override+ {+ return alsa_data::set_client_name(clientName);+ }++ stdx::error set_port_name(std::string_view portName) override+ {+ return alsa_data::set_port_name(portName);+ }++ stdx::error send_message(const unsigned char* message, std::size_t size) override+ {+ int64_t result{};+ if (size > this->bufferSize)+ {+ this->bufferSize = size;+ result = snd.midi.event_resize_buffer(this->coder, size);+ if (result != 0)+ {+ libremidi_handle_error(+ this->configuration,+ "ALSA error resizing MIDI event "+ "buffer.");+ return std::errc::no_buffer_space;+ }+ }++ std::size_t offset = 0;+ while (offset < size)+ {+ snd_seq_event_t ev;+ snd_seq_ev_clear(&ev);+ snd_seq_ev_set_source(&ev, this->vport);+ snd_seq_ev_set_subs(&ev);+ // FIXME direct is set but snd_seq_event_output_direct is not used...+ snd_seq_ev_set_direct(&ev);++ const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below+ result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev);+ if (result < 0)+ {+ libremidi_handle_warning(this->configuration, "event parsing error!");+ return std::errc::bad_message;+ }++ if (ev.type == SND_SEQ_EVENT_NONE)+ {+ libremidi_handle_warning(this->configuration, "incomplete message!");+ return std::errc::message_size;+ }++ offset += result;++ result = snd.seq.event_output(this->seq, &ev);+ if (result < 0)+ {+ libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");+ return std::errc::io_error;+ }+ }+ snd.seq.drain_output(this->seq);+ return stdx::error{};+ }++private:+ uint64_t bufferSize{32};+};+}
+ vendor/libremidi/backends/alsa_seq/observer.hpp view
@@ -0,0 +1,355 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/detail/observer.hpp>++#include <alsa/asoundlib.h>++#include <bitset>+#include <map>++namespace libremidi::alsa_seq+{++struct port_info+{+ std::string client_name;+ std::string port_name;+ int client{};+ int port{};+ bool isInput{};+ bool isOutput{};+};++template <typename ConfigurationImpl>+class observer_impl+ : public observer_api+ , public alsa_data+ , public error_handler+{+public:+ struct+ : libremidi::observer_configuration+ , ConfigurationImpl+ {+ } configuration;++ explicit observer_impl(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ using namespace std::literals;+ if (int err = init_client(configuration); err < 0)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating ALSA sequencer client "+ "object.");+ return;+ }++ if (!configuration.has_callbacks())+ return;++ // Init with the existing ports+ if (configuration.notify_in_constructor)+ init_all_ports();++ // Create the port to listen on the server events+ {+#if __has_include(<alsa/ump.h>)+ constexpr int midi2_cap+ = ConfigurationImpl::midi_version == 2 ? SND_SEQ_PORT_CAP_UMP_ENDPOINT : 0;+#else+ constexpr int midi2_cap = 0;+#endif++ constexpr int caps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE+ | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE | midi2_cap;+ int err = alsa_data::create_port(+ *this, "libremidi-observe", caps, SND_SEQ_PORT_TYPE_APPLICATION, false);+ if (err < 0)+ {+ libremidi_handle_error(this->configuration, "error creating ALSA sequencer port.");+ return;+ }+ }++ // Connect the ALSA server events to our port+ {+ int err+ = snd.seq.connect_from(seq, vport, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);+ if (err < 0)+ {+ libremidi_handle_error(this->configuration, "error connecting to ALSA sequencer.");+ return;+ }+ }+ }++ std::optional<port_info> get_info(int client, int port) const noexcept+ {+ port_info p;+ p.client = client;+ p.port = port;++ snd_seq_client_info_t* cinfo;+ snd_seq_client_info_alloca(&cinfo);+ if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0)+ return std::nullopt;++ snd_seq_port_info_t* pinfo;+ snd_seq_port_info_alloca(&pinfo);+ if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0)+ return std::nullopt;++ const auto tp = snd.seq.port_info_get_type(pinfo);+ bool ok = this->configuration.track_any;++ static constexpr auto virtual_port+ = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER;++ if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)+ ok = true;+ else if ((tp & virtual_port) && this->configuration.track_virtual)+ ok = true;+ if (!ok)+ return {};++ if (auto name = snd.seq.client_info_get_name(cinfo))+ p.client_name = name;++ if (auto name = snd.seq.port_info_get_name(pinfo))+ p.port_name = name;++ auto cap = snd.seq.port_info_get_capability(pinfo);+ p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ);+ p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE);++ return p;+ }++ template <bool Input>+ auto to_port_info(port_info p) const noexcept+ -> std::conditional_t<Input, input_port, output_port>+ {+ static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle));+ static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle));+ return {+ {.client = std::uintptr_t(this->seq),+ .port = alsa_seq::seq_to_port_handle(p.client, p.port),+ .manufacturer = "",+ .device_name = p.client_name,+ .port_name = p.port_name,+ .display_name = p.port_name}};+ }++ void init_all_ports()+ {+ alsa_seq::for_all_ports(+ snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {+ int clt = snd.seq.client_info_get_client(&client);+ int pt = snd.seq.port_info_get_port(&port);+ register_port(clt, pt);+ });+ }++ libremidi::API get_current_api() const noexcept override+ {+ if constexpr (ConfigurationImpl::midi_version == 1)+ return libremidi::API::ALSA_SEQ;+ else+ return libremidi::API::ALSA_SEQ_UMP;+ }++ std::vector<libremidi::input_port> get_input_ports() const noexcept override+ {+ std::vector<libremidi::input_port> ret;+ alsa_seq::for_all_ports(+ snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {+ int clt = snd.seq.client_info_get_client(&client);+ int pt = snd.seq.port_info_get_port(&port);+ if (auto p = get_info(clt, pt))+ if (p->isInput)+ ret.push_back(to_port_info<true>(*p));+ });+ return ret;+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept override+ {+ std::vector<libremidi::output_port> ret;+ alsa_seq::for_all_ports(+ snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {+ int clt = snd.seq.client_info_get_client(&client);+ int pt = snd.seq.port_info_get_port(&port);+ if (auto p = get_info(clt, pt))+ if (p->isOutput)+ ret.push_back(to_port_info<false>(*p));+ });+ return ret;+ }++ void register_port(int client, int port)+ {+ auto pp = get_info(client, port);+ if (!pp)+ return;+ auto& p = *pp;+ if (p.client == snd.seq.client_id(seq))+ return;++ knownClients_[{p.client, p.port}] = p;+ if (p.isInput && configuration.input_added)+ {+ configuration.input_added(to_port_info<true>(p));+ }++ if (p.isOutput && configuration.output_added)+ {+ configuration.output_added(to_port_info<false>(p));+ }+ }++ void unregister_port(int client, int port)+ {+ auto it = knownClients_.find({client, port});+ if (it != knownClients_.end())+ {+ auto p = it->second;+ knownClients_.erase(it);++ if (p.isInput && configuration.input_removed)+ {+ configuration.input_removed(to_port_info<true>(p));+ }++ if (p.isOutput && configuration.output_removed)+ {+ configuration.output_removed(to_port_info<false>(p));+ }+ }+ }++ void handle_event(const snd_seq_event_t& ev)+ {+ switch (ev.type)+ {+ case SND_SEQ_EVENT_PORT_START: {+ register_port(ev.data.addr.client, ev.data.addr.port);+ break;+ }+ case SND_SEQ_EVENT_PORT_EXIT: {+ unregister_port(ev.data.addr.client, ev.data.addr.port);+ break;+ }+ case SND_SEQ_EVENT_PORT_CHANGE:+ default:+ break;+ }+ }++ ~observer_impl()+ {+ if (seq)+ {+ if (vport)+ snd.seq.delete_port(seq, vport);++ if (!configuration.context)+ snd.seq.close(seq);+ }+ }++private:+ std::map<std::pair<int, int>, port_info> knownClients_;+};++template <typename ConfigurationImpl>+class observer_threaded : public observer_impl<ConfigurationImpl>+{+public:+ observer_threaded(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)+ : observer_impl<ConfigurationImpl>{std::move(conf), std::move(apiconf)}+ {+ // Create relevant descriptors+ auto& snd = alsa_data::snd;+ const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN);+ descriptors_.resize(N + 1);+ snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN);+ descriptors_.back() = this->termination_event;++ // Start the listening thread+ thread = std::thread{[this] {+ auto& snd = alsa_data::snd;+ const auto period+ = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)+ .count();+ for (;;)+ {+ int err = poll(descriptors_.data(), descriptors_.size(), period);+ if (err >= 0)+ {+ // We got our stop-thread signal+ if (descriptors_.back().revents & POLLIN)+ break;++ snd_seq_event_t* ev{};+ event_handle handle{snd};+ while (snd.seq.event_input(this->seq, &ev) >= 0)+ {+ handle.reset(ev);+ this->handle_event(*ev);+ }+ }+ }+ }};+ }++ ~observer_threaded()+ {+ termination_event.notify();++ if (thread.joinable())+ thread.join();+ }++ eventfd_notifier termination_event{};+ std::thread thread;+ std::vector<pollfd> descriptors_;+};++template <typename ConfigurationImpl>+class observer_manual : public observer_impl<ConfigurationImpl>+{+public:+ observer_manual(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)+ : observer_impl<ConfigurationImpl>{std::move(conf), std::move(apiconf)}+ {+ this->configuration.manual_poll(+ poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) {+ this->handle_event(v);+ return 0;+ }});+ }++ ~observer_manual() { this->configuration.stop_poll(this->vaddr); }+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<observer_api>+make<alsa_seq::observer_impl<alsa_seq::observer_configuration>>(+ libremidi::observer_configuration&& conf, libremidi::alsa_seq::observer_configuration&& api)+{+ if (api.manual_poll)+ return std::make_unique<alsa_seq::observer_manual<alsa_seq::observer_configuration>>(+ std::move(conf), std::move(api));+ else+ return std::make_unique<alsa_seq::observer_threaded<alsa_seq::observer_configuration>>(+ std::move(conf), std::move(api));+}+}
@@ -0,0 +1,197 @@+#pragma once+#if __has_include(<boost/lockfree/spsc_queue.hpp>)+ #include <libremidi/backends/alsa_seq/config.hpp>+ #include <libremidi/backends/alsa_seq/helpers.hpp>+ #include <libremidi/backends/linux/helpers.hpp>+ #include <libremidi/shared_context.hpp>++ #include <boost/lockfree/spsc_queue.hpp>++ #include <variant>++namespace libremidi::alsa_seq+{++struct shared_handler : public libremidi::shared_context+{+ const libasound& snd = libasound::instance();+ struct equals_addr+ {+ constexpr bool operator()(const snd_seq_addr_t& lhs, const snd_seq_addr_t& rhs) noexcept+ {+ return lhs.client == rhs.client && lhs.port == rhs.port;+ }+ };++ explicit shared_handler(std::string_view v)+ {+ if (int err = snd.seq.open(&client, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); err < 0)+ {+ client = nullptr;+ // fixme throw?+ return;+ }++ if (!v.empty())+ snd.seq.set_client_name(client, v.data());++ // Last descriptor is the eventfd one+ int fds_size = snd.seq.poll_descriptors_count(client, POLLIN);+ fds.reserve(fds_size + 2);+ fds.resize(fds_size);+ snd.seq.poll_descriptors(client, fds.data(), fds_size, POLLIN);+ fds.push_back(termination_event);+ fds.push_back(queue_event);+ }++ void start_processing() override+ {+ thread = std::thread{[this] { process(); }};+ }++ void stop_processing() override+ {+ termination_event.notify();+ if (thread.joinable())+ thread.join();+ termination_event.consume();+ }++ static shared_configurations make(std::string_view client_name)+ {+ auto clt = std::make_shared<shared_handler>(client_name);++ auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) {+ if (auto clt = client.lock())+ {+ clt->events.push(+ {.type = shared_handler::event_type::callback_added, .payload = std::move(params)});+ clt->queue_event.notify();+ }+ return true;+ };++ auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) {+ if (auto clt = client.lock())+ {+ clt->events.push(+ {.type = shared_handler::event_type::callback_removed, .payload = std::move(id)});+ clt->queue_event.notify();+ }+ return true;+ };+ return {+ .context = clt,+ .observer = alsa_seq::+ observer_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb},+ .in = alsa_seq::+ input_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb},+ .out = alsa_seq::output_configuration{.context = clt->client},+ };+ }++ int64_t index_of_address(snd_seq_addr_t addr)+ {+ auto it = std::find_if(addresses.begin(), addresses.end(), [=](snd_seq_addr_t other) {+ return equals_addr{}(addr, other);+ });+ if (it != addresses.end())+ {+ return std::distance(addresses.begin(), it);+ }+ else+ {+ return -1;+ }+ }++ void process()+ {+ for (;;)+ {+ int err = poll(fds.data(), fds.size(), -1);+ if (err < 0)+ return;+ // Check for termination signal+ if (termination_event.ready(fds[fds.size() - 2]))+ return;++ // Check for queue processing signal+ if (queue_event.ready(fds[fds.size() - 1]))+ {+ this->queue_event.consume();++ event ev;+ while (this->events.pop(ev))+ {+ switch (ev.type)+ {+ case callback_added: {+ auto [addr, cb]+ = std::move(*std::get_if<libremidi::alsa_seq::poll_parameters>(&ev.payload));+ addresses.push_back(addr);+ callbacks.push_back(std::move(cb));+ break;+ }+ case callback_removed:+ auto addr = std::move(*std::get_if<snd_seq_addr_t>(&ev.payload));+ if (auto index = index_of_address(addr); index >= 0)+ {+ addresses.erase(addresses.begin() + index);+ callbacks.erase(callbacks.begin() + index);+ }+ break;+ }+ }+ }++ // Look for who's ready+ for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++)+ {+ if (fds[i].revents & POLLIN)+ {+ // Read alsa event+ snd_seq_event_t* ev{};+ event_handle handle{snd};+ int result = 0;+ while ((result = snd.seq.event_input(client, &ev)) > 0)+ {+ handle.reset(ev);++ if (auto index = index_of_address(ev->dest); index >= 0)+ {+ // Dispatch the event to the correct observer or midi_in object+ int err = callbacks[index](*ev);+ if (err < 0 && err != -EAGAIN)+ return;+ }+ }+ }+ }+ }+ }++ ~shared_handler() { snd.seq.close(client); }++ enum event_type+ {+ callback_added,+ callback_removed,+ };+ struct event+ {+ event_type type;+ std::variant<libremidi::alsa_seq::poll_parameters, snd_seq_addr_t> payload;+ };++ snd_seq_t* client{};++ boost::lockfree::spsc_queue<event> events{16};+ std::vector<snd_seq_addr_t> addresses;+ std::vector<std::function<int(const snd_seq_event_t&)>> callbacks;+ std::vector<pollfd> fds;+ eventfd_notifier termination_event, queue_event{false};+ std::thread thread;+};+}+#endif
+ vendor/libremidi/backends/alsa_seq_ump.hpp view
@@ -0,0 +1,62 @@+#pragma once++#include <libremidi/backends/alsa_seq/midi_in.hpp>+#include <libremidi/backends/alsa_seq/observer.hpp>+#include <libremidi/backends/alsa_seq_ump/config.hpp>+#include <libremidi/backends/alsa_seq_ump/midi_out.hpp>++namespace libremidi+{+template <>+inline std::unique_ptr<observer_api>+make<alsa_seq::observer_impl<alsa_seq_ump::observer_configuration>>(+ libremidi::observer_configuration&& conf,+ libremidi::alsa_seq_ump::observer_configuration&& api)+{+ if (api.manual_poll)+ return std::make_unique<alsa_seq::observer_manual<alsa_seq_ump::observer_configuration>>(+ std::move(conf), std::move(api));+ else+ return std::make_unique<alsa_seq::observer_threaded<alsa_seq_ump::observer_configuration>>(+ std::move(conf), std::move(api));+}++template <>+inline std::unique_ptr<midi_in_api> make<+ alsa_seq::midi_in_impl<libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(+ libremidi::ump_input_configuration&& conf, libremidi::alsa_seq_ump::input_configuration&& api)+{+ if (api.manual_poll)+ return std::make_unique<alsa_seq::midi_in_alsa_manual<+ libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(+ std::move(conf), std::move(api));+ else+ return std::make_unique<alsa_seq::midi_in_alsa_threaded<+ libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(+ std::move(conf), std::move(api));+}++}++namespace libremidi::alsa_seq_ump+{+struct backend+{+ using midi_in = alsa_seq::midi_in_impl<+ libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>;+ using midi_out = alsa_seq_ump::midi_out_impl;+ using midi_observer = alsa_seq::observer_impl<alsa_seq_ump::observer_configuration>;+ using midi_in_configuration = alsa_seq_ump::input_configuration;+ using midi_out_configuration = alsa_seq_ump::output_configuration;+ using midi_observer_configuration = alsa_seq_ump::observer_configuration;+ static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP;+ static const constexpr auto name = "alsa_seq_ump";+ static const constexpr auto display_name = "ALSA (sequencer, UMP)";++ static inline bool available() noexcept+ {+ static const libasound& snd = libasound::instance();+ return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available;+ }+};+}
+ vendor/libremidi/backends/alsa_seq_ump/config.hpp view
@@ -0,0 +1,55 @@+#pragma once+#include <libremidi/backends/alsa_seq/config.hpp>++#if __has_include(<alsa/asoundlib.h>)+ #include <alsa/asoundlib.h>+#endif++// Necessary for the versions of ALSA that did have UMP+// through snd_seq_event, before snd_seq_ump_event existed.+extern "C" {+typedef struct snd_seq_ump_event snd_seq_ump_event_t;+}++namespace libremidi::alsa_seq_ump+{++struct poll_parameters+{+ snd_seq_addr_t addr{};+ std::function<int(const snd_seq_ump_event_t&)> callback;+};++struct input_configuration+{+ using poll_parameters_type = poll_parameters;++ std::string client_name = "libremidi client";+ snd_seq_t* context{};+ std::function<bool(const poll_parameters&)> manual_poll;+ std::function<bool(snd_seq_addr_t)> stop_poll;+ std::chrono::milliseconds poll_period{2};++ static constexpr int midi_version = 2;+};++struct output_configuration+{+ std::string client_name = "libremidi client";+ snd_seq_t* context{};++ static constexpr int midi_version = 2;+};++struct observer_configuration+{+ std::string client_name = "libremidi client";+ snd_seq_t* context{};+ std::function<bool(const libremidi::alsa_seq::poll_parameters&)> manual_poll;+ std::function<bool(snd_seq_addr_t)> stop_poll;+ std::chrono::milliseconds poll_period{100};++ static constexpr int midi_version = 2;+};++}
+ vendor/libremidi/backends/alsa_seq_ump/helpers.hpp view
+ vendor/libremidi/backends/alsa_seq_ump/midi_out.hpp view
@@ -0,0 +1,144 @@+#pragma once+#include <libremidi/backends/alsa_seq/helpers.hpp>+#include <libremidi/backends/alsa_seq_ump/config.hpp>+#include <libremidi/backends/alsa_seq_ump/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>+#include <libremidi/detail/ump_stream.hpp>++namespace libremidi::alsa_seq_ump+{++class midi_out_impl final+ : public midi2::out_api+ , private alsa_seq::alsa_data+ , public error_handler+{+public:+ struct+ : libremidi::output_configuration+ , alsa_seq_ump::output_configuration+ {+ } configuration;++ midi_out_impl(+ libremidi::output_configuration&& conf, alsa_seq_ump::output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ assert(snd.seq.ump.available);+ if (init_client(configuration) < 0)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating ALSA sequencer client "+ "object.");+ return;+ }++ this->client_open_ = stdx::error{};+ }++ ~midi_out_impl() override+ {+ // Close a connection if it exists.+ midi_out_impl::close_port();++ // Cleanup.+ if (this->vport >= 0)+ snd.seq.delete_port(this->seq, this->vport);++ if (!configuration.context)+ snd.seq.close(this->seq);++ client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ_UMP; }++ [[nodiscard]] int create_port(std::string_view portName)+ {+ return alsa_data::create_port(+ *this, portName,+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_UMP_ENDPOINT,+ SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);+ }++ stdx::error open_port(const output_port& p, std::string_view portName) override+ {+ unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);+ if (nSrc < 1)+ {+ libremidi_handle_error(this->configuration, "no MIDI output sources found!");+ return make_error_code(std::errc::no_such_device);+ }++ auto sink = get_port_info(p);+ if (!sink)+ return std::errc::invalid_argument;++ if (int err = create_port(portName); err < 0)+ {+ libremidi_handle_error(configuration, "ALSA error creating port.");+ return from_errc(err);+ }++ snd_seq_addr_t source{+ .client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};+ if (int err = create_connection(*this, source, *sink, true); err < 0)+ {+ libremidi_handle_error(configuration, "ALSA error making port connection.");+ return from_errc(err);+ }++ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ if (int err = create_port(portName); err < 0)+ return from_errc(err);+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ unsubscribe();+ return stdx::error{};+ }++ stdx::error set_client_name(std::string_view clientName) override+ {+ return alsa_data::set_client_name(clientName);+ }++ stdx::error set_port_name(std::string_view portName) override+ {+ return alsa_data::set_port_name(portName);+ }++ stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override+ {+ snd_seq_ump_event_t ev;++ memset(&ev, 0, sizeof(snd_seq_ump_event_t));+ snd_seq_ev_set_ump(&ev);+ snd_seq_ev_set_source(&ev, this->vport);+ snd_seq_ev_set_subs(&ev);+ snd_seq_ev_set_direct(&ev);++ auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) -> std::errc {+ std::memcpy(ev.ump, ump, bytes);+ const int ret = snd.seq.ump.event_output_direct(this->seq, &ev);+ if (ret < 0)+ {+ libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");+ return static_cast<std::errc>(-ret);+ }+ return std::errc{0};+ };+ segment_ump_stream(ump_stream, count, write_func, []() {});++ snd.seq.drain_output(this->seq);+ return stdx::error{};+ }+};+}
+ vendor/libremidi/backends/coremidi.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/coremidi/midi_in.hpp>+#include <libremidi/backends/coremidi/midi_out.hpp>+#include <libremidi/backends/coremidi/observer.hpp>++namespace libremidi+{+struct core_backend+{+ using midi_in = midi_in_core;+ using midi_out = midi_out_core;+ using midi_observer = observer_core;+ using midi_in_configuration = coremidi_input_configuration;+ using midi_out_configuration = coremidi_output_configuration;+ using midi_observer_configuration = coremidi_observer_configuration;+ static const constexpr auto API = libremidi::API::COREMIDI;+ static const constexpr auto name = "core";+ static const constexpr auto display_name = "CoreMIDI";++ static constexpr inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/coremidi/config.hpp view
@@ -0,0 +1,42 @@+#pragma once+#include <libremidi/config.hpp>++#include <cstdint>+#include <functional>+#include <optional>+#include <string>++#if defined(__APPLE__)+ #if __LP64__+typedef unsigned int UInt32;+ #else+typedef unsigned long UInt32;+ #endif+typedef UInt32 MIDIObjectRef;+typedef MIDIObjectRef MIDIClientRef;+#else+using MIDIClientRef = uint32_t;+using MIDIObjectRef = uint32_t;+#endif+namespace libremidi+{++struct coremidi_input_configuration+{+ std::string client_name = "libremidi client";+ std::optional<MIDIClientRef> context{};+};++struct coremidi_output_configuration+{+ std::string client_name = "libremidi client";+ std::optional<MIDIClientRef> context{};+};++struct coremidi_observer_configuration+{+ std::string client_name = "libremidi client";+ std::function<void(MIDIClientRef)> on_create_context{};+};++}
+ vendor/libremidi/backends/coremidi/error_domain.hpp view
@@ -0,0 +1,72 @@+#pragma once+#include <libremidi/error.hpp>++#include <CoreMIDI/CoreMIDI.h>++namespace libremidi+{+struct coremidi_error_domain : public stdx::error_domain+{+public:+ constexpr coremidi_error_domain() noexcept+ : error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}+ {+ }++ stdx::string_ref name() const noexcept override { return "coremidi"; }++ bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override+ {+ if (lhs.domain() == rhs.domain())+ return error_cast<std::errc>(lhs) == error_cast<std::errc>(rhs);++ return false;+ }++ stdx::string_ref message(const stdx::error& e) const noexcept override+ {+ switch (error_cast<OSStatus>(e))+ {+ case kMIDIInvalidClient:+ return "Invalid Client";+ case kMIDIInvalidPort:+ return "Invalid Port";+ case kMIDIWrongEndpointType:+ return "Wrong EndpointT ype";+ case kMIDINoConnection:+ return "No Connection";+ case kMIDIUnknownEndpoint:+ return "Unknown Endpoint";+ case kMIDIUnknownProperty:+ return "Unknown Property";+ case kMIDIWrongPropertyType:+ return "Wrong Property Type";+ case kMIDINoCurrentSetup:+ return "No Current Setup";+ case kMIDIMessageSendErr:+ return "Message Send Error";+ case kMIDIServerStartErr:+ return "Server Start Error";+ case kMIDISetupFormatErr:+ return "Setup Format Error";+ case kMIDIWrongThread:+ return "Wrong Thread";+ case kMIDIObjectNotFound:+ return "Object Not Found";+ case kMIDIIDNotUnique:+ return "ID Not Unique";+ case kMIDINotPermitted:+ return "Not Permitted";+ case kMIDIUnknownError:+ return "Unknown Error";+ }+ return "Unknown error code";+ }+};++inline stdx::error from_osstatus(OSStatus ret) noexcept+{+ static constexpr coremidi_error_domain domain{};+ return {ret, domain};+}+}
+ vendor/libremidi/backends/coremidi/helpers.hpp view
@@ -0,0 +1,351 @@+#pragma once+#include <libremidi/detail/memory.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/error_handler.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/backends/coremidi/error_domain.hpp>++#include <CoreMIDI/CoreMIDI.h>+#include <CoreServices/CoreServices.h>++#include <cmath>++#include <bit>++#if TARGET_OS_IPHONE+ #include <CoreAudio/CoreAudioTypes.h>+ #include <mach/mach_time.h>+ #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME mach_absolute_time+#else+ #include <CoreAudio/HostTime.h>+ #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime+#endif++namespace libremidi+{+using CFString_handle = unique_handle<const __CFString, CFRelease>;+using CFStringMutable_handle = unique_handle<__CFString, CFRelease>;+namespace+{+static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept+{+ CFStringRef res;+ if (MIDIObjectGetStringProperty(object, property, &res) || !res)+ return {};++ char name[256];+ CFStringGetCString(res, name, sizeof(name), kCFStringEncodingUTF8);+ CFRelease(res);+ return name;+}++static inline int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept+{+ SInt32 res;+ MIDIObjectGetIntegerProperty(object, property, &res);+ return res;+}++static inline CFString_handle toCFString(std::string_view str) noexcept+{+ return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)};+}++#if TARGET_OS_IPHONE+inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)+{+ static const struct mach_timebase_info timebase = [] {+ struct mach_timebase_info theTimeBaseInfo;+ mach_timebase_info(&theTimeBaseInfo);+ return theTimeBaseInfo;+ }();+ const auto numer = timebase.numer;+ const auto denom = timebase.denom;++ __uint128_t res = hostTime;+ if (numer != denom)+ {+ res *= numer;+ res /= denom;+ }+ return static_cast<uint64_t>(res);+}+#endif+// This function was submitted by Douglas Casey Tucker and apparently+// derived largely from PortMidi.+inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)+{+ CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);++ static constexpr auto getProp = [](MIDIObjectRef prop) {+ CFStringRef str = nullptr;+ MIDIObjectGetStringProperty(prop, kMIDIPropertyName, &str);+ return CFString_handle{str};+ };++ // Begin with the endpoint's name.+ if (auto endpoint_name = getProp(endpoint))+ {+ CFStringAppend(result, endpoint_name.get());+ }++ // some MIDI devices have a leading space in endpoint name. trim+ CFStringTrim(result, CFSTR(" "));++ MIDIEntityRef entity = 0;+ MIDIDeviceRef device = 0;+ MIDIEndpointGetEntity(endpoint, &entity);+ if (entity == 0)+ goto finish;++ if (CFStringGetLength(result) == 0)+ {+ // endpoint name has zero length -- try the entity+ if (auto entity_name = getProp(entity))+ {+ CFStringAppend(result, entity_name.get());+ }+ }++ // now consider the device's name+ MIDIEntityGetDevice(entity, &device);+ if (device == 0)+ goto finish;++ if (auto dev_name = getProp(device))+ {+ const auto dev_strlen = CFStringGetLength(dev_name.get());++ // if an external device has only one entity, throw away+ // the endpoint name and just use the device name+ if (CFStringGetLength(result) == 0+ || (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2))+ {+ CFStringAppend(result, dev_name.get());+ goto finish;+ }++ // does the entity name already start with the device name?+ // (some drivers do this though they shouldn't)+ // if so, do not prepend+ if (CFStringCompareWithOptions(result, dev_name.get(), CFRangeMake(0, dev_strlen), 0)+ != kCFCompareEqualTo)+ {+ // prepend the device name to the entity name+ if (CFStringGetLength(result) > 0)+ CFStringInsert(result, 0, CFSTR(" "));+ CFStringInsert(result, 0, dev_name.get());+ }+ }++finish:+ if (CFStringGetLength(result) == 0)+ return CFSTR("No name");+ else+ return result;+}++// This function was submitted by Douglas Casey Tucker and apparently+// derived largely from PortMidi.+inline CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)+{+ CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);+ CFStringRef str{};++ // Does the endpoint have connections?+ CFDataRef connections = nullptr;+ std::size_t nConnected = 0;+ bool anyStrings = false;+ MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);+ if (connections != nullptr)+ {+ // It has connections, follow them+ // Concatenate the names of all connected devices+ nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID);+ if (nConnected)+ {+ const SInt32* pid = (const SInt32*)(CFDataGetBytePtr(connections));+ for (std::size_t i = 0; i < nConnected; ++i, ++pid)+ {+ MIDIUniqueID id = CFSwapInt32BigToHost(*pid);+ MIDIObjectRef connObject;+ MIDIObjectType connObjectType;+ auto err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);+ if (err == noErr)+ {+ if (connObjectType == kMIDIObjectType_ExternalSource+ || connObjectType == kMIDIObjectType_ExternalDestination)+ {+ // Connected to an external device's endpoint (10.3 and later).+ str = EndpointName((MIDIEndpointRef)(connObject), true);+ }+ else+ {+ // Connected to an external device (10.2) (or something else,+ // catch-+ str = nullptr;+ MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);+ }+ if (str != nullptr)+ {+ if (anyStrings)+ CFStringAppend(result, CFSTR(", "));+ else+ anyStrings = true;+ CFStringAppend(result, str);+ CFRelease(str);+ }+ }+ }+ }+ CFRelease(connections);+ }+ if (anyStrings)+ return result;++ CFRelease(result);++ // Here, either the endpoint had no connections, or we failed to obtain names+ return EndpointName(endpoint, false);+}++inline MIDIObjectRef+locate_object(auto& self, const port_information& info, MIDIObjectType requested_type)+{+ auto uid = std::bit_cast<std::int32_t>((uint32_t)info.port);+ MIDIObjectRef object{};+ MIDIObjectType type{};+ auto ret = MIDIObjectFindByUniqueID(uid, &object, &type);+ if (ret != noErr)+ {+ self.libremidi_handle_error(+ self.configuration, "cannot find port: " + info.port_name);+ return 0;+ }++ if (type != requested_type || object == 0)+ {+ self.libremidi_handle_error(+ self.configuration, "invalid object: " + info.port_name + " : "+ + std::to_string(object));+ return 0;+ }++ return object;+}+}++// A structure to hold variables related to the CoreMIDI API+// implementation.+struct coremidi_data+{+ MIDIClientRef client{};+ MIDIPortRef port{};+ MIDIEndpointRef endpoint = 0;++ [[nodiscard]] OSStatus init_client(auto& configuration)+ {+ if (configuration.context)+ {+ client = *configuration.context;+ return noErr;+ }+ else+ {+ // Set up our client.+ return MIDIClientCreate(+ toCFString(configuration.client_name).get(), nullptr, nullptr, &client);+ }+ }++ void close_client(auto& self)+ {+ self.client_open_ = std::errc::not_connected;+ if (!self.configuration.context)+ MIDIClientDispose(self.client);+ }++ static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept+ {+ if (tp == 0)+ { // this happens when receiving asynchronous sysex messages+ return clock_gettime_nsec_np(CLOCK_UPTIME_RAW);+ }+ else+ {+ return AudioConvertHostTimeToNanos(tp);+ }+ }++ static void set_timestamp(auto& self, MIDITimeStamp packet, timestamp& msg) noexcept+ {+ // packet.timeStamp is in mach_absolute_time units+ // We want a timestamp in nanoseconds++ switch (self.configuration.timestamps)+ {+ case timestamp_mode::NoTimestamp:+ msg = 0;+ return;+ case timestamp_mode::Relative: {+ if (self.firstMessage)+ {+ self.firstMessage = false;+ msg = 0;+ return;+ }+ else+ {+ if constexpr (requires { self.continueSysex; })+ {+ if (self.continueSysex)+ return;+ }++ auto time = time_in_nanos(packet);+ time -= self.last_time;+ msg = time;+ }+ break;+ }+ case timestamp_mode::Absolute:+ case timestamp_mode::SystemMonotonic:+ if constexpr (requires { self.continueSysex; })+ {+ if (self.continueSysex)+ return;+ }+ msg = time_in_nanos(packet);+ break;++ case timestamp_mode::Custom: {+ if constexpr (requires { self.continueSysex; })+ {+ if (self.continueSysex)+ return;+ }+ msg = self.configuration.get_timestamp(time_in_nanos(packet));+ break;+ }+ }+ }++ stdx::error close_port()+ {+ if (this->endpoint)+ {+ MIDIEndpointDispose(this->endpoint);+ this->endpoint = 0;+ }++ if (this->port)+ {+ MIDIPortDispose(this->port);+ this->port = 0;+ }++ return stdx::error{};+ }+};++}
+ vendor/libremidi/backends/coremidi/midi_in.hpp view
@@ -0,0 +1,154 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+class midi_in_core final+ : public midi1::in_api+ , public coremidi_data+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : input_configuration+ , coremidi_input_configuration+ {+ } configuration;++ midi_in_core(input_configuration&& conf, coremidi_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (auto result = init_client(configuration); result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating MIDI client object: " + std::to_string(result));+ client_open_ = from_osstatus(result);+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_in_core() override+ {+ // Close a connection if it exists.+ midi_in_core::close_port();++ if (this->endpoint)+ MIDIEndpointDispose(this->endpoint);++ close_client(*this);+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }++ stdx::error open_port(const input_port& info, std::string_view portName) override+ {+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++ auto source = locate_object(*this, info, kMIDIObjectType_Source);+ if (source == 0)+ return std::errc::invalid_argument;++ // Create our local sink+ MIDIPortRef port;+ OSStatus result = MIDIInputPortCreate(+ this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &port);++ if (result != noErr)+ {+ close_client(*this);+ libremidi_handle_error(+ this->configuration, "error creating macOS MIDI input port: "+ + std::to_string(result));+ return from_osstatus(result);+ }++ // Make the connection.+ if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)+ {+ MIDIPortDispose(port);+ close_client(*this);+ libremidi_handle_error(+ this->configuration, "error connecting macOS MIDI input port.");+ return from_osstatus(result);+ }++ // Save our api-specific port information.+ this->port = port;+ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ // Create a virtual MIDI input destination.+ MIDIEndpointRef endpoint;+ OSStatus result = MIDIDestinationCreate(+ this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &endpoint);++ if (result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating virtual macOS MIDI "+ "destination.");+ return from_osstatus(result);+ }++ // Save our api-specific connection information.+ this->endpoint = endpoint;+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ return coremidi_data::close_port();+ }++ timestamp absolute_timestamp() const noexcept override+ {+ return coremidi_data::time_in_nanos(LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME());+ }++ static void midiInputCallback(const MIDIPacketList* list, void* procRef, void* /*srcRef*/)+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ auto& self = *(midi_in_core*)procRef;++ const MIDIPacket* packet = &list->packet[0];+ for (unsigned int i = 0; i < list->numPackets; ++i)+ {+ // My interpretation of the CoreMIDI documentation: all message+ // types, except sysex, are complete within a packet and there may+ // be several of them in a single packet. Sysex messages can be+ // broken across multiple packets and PacketLists but are bundled+ // alone within each packet (these packets do not contain other+ // message types). If sysex messages are split across multiple+ // MIDIPacketLists, they must be handled by multiple calls to this+ // function.++ if (packet->length > 0)+ {+ auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };+ self.m_processing.on_bytes_multi(+ {packet->data, packet->data + packet->length},+ self.m_processing.timestamp<timestamp_info>(to_ns, 0));+ }++ packet = MIDIPacketNext(packet);+ }+ }++ midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/coremidi/midi_out.hpp view
@@ -0,0 +1,178 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{+class midi_out_core final+ : public midi1::out_api+ , public coremidi_data+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : output_configuration+ , coremidi_output_configuration+ {+ } configuration;++ midi_out_core(output_configuration&& conf, coremidi_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (auto result = init_client(configuration); result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating MIDI client object: " + std::to_string(result));+ client_open_ = from_osstatus(result);+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_out_core()+ {+ midi_out_core::close_port();++ if (this->endpoint)+ MIDIEndpointDispose(this->endpoint);++ close_client(*this);+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }++ stdx::error open_port(const output_port& info, std::string_view portName) override+ {+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++ // Find where we want to send+ auto destination = locate_object(*this, info, kMIDIObjectType_Destination);+ if (destination == 0)+ return std::errc::invalid_argument;++ // Create our local source+ MIDIPortRef port;+ OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);+ if (result != noErr)+ {+ close_client(*this);+ libremidi_handle_error(+ this->configuration, "error creating macOS MIDI output port.");+ return from_osstatus(result);+ }++ // Save our api-specific connection information.+ this->port = port;+ this->destinationId = destination;++ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ // Create a virtual MIDI output source.+ MIDIEndpointRef endpoint;+ OSStatus result = MIDISourceCreate(this->client, toCFString(portName).get(), &endpoint);++ if (result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating macOS virtual MIDI source.");++ return from_osstatus(result);+ }++ // Save our api-specific connection information.+ this->endpoint = endpoint;+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ return coremidi_data::close_port();+ }++ stdx::error send_message(const unsigned char* message, size_t size) override+ {+ unsigned int nBytes = static_cast<unsigned int>(size);+ if (nBytes == 0)+ {+ libremidi_handle_warning(configuration, "no data in message argument!");+ return std::errc::invalid_argument;+ }++ if (message[0] != 0xF0 && nBytes > 3)+ {+ libremidi_handle_warning(+ configuration,+ "message format problem ... not sysex but "+ "> 3 bytes?");+ return std::errc::bad_message;+ }++ const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();++ const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes;+ Byte buffer[bufsize + 16]; // pad for other struct members+ ByteCount listSize = sizeof(buffer);+ MIDIPacketList* packetList = (MIDIPacketList*)buffer;++ ByteCount remainingBytes = nBytes;+ while (remainingBytes)+ {+ MIDIPacket* packet = MIDIPacketListInit(packetList);+ // A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer,+ // break it up into chunks of 64K or less and send out as a MIDIPacketList with only one+ // MIDIPacket. Here, we reuse the memory allocated above on the stack for all.+ ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes;+ const Byte* dataStartPtr = (const Byte*)&message[nBytes - remainingBytes];+ packet = MIDIPacketListAdd(+ packetList, listSize, packet, timestamp, bytesForPacket, dataStartPtr);+ remainingBytes -= bytesForPacket;++ if (!packet)+ {+ libremidi_handle_error(+ this->configuration, "could not allocate packet list");++ return std::errc::message_size;+ }++ // Send to any destinations that may have connected to us.+ if (this->endpoint)+ {+ auto result = MIDIReceived(this->endpoint, packetList);+ if (result != noErr)+ {+ libremidi_handle_warning(+ this->configuration,+ "error sending MIDI to virtual "+ "destinations.");+ return std::errc::io_error;+ }+ }++ // And send to an explicit destination port if we're connected.+ if (this->destinationId != 0)+ {+ auto result = MIDISend(this->port, this->destinationId, packetList);+ if (result != noErr)+ {+ libremidi_handle_warning(+ this->configuration,+ "error sending MIDI message to port.");+ return std::errc::io_error;+ }+ }+ }+ return stdx::error{};+ }++ MIDIEndpointRef destinationId{};+};+}
+ vendor/libremidi/backends/coremidi/observer.hpp view
@@ -0,0 +1,175 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <CoreMIDI/CoreMIDI.h>++#include <bit>++namespace libremidi+{+class observer_core+ : public observer_api+ , public error_handler+{+public:+ struct+ : observer_configuration+ , coremidi_observer_configuration+ {+ } configuration;++ explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (configuration.client_name.empty())+ configuration.client_name = "libremidi observer";++ if (!configuration.has_callbacks())+ return;++ auto result = MIDIClientCreate(+ toCFString(configuration.client_name).get(),+ +[](const MIDINotification* message, void* ctx) {+ ((observer_core*)ctx)->notify(message);+ },+ this, &client);++ if (result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating MIDI client object: " + std::to_string(result));+ return;+ }++ if (configuration.on_create_context)+ configuration.on_create_context(client);++ if (configuration.notify_in_constructor)+ {+ if (this->configuration.input_added)+ for (auto& p : get_input_ports())+ this->configuration.input_added(p);++ if (this->configuration.output_added)+ for (auto& p : get_output_ports())+ this->configuration.output_added(p);+ }+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }++ template <bool Input>+ auto to_port_info(MIDIObjectRef obj) const noexcept+ -> std::optional<std::conditional_t<Input, input_port, output_port>>+ {+ MIDIEntityRef e{};+ MIDIEndpointGetEntity(obj, &e);+ bool physical = bool(e);++ bool ok = this->configuration.track_any;+ if (physical && this->configuration.track_hardware)+ ok |= true;+ else if ((!physical) && this->configuration.track_virtual)+ ok |= true;++ if (!ok)+ return {};++ return std::conditional_t<Input, input_port, output_port>{+ {.client = (std::uintptr_t)this->client,+ .port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),+ .manufacturer = get_string_property(obj, kMIDIPropertyManufacturer),+ .device_name = get_string_property(obj, kMIDIPropertyModel),+ .port_name = get_string_property(obj, kMIDIPropertyName),+ .display_name = get_string_property(obj, kMIDIPropertyDisplayName)}};+ }++ std::vector<libremidi::input_port> get_input_ports() const noexcept override+ {+ std::vector<libremidi::input_port> ret;++ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);+ for (ItemCount i = 0; i < MIDIGetNumberOfSources(); i++)+ {+ if (auto p = to_port_info<true>(MIDIGetSource(i)))+ ret.push_back(std::move(*p));+ }++ return ret;+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept override+ {+ std::vector<libremidi::output_port> ret;++ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);+ for (ItemCount i = 0; i < MIDIGetNumberOfDestinations(); i++)+ {+ if (auto p = to_port_info<false>(MIDIGetDestination(i)))+ ret.push_back(std::move(*p));+ }+ return ret;+ }++ void notify(const MIDINotification* message)+ {+ switch (message->messageID)+ {+ case kMIDIMsgObjectAdded: {+ auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);++ switch (obj->childType)+ {+ case kMIDIObjectType_Source:+ if (auto& cb = configuration.input_added)+ if (auto p = to_port_info<true>(obj->child))+ cb(std::move(*p));+ break;+ case kMIDIObjectType_Destination:+ if (auto& cb = configuration.output_added)+ if (auto p = to_port_info<false>(obj->child))+ cb(std::move(*p));+ break;+ default:+ break;+ }++ break;+ }++ case kMIDIMsgObjectRemoved: {+ auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);++ switch (obj->childType)+ {+ case kMIDIObjectType_Source:+ if (auto& cb = configuration.input_removed)+ if (auto p = to_port_info<true>(obj->child))+ cb(std::move(*p));+ break;+ case kMIDIObjectType_Destination:+ if (auto& cb = configuration.output_removed)+ if (auto p = to_port_info<false>(obj->child))+ cb(std::move(*p));+ break;+ default:+ break;+ }++ break;+ }++ default:+ break;+ }+ }++ ~observer_core() { MIDIClientDispose(this->client); }++private:+ MIDIClientRef client{};+};+}
+ vendor/libremidi/backends/coremidi_ump.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/coremidi_ump/midi_in.hpp>+#include <libremidi/backends/coremidi_ump/midi_out.hpp>+#include <libremidi/backends/coremidi_ump/observer.hpp>++namespace libremidi::coremidi_ump+{+struct backend+{+ using midi_in = midi_in_impl;+ using midi_out = midi_out_impl;+ using midi_observer = observer_impl;+ using midi_in_configuration = coremidi_ump::input_configuration;+ using midi_out_configuration = coremidi_ump::output_configuration;+ using midi_observer_configuration = coremidi_ump::observer_configuration;+ static const constexpr auto API = libremidi::API::COREMIDI_UMP;+ static const constexpr auto name = "core_ump";+ static const constexpr auto display_name = "CoreMIDI UMP";++ static constexpr inline bool available() noexcept { return true; /* todo? */ }+};+}
+ vendor/libremidi/backends/coremidi_ump/config.hpp view
@@ -0,0 +1,23 @@+#pragma once+#include <libremidi/backends/coremidi/config.hpp>++namespace libremidi::coremidi_ump+{+struct input_configuration+{+ std::string client_name = "libremidi client";+ std::optional<MIDIClientRef> context{};+};++struct output_configuration+{+ std::string client_name = "libremidi client";+ std::optional<MIDIClientRef> context{};+};++struct observer_configuration+{+ std::string client_name = "libremidi client";+ std::function<void(MIDIClientRef)> on_create_context{};+};+}
+ vendor/libremidi/backends/coremidi_ump/helpers.hpp view
@@ -0,0 +1,2 @@+#pragma once+#include <libremidi/backends/coremidi/helpers.hpp>
+ vendor/libremidi/backends/coremidi_ump/midi_in.hpp view
@@ -0,0 +1,152 @@+#pragma once+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/coremidi_ump/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi::coremidi_ump+{++class midi_in_impl final+ : public midi2::in_api+ , public coremidi_data+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : ump_input_configuration+ , coremidi_ump::input_configuration+ {+ } configuration;++ midi_in_impl(ump_input_configuration&& conf, coremidi_ump::input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (auto result = init_client(configuration); result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating MIDI client object: " + std::to_string(result));+ client_open_ = from_osstatus(result);+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_in_impl() override+ {+ // Close a connection if it exists.+ close_port();++ if (this->endpoint)+ MIDIEndpointDispose(this->endpoint);++ close_client(*this);+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }++ stdx::error open_port(const input_port& info, std::string_view portName) override+ {+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++ auto source = locate_object(*this, info, kMIDIObjectType_Source);+ if (source == 0)+ return std::errc::invalid_argument;++ // Create our local sink+ MIDIPortRef port;++ OSStatus result = MIDIInputPortCreateWithProtocol(+ this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &port,+ ^(const MIDIEventList* evtlist, void* __nullable srcConnRefCon) {+ this->midiInputCallback(evtlist, srcConnRefCon);+ });++ if (result != noErr)+ {+ close_client(*this);+ libremidi_handle_error(+ this->configuration, "error creating macOS MIDI input port: "+ + std::to_string(result));+ return from_osstatus(result);+ }++ // Make the connection.+ if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)+ {+ MIDIPortDispose(port);+ close_client(*this);+ libremidi_handle_error(+ this->configuration, "error connecting macOS MIDI input port.");+ return from_osstatus(result);+ }++ // Save our api-specific port information.+ this->port = port;+ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ // Create a virtual MIDI input destination.+ MIDIEndpointRef endpoint;+ OSStatus result = MIDIDestinationCreateWithProtocol(+ this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &endpoint,+ ^(const MIDIEventList* evtlist, void* __nullable srcConnRefCon) {+ this->midiInputCallback(evtlist, srcConnRefCon);+ });++ if (result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating virtual macOS MIDI "+ "destination.");+ return from_osstatus(result);+ }++ // Save our api-specific connection information.+ this->endpoint = endpoint;+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ return coremidi_data::close_port();+ }++ timestamp absolute_timestamp() const noexcept override+ {+ return coremidi_data::time_in_nanos(LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME());+ }++ void midiInputCallback(const MIDIEventList* list, void* /*srcRef*/)+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ const MIDIEventPacket* packet = &list->packet[0];+ for (unsigned int i = 0; i < list->numPackets; ++i)+ {+ if (packet->wordCount > 0)+ {+ auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };+ m_processing.on_bytes_multi(+ {packet->words, packet->words + packet->wordCount},+ m_processing.timestamp<timestamp_info>(to_ns, 0));+ }++ packet = MIDIEventPacketNext(packet);+ }+ }++ midi2::input_state_machine m_processing{this->configuration};+};++}
+ vendor/libremidi/backends/coremidi_ump/midi_out.hpp view
@@ -0,0 +1,159 @@+#pragma once+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/coremidi_ump/helpers.hpp>+#include <libremidi/cmidi2.hpp>+#include <libremidi/detail/midi_out.hpp>+#include <libremidi/detail/ump_stream.hpp>++namespace libremidi::coremidi_ump+{+class midi_out_impl final+ : public midi2::out_api+ , public coremidi_data+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : libremidi::output_configuration+ , coremidi_ump::output_configuration+ {+ } configuration;++ midi_out_impl(+ libremidi::output_configuration&& conf, coremidi_ump::output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (auto result = init_client(configuration); result != noErr)+ {+ libremidi_handle_error(+ this->configuration,+ "error creating MIDI client object: " + std::to_string(result));+ client_open_ = from_osstatus(result);+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_out_impl()+ {+ midi_out_impl::close_port();++ if (this->endpoint)+ MIDIEndpointDispose(this->endpoint);++ close_client(*this);+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }++ stdx::error open_port(const output_port& info, std::string_view portName) override+ {+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);++ // Find where we want to send+ auto destination = locate_object(*this, info, kMIDIObjectType_Destination);+ if (destination == 0)+ return std::errc::invalid_argument;++ // Create our local source+ MIDIPortRef port;+ OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);+ if (result != noErr)+ {+ close_client(*this);+ libremidi_handle_error(+ this->configuration, "error creating macOS MIDI output port.");+ return from_osstatus(result);+ }++ // Save our api-specific connection information.+ this->port = port;+ this->destinationId = destination;++ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ // Create a virtual MIDI output source.+ OSStatus result = MIDISourceCreateWithProtocol(+ this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &this->endpoint);++ if (result != noErr)+ {+ this->endpoint = 0;+ libremidi_handle_error(+ this->configuration,+ "error creating macOS virtual MIDI source.");++ return from_osstatus(result);+ }++ return stdx::error{};+ }++ stdx::error close_port() override+ {+ return coremidi_data::close_port();+ }++ stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override+ {+ MIDIEventList* eventList = reinterpret_cast<MIDIEventList*>(m_eventListBuffer);+ MIDIEventPacket* packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);+ const MIDITimeStamp ts = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();++ auto write_fun = [ts, &packet, &eventList](const uint32_t* ump, int bytes) -> std::errc {+ packet = MIDIEventListAdd(eventList, event_list_max_size, packet, ts, bytes / 4, ump);+ if (packet)+ return std::errc{0};+ else+ return std::errc::not_enough_memory;+ };++ auto realloc_fun = [this, &packet, &eventList] {+ push_event_list(eventList);+ packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);+ };++ segment_ump_stream(ump_stream, count, write_fun, realloc_fun);+ return push_event_list(eventList);+ }++ stdx::error push_event_list(MIDIEventList* eventList)+ {+ if (this->endpoint)+ {+ auto result = MIDIReceivedEventList(this->endpoint, eventList);+ if (result != noErr)+ {+ libremidi_handle_warning(+ this->configuration,+ "error sending MIDI to virtual "+ "destinations.");+ return std::errc::io_error;+ }+ }++ if (this->destinationId != 0)+ {+ auto result = MIDISendEventList(this->port, this->destinationId, eventList);+ if (result != noErr)+ {+ libremidi_handle_warning(+ this->configuration,+ "error sending MIDI message to port.");+ return std::errc::io_error;+ }+ }+ return stdx::error{};+ }++ MIDIEndpointRef destinationId{};++ static constexpr int event_list_max_size = 65535;+ unsigned char m_eventListBuffer[sizeof(MIDIEventList) + event_list_max_size];+};+}
+ vendor/libremidi/backends/coremidi_ump/observer.hpp view
@@ -0,0 +1,20 @@+#pragma once+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/coremidi/observer.hpp>++namespace libremidi::coremidi_ump+{++class observer_impl final : public libremidi::observer_core+{+public:+ explicit observer_impl(libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf)+ : observer_core{std::move(conf), coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}}+ {++ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }+};++}
+ vendor/libremidi/backends/dummy.hpp view
@@ -0,0 +1,105 @@+#pragma once+#include <libremidi/configurations.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_out.hpp>+#include <libremidi/detail/observer.hpp>++namespace libremidi+{+class observer_dummy : public observer_api+{+public:+ explicit observer_dummy(const auto& /*configuration*/, const auto&) { }++ ~observer_dummy() { }+ libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }+ std::vector<libremidi::input_port> get_input_ports() const noexcept override { return {}; }+ std::vector<libremidi::output_port> get_output_ports() const noexcept override { return {}; }+};++class midi_in_dummy final+ : public midi1::in_api+ , public error_handler+{+public:+ explicit midi_in_dummy(const input_configuration& configuration, std::any)+ {+ libremidi_handle_warning(configuration, "This class provides no functionality.");+ }+ explicit midi_in_dummy(const ump_input_configuration& configuration, std::any)+ {+ libremidi_handle_warning(configuration, "This class provides no functionality.");+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }+ stdx::error open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override+ {+ return stdx::error{};+ }+ stdx::error open_virtual_port(std::string_view /*portName*/) override+ {+ return stdx::error{};+ }+ stdx::error close_port() override { return stdx::error{}; }+ stdx::error set_client_name(std::string_view /*clientName*/) override+ {+ return stdx::error{};+ }+ stdx::error set_port_name(std::string_view /*portName*/) override+ {+ return stdx::error{};+ }+ timestamp absolute_timestamp() const noexcept override { return 0; }+};++class midi_out_dummy final+ : public midi1::out_api+ , public error_handler+{+public:+ explicit midi_out_dummy(const output_configuration& configuration, std::any)+ {+ libremidi_handle_warning(configuration, "This class provides no functionality.");+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }+ stdx::error open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override+ {+ return stdx::error{};+ }+ stdx::error open_virtual_port(std::string_view /*portName*/) override+ {+ return stdx::error{};+ }++ stdx::error close_port() override { return stdx::error{}; }+ stdx::error set_client_name(std::string_view /*clientName*/) override+ {+ return stdx::error{};+ }+ stdx::error set_port_name(std::string_view /*portName*/) override+ {+ return stdx::error{};+ }+ stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override+ {+ return stdx::error{};+ }+};++struct dummy_backend+{+ using midi_in = midi_in_dummy;+ using midi_out = midi_out_dummy;+ using midi_observer = observer_dummy;+ using midi_in_configuration = dummy_configuration;+ using midi_out_configuration = dummy_configuration;+ using midi_observer_configuration = dummy_configuration;+ static const constexpr auto API = libremidi::API::DUMMY;+ static const constexpr auto name = "dummy";+ static const constexpr auto display_name = "Dummy";++ static constexpr inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/emscripten.hpp view
@@ -0,0 +1,30 @@+#pragma once++#if defined(__EMSCRIPTEN__)++ #include <libremidi/backends/emscripten/config.hpp>+ #include <libremidi/backends/emscripten/midi_in.hpp>+ #include <libremidi/backends/emscripten/midi_out.hpp>+ #include <libremidi/backends/emscripten/observer.hpp>++namespace libremidi+{++struct emscripten_backend+{+ using midi_in = midi_in_emscripten;+ using midi_out = midi_out_emscripten;+ using midi_observer = observer_emscripten;+ using midi_in_configuration = emscripten_input_configuration;+ using midi_out_configuration = emscripten_output_configuration;+ using midi_observer_configuration = emscripten_observer_configuration;+ static const constexpr auto API = libremidi::API::WEBMIDI;+ static const constexpr auto name = "webmidi";+ static const constexpr auto display_name = "WebMIDI";++ static constexpr inline bool available() noexcept { return true; }+};++}++#endif
+ vendor/libremidi/backends/emscripten/config.hpp view
@@ -0,0 +1,17 @@+#pragma once+#include <libremidi/config.hpp>++namespace libremidi+{++struct emscripten_input_configuration+{+};+struct emscripten_output_configuration+{+};+struct emscripten_observer_configuration+{+};++}
+ vendor/libremidi/backends/emscripten/helpers.hpp view
@@ -0,0 +1,17 @@+#pragma once+#include <libremidi/config.hpp>++#include <string>++namespace libremidi+{+namespace webmidi_helpers+{+struct device_information+{+ std::string id;+ std::string name;+ bool connected{};+};+}+}
+ vendor/libremidi/backends/emscripten/midi_access.hpp view
@@ -0,0 +1,303 @@+#pragma once+#include <libremidi/backends/emscripten/midi_in.hpp>+#include <libremidi/backends/emscripten/midi_out.hpp>+#include <libremidi/backends/emscripten/observer.hpp>+#include <libremidi/libremidi.hpp>++#include <emscripten.h>++#include <map>++extern "C" {+EMSCRIPTEN_KEEPALIVE+void libremidi_devices_poll();++EMSCRIPTEN_KEEPALIVE+void libremidi_devices_input(int port, double timestamp, int len, char* bytes);+}++namespace libremidi+{+namespace webmidi_helpers+{++class midi_access_emscripten+{+public:+ static midi_access_emscripten& instance() noexcept+ {+ static midi_access_emscripten inst;+ return inst;+ }++ bool available() const noexcept+ {+ return EM_ASM_INT(return typeof globalThis.__libreMidi_access !== undefined;);+ }++ int input_count() const noexcept+ {+ return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;);+ }++ int output_count() const noexcept+ {+ return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;);+ }++ void load_current_infos() noexcept+ {+#define get_js_string(variable_to_read, ...) \+ (char*)EM_ASM_INT( \+ { \+ var jsstr = variable_to_read; \+ var bytes = lengthBytesUTF8(jsstr) + 1; \+ var str = _malloc(bytes); \+ stringToUTF8(jsstr, str, bytes); \+ return str; \+ }, \+ __VA_ARGS__);++ EM_ASM_INT({+ let inputs = [];+ let outputs = [];+ for (let inpt of globalThis.__libreMidi_access.inputs.values())+ {+ inputs.push(inpt);+ }+ for (let outpt of globalThis.__libreMidi_access.outputs.values())+ {+ outputs.push(outpt);+ }+ globalThis.__libreMidi_currentInputs = inputs;+ globalThis.__libreMidi_currentOutputs = outputs;+ });+ const int inputs = input_count();+ const int outputs = output_count();++ m_current_inputs.resize(inputs);+ m_current_outputs.resize(outputs);++ for (int i = 0; i < inputs; i++)+ {+ int device_index = -1;+ char* midi_id = get_js_string(globalThis.__libreMidi_currentInputs[$0].id, i);+ auto it = m_input_indices.find(midi_id); // TODO transparent comparator, string_view...+ if (it == m_input_indices.end())+ {+ device_index = m_input_indices.size();+ m_input_indices[midi_id] = device_index;+ }+ else+ {+ device_index = it->second;+ }++ char* midi_name = get_js_string(globalThis.__libreMidi_currentInputs[$0].name, i);++ const bool connected+ = EM_ASM_INT(return globalThis.__libreMidi_currentInputs[$0].state === "connected", i);++ m_current_inputs[device_index].id = midi_id;+ m_current_inputs[device_index].name = midi_name;+ m_current_inputs[device_index].connected = connected;++ free(midi_id);+ free(midi_name);+ }++ for (int i = 0; i < outputs; i++)+ {+ int device_index = -1;+ char* midi_id = get_js_string(globalThis.__libreMidi_currentOutputs[$0].id, i);++ auto it = m_output_indices.find(midi_id); // TODO transparent comparator, string_view...+ if (it == m_output_indices.end())+ {+ device_index = m_output_indices.size();+ m_output_indices[midi_id] = device_index;+ }+ else+ {+ device_index = it->second;+ }++ char* midi_name = get_js_string(globalThis.__libreMidi_currentOutputs[$0].name, i);++ const bool connected+ = EM_ASM_INT(return globalThis.__libreMidi_currentOutputs[$0].state === "connected", i);++ m_current_outputs[device_index].id = midi_id;+ m_current_outputs[device_index].name = midi_name;+ m_current_outputs[device_index].connected = connected;++ free(midi_id);+ free(midi_name);+ }++#undef get_js_string+ }++ void register_observer(observer_emscripten& obs)+ {+ m_observers.push_back(&obs);+ if (m_observers.size() == 1)+ {+ start_observing();+ }+ }++ void unregister_observer(observer_emscripten& obs)+ {+ if (m_observers.size() == 1)+ {+ stop_observing();+ }+ auto it = std::find(m_observers.begin(), m_observers.end(), &obs);+ if (it != m_observers.end())+ {+ m_observers.erase(it);+ }+ }++ void devices_poll()+ {+ load_current_infos();++ for (auto& obs : m_observers)+ {+ obs->update(m_current_inputs, m_current_outputs);+ }+ }++ void open_input(int port_index, midi_in_emscripten& input)+ {+ auto& vec = m_opened_inputs[port_index];+ vec.push_back(&input);+ if (vec.size() != 1)+ return;++ start_stream(port_index);+ }++ void close_input(int port_index, midi_in_emscripten& input)+ {+ auto& vec = m_opened_inputs[port_index];+ auto it = std::find(vec.begin(), vec.end(), &input);+ if (it != vec.end())+ {+ vec.erase(it);+ }++ if (vec.empty())+ {+ stop_stream(port_index);+ }+ }++ void devices_input(int port, double timestamp, int len, char* data)+ {+ unsigned char* bytes = reinterpret_cast<unsigned char*>(data);+ for (auto input : m_opened_inputs[port])+ {+ input->on_input(timestamp, bytes, bytes + len);+ }+ }++ stdx::error send_message(int port_index, const char* bytes, int len)+ {+ const auto& id = m_current_outputs[port_index].id;+ EM_ASM(+ {+ let data = HEAPU8.subarray($0, $0 + $1);+ const id = UTF8ToString($2);+ let output = globalThis.__libreMidi_access.outputs.get(id);+ let bytes = HEAPU8.subarray($0, $0 + $1);+ output.send(Array.from(bytes));+ },+ bytes, len, id.c_str());+ }++ const std::vector<device_information>& inputs() const noexcept { return m_current_inputs; }+ const std::vector<device_information>& outputs() const noexcept { return m_current_outputs; }++private:+ midi_access_emscripten() noexcept+ {+ EM_ASM(+ if (navigator.requestMIDIAccess) {+ navigator.requestMIDIAccess().then(+ (midiAccess) => { globalThis.__libreMidi_access = midiAccess; },+ () => console.log('MIDI support rejected, MIDI will not be available;'));+ } else { console.log('WebMIDI is not supported in this browser.'); });+ }++ ~midi_access_emscripten() { stop_observing(); }++ void start_observing()+ {+ EM_ASM(+ let id = setInterval(Module._libremidi_devices_poll, 100);+ globalThis.__libreMidi_timer = id;);+ }++ void stop_observing()+ {+ EM_ASM(clearInterval(globalThis.__libreMidi_timer); globalThis.__libreMidi_timer = undefined;);+ }++ void start_stream(int port_index)+ {+ // Isn't life great...+ // https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays+ const auto& id = m_current_inputs[port_index].id;+ EM_ASM(+ const port_index = $0;+ const id = UTF8ToString($1);++ let input = globalThis.__libreMidi_access.inputs.get(id);++ function _arrayToHeap(typedArray){+ const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;+ const ptr = Module._malloc(numBytes);+ const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes);+ heapBytes.set(new Uint8Array(typedArray.buffer));+ return heapBytes;+ }++ function _freeArray(heapBytes){+ Module._free(heapBytes.byteOffset);+ }++ input.onmidimessage = (message) => {+ let bytes = message.data;+ var heapBytes = _arrayToHeap(bytes);+ Module._libremidi_devices_input(port_index, message.timeStamp, bytes.length, heapBytes.byteOffset);+ _freeArray(heapBytes);+ };+ , port_index+ , id.c_str()+ );+ }++ void stop_stream(int port_index)+ {+ const auto& id = m_current_inputs[port_index].id;+ EM_ASM(const id = UTF8ToString($1);++ let input = globalThis.__libreMidi_access.inputs.get(id);+ input.onmidimessage = undefined;, id.c_str());+ }++ std::vector<observer_emscripten*> m_observers;+ std::vector<device_information> m_current_inputs;+ std::vector<device_information> m_current_outputs;++ std::map<int, std::vector<midi_in_emscripten*>> m_opened_inputs;++ std::map<std::string, int> m_input_indices;+ std::map<std::string, int> m_output_indices;+};++}+}
+ vendor/libremidi/backends/emscripten/midi_in.hpp view
@@ -0,0 +1,38 @@+#pragma once+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/emscripten/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+class midi_in_emscripten final+ : public midi1::in_api+ , public error_handler+{+public:+ struct+ : input_configuration+ , emscripten_input_configuration+ {+ } configuration;++ midi_in_emscripten(input_configuration&& conf, emscripten_input_configuration&& apiconf);+ ~midi_in_emscripten() override;++ libremidi::API get_current_api() const noexcept override;++ stdx::error open_port(int portNumber, std::string_view);+ stdx::error open_port(const input_port& p, std::string_view) override;+ stdx::error close_port() override;++ timestamp absolute_timestamp() const noexcept override;++ void on_input(double ts, unsigned char* begin, unsigned char* end);++private:+ int portNumber_{};++ midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/emscripten/midi_out.hpp view
@@ -0,0 +1,33 @@+#pragma once+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/emscripten/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{+class midi_out_emscripten final+ : public midi1::out_api+ , public error_handler+{+public:+ struct+ : output_configuration+ , emscripten_output_configuration+ {+ } configuration;++ midi_out_emscripten(output_configuration&& conf, emscripten_output_configuration&& apiconf);+ ~midi_out_emscripten() override;++ libremidi::API get_current_api() const noexcept override;++ stdx::error open_port(int portNumber, std::string_view);+ stdx::error open_port(const output_port& p, std::string_view) override;+ stdx::error close_port() override;++ stdx::error send_message(const unsigned char* message, size_t size) override;++private:+ int portNumber_{-1};+};+}
+ vendor/libremidi/backends/emscripten/observer.hpp view
@@ -0,0 +1,37 @@+#pragma once+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/emscripten/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <vector>++namespace libremidi+{+class observer_emscripten final : public observer_api+{+ using device = webmidi_helpers::device_information;++public:+ struct+ : observer_configuration+ , emscripten_observer_configuration+ {+ } configuration;++ explicit observer_emscripten(+ observer_configuration&& conf, emscripten_observer_configuration&& apiconf);+ ~observer_emscripten();++ void+ update(const std::vector<device>& current_inputs, const std::vector<device>& current_outputs);++ libremidi::API get_current_api() const noexcept override;++ std::vector<libremidi::input_port> get_input_ports() const noexcept override;+ std::vector<libremidi::output_port> get_output_ports() const noexcept override;++private:+ std::vector<device> m_known_inputs;+ std::vector<device> m_known_outputs;+};+}
+ vendor/libremidi/backends/jack.hpp view
@@ -0,0 +1,37 @@+#pragma once+#include <libremidi/backends/jack/midi_in.hpp>+#include <libremidi/backends/jack/midi_out.hpp>+#include <libremidi/backends/jack/observer.hpp>++//*********************************************************************//+// API: UNIX JACK+//+// Written primarily by Alexander Svetalkin, with updates for delta+// time by Gary Scavone, April 2011.+//+// *********************************************************************//++namespace libremidi+{+struct jack_backend+{+ using midi_in = midi_in_jack;+ using midi_out = midi_out_jack;+ using midi_observer = observer_jack;+ using midi_in_configuration = jack_input_configuration;+ using midi_out_configuration = jack_output_configuration;+ using midi_observer_configuration = jack_observer_configuration;+ static const constexpr auto API = libremidi::API::JACK_MIDI;+ static const constexpr auto name = "jack";+ static const constexpr auto display_name = "JACK";++ static inline bool available() noexcept+ {+#if LIBREMIDI_WEAKJACK+ return WeakJack::instance().available() == 0;+#else+ return true;+#endif+ }+};+}
+ vendor/libremidi/backends/jack/config.hpp view
@@ -0,0 +1,49 @@+#pragma once+#include <libremidi/config.hpp>++#include <cstdint>+#include <functional>+#include <string>++extern "C" {+typedef struct _jack_client jack_client_t;+typedef uint32_t jack_nframes_t;+typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg);+}++namespace libremidi+{+using jack_callback_function = std::function<void(int nframes)>;+struct jack_callback+{+ int64_t token;+ std::function<void(int nframes)> callback;+};+struct jack_input_configuration+{+ std::string client_name = "libremidi client";++ jack_client_t* context{};+ std::function<void(jack_callback)> set_process_func{};+ std::function<void(int64_t)> clear_process_func{};+};++struct jack_output_configuration+{+ std::string client_name = "libremidi client";++ jack_client_t* context{};+ std::function<void(jack_callback)> set_process_func{};+ std::function<void(int64_t)> clear_process_func{};++ int32_t ringbuffer_size = 16384;+ bool direct = false;+};++struct jack_observer_configuration+{+ std::string client_name = "libremidi client";+ jack_client_t* context{};+};++}
+ vendor/libremidi/backends/jack/error_domain.hpp view
@@ -0,0 +1,78 @@+#pragma once+#include <libremidi/error.hpp>++#if __has_include(<weakjack/weak_libjack.h>)+ #include <weakjack/weak_libjack.h>+#elif __has_include(<weak_libjack.h>)+ #include <weak_libjack.h>+#elif __has_include(<jack/jack.h> )+ #include <jack/jack.h>+ #include <jack/midiport.h>+ #include <jack/ringbuffer.h>+#endif++namespace libremidi+{+struct jack_error_domain : public stdx::error_domain+{+public:+ constexpr jack_error_domain() noexcept+ : error_domain{{0xc714ea32b705080aULL, 0xe409a437daf5f921ULL}}+ {+ }++ stdx::string_ref name() const noexcept override { return "jack"; }++ bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override+ {+ if (lhs.domain() == rhs.domain())+ return error_cast<jack_status_t>(lhs) == error_cast<jack_status_t>(rhs);++ return false;+ }++ stdx::string_ref message(const stdx::error& e) const noexcept override+ {+ const auto status = error_cast<jack_status_t>(e);+ if(status == jack_status_t{})+ return "Success";++ if(status & JackInvalidOption)+ return "The operation contained an invalid or unsupported option";+ if(status & JackServerFailed)+ return "Unable to connect to the JACK server";+ if(status & JackServerError)+ return "Communication error with the JACK server";+ if(status & JackNoSuchClient)+ return "Requested client does not exist";+ if(status & JackLoadFailure)+ return "Unable to load internal client";+ if(status & JackInitFailure)+ return "Unable to initialize client";+ if(status & JackShmFailure)+ return "Unable to access shared memory";+ if(status & JackVersionError)+ return "Client's protocol version does not match";+ if(status & JackBackendError)+ return "Backend error";+ if(status & JackClientZombie)+ return "Client zombified failure";+ if(status & JackFailure)+ return "Failure";+ if(status & JackNameNotUnique)+ return "The desired client name was not unique";++ // Can't happen in libremidi as we set JackNoStartServer+ if(status & JackServerStarted)+ return "Server was started";++ return "Unknown JACK status code";+ }+};++inline stdx::error from_jack_status(jack_status_t ret) noexcept+{+ static constexpr jack_error_domain domain{};+ return {ret, domain};+}+}
+ vendor/libremidi/backends/jack/helpers.hpp view
@@ -0,0 +1,241 @@+#pragma once++#include <libremidi/backends/jack/error_domain.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/semaphore.hpp>++#include <atomic>+#include <semaphore>++namespace libremidi+{+struct jack_client+{+ jack_client_t* client{};++ static std::string get_port_display_name(jack_port_t* port)+ {+ auto p1 = std::make_unique<char[]>(jack_port_name_size());+ auto p2 = std::make_unique<char[]>(jack_port_name_size());+ char* aliases[3] = {p1.get(), p2.get(), nullptr};+ int n = jack_port_get_aliases(port, aliases);+ if (n > 1)+ {+ return aliases[1];+ }+ else if (n > 0)+ {+ std::string str = aliases[0];+ if (str.starts_with("alsa_pcm:"))+ str.erase(0, strlen("alsa_pcm:"));+ return str;+ }+ else+ {+ const auto short_name = jack_port_short_name(port);+ if (short_name && strlen(short_name) > 0)+ return short_name;+ return jack_port_name(port);+ }+ }++ template <bool Input>+ static auto to_port_info(jack_client_t* client, jack_port_t* port)+ -> std::conditional_t<Input, input_port, output_port>+ {+ return {{+ .client = reinterpret_cast<std::uintptr_t>(client),+ .port = 0,+ .manufacturer = "",+ .device_name = "",+ .port_name = jack_port_name(port),+ .display_name = get_port_display_name(port),+ }};+ }++ template <bool Input>+ static auto+ get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept+ -> std::vector<std::conditional_t<Input, input_port, output_port>>+ {+ std::vector<std::conditional_t<Input, input_port, output_port>> ret;++ if (!client)+ return {};++ const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags);++ if (ports == nullptr)+ return {};++ int i = 0;+ while (ports[i] != nullptr)+ {+ // FIXME this does not take into account filtering sw / hw ports+ auto port = jack_port_by_name(client, ports[i]);+ ret.push_back(to_port_info<Input>(client, port));+ i++;+ }++ jack_free(ports);++ return ret;+ }+};++struct jack_helpers : jack_client+{+ struct port_handle+ {+ port_handle& operator=(jack_port_t* p)+ {+ impl.get()->store(p);+ return *this;+ }++ operator jack_port_t*() const noexcept+ {+ if (impl)+ return impl.get()->load();+ return {};+ }++ std::shared_ptr<std::atomic<jack_port_t*>> impl+ = std::make_shared<std::atomic<jack_port_t*>>(nullptr);+ } port;++ int64_t this_instance{};++ semaphore_pair_lock thread_lock;++ jack_helpers()+ {+ static std::atomic_int64_t instance{};+ this_instance = ++instance;+ }++ template <typename Self>+ jack_status_t connect(Self& self)+ {+ auto& configuration = self.configuration;++ if (this->client)+ return jack_status_t{};++ // Initialize JACK client+ if (configuration.context)+ {+ if (!configuration.set_process_func)+ return JackFailure;++ configuration.set_process_func(+ {.token = this_instance,+ .callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {+ if (auto pt = p.lock())+ if (auto ppt = pt->load())+ self.process(nf);++ self.thread_lock.check_client_released();+ return 0;+ }});++ this->client = configuration.context;+ return jack_status_t{};+ }+ else+ {+ jack_status_t status{};+ this->client+ = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);+ if (this->client != nullptr)+ {+ if(status & JackNameNotUnique) {+ self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed.");+ }++ jack_set_process_callback(+ this->client,+ +[](jack_nframes_t nf, void* ctx) -> int {+ auto& self = *static_cast<Self*>(ctx);+ jack_port_t* port = self.port;++ // Is port created?+ if (port == nullptr)+ return 0;++ self.process(nf);++ self.thread_lock.check_client_released();+ return 0;+ },+ &self);+ jack_activate(this->client);+ }+ return status;+ }+ }++ template <typename Self>+ void disconnect(Self& self)+ {+ if (self.configuration.context)+ {+ if (self.configuration.clear_process_func)+ {+ self.configuration.clear_process_func(this_instance);+ }+ }++ if (this->client && !self.configuration.context)+ jack_client_close(this->client);++ self.client_open_ = std::errc::not_connected;+ }++ stdx::error+ create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)+ {+ // full name: "client_name:port_name\0"+ if (portName.empty())+ portName = flags & JackPortIsInput ? "i" : "o";++ if (self.configuration.client_name.size() + portName.size() + 2u+ >= static_cast<size_t>(jack_port_name_size()))+ {+ self.libremidi_handle_error(+ self.configuration, "port name length limit exceeded");+ return std::errc::invalid_argument;+ }++ if (!this->port)+ {+ this->port+ = jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0);+ }++ if (!this->port)+ {+ self.libremidi_handle_error(self.configuration, "error creating port");+ return std::errc::operation_not_supported;+ }+ return stdx::error{};+ }++ stdx::error do_close_port()+ {+ if (this->port == nullptr)+ return stdx::error{};++ // 1. Ensure that the next time the cycle runs it sees the port as nullptr+ jack_port_t* port_ptr = this->port.impl->load();+ this->port = nullptr;++ // 2. Signal through the semaphore and wait for the signal return+ this->thread_lock.prepare_release_client();++ // 3. Now we are sure that the client is not going to use the port anymore+ int err = jack_port_unregister(this->client, port_ptr);+ return from_errc(err);+ }+};+}
+ vendor/libremidi/backends/jack/midi_in.hpp view
@@ -0,0 +1,117 @@+#pragma once+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/jack/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <chrono>++namespace libremidi+{+class midi_in_jack final+ : public midi1::in_api+ , public jack_helpers+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : input_configuration+ , jack_input_configuration+ {+ } configuration;++ explicit midi_in_jack(input_configuration&& conf, jack_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ auto status = connect(*this);+ if (!this->client)+ {+ libremidi_handle_error(configuration, "Could not create JACK client");+ client_open_ = from_jack_status(status);+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_in_jack() override+ {+ midi_in_jack::close_port();++ disconnect(*this);+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }++ stdx::error open_port(const input_port& port, std::string_view portName) override+ {+ if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{})+ return err;++ if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));+ err != 0 && err != EEXIST)+ {+ libremidi_handle_error(+ configuration, "could not connect to port: " + port.port_name + " -> "+ + jack_port_name(this->port));+ return from_errc(err);+ }+ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ return create_local_port(*this, portName, JackPortIsInput);+ }++ stdx::error close_port() override { return do_close_port(); }++ stdx::error set_port_name(std::string_view portName) override+ {+ int ret = jack_port_rename(this->client, this->port, portName.data());+ return from_errc(ret);+ }++ timestamp absolute_timestamp() const noexcept override+ {+ return 1000 * jack_frames_to_time(client, jack_frame_time(client));+ }++ int process(jack_nframes_t nframes)+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = true,+ .has_samples = true,+ };+ void* buff = jack_port_get_buffer(this->port, nframes);++ // Timing+ jack_nframes_t current_frames{};+ jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC+ jack_time_t next_usecs{};+ float period_usecs{};+ jack_get_cycle_times(+ this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);++ // We have midi events in buffer+ uint32_t evCount = jack_midi_get_event_count(buff);+ for (uint32_t j = 0; j < evCount; j++)+ {+ jack_midi_event_t event{};+ jack_midi_event_get(&event, buff, j);+ const auto to_ns+ = [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); };++ m_processing.on_bytes(+ {event.buffer, event.buffer + event.size},+ m_processing.timestamp<timestamp_info>(to_ns, event.time));+ }++ return 0;+ }++ midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/jack/midi_out.hpp view
@@ -0,0 +1,244 @@+#pragma once+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/jack/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <semaphore>++namespace libremidi+{+struct jack_queue+{+public:+ static constexpr auto size_sz = sizeof(int32_t);++ jack_queue() = default;+ jack_queue(const jack_queue&) = delete;+ jack_queue(jack_queue&&) = delete;+ jack_queue& operator=(const jack_queue&) = delete;++ jack_queue& operator=(jack_queue&& other) noexcept+ {+ ringbuffer = other.ringbuffer;+ ringbuffer_space = other.ringbuffer_space;+ other.ringbuffer = nullptr;+ return *this;+ }++ explicit jack_queue(int64_t sz) noexcept+ {+ ringbuffer = jack_ringbuffer_create(sz);+ ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);+ }++ ~jack_queue() noexcept+ {+ if (ringbuffer)+ jack_ringbuffer_free(ringbuffer);+ }++ stdx::error write(const unsigned char* data, int64_t sz) const noexcept+ {+ if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)+ return std::errc::no_buffer_space;++ while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)+ sched_yield();++ jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);+ jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);++ return stdx::error{};+ }++ void read(void* jack_events) const noexcept+ {+ int32_t sz;+ while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz+ && jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)+ {+ jack_ringbuffer_read_advance(ringbuffer, size_sz);++ if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))+ jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);+ else+ jack_ringbuffer_read_advance(ringbuffer, sz);+ }+ }++ jack_ringbuffer_t* ringbuffer{};+ std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer+};++class midi_out_jack+ : public midi1::out_api+ , public jack_helpers+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : output_configuration+ , jack_output_configuration+ {+ } configuration;++ midi_out_jack(output_configuration&& conf, jack_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ }++ ~midi_out_jack() override { }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }++ stdx::error open_port(const output_port& port, std::string_view portName) override+ {+ if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{})+ return err;++ // Connecting to the output+ if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());+ err != 0 && err != EEXIST)+ {+ libremidi_handle_error(+ configuration, "could not connect to port" + port.port_name);+ return from_errc(err);+ }++ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ return create_local_port(*this, portName, JackPortIsOutput);+ }++ stdx::error close_port() override { return do_close_port(); }++ stdx::error set_port_name(std::string_view portName) override+ {+ int ret = jack_port_rename(this->client, this->port, portName.data());+ return from_errc(ret);+ }+};++class midi_out_jack_queued final : public midi_out_jack+{+public:+ midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf)+ : midi_out_jack{std::move(conf), std::move(apiconf)}+ , queue{configuration.ringbuffer_size}+ {+ auto status = connect(*this);+ if (!this->client)+ {+ libremidi_handle_error(configuration, "Could not create JACK client");+ client_open_ = from_jack_status(status);+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_out_jack_queued() override+ {+ midi_out_jack::close_port();++ disconnect(*this);+ }++ stdx::error send_message(const unsigned char* message, std::size_t size) override+ {+ return queue.write(message, size);+ }++ int process(jack_nframes_t nframes)+ {+ void* buff = jack_port_get_buffer(this->port, nframes);+ jack_midi_clear_buffer(buff);++ this->queue.read(buff);++ return 0;+ }++private:+ jack_queue queue;+};++class midi_out_jack_direct final : public midi_out_jack+{+public:+ midi_out_jack_direct(output_configuration&& conf, jack_output_configuration&& apiconf)+ : midi_out_jack{std::move(conf), std::move(apiconf)}+ {+ auto status = connect(*this);+ if (!this->client)+ {+ libremidi_handle_error(configuration, "Could not create JACK client");+ client_open_ = from_jack_status(status);+ return;+ }++ buffer_size = jack_get_buffer_size(this->client);+ client_open_ = stdx::error{};+ }++ ~midi_out_jack_direct() override+ {+ midi_out_jack::close_port();++ disconnect(*this);+ }++ int process(jack_nframes_t nframes)+ {+ void* buff = jack_port_get_buffer(this->port, nframes);+ jack_midi_clear_buffer(buff);+ return 0;+ }++ stdx::error send_message(const unsigned char* message, size_t size) override+ {+ void* buff = jack_port_get_buffer(this->port, buffer_size);+ int ret = jack_midi_event_write(buff, 0, message, size);+ return from_errc(ret);+ }++ int convert_timestamp(int64_t user) const noexcept+ {+ switch (configuration.timestamps)+ {+ case timestamp_mode::AudioFrame:+ return static_cast<int>(user);++ default:+ // TODO+ return 0;+ }+ }++ stdx::error schedule_message(int64_t ts, const unsigned char* message, size_t size) override+ {+ void* buff = jack_port_get_buffer(this->port, buffer_size);+ int ret = jack_midi_event_write(buff, convert_timestamp(ts), message, size);+ return from_errc(ret);+ }++ int buffer_size{};+};+}++namespace libremidi+{+template <>+inline std::unique_ptr<midi_out_api> make<midi_out_jack>(+ libremidi::output_configuration&& conf, libremidi::jack_output_configuration&& api)+{+ if (api.direct)+ return std::make_unique<midi_out_jack_direct>(std::move(conf), std::move(api));+ else+ return std::make_unique<midi_out_jack_queued>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/jack/observer.hpp view
@@ -0,0 +1,224 @@+#pragma once+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/jack/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <unordered_set>++namespace libremidi+{+class observer_jack final+ : public observer_api+ , private jack_client+ , private error_handler+{+public:+ struct+ : observer_configuration+ , jack_observer_configuration+ {+ } configuration;++ explicit observer_jack(observer_configuration&& conf, jack_observer_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ // Initialize JACK client+ if (configuration.context)+ {+ this->client = configuration.context;+ set_callbacks();+ }+ else+ {+ jack_status_t status{};+ this->client+ = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);+ if (status != jack_status_t{})+ libremidi_handle_error(configuration, std::to_string((int)status));++ if (this->client != nullptr)+ {+ set_callbacks();++ jack_activate(this->client);+ }+ }+ }++ void initial_callback()+ {+ {+ const char** ports+ = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);++ if (ports != nullptr)+ {+ int i = 0;+ while (ports[i] != nullptr)+ {+ auto port = jack_port_by_name(client, ports[i]);+ auto flags = jack_port_flags(port);++ bool physical = flags & JackPortIsPhysical;+ bool ok = configuration.track_any;+ if (configuration.track_hardware)+ ok |= physical;+ if (configuration.track_virtual)+ ok |= !physical;++ if (ok)+ {+ seen_input_ports.insert(ports[i]);+ if (this->configuration.input_added && configuration.notify_in_constructor)+ this->configuration.input_added(to_port_info<true>(client, port));+ }+ i++;+ }+ }++ jack_free(ports);+ }++ {+ const char** ports+ = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);++ if (ports != nullptr)+ {+ int i = 0;+ while (ports[i] != nullptr)+ {+ auto port = jack_port_by_name(client, ports[i]);+ auto flags = jack_port_flags(port);++ bool physical = flags & JackPortIsPhysical;+ bool ok = configuration.track_any;+ if (configuration.track_hardware)+ ok |= physical;+ if (configuration.track_virtual)+ ok |= !physical;++ if (ok)+ {+ seen_output_ports.insert(ports[i]);+ if (this->configuration.output_added && configuration.notify_in_constructor)+ this->configuration.output_added(to_port_info<false>(client, port));+ }+ i++;+ }+ }++ jack_free(ports);+ }+ }++ void on_port_callback(jack_port_t* port, bool reg)+ {+ auto flags = jack_port_flags(port);+ std::string name = jack_port_name(port);+ if (reg)+ {+ std::string_view type = jack_port_type(port);+ if (type != JACK_DEFAULT_MIDI_TYPE)+ return;++ bool physical = flags & JackPortIsPhysical;+ bool ok = configuration.track_any;+ if (configuration.track_hardware)+ ok |= physical;+ if (configuration.track_virtual)+ ok |= !physical;+ if (!ok)+ return;++ // Note: we keep track of the ports as+ // when disconnecting, jack_port_type and jack_port_flags aren't correctly+ // set anymore.++ if (flags & JackPortIsOutput)+ {+ seen_input_ports.insert(name);+ if (this->configuration.input_added)+ this->configuration.input_added(to_port_info<true>(client, port));+ }+ else if (flags & JackPortIsInput)+ {+ seen_output_ports.insert(name);+ if (this->configuration.output_added)+ this->configuration.output_added(to_port_info<false>(client, port));+ }+ }+ else+ {+ if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())+ {+ if (this->configuration.input_removed)+ this->configuration.input_removed(to_port_info<true>(client, port));+ seen_input_ports.erase(it);+ }+ if (auto it = seen_output_ports.find(name); it != seen_output_ports.end())+ {+ if (this->configuration.output_removed)+ this->configuration.output_removed(to_port_info<false>(client, port));+ seen_output_ports.erase(it);+ }+ }+ }++ void set_callbacks()+ {+ initial_callback();++ if (!configuration.has_callbacks())+ return;++ jack_set_port_registration_callback(+ this->client,+ +[](jack_port_id_t p, int r, void* arg) {+ auto& self = *(observer_jack*)arg;+ if (auto port = jack_port_by_id(self.client, p))+ {+ self.on_port_callback(port, r != 0);+ }+ },+ this);++ jack_set_port_rename_callback(+ this->client,+ +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) {+ const auto& self = *static_cast<observer_jack*>(arg);++ auto port = jack_port_by_id(self.client, p);+ if (!port)+ return;+ },+ this);+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }++ std::vector<libremidi::input_port> get_input_ports() const noexcept override+ {+ return get_ports<true>(this->client, nullptr, JackPortIsOutput);+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept override+ {+ return get_ports<false>(this->client, nullptr, JackPortIsInput);+ }++ ~observer_jack()+ {+ if (client && !configuration.context)+ {+ // If we own the client, deactivate it+ jack_deactivate(this->client);+ jack_client_close(this->client);+ this->client = nullptr;+ }+ }++ std::unordered_set<std::string> seen_input_ports;+ std::unordered_set<std::string> seen_output_ports;+};+}
@@ -0,0 +1,153 @@+#pragma once+#if __has_include(<boost/lockfree/spsc_queue.hpp>)+ #include <libremidi/backends/jack/config.hpp>+ #include <libremidi/backends/jack/helpers.hpp>+ #include <libremidi/shared_context.hpp>++ #include <boost/lockfree/spsc_queue.hpp>++ #include <variant>++namespace libremidi::jack+{++// Create a JACK client which will be shared across objects+struct shared_handler : public libremidi::shared_context+{+ explicit shared_handler(std::string_view v)+ {+ midiin_callbacks.reserve(64);+ midiout_callbacks.reserve(64);++ jack_status_t status{};+ client = jack_client_open(v.data(), JackNoStartServer, &status);+ assert(client);+ assert(status == 0);+ jack_set_process_callback(+ client,+ +[](jack_nframes_t cnt, void* ctx) -> int {+ ((shared_handler*)ctx)->jack_callback(cnt);+ return 0;+ },+ this);+ }++ virtual void start_processing() override { jack_activate(client); }+ virtual void stop_processing() override { jack_deactivate(client); }++ static shared_configurations make(std::string_view client_name)+ {+ auto clt = std::make_shared<shared_handler>(client_name);+ auto add_in_cb = [client = std::weak_ptr{clt}](libremidi::jack_callback cb) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::in_callback_added, std::move(cb)});+ };+ auto clear_in_cb = [client = std::weak_ptr{clt}](int64_t index) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::in_callback_removed, index});+ };+ auto add_out_cb = [client = std::weak_ptr{clt}](libremidi::jack_callback cb) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::out_callback_added, std::move(cb)});+ };+ auto clear_out_cb = [client = std::weak_ptr{clt}](int64_t index) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::out_callback_removed, index});+ };+ return {+ .context = clt,+ .observer = jack_observer_configuration{.context = clt->client},+ .in+ = jack_input_configuration{.context = clt->client, .set_process_func = add_in_cb, .clear_process_func = clear_in_cb},+ .out+ = jack_output_configuration{.context = clt->client, .set_process_func = add_out_cb, .clear_process_func = clear_out_cb},+ };+ }++ int jack_callback(jack_nframes_t cnt)+ {+ // 1. Process the events that will change the callback list+ event ev;+ while (events.pop(ev))+ {+ switch (ev.type)+ {+ case in_callback_added:+ midiin_callbacks.push_back(+ std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));+ break;+ case in_callback_removed: {+ auto idx = *std::get_if<int64_t>(&ev.payload);+ for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)+ {+ if (it->token == idx)+ {+ midiin_callbacks.erase(it);+ break;+ }+ else+ {+ ++it;+ }+ }+ break;+ }+ case out_callback_added:+ midiout_callbacks.push_back(+ std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));+ break;+ case out_callback_removed:+ auto idx = *std::get_if<int64_t>(&ev.payload);+ for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)+ {+ if (it->token == idx)+ {+ midiout_callbacks.erase(it);+ break;+ }+ else+ {+ ++it;+ }+ }+ break;+ }+ }++ for (auto& cb : midiin_callbacks)+ cb.callback(cnt);++ for (auto& cb : midiout_callbacks)+ cb.callback(cnt);++ return 0;+ }++ ~shared_handler()+ {+ jack_deactivate(client);+ jack_client_close(client);+ }++ jack_client_t* client{};++ enum event_type+ {+ in_callback_added,+ in_callback_removed,+ out_callback_added,+ out_callback_removed,+ };+ struct event+ {+ event_type type;+ std::variant<libremidi::jack_callback, int64_t> payload;+ };++ boost::lockfree::spsc_queue<event> events{16};++ std::vector<libremidi::jack_callback> midiin_callbacks;+ std::vector<libremidi::jack_callback> midiout_callbacks;+};+}+#endif
+ vendor/libremidi/backends/keyboard.hpp view
@@ -0,0 +1,21 @@+#pragma once+#include <libremidi/backends/dummy.hpp>+#include <libremidi/backends/keyboard/midi_in.hpp>++namespace libremidi+{+struct kbd_backend+{+ using midi_in = midi_in_kbd;+ using midi_in_configuration = kbd_input_configuration;+ using midi_out = midi_out_dummy;+ using midi_observer = observer_dummy;+ using midi_out_configuration = dummy_configuration;+ using midi_observer_configuration = dummy_configuration;+ static const constexpr auto API = libremidi::API::KEYBOARD;+ static const constexpr auto name = "keyboard";+ static const constexpr auto display_name = "Computer keyboard";++ static inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/keyboard/config.hpp view
@@ -0,0 +1,92 @@+#pragma once+#include <libremidi/config.hpp>++#include <functional>+#include <map>++namespace libremidi+{+/**+ * Used to set up keyboard input.+ * Your app should pass a function that will give you a callback+ * that you should call whenever you are getting a key input (press or release).+ */+struct kbd_input_configuration+{+ using scancode_callback = std::function<void(int)>;+ // First argument is on key press, second on key release+ std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks+ = [](scancode_callback, scancode_callback) {};++ enum kbd_event+ {+ NOTE_0 = 0x0, // C+ VEL_0 = NOTE_0 + 128, // Set velocity to 0+ OCT_0 = VEL_0 + 128, // Set octave to 0+ OCTAVE_PLUS = OCT_0 + 128,+ OCTAVE_MINUS,+ VELOCITY_PLUS,+ VELOCITY_MINUS,+ };++ // Default map+ //+ // ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,+ // | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |+ // |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|+ // | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |+ // |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |+ // | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |+ // |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|+ // | -^ | | O-| O+| V-| V+| | | | | | | ----^ |+ // |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|+ // | ctrl | | alt | |altgr | | ctrl |+ // '------' '-----'--------------------------'------' '------'++ // clang-format off+ // Use: https://kbdlayout.info/KBDUSX/scancodes+ std::map<int, int> scancode_map{+ { 0x1E, NOTE_0}, // C0+ { 0x11, NOTE_0 + 1},+ { 0x1F, NOTE_0 + 2},+ { 0x12, NOTE_0 + 3},+ { 0x20, NOTE_0 + 4},+ { 0x21, NOTE_0 + 5},+ { 0x14, NOTE_0 + 6},+ { 0x22, NOTE_0 + 7},+ { 0x15, NOTE_0 + 8},+ { 0x23, NOTE_0 + 9},+ { 0x16, NOTE_0 + 10},+ { 0x24, NOTE_0 + 11},+ { 0x25, NOTE_0 + 12}, // C1+ { 0x18, NOTE_0 + 13}, + { 0x26, NOTE_0 + 14}, + { 0x19, NOTE_0 + 15},+ { 0x27, NOTE_0 + 16},+ { 0x28, NOTE_0 + 17},+ { 0x1B, NOTE_0 + 18},+ { 0x2B, NOTE_0 + 19},++ { 0x29, VEL_0 + int(0)},+ { 0x02, VEL_0 + int(1 * 127 / 12)},+ { 0x03, VEL_0 + int(2 * 127 / 12)},+ { 0x04, VEL_0 + int(3 * 127 / 12)},+ { 0x05, VEL_0 + int(4 * 127 / 12)},+ { 0x06, VEL_0 + int(5 * 127 / 12)},+ { 0x07, VEL_0 + int(6 * 127 / 12)},+ { 0x08, VEL_0 + int(7 * 127 / 12)},+ { 0x09, VEL_0 + int(8 * 127 / 12)},+ { 0x0A, VEL_0 + int(9 * 127 / 12)},+ { 0x0B, VEL_0 + int(10 * 127 / 12)},+ { 0x0C, VEL_0 + int(11 * 127 / 12)},+ { 0x0D, VEL_0 + int(127)},++ { 0x2C, OCTAVE_MINUS },+ { 0x2D, OCTAVE_PLUS },+ { 0x2E, VELOCITY_MINUS },+ { 0x2F, VELOCITY_PLUS}, + };+ // clang-format on+};++}
+ vendor/libremidi/backends/keyboard/midi_in.hpp view
@@ -0,0 +1,112 @@+#pragma once+#include <libremidi/backends/keyboard/config.hpp>+#include <libremidi/detail/midi_in.hpp>++#include <chrono>+#include <unordered_map>++namespace libremidi+{+class midi_in_kbd final+ : public midi1::in_api+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : input_configuration+ , kbd_input_configuration+ {+ } configuration;++ explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ configuration.set_input_scancode_callbacks(+ [this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });+ }++ ~midi_in_kbd() override { }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }++ stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; }++ stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; }++ stdx::error close_port() override { return stdx::error{}; }++ stdx::error set_port_name(std::string_view) override { return stdx::error{}; }++ timestamp absolute_timestamp() const noexcept override+ {+ return std::chrono::steady_clock::now().time_since_epoch().count();+ }++ void on_keypress(int scancode)+ {+ using kevent = kbd_input_configuration::kbd_event;++ auto it = configuration.scancode_map.find(scancode);+ if (it == configuration.scancode_map.end())+ return;++ if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))+ {+ int note = it->second - kevent::NOTE_0 + 12 * m_current_octave;+ this->configuration.on_message(+ libremidi::channel_events::note_on(0, note, m_current_velocity));+ m_current_notes_scancodes[scancode] = note;+ }+ else if (it->second >= kevent::VEL_0 && it->second < (kevent::VEL_0 + 128))+ {+ m_current_velocity = it->second - kevent::VEL_0;+ }+ else if (it->second >= kevent::OCT_0 && it->second < (kevent::OCT_0 + 128))+ {+ m_current_octave = it->second - kevent::OCT_0;+ }+ else+ {+ switch (it->second)+ {+ case kevent::VELOCITY_MINUS:+ m_current_velocity = std::clamp(m_current_velocity - 10, 0, 127);+ break;+ case kevent::VELOCITY_PLUS:+ m_current_velocity = std::clamp(m_current_velocity + 10, 0, 127);+ break;+ case kevent::OCTAVE_MINUS:+ m_current_octave = std::clamp(m_current_octave - 1, 0, 127);+ break;+ case kevent::OCTAVE_PLUS:+ m_current_octave = std::clamp(m_current_octave + 1, 0, 127);+ break;+ }+ }+ }++ void on_keyrelease(int scancode)+ {+ using kevent = kbd_input_configuration::kbd_event;++ auto it = configuration.scancode_map.find(scancode);+ if (it == configuration.scancode_map.end())+ return;++ if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))+ {+ if (auto note_it = m_current_notes_scancodes.find(scancode);+ note_it != m_current_notes_scancodes.end())+ {+ this->configuration.on_message(libremidi::channel_events::note_off(0, note_it->second, 0));+ m_current_notes_scancodes.erase(note_it);+ }+ }+ }++ int m_current_octave{3};+ int m_current_velocity{80};+ std::unordered_map<int, int> m_current_notes_scancodes;+};+}
+ vendor/libremidi/backends/linux/alsa.hpp view
@@ -0,0 +1,459 @@+#pragma once++#include <libremidi/backends/linux/dylib_loader.hpp>++#include <alsa/asoundlib.h>++#if defined(SND_LIB_VERSION)+ #if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)+ #define LIBREMIDI_ALSA_HAS_RAMWIDI 1+ #define LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD 1+ #endif++ #if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10)+ #define LIBREMIDI_ALSA_HAS_UMP 1+ #endif+#endif++namespace libremidi+{++struct libasound+{+ // Useful one-liner:+ // nm -A * | grep ' snd_' | grep -v '@' | cut -f 2 -d 'U' | sort | uniq | sed 's/ snd_//' | sed 's/_/, /' | awk ' { print "LIBREMIDI_SYMBOL_DEF(snd_"$1 " " $2 ");" }'++ explicit libasound()+ : library{"libasound.so.2"}+ {+ if (!library)+ {+ available = false;+ return;+ }++ strerror = library.symbol<decltype(&::snd_strerror)>("snd_strerror");+ if (!strerror)+ available = false;+ }++ static const libasound& instance()+ {+ static const libasound self;+ return self;+ }++ dylib_loader library;+ decltype(&::snd_strerror) strerror{};+ bool available{true};++ struct card_t+ {+ explicit card_t(const dylib_loader& library)+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(snd_card, get_name)+ LIBREMIDI_SYMBOL_INIT(snd_card, next)+ }+ bool available{true};++ LIBREMIDI_SYMBOL_DEF(snd_card, get_name)+ LIBREMIDI_SYMBOL_DEF(snd_card, next)+ } card{library};++ struct ctl_t+ {+ explicit ctl_t(const dylib_loader& library)+ : rawmidi{library}+#if LIBREMIDI_ALSA_HAS_UMP+ , ump{library}+#endif+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(snd_ctl, close)+ LIBREMIDI_SYMBOL_INIT(snd_ctl, open)+ }+ bool available{true};++ LIBREMIDI_SYMBOL_DEF(snd_ctl, close)+ LIBREMIDI_SYMBOL_DEF(snd_ctl, open)++ struct rawmidi_t+ {+ explicit rawmidi_t(const dylib_loader& library)+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info)+ LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device)+ }+ bool available{true};+ LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info)+ LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device)+ } rawmidi;++#if LIBREMIDI_ALSA_HAS_UMP+ struct ump_t+ {+ explicit ump_t(const dylib_loader& library)+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, block_info)+ LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, endpoint_info)+ LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, next_device)+ }+ bool available{true};+ LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, block_info)+ LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, endpoint_info)+ LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, next_device)+ } ump;+#endif+ } ctl{library};++ struct midi_t+ {+ explicit midi_t(const dylib_loader& library)+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(snd_midi, event_decode)+ LIBREMIDI_SYMBOL_INIT(snd_midi, event_encode)+ LIBREMIDI_SYMBOL_INIT(snd_midi, event_free)+ LIBREMIDI_SYMBOL_INIT(snd_midi, event_init)+ LIBREMIDI_SYMBOL_INIT(snd_midi, event_new)+ LIBREMIDI_SYMBOL_INIT(snd_midi, event_no_status)+ LIBREMIDI_SYMBOL_INIT(snd_midi, event_resize_buffer)+ }++ bool available{true};+ LIBREMIDI_SYMBOL_DEF(snd_midi, event_decode)+ LIBREMIDI_SYMBOL_DEF(snd_midi, event_encode)+ LIBREMIDI_SYMBOL_DEF(snd_midi, event_free)+ LIBREMIDI_SYMBOL_DEF(snd_midi, event_init)+ LIBREMIDI_SYMBOL_DEF(snd_midi, event_new)+ LIBREMIDI_SYMBOL_DEF(snd_midi, event_no_status)+ LIBREMIDI_SYMBOL_DEF(snd_midi, event_resize_buffer)+ } midi{library};++#if LIBREMIDI_ALSA_HAS_RAMWIDI+ struct rawmidi_t+ {+ explicit rawmidi_t(const dylib_loader& library)+ {+ if (!library)+ {+ available = false;+ return;+ }+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, close)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_name)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevice_name)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevices_count)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_device)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_stream)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_subdevice)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, open)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_current)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_get_buffer_size)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_clock_type)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_no_active_sensing)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_read_mode)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_count)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_revents)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, read)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_get_avail)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, tread)+ LIBREMIDI_SYMBOL_INIT(snd_rawmidi, write)+ }++ bool available{true};+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, close)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_name)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevice_name)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevices_count)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_device)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_stream)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_subdevice)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_sizeof)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, open)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_current)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_get_buffer_size)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_clock_type)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_no_active_sensing)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_read_mode)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_sizeof)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_count)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_revents)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, read)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_get_avail)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_sizeof)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, tread)+ LIBREMIDI_SYMBOL_DEF(snd_rawmidi, write)+ } rawmidi{library};+#endif++ struct seq_t+ {+ explicit seq_t(const dylib_loader& library)+#if LIBREMIDI_ALSA_HAS_UMP+ : ump{library}+#endif+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(snd_seq, alloc_queue)+ LIBREMIDI_SYMBOL_INIT(snd_seq, client_id)+ LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client)+ LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name)+ LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client)+ LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_seq, close)+ LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from)+ LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue)+ LIBREMIDI_SYMBOL_INIT(snd_seq, create_port)+ LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port)+ LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output)+ LIBREMIDI_SYMBOL_INIT(snd_seq, event_input)+ LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending)+ LIBREMIDI_SYMBOL_INIT(snd_seq, event_output)+ LIBREMIDI_SYMBOL_INIT(snd_seq, free_event)+ LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue)+ LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info)+ LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info)+ LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info)+ LIBREMIDI_SYMBOL_INIT(snd_seq, open)+ LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors)+ LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)+ LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update)+ LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client)+ LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port)+ LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq)+ LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo)+ LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name)+ LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info)+ LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo)+ LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port)+ LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port)+ }++ bool available{true};+ LIBREMIDI_SYMBOL_DEF(snd_seq, alloc_queue)+ LIBREMIDI_SYMBOL_DEF(snd_seq, client_id)+ LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client)+ LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name)+ LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client)+ LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)+ LIBREMIDI_SYMBOL_DEF(snd_seq, close)+ LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from)+ LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue)+ LIBREMIDI_SYMBOL_DEF(snd_seq, create_port)+ LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port)+ LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output)+ LIBREMIDI_SYMBOL_DEF(snd_seq, event_input)+ LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending)+ LIBREMIDI_SYMBOL_DEF(snd_seq, event_output)+ LIBREMIDI_SYMBOL_DEF(snd_seq, free_event)+ LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue)+ LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info)+ LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info)+ LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info)+ LIBREMIDI_SYMBOL_DEF(snd_seq, open)+ LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors)+ LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real)+ LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update)+ LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client)+ LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port)+ LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq)+ LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo)+ LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof)++ LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_name)+ LIBREMIDI_SYMBOL_DEF(snd_seq, set_port_info)+ LIBREMIDI_SYMBOL_DEF(snd_seq, set_queue_tempo)+ LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port)+ LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port)++#if LIBREMIDI_ALSA_HAS_UMP+ struct ump_t+ {+ explicit ump_t(const dylib_loader& library)+ {+ if (!library)+ {+ available = false;+ return;+ }+ LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version)+ LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input)+ LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output)+ LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct)+ }++ bool available{true};++ LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version)+ LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input)+ LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output)+ LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct)+ } ump;+#endif+ } seq{library};++#if LIBREMIDI_ALSA_HAS_UMP+ struct ump_t+ {+ explicit ump_t(const dylib_loader& library)+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name)+ LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_ump, close)+ LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name)+ LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof)+ LIBREMIDI_SYMBOL_INIT(snd_ump, open)+ LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors)+ LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count)+ LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents)+ LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi)+ LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params)+ LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current)+ LIBREMIDI_SYMBOL_INIT(snd_ump, read)+ LIBREMIDI_SYMBOL_INIT(snd_ump, tread)+ LIBREMIDI_SYMBOL_INIT(snd_ump, write)+ }++ bool available{true};+ LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name)+ LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof)+ LIBREMIDI_SYMBOL_DEF(snd_ump, close)+ LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name)+ LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof)+ LIBREMIDI_SYMBOL_DEF(snd_ump, open)+ LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors)+ LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count)+ LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents)+ LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi)+ LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params)+ LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current)+ LIBREMIDI_SYMBOL_DEF(snd_ump, read)+ LIBREMIDI_SYMBOL_DEF(snd_ump, tread)+ LIBREMIDI_SYMBOL_DEF(snd_ump, write)+ } ump{library};+#endif+};++#undef snd_dylib_alloca+#define snd_dylib_alloca(ptr, access, type) \+ { \+ *ptr = (snd_##access##_##type##_t*)alloca(snd.access.type##_sizeof()); \+ memset(*ptr, 0, snd.access.type##_sizeof()); \+ }+#define snd_dylib_alloca2(ptr, access1, access2, type) \+ { \+ *ptr = (snd_##access1##_access2##_##type##_t*)alloca(snd.access1.access2.type##_sizeof()); \+ memset(*ptr, 0, snd.access1.access2.type##_sizeof()); \+ }++#undef snd_rawmidi_info_alloca+#define snd_rawmidi_info_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, info)+#undef snd_rawmidi_params_alloca+#define snd_rawmidi_params_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, params)+#undef snd_rawmidi_status_alloca+#define snd_rawmidi_status_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, status)++#undef snd_seq_client_info_alloca+#define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info)+#undef snd_seq_port_info_alloca+#define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info)+#undef snd_seq_queue_tempo_alloca+#define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo)++#if LIBREMIDI_ALSA_HAS_UMP+ #undef snd_ump_block_info_alloca+ #define snd_ump_block_info_alloca(ptr) snd_dylib_alloca(ptr, ump, block_info)+ #undef snd_ump_endpoint_info_alloca+ #define snd_ump_endpoint_info_alloca(ptr) snd_dylib_alloca(ptr, ump, endpoint_info)+#endif+}
+ vendor/libremidi/backends/linux/dylib_loader.hpp view
@@ -0,0 +1,85 @@+#pragma once+#if __has_include(<dlfcn.h>)+ #include <dlfcn.h>++ #include <cassert>++namespace libremidi+{+class dylib_loader+{+public:+ explicit dylib_loader(const char* const so)+ {+ impl = dlopen(so, RTLD_LAZY | RTLD_LOCAL | RTLD_NODELETE);+ }++ dylib_loader(const dylib_loader&) noexcept = delete;+ dylib_loader& operator=(const dylib_loader&) noexcept = delete;+ dylib_loader(dylib_loader&& other) noexcept+ {+ impl = other.impl;+ other.impl = nullptr;+ }++ dylib_loader& operator=(dylib_loader&& other) noexcept+ {+ impl = other.impl;+ other.impl = nullptr;+ return *this;+ }++ ~dylib_loader()+ {+ if (impl)+ {+ dlclose(impl);+ }+ }++ template <typename T>+ T symbol(const char* const sym) const noexcept+ {+ assert(impl);+ return reinterpret_cast<T>(dlsym(impl, sym));+ }++ operator bool() const noexcept { return bool(impl); }++private:+ void* impl{};+};++}++ #define LIBREMIDI_SYMBOL_NAME_S(prefix, name) #prefix "_" #name+ #define LIBREMIDI_SYMBOL_NAME(prefix, name) prefix##_##name+ #define LIBREMIDI_SYMBOL_DEF(prefix, name) \+ decltype(&::LIBREMIDI_SYMBOL_NAME(prefix, name)) name{};+ #define LIBREMIDI_SYMBOL_INIT(prefix, name) \+ { \+ name = library.symbol<decltype(&::LIBREMIDI_SYMBOL_NAME(prefix, name))>( \+ LIBREMIDI_SYMBOL_NAME_S(prefix, name)); \+ if (!name) \+ { \+ available = false; \+ return; \+ } \+ }++// Because some libs have names that are C++ keywords, e.g. udev_new:+ #define LIBREMIDI_SYMBOL_NAME2_S(prefix, name, varname) #prefix "_" #name+ #define LIBREMIDI_SYMBOL_NAME2(prefix, name, varname) prefix##_##name+ #define LIBREMIDI_SYMBOL_DEF2(prefix, name, varname) \+ decltype(&::LIBREMIDI_SYMBOL_NAME2(prefix, name, varname)) varname{};+ #define LIBREMIDI_SYMBOL_INIT2(prefix, name, varname) \+ { \+ varname = library.symbol<decltype(&::LIBREMIDI_SYMBOL_NAME2(prefix, name, varname))>( \+ LIBREMIDI_SYMBOL_NAME2_S(prefix, name, varname)); \+ if (!varname) \+ { \+ available = false; \+ return; \+ } \+ }+#endif
+ vendor/libremidi/backends/linux/helpers.hpp view
@@ -0,0 +1,77 @@+#pragma once+#include <sys/eventfd.h>+#include <sys/timerfd.h>++#include <poll.h>+#include <unistd.h>++#include <iostream>++namespace libremidi+{+struct eventfd_notifier+{+ eventfd_notifier(bool semaphore = true)+ {+ if (semaphore)+ this->fd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);+ else+ this->fd = eventfd(0, EFD_NONBLOCK);+ }+ ~eventfd_notifier() { close(this->fd); }++ eventfd_notifier(const eventfd_notifier&) = delete;+ eventfd_notifier(eventfd_notifier&&) = delete;+ eventfd_notifier& operator=(const eventfd_notifier&) = delete;+ eventfd_notifier& operator=(eventfd_notifier&&) = delete;++ void notify() noexcept { eventfd_write(fd, 1); }+ static bool ready(pollfd res) noexcept { return res.revents & POLLIN; }+ eventfd_t consume() noexcept+ {+ eventfd_t val;+ eventfd_read(fd, &val);+ return val;+ }++ operator int() const noexcept { return fd; }+ operator pollfd() const noexcept { return {.fd = fd, .events = POLLIN, .revents = 0}; }+ int fd{-1};+};++struct timerfd_timer+{+ timerfd_timer() { this->fd = timerfd_create(CLOCK_MONOTONIC, 0); }+ ~timerfd_timer() { close(this->fd); }++ timerfd_timer(const timerfd_timer&) = delete;+ timerfd_timer(timerfd_timer&&) = delete;+ timerfd_timer& operator=(const timerfd_timer&) = delete;+ timerfd_timer& operator=(timerfd_timer&&) = delete;++ void oneshot(int64_t nsec)+ {+ itimerspec t{};+ t.it_value.tv_nsec = nsec;+ timerfd_settime(this->fd, 0, &t, nullptr);+ }++ void restart(int64_t nsec)+ {+ itimerspec t{};+ t.it_value.tv_nsec = nsec;+ t.it_interval.tv_nsec = nsec;+ timerfd_settime(this->fd, 0, &t, nullptr);+ }++ void cancel()+ {+ itimerspec t{};+ timerfd_settime(this->fd, 0, &t, nullptr);+ }++ operator int() const noexcept { return fd; }+ operator pollfd() const noexcept { return {.fd = fd, .events = POLLIN, .revents = 0}; }+ int fd{-1};+};+}
+ vendor/libremidi/backends/linux/pipewire.hpp view
@@ -0,0 +1,169 @@+#pragma once++#include <libremidi/backends/linux/dylib_loader.hpp>++#include <pipewire/pipewire.h>++namespace libremidi+{++#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"+class libpipewire+{+public:+ decltype(&::pw_init) init{};+ decltype(&::pw_deinit) deinit{};++ decltype(&::pw_context_new) context_new{};+ decltype(&::pw_context_connect) context_connect{};+ decltype(&::pw_context_destroy) context_destroy{};++ decltype(&::pw_core_disconnect) core_disconnect{};++ decltype(&::pw_proxy_add_listener) proxy_add_listener{};+ decltype(&::pw_proxy_destroy) proxy_destroy{};++ decltype(&::pw_main_loop_new) main_loop_new{};+ decltype(&::pw_main_loop_destroy) main_loop_destroy{};+ decltype(&::pw_main_loop_quit) main_loop_quit{};+ decltype(&::pw_main_loop_run) main_loop_run{};+ decltype(&::pw_main_loop_get_loop) main_loop_get_loop{};+ /*+ decltype(&::pw_thread_loop_new) thread_loop_new{};+ decltype(&::pw_thread_loop_destroy) thread_loop_destroy{};+ decltype(&::pw_thread_loop_wait) thread_loop_wait{};+ decltype(&::pw_thread_loop_start) thread_loop_start{};+ decltype(&::pw_thread_loop_stop) thread_loop_stop;+ decltype(&::pw_thread_loop_lock) thread_loop_lock{};+ decltype(&::pw_thread_loop_unlock) thread_loop_unlock;+ decltype(&::pw_thread_loop_get_loop) thread_loop_get_loop{};+*/+ decltype(&::pw_properties_new) properties_new{};+ decltype(&::pw_properties_free) properties_free{};+ decltype(&::pw_properties_get) properties_get{};++ decltype(&::pw_filter_new_simple) filter_new_simple{};+ decltype(&::pw_filter_get_node_id) filter_get_node_id{};+ decltype(&::pw_filter_get_properties) filter_get_properties{};+ decltype(&::pw_filter_add_port) filter_add_port{};+ decltype(&::pw_filter_remove_port) filter_remove_port{};+ decltype(&::pw_filter_update_properties) filter_update_properties{};+ decltype(&::pw_filter_update_params) filter_update_params{};+ decltype(&::pw_filter_get_time) filter_get_time{};+ decltype(&::pw_filter_destroy) filter_destroy{};+ decltype(&::pw_filter_connect) filter_connect{};+ decltype(&::pw_filter_get_dsp_buffer) filter_get_dsp_buffer{};+ decltype(&::pw_filter_queue_buffer) filter_queue_buffer{};+ decltype(&::pw_filter_dequeue_buffer) filter_dequeue_buffer{};+ decltype(&::pw_filter_flush) filter_flush{};++ static const libpipewire& instance()+ {+ static const libpipewire self;+ return self;+ }++ bool available{true};++private:+ dylib_loader library;++ libpipewire()+ : library("libpipewire-0.3.so.0")+ {+ if (!library)+ {+ available = false;+ return;+ }++ // in terms of regex:+ // decltype\(&::([a-z_]+)\) [a-z_]+{};+ // \1 = library.symbol<decltype(&::\1)>("\1");+ init = library.symbol<decltype(&::pw_init)>("pw_init");+ deinit = library.symbol<decltype(&::pw_deinit)>("pw_deinit");++ context_new = library.symbol<decltype(&::pw_context_new)>("pw_context_new");+ context_connect = library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");+ context_destroy = library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");++ core_disconnect = library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");++ proxy_add_listener+ = library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");+ proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");++ main_loop_new = library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");+ main_loop_destroy = library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");+ main_loop_quit = library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");+ main_loop_run = library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");+ main_loop_get_loop+ = library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");++ properties_new = library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");+ properties_free = library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");+ properties_get = library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");++ filter_new_simple = library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");+ filter_get_node_id+ = library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");+ filter_get_properties+ = library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");+ filter_add_port = library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");+ filter_remove_port+ = library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");+ filter_update_properties+ = library.symbol<decltype(&::pw_filter_update_properties)>("pw_filter_update_properties");+ filter_update_params+ = library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");+ filter_get_time = library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");+ filter_destroy = library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");+ filter_connect = library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");+ filter_get_dsp_buffer+ = library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");+ filter_dequeue_buffer+ = library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");+ filter_queue_buffer+ = library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");+ filter_flush = library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");++ assert(init);+ assert(deinit);++ assert(context_new);+ assert(context_connect);+ assert(context_destroy);++ assert(core_disconnect);++ assert(proxy_destroy);++ assert(main_loop_new);+ assert(main_loop_destroy);+ assert(main_loop_quit);+ assert(main_loop_run);+ assert(main_loop_get_loop);++ assert(properties_new);+ assert(properties_free);+ assert(properties_get);++ assert(filter_new_simple);+ assert(filter_get_node_id);+ assert(filter_get_properties);+ assert(filter_add_port);+ assert(filter_remove_port);+ assert(filter_update_properties);+ assert(filter_update_params);+ assert(filter_get_time);+ assert(filter_destroy);+ assert(filter_connect);+ assert(filter_get_dsp_buffer);+ assert(filter_dequeue_buffer);+ assert(filter_queue_buffer);+ assert(filter_flush);+ }+};+#pragma GCC diagnostic pop+}
+ vendor/libremidi/backends/linux/udev.hpp view
@@ -0,0 +1,92 @@+#pragma once+#include <libremidi/backends/linux/dylib_loader.hpp>+#include <libremidi/backends/linux/helpers.hpp>++#include <libudev.h>++#include <cassert>++namespace libremidi+{++struct libudev+{+ // Useful one-liner:+ // nm -A * | grep ' udev_' | grep -v '@' | cut -f 2 -d 'U' | sort | uniq | sed 's/ udev_//'| awk ' { print "LIBREMIDI_SYMBOL_DEF(udev, "$1");" }'++ explicit libudev()+ : library{"libudev.so.1"}+ {+ if (!library)+ {+ available = false;+ return;+ }++ LIBREMIDI_SYMBOL_INIT(udev, device_get_action)+ LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem)+ LIBREMIDI_SYMBOL_INIT(udev, device_unref)+ LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)+ LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)+ LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)+ LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)+ LIBREMIDI_SYMBOL_INIT(udev, monitor_unref)+ LIBREMIDI_SYMBOL_INIT2(udev, new, create)+ LIBREMIDI_SYMBOL_INIT(udev, unref)+ }++ static const libudev& instance()+ {+ static const libudev self;+ return self;+ }++ dylib_loader library;+ bool available{true};++ LIBREMIDI_SYMBOL_DEF(udev, device_get_action)+ LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem)+ LIBREMIDI_SYMBOL_DEF(udev, device_unref)+ LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)+ LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)+ LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)+ LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)+ LIBREMIDI_SYMBOL_DEF(udev, monitor_unref)+ LIBREMIDI_SYMBOL_DEF2(udev, new, create)+ LIBREMIDI_SYMBOL_DEF(udev, unref)+};++struct udev_helper+{+ udev_helper()+ {+ instance = udev.create();+ assert(instance);++ monitor = udev.monitor_new_from_netlink(instance, "udev");+ assert(monitor);+ udev.monitor_enable_receiving(monitor);+ }++ ~udev_helper()+ {+ udev.monitor_unref(monitor);+ udev.unref(instance);+ }++ udev_helper(const udev_helper&) = delete;+ udev_helper(udev_helper&&) = delete;+ udev_helper& operator=(const udev_helper&) = delete;+ udev_helper& operator=(udev_helper&&) = delete;++ operator pollfd() const noexcept+ {+ return {.fd = udev.monitor_get_fd(monitor), .events = POLLIN, .revents = 0};+ }++ const libudev& udev = libudev::instance();+ struct udev* instance{};+ udev_monitor* monitor{};+};++}
+ vendor/libremidi/backends/net/config.hpp view
@@ -0,0 +1,86 @@+#pragma once+#include <libremidi/config.hpp>++#include <string>+namespace boost::asio+{+struct io_context;+}++namespace libremidi::net+{+enum class protocol+{+ OSC_MIDI,+};++struct dgram_input_configuration+{+ std::string client_name = "libremidi client";++ enum protocol protocol = protocol::OSC_MIDI;+ std::string accept = "0.0.0.0";+ int port{};++ boost::asio::io_context* io_context{};+};++struct dgram_output_configuration+{+ std::string client_name = "libremidi client";++ enum protocol protocol = protocol::OSC_MIDI;+ std::string host = "127.0.0.1";+ int port{};+ bool broadcast{};++ boost::asio::io_context* io_context{};+};++struct dgram_observer_configuration+{+ std::string client_name = "libremidi client";++ boost::asio::io_context* io_context{};+};++}++namespace libremidi::net_ump+{+enum class protocol+{+ OSC_MIDI2,+};++struct dgram_input_configuration+{+ std::string client_name = "libremidi client";++ enum protocol protocol = protocol::OSC_MIDI2;+ std::string accept = "0.0.0.0";+ int port{};++ boost::asio::io_context* io_context{};+};++struct dgram_output_configuration+{+ std::string client_name = "libremidi client";++ enum protocol protocol = protocol::OSC_MIDI2;+ std::string host = "127.0.0.1";+ int port{};+ bool broadcast{};++ boost::asio::io_context* io_context{};+};++struct dgram_observer_configuration+{+ std::string client_name = "libremidi client";++ boost::asio::io_context* io_context{};+};++}
+ vendor/libremidi/backends/net/helpers.hpp view
@@ -0,0 +1,91 @@+#pragma once+#include <libremidi/config.hpp>++#include <boost/asio/ip/udp.hpp>++#include <memory>+namespace stdx+{ /*++class boost_system_error_domain : public error_domain+{+public:+ constexpr boost_system_error_domain() noexcept+ : error_domain{+ {1 - 0x3c223c0aa3cf45e5ULL, 1 - 0x80dac24345cfb9fcULL},+ default_error_resource_management_t<detail::exception_ptr_wrapper>{}}+ {+ }++ string_ref name() const noexcept override { return "boost::system_error domain"; }++ bool equivalent(const error& lhs, const error& rhs) const noexcept override { return false; }++ string_ref message(const error& e) const noexcept override {+ auto err = e.m_value;+ + }++ [[noreturn]] void throw_exception(const error& e) const override+ {+ assert(e.domain() == *this);+ std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());+ }+};+*/+}++namespace libremidi+{+template <typename T>+struct optionally_owned+{+public:+ explicit optionally_owned(T* maybe_existing)+ : storage{.ref = maybe_existing}+ {+ if (storage.ref)+ {+ ownership = unowned;+ return;+ }+ else+ {+ std::destroy_at(&storage.ref);+ std::construct_at<T>(reinterpret_cast<T*>(storage.object));+ ownership = owned;+ }+ }++ ~optionally_owned()+ {+ if (is_owned())+ std::destroy_at(reinterpret_cast<T*>(storage.object));+ }++ T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); }+ const T& get() const noexcept+ {+ return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref);+ }++ optionally_owned(const optionally_owned&) = delete;+ optionally_owned(optionally_owned&&) noexcept = delete;+ optionally_owned& operator=(const optionally_owned&) = delete;+ optionally_owned& operator=(optionally_owned&&) noexcept = delete;++ bool is_owned() const noexcept { return ownership == owned; }++private:+ union+ {+ alignas(T) unsigned char object[sizeof(T)];+ T* ref;+ } storage;+ enum+ {+ owned,+ unowned+ } ownership{};+};+}
+ vendor/libremidi/backends/net/midi_in.hpp view
@@ -0,0 +1,440 @@+#pragma once+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+template <typename Impl, typename F>+struct osc_parser+{+ [[no_unique_address]]+ Impl impl;+ F on_message;+ std::string_view port_name;++ stdx::error parse_bundle(const char* data, std::size_t sz)+ {+ std::string_view txt(data, sz);+ if (!txt.starts_with("#bundle"))+ return std::errc::bad_message;+ return std::errc::protocol_not_supported;+ }++ stdx::error parse_message(const char* data, std::size_t sz)+ {+ const auto begin = data;+ const auto end = data + sz;+ std::size_t pattern_len = strnlen(data, sz);+ if (pattern_len == sz)+ return std::errc::bad_message;++ if (auto pat = std::string_view(data, pattern_len); pat != port_name)+ return std::errc::bad_address;++ data += pattern_len;+ for (; data < end; ++data)+ if (*data == 0)+ continue;+ else+ break;++ // Now we shold reach the typetag beginning, ","+ if (data == end)+ return std::errc::bad_message;+ if ((data - begin) % 4 != 0)+ return std::errc::bad_message;+ if (*data++ != ',')+ return std::errc::bad_message;++ // Count the ,mmmmm arguments... yummy+ int num_msgs = 0;+ for (; data < end; ++data)+ if (*data == Impl::typetag)+ num_msgs++;+ else if (*data == 0)+ break;+ else+ return std::errc::bad_message;++ if (num_msgs == 0)+ return std::errc::no_message;++ // By now there's at least 5 bytes left as the ,mmm has to be round-up to 4 and padded with zeros:+ if (end - data < 4)+ return std::errc::bad_message;++ switch ((1 + num_msgs) % 4)+ {+ case 0:+ data += 4;+ break;+ case 1:+ data += 3;+ break;+ case 2:+ data += 2;+ break;+ case 3:+ data += 1;+ break;+ }++ // Data starts+ return impl.process_midi_bytes(on_message, num_msgs, data, end - data);+ }++ stdx::error parse_int_message(const char* /* data */, std::size_t /* sz */)+ {+ return std::errc::protocol_not_supported;+ }++ stdx::error parse_packet(const char* data, std::size_t sz)+ {+ if (sz == 0)+ return std::errc::no_message;++ switch (data[0])+ {+ case '#':+ return parse_bundle(data, sz);+ break;+ case '/':+ return parse_message(data, sz);+ break;+ case '\0':+ return parse_int_message(data, sz);+ break;+ default:+ return std::errc::bad_message;+ }+ }+};++}++namespace libremidi::net+{++struct osc_parser_midi1+{+ static constexpr char typetag = 'm';++ stdx::error+ process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t sz)+ {+ if (sz != num_msgs * 4)+ return std::errc::bad_message;++ auto end = data + sz;+ for (; data < end; data += 4)+ on_message(data + 1);++ return {};+ }+};++class midi_in final+ : public midi1::in_api+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : input_configuration+ , dgram_input_configuration+ {+ } configuration;++ explicit midi_in(input_configuration&& conf, dgram_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ , m_ctx{configuration.io_context}+ , m_socket{m_ctx.get()}+ {+ m_socket.open(boost::asio::ip::udp::v4());+ m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));++ m_endpoint+ = {boost::asio::ip::make_address(this->configuration.accept),+ static_cast<unsigned short>(this->configuration.port)};++ boost::system::error_code ec;+ m_socket.bind(m_endpoint, ec);+ if (ec != boost::system::error_code{})+ {+ client_open_ = std::errc::address_in_use;+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_in() override { close_port(); }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }++ stdx::error open_port(const input_port&, std::string_view port) override+ {+ return open_virtual_port(port);+ }++ stdx::error open_virtual_port(std::string_view port) override+ {+ m_portname = std::string(port);++ receive();++ if (m_ctx.is_owned())+ {+ m_thread = std::jthread{[this, &ctx = m_ctx.get()] {+ auto wg = boost::asio::make_work_guard(m_ctx);+ ctx.run();+ }};+ }++ return stdx::error{};+ }++ void receive()+ {+ m_socket.async_receive_from(+ boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,+ [this](auto ec, std::size_t sz) {+ if (ec == boost::asio::error::operation_aborted)+ return;++ if (!ec && sz > 0)+ this->on_bytes(reinterpret_cast<const char*>(m_data), sz);++ this->receive();+ });+ }++ stdx::error close_port() override+ {+ // FIXME async close+ if (m_socket.is_open())+ m_socket.close();+ return {};+ }++ stdx::error set_port_name(std::string_view) override { return stdx::error{}; }++ timestamp absolute_timestamp() const noexcept override+ {+ return std::chrono::steady_clock::now().time_since_epoch().count();+ }++ void on_bytes(const char* data, std::size_t size)+ {+ const auto on_msg = [this](const char* bytes) { this->on_message(bytes); };+ osc_parser<osc_parser_midi1, decltype(on_msg)> parser{{}, on_msg, m_portname};+ parser.parse_packet(data, size);+ }++ void on_message(const char* data)+ {+ // OSC enforces always 3 bytes+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = false,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ static constexpr auto to_ns = []() {+ return std::chrono::duration_cast<std::chrono::nanoseconds>(+ std::chrono::steady_clock::now().time_since_epoch())+ .count();+ };++ auto ptr = reinterpret_cast<const uint8_t*>(data);+ m_processing.on_bytes({ptr, ptr + 3}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }++ midi1::input_state_machine m_processing{this->configuration};++ libremidi::optionally_owned<boost::asio::io_context> m_ctx;+ boost::asio::ip::udp::endpoint m_endpoint;+ boost::asio::ip::udp::socket m_socket;++ std::jthread m_thread;++ std::string m_portname;+ alignas(16) unsigned char m_data[65535];+};+}++namespace libremidi::net_ump+{+struct osc_parser_midi2+{+ static constexpr char typetag = 'M';++ stdx::error+ process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t byte_sz)+ {+ if (byte_sz % 4 != 0)+ return std::errc::bad_message;++ auto sz = byte_sz / 4;++ auto begin = reinterpret_cast<const uint32_t*>(data);+ auto end = begin + sz;+ std::size_t accounted = 0;+ for (auto it = begin; it < end && accounted < num_msgs;)+ {+ const auto N = cmidi2_ump_get_message_size_bytes(it) / 4;+ switch (N)+ {+ case 1:+ case 2:+ case 4:+ if (it + N <= end)+ {+ on_message(it, N);+ accounted++;+ }+ else+ return std::errc::bad_message;+ break;+ default:+ return std::errc::bad_message;+ }+ it += N;+ }++ return {};+ }+};++class midi_in final+ : public midi2::in_api+ , public error_handler+{+public:+ using midi_api::client_open_;+ struct+ : ump_input_configuration+ , dgram_input_configuration+ {+ } configuration;++ explicit midi_in(ump_input_configuration&& conf, dgram_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ , m_ctx{configuration.io_context}+ , m_socket{m_ctx.get()}+ {+ m_socket.open(boost::asio::ip::udp::v4());+ m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));++ m_endpoint+ = {boost::asio::ip::make_address(this->configuration.accept),+ static_cast<unsigned short>(this->configuration.port)};++ boost::system::error_code ec;+ m_socket.bind(m_endpoint, ec);+ if (ec != boost::system::error_code{})+ {+ client_open_ = std::errc::address_in_use;+ return;+ }++ client_open_ = stdx::error{};+ }++ ~midi_in() override { close_port(); }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }++ stdx::error open_port(const input_port&, std::string_view port) override+ {+ return open_virtual_port(port);+ }++ stdx::error open_virtual_port(std::string_view port) override+ {+ m_portname = std::string(port);++ receive();++ if (m_ctx.is_owned())+ {+ m_thread = std::jthread{[this, &ctx = m_ctx.get()] {+ auto wg = boost::asio::make_work_guard(m_ctx);+ ctx.run();+ }};+ }+ return stdx::error{};+ }++ void receive()+ {+ m_socket.async_receive_from(+ boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,+ [this](auto ec, std::size_t sz) {+ if (ec == boost::asio::error::operation_aborted)+ return;++ if (!ec && sz > 0)+ this->on_bytes(reinterpret_cast<const char*>(m_data), sz);++ this->receive();+ });+ }++ stdx::error close_port() override+ {+ // FIXME async close+ if (m_ctx.is_owned())+ m_ctx.get().stop();++ if (m_socket.is_open())+ m_socket.close();+ return {};+ }++ stdx::error set_port_name(std::string_view) override { return stdx::error{}; }++ timestamp absolute_timestamp() const noexcept override+ {+ return std::chrono::steady_clock::now().time_since_epoch().count();+ }++ void on_bytes(const char* data, std::size_t size)+ {+ const auto on_msg+ = [this](const uint32_t* bytes, std::size_t N) { this->on_message(bytes, N); };+ osc_parser<osc_parser_midi2, decltype(on_msg)> parser{{}, on_msg, m_portname};+ parser.parse_packet(data, size);+ }++ void on_message(const uint32_t* ump, std::size_t sz)+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = false,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ static constexpr auto to_ns = []() {+ return std::chrono::duration_cast<std::chrono::nanoseconds>(+ std::chrono::steady_clock::now().time_since_epoch())+ .count();+ };++ m_processing.on_bytes({ump, sz}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }++ midi2::input_state_machine m_processing{this->configuration};++ libremidi::optionally_owned<boost::asio::io_context> m_ctx;+ boost::asio::ip::udp::endpoint m_endpoint;+ boost::asio::ip::udp::socket m_socket;++ std::jthread m_thread;++ std::string m_portname;+ alignas(16) unsigned char m_data[65535];+};+}
+ vendor/libremidi/backends/net/midi_out.hpp view
@@ -0,0 +1,288 @@+#pragma once+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/helpers.hpp>+#include <libremidi/cmidi2.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <boost/asio/io_context.hpp>+#include <boost/asio/ip/udp.hpp>+#include <boost/endian.hpp>++namespace libremidi::net+{++struct osc_midi1_packet+{+ stdx::error init_packet(std::string_view osc_pattern)+ {+ int i = 0;+ for (; i < std::ssize(osc_pattern); i++)+ if (osc_pattern.data()[i] != 0)+ bytes[i] = osc_pattern.data()[i];+ else+ break;+ bytes[i] = 0;+ while (i % 4 != 3)+ {+ ++i;+ bytes[i] = 0;+ }++ bytes[++i] = ',';+ bytes[++i] = 'm';+ bytes[++i] = 0;+ bytes[++i] = 0;++ // MIDI message (m) spec: port n°, status byte, data 1, data 2+ bytes[++i] = 0;+ message_size = ++i + 3;++ return stdx::error{}; // FIXME+ }++ stdx::error deinit()+ {+ message_size = 0;+ return stdx::error{};+ }++ stdx::error set_packet_content(const unsigned char* message, size_t size)+ {+ if (message_size == 0)+ return std::errc::not_connected;++ if (size != 3)+ return std::errc::message_size;++ std::memcpy(this->bytes + message_size - 3, message, 3);++ return stdx::error{};+ }++ std::span<const char> get_data() { return std::span(this->bytes, this->message_size); }+ char bytes[512 + 8 + 8];+ int message_size{};+};++class midi_out final+ : public midi1::out_api+ , public error_handler+{+public:+ struct+ : output_configuration+ , dgram_output_configuration+ {+ } configuration;++ midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ , ctx{configuration.io_context}+ , m_socket{ctx.get()}+ {+ m_socket.open(boost::asio::ip::udp::v4());+ m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));+ m_socket.set_option(boost::asio::socket_base::broadcast(true));++ m_endpoint+ = {boost::asio::ip::make_address(this->configuration.host),+ static_cast<unsigned short>(this->configuration.port)};++ this->client_open_ = stdx::error{};+ }++ ~midi_out() override { close_port(); }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }++ stdx::error open_port(const output_port& /* port */, std::string_view portName) override+ {+ return open_virtual_port(portName);+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ // Random arbitrary limit to avoid abuse+ if (portName.size() >= 512)+ return std::errc::invalid_argument;++ pkt.init_packet(portName);++ return stdx::error{};+ }++ stdx::error close_port() override+ {+ pkt.deinit();+ // FIXME async close+ if (m_socket.is_open())+ m_socket.close();+ return {};+ }++ stdx::error send_message(const unsigned char* message, size_t size) override+ {+ if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})+ return err;++ auto dat = pkt.get_data();+ boost::system::error_code ec;+ m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);+ return stdx::error{}; // FIXME+ }++ stdx::error schedule_message(int64_t, const unsigned char*, size_t) override+ {+ int ret = 0;+ return from_errc(ret);+ }++ libremidi::optionally_owned<boost::asio::io_context> ctx;+ boost::asio::ip::udp::endpoint m_endpoint;+ boost::asio::ip::udp::socket m_socket;++ osc_midi1_packet pkt;+};++}++namespace libremidi::net_ump+{+struct osc_midi2_packet+{+ stdx::error init_packet(std::string_view osc_pattern)+ {+ int i = 0;+ for (; i < std::ssize(osc_pattern); i++)+ if (osc_pattern.data()[i] != 0)+ bytes[i] = osc_pattern.data()[i];+ else+ break;+ bytes[i] = 0;+ while (i % 4 != 3)+ {+ ++i;+ bytes[i] = 0;+ }++ bytes[++i] = ',';+ bytes[++i] = 'M';+ bytes[++i] = 0;+ bytes[++i] = 0;++ // MIDI 2 message (M) spec: an UMP+ header_size = ++i;++ return stdx::error{}; // FIXME+ }++ stdx::error deinit()+ {+ header_size = 0;+ return stdx::error{};+ }++ stdx::error set_packet_content(const uint32_t* message, size_t size)+ {+ if (header_size == 0)+ return std::errc::not_connected;++ if (size > 4)+ return std::errc::message_size;++ message_size = 4 * size;++ std::memcpy(this->bytes + header_size, message, message_size);++ return stdx::error{};+ }++ std::span<const char> get_data()+ {+ return std::span(this->bytes, this->header_size + this->message_size);+ }+ char bytes[512 + 8 + 64];+ int header_size{};+ int message_size{};+};++class midi_out final+ : public midi2::out_api+ , public error_handler+{+public:+ struct+ : output_configuration+ , dgram_output_configuration+ {+ } configuration;++ midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ , ctx{configuration.io_context}+ , m_socket{ctx.get()}+ {+ m_socket.open(boost::asio::ip::udp::v4());+ m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));+ m_socket.set_option(boost::asio::socket_base::broadcast(true));++ m_endpoint+ = {boost::asio::ip::make_address(this->configuration.host),+ static_cast<unsigned short>(this->configuration.port)};++ this->client_open_ = stdx::error{};+ }++ ~midi_out() override { close_port(); }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }++ stdx::error open_port(const output_port& /* port */, std::string_view portName) override+ {+ return open_virtual_port(portName);+ }++ stdx::error open_virtual_port(std::string_view portName) override+ {+ // Random arbitrary limit to avoid abuse+ if (portName.size() >= 512)+ return std::errc::invalid_argument;++ pkt.init_packet(portName);++ return stdx::error{};+ }++ stdx::error close_port() override+ {+ pkt.deinit();+ if (m_socket.is_open())+ m_socket.close();+ return {};+ }++ stdx::error send_ump(const uint32_t* message, size_t size) override+ {+ if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})+ return err;++ auto dat = pkt.get_data();+ boost::system::error_code ec;+ m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);+ return stdx::error{}; // FIXME+ }++ stdx::error schedule_ump(int64_t, const uint32_t*, size_t) override+ {+ int ret = 0;+ return from_errc(ret);+ }++ libremidi::optionally_owned<boost::asio::io_context> ctx;+ boost::asio::ip::udp::endpoint m_endpoint;+ boost::asio::ip::udp::socket m_socket;++ osc_midi2_packet pkt;+};++}
+ vendor/libremidi/backends/net/observer.hpp view
@@ -0,0 +1,14 @@+#pragma once+#include <libremidi/backends/dummy.hpp>++namespace libremidi::net+{+using net_observer_configuration = libremidi::dummy_configuration;+using observer = libremidi::observer_dummy;+}++namespace libremidi::net_ump+{+using net_observer_configuration = libremidi::dummy_configuration;+using observer = libremidi::observer_dummy;+}
+ vendor/libremidi/backends/network.hpp view
@@ -0,0 +1,24 @@+#pragma once+#include <libremidi/backends/dummy.hpp>+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/midi_in.hpp>+#include <libremidi/backends/net/midi_out.hpp>+#include <libremidi/backends/net/observer.hpp>++namespace libremidi::net+{+struct backend+{+ using midi_in = libremidi::net::midi_in;+ using midi_out = libremidi::net::midi_out;+ using midi_observer = libremidi::net::observer;+ using midi_in_configuration = libremidi::net::dgram_input_configuration;+ using midi_out_configuration = libremidi::net::dgram_output_configuration;+ using midi_observer_configuration = libremidi::net::net_observer_configuration;+ static const constexpr auto API = libremidi::API::NETWORK;+ static const constexpr auto name = "network";+ static const constexpr auto display_name = "Network";++ static inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/network_ump.hpp view
@@ -0,0 +1,23 @@+#pragma once+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/net/midi_in.hpp>+#include <libremidi/backends/net/midi_out.hpp>+#include <libremidi/backends/net/observer.hpp>++namespace libremidi::net_ump+{+struct backend+{+ using midi_in = libremidi::net_ump::midi_in;+ using midi_out = libremidi::net_ump::midi_out;+ using midi_observer = libremidi::net_ump::observer;+ using midi_in_configuration = libremidi::net_ump::dgram_input_configuration;+ using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;+ using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;+ static const constexpr auto API = libremidi::API::NETWORK_UMP;+ static const constexpr auto name = "network (UMP)";+ static const constexpr auto display_name = "Network (UMP)";++ static inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/pipewire.hpp view
@@ -0,0 +1,30 @@+#pragma once+#include <libremidi/backends/dummy.hpp>+#include <libremidi/backends/linux/pipewire.hpp>+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/backends/pipewire/midi_in.hpp>+#include <libremidi/backends/pipewire/midi_out.hpp>+#include <libremidi/backends/pipewire/observer.hpp>++namespace libremidi::pipewire+{+struct backend+{+ using midi_in = midi_in_pipewire;+ using midi_out = midi_out_pipewire;+ using midi_observer = observer_pipewire;+ using midi_in_configuration = pipewire_input_configuration;+ using midi_out_configuration = pipewire_output_configuration;+ using midi_observer_configuration = pipewire_observer_configuration;+ static const constexpr auto API = libremidi::API::PIPEWIRE;+ static const constexpr auto name = "pipewire";+ static const constexpr auto display_name = "PipeWire";++ static inline bool available() noexcept+ {+ static const libpipewire& pw = libpipewire::instance();+ return pw.available;+ }+};+}
+ vendor/libremidi/backends/pipewire/config.hpp view
@@ -0,0 +1,53 @@+#pragma once+#include <libremidi/config.hpp>++#include <cinttypes>+#include <cstdint>+#include <functional>+#include <string>++extern "C" {+struct pw_main_loop;+struct pw_filter;+struct spa_io_position;+}++namespace libremidi+{+using pipewire_callback_function = std::function<void(spa_io_position*)>;+struct pipewire_callback+{+ int64_t token;+ pipewire_callback_function callback;+};++struct pipewire_input_configuration+{+ std::string client_name = "libremidi client";++ pw_main_loop* context{};+ pw_filter* filter{};+ std::function<void(pipewire_callback)> set_process_func{};+ std::function<void(int64_t)> clear_process_func{};+};++struct pipewire_output_configuration+{+ std::string client_name = "libremidi client";++ pw_main_loop* context{};+ pw_filter* filter{};+ std::function<void(pipewire_callback)> set_process_func{};+ std::function<void(int64_t)> clear_process_func{};++ int64_t output_buffer_size{65536};+};++struct pipewire_observer_configuration+{+ std::string client_name = "libremidi client";++ pw_main_loop* context{};+};++}
+ vendor/libremidi/backends/pipewire/context.hpp view
@@ -0,0 +1,612 @@+#pragma once+#include <libremidi/backends/linux/pipewire.hpp>+#include <libremidi/detail/memory.hpp>+#include <libremidi/error.hpp>++#include <pipewire/filter.h>+#include <pipewire/pipewire.h>+#include <spa/control/control.h>+#include <spa/param/props.h>+#include <spa/utils/defs.h>+#include <spa/utils/result.h>++#include <algorithm>+#include <array>+#include <atomic>+#include <functional>+#include <iostream>+#include <memory>+#include <string>+#include <unordered_map>+#include <vector>++#include <spa/param/audio/format-utils.h>++#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"+namespace libremidi+{+template <typename K, typename V>+using hash_map = std::unordered_map<K, V>;++struct pipewire_instance+{+ const libpipewire& pw = libpipewire::instance();+ pipewire_instance()+ {+ /// Initialize the PipeWire main loop, context, etc.+ int argc = 0;+ char* argv[] = {NULL};+ char** aa = argv;+ pw.init(&argc, &aa);+ }++ ~pipewire_instance() { pw.deinit(); }+};++struct pipewire_context+{+ struct listened_port+ {+ uint32_t id{};+ pw_port* port{};+ std::unique_ptr<spa_hook> listener;+ };++ struct port_info+ {+ uint32_t id{};++ std::string format;+ std::string port_name;+ std::string port_alias;+ std::string object_path;+ std::string node_id;+ std::string port_id;++ bool physical{};+ bool terminal{};+ bool monitor{};+ pw_direction direction{};+ };++ struct node+ {+ std::vector<port_info> inputs;+ std::vector<port_info> outputs;+ };++ struct graph+ {+ mutable std::mutex mtx;+ libremidi::hash_map<uint32_t, node> physical_audio;+ libremidi::hash_map<uint32_t, node> physical_midi;+ libremidi::hash_map<uint32_t, node> software_audio;+ libremidi::hash_map<uint32_t, node> software_midi;+ libremidi::hash_map<uint32_t, port_info> port_cache;++ void for_each_port(auto func)+ {+ for (auto& map : {physical_audio, physical_midi, software_audio, software_midi})+ {+ for (auto& [id, node] : map)+ {+ for (auto& port : node.inputs)+ func(port);+ for (auto& port : node.outputs)+ func(port);+ }+ }+ }++ void remove_port(uint32_t id)+ {+ port_cache.erase(id);+ for (auto map : {&physical_audio, &physical_midi, &software_audio, &software_midi})+ {+ for (auto& [_, node] : *map)+ {+ std::erase_if(node.inputs, [id](const port_info& p) { return p.id == id; });+ std::erase_if(node.outputs, [id](const port_info& p) { return p.id == id; });+ }+ }+ }+ } current_graph;++ explicit pipewire_context(pw_main_loop* inst)+ : main_loop{inst}+ , owns_main_loop{false}+ {+ assert(main_loop);++ initialize();+ }++ explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)+ : global_instance{inst}+ , owns_main_loop{true}+ {+ this->main_loop = pw.main_loop_new(nullptr);+ if (!this->main_loop)+ {+ // libremidi::logger().libremidi_handle_error("main_loop_new failed!");+ return;+ }+ initialize();+ }++ void initialize()+ {+ this->lp = pw.main_loop_get_loop(this->main_loop);+ if (!lp)+ {+ // libremidi::logger().libremidi_handle_error("main_loop_get_loop failed!");+ return;+ }++ this->context = pw.context_new(lp, nullptr, 0);+ if (!this->context)+ {+ // libremidi::logger().libremidi_handle_error("context_new failed!");+ return;+ }++ this->core = pw.context_connect(this->context, nullptr, 0);+ if (!this->core)+ {+ // libremidi::logger().libremidi_handle_error("context_connect failed!");+ return;+ }++ this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);+ if (!this->registry)+ {+ // libremidi::logger().libremidi_handle_error("core_get_registry failed!");+ return;+ }++ initialize_observation();++ synchronize();++ // Add a manual 1ms event loop iteration at the end of+ // ctor to ensure synchronous clients will still see the ports+ pw_loop_iterate(this->lp, 1);+ }++ void initialize_observation()+ {+ // Register a listener which will listen on when ports are added / removed+ spa_zero(registry_listener);++ static constexpr const struct pw_registry_events registry_events = {+ .version = PW_VERSION_REGISTRY_EVENTS,+ .global =+ [](void* object, uint32_t id, uint32_t /*permissions*/, const char* type,+ uint32_t /*version*/, const struct spa_dict* /*props*/) {+ pipewire_context& self = *(pipewire_context*)object;+ if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0)+ self.register_port(id, type);+ },+ .global_remove =+ [](void* object, uint32_t id) {+ pipewire_context& self = *(pipewire_context*)object;+ self.unregister_port(id);+ },+ };++ // Start listening+ pw_registry_add_listener(this->registry, &this->registry_listener, ®istry_events, this);+ }++ void register_port(uint32_t id, const char* type)+ {+ auto port = (pw_port*)pw_registry_bind(registry, id, type, PW_VERSION_PORT, 0);+ port_listener.push_back({id, port, std::make_unique<spa_hook>()});+ auto& l = port_listener.back();++ static constexpr const struct pw_port_events port_events = {+ .version = PW_VERSION_PORT_EVENTS,+ .info+ = [](void* object,+ const pw_port_info* info) { ((pipewire_context*)object)->update_port_info(info); },+ };+ pw_port_add_listener(l.port, l.listener.get(), &port_events, this);+ }++ void unregister_port(uint32_t id)+ {+ // When a port is removed:+ // Notify+ std::unique_lock _{current_graph.mtx, std::defer_lock};+ if (on_port_removed)+ {+ _.lock();+ if (auto it = current_graph.port_cache.find(id); it != current_graph.port_cache.end())+ {+ auto copy = it->second;+ _.unlock();+ on_port_removed(copy);+ }+ else+ {+ _.unlock();+ }+ }++ // Remove from the graph+ {+ _.lock();+ current_graph.remove_port(id);+ _.unlock();+ }++ // Remove from the listeners+ auto it+ = std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) {+ return l.id == id;+ });+ if (it != port_listener.end())+ {+ pw.proxy_destroy((pw_proxy*)it->port);+ port_listener.erase(it);+ }+ }++ void synchronize()+ {+ pending = 0;+ done = 0;++ if (!core)+ return;++ spa_hook core_listener;++ static constexpr struct pw_core_events core_events = {+ .version = PW_VERSION_CORE_EVENTS,+ .done =+ [](void* object, uint32_t id, int seq) {+ auto& self = *(pipewire_context*)object;+ if(id == PW_ID_CORE && seq == self.pending)+ {+ self.done = 1;+ libpipewire::instance().main_loop_quit(self.main_loop);+ }+ },+ };++ spa_zero(core_listener);+ pw_core_add_listener(core, &core_listener, &core_events, this);++ pending = pw_core_sync(core, PW_ID_CORE, 0);+ while (!done)+ {+ pw.main_loop_run(this->main_loop);+ }+ spa_hook_remove(&core_listener);+ }++ [[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_t in_port)+ {+ auto props = pw.properties_new(+ PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT,+ std::to_string(in_port).c_str(), nullptr);++ auto proxy = (pw_proxy*)pw_core_create_object(+ this->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);++ if (!proxy)+ {+ pw.properties_free(props);+ return nullptr;+ }++ synchronize();+ pw.properties_free(props);+ return proxy;+ }++ void unlink_ports(pw_proxy* link) { pw.proxy_destroy(link); }++ void update_port_info(const pw_port_info* info)+ {+ const spa_dict_item* item{};++ port_info p;+ p.id = info->id;++ spa_dict_for_each(item, info->props)+ {+ std::string_view k{item->key}, v{item->value};+ if (k == "format.dsp")+ p.format = v;+ else if (k == "port.name")+ p.port_name = v;+ else if (k == "port.alias")+ p.port_alias = v;+ else if (k == "object.path")+ p.object_path = v;+ else if (k == "port.id")+ p.port_id = v;+ else if (k == "node.id")+ p.node_id = v;+ else if (k == "port.physical" && v == "true")+ p.physical = true;+ else if (k == "port.terminal" && v == "true")+ p.terminal = true;+ else if (k == "port.monitor" && v == "true")+ p.monitor = true;+ else if (k == "port.direction")+ {+ if (v == "out")+ {+ p.direction = pw_direction::SPA_DIRECTION_OUTPUT;+ }+ else+ {+ p.direction = pw_direction::SPA_DIRECTION_INPUT;+ }+ }+ }++ if (p.node_id.empty())+ return;++ const auto nid = std::stoul(p.node_id);+ auto get_node = [&]() -> node* {+ if (p.physical)+ {+ if (p.format.find("audio") != p.format.npos)+ return &this->current_graph.physical_audio[nid];+ else if (p.format.find("midi") != p.format.npos)+ return &this->current_graph.physical_midi[nid];+ }+ else+ {+ if (p.format.find("audio") != p.format.npos)+ return &this->current_graph.software_audio[nid];+ else if (p.format.find("midi") != p.format.npos)+ return &this->current_graph.software_midi[nid];+ }+ return nullptr;+ };++ {+ std::lock_guard _{current_graph.mtx};+ current_graph.port_cache[p.id] = p;+ if (auto node = get_node())+ {+ if (p.direction == pw_direction::SPA_DIRECTION_OUTPUT)+ node->outputs.push_back(p);+ else+ node->inputs.push_back(p);+ }+ }++ if (on_port_added)+ on_port_added(p);+ }++ int get_fd() const noexcept+ {+ if (!this->lp)+ return -1;++ auto spa_callbacks = this->lp->control->iface.cb;+ auto spa_loop_methods = (const spa_loop_control_methods*)spa_callbacks.funcs;+ if (spa_loop_methods->get_fd)+ return spa_loop_methods->get_fd(spa_callbacks.data);+ else+ return -1;+ }++ ~pipewire_context()+ {+ if (this->registry)+ pw.proxy_destroy((pw_proxy*)this->registry);+ for (auto& [id, p, l] : this->port_listener)+ if (l)+ pw.proxy_destroy((pw_proxy*)p);+ if (this->core)+ pw.core_disconnect(this->core);+ if (this->context)+ pw.context_destroy(this->context);+ if (owns_main_loop && this->main_loop)+ pw.main_loop_destroy(this->main_loop);+ }++ friend struct pipewire_filter;+ const libpipewire& pw = libpipewire::instance();+ std::shared_ptr<pipewire_instance> global_instance;++ pw_main_loop* main_loop{};+ pw_loop* lp{};++ pw_context* context{};+ pw_core* core{};++ pw_registry* registry{};+ spa_hook registry_listener{};++ std::function<void(const port_info&)> on_port_added;+ std::function<void(const port_info&)> on_port_removed;++ std::vector<listened_port> port_listener{};++ std::atomic<int> pending{};+ std::atomic<int> done{};+ bool owns_main_loop{true};+ int sync{};+};++struct pipewire_filter+{+ const libpipewire& pw = libpipewire::instance();+ std::shared_ptr<pipewire_context> loop{};+ pw_filter* filter{};+ std::vector<pw_proxy*> links{};++ struct port+ {+ void* data;+ }* port{};++ explicit pipewire_filter(std::shared_ptr<pipewire_context> loop)+ : loop{loop}+ {+ }++ explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)+ : loop{loop}+ , filter{filter}+ {+ }++ void create_filter(std::string_view filter_name, const pw_filter_events& events, void* context)+ {+ assert(!filter);++ auto& pw = libpipewire::instance();+ // clang-format off+ this->filter = pw.filter_new_simple(+ loop->lp,+ filter_name.data(),+ pw.properties_new(+ PW_KEY_MEDIA_TYPE, "Midi",+ PW_KEY_MEDIA_CATEGORY, "Filter",+ PW_KEY_MEDIA_ROLE, "DSP",+ PW_KEY_MEDIA_NAME, "",+#if defined(PW_KEY_NODE_LOCK_RATE)+ PW_KEY_NODE_LOCK_RATE, "true",+#endif+ PW_KEY_NODE_ALWAYS_PROCESS, "true",+ PW_KEY_NODE_PAUSE_ON_IDLE, "false",+#if defined(PW_KEY_NODE_SUSPEND_ON_IDLE)+ PW_KEY_NODE_SUSPEND_ON_IDLE, "false",+#endif+ nullptr),+ &events,+ context);+ // clang-format on+ assert(filter);+ }++ void destroy()+ {+ if (this->filter)+ pw.filter_destroy(this->filter);+ }++ stdx::error create_local_port(std::string_view port_name, spa_direction direction)+ {+ // clang-format off+ this->port = (struct port*)pw.filter_add_port(+ this->filter,+ direction,+ PW_FILTER_PORT_FLAG_MAP_BUFFERS,+ sizeof(struct port),+ pw.properties_new(+ PW_KEY_FORMAT_DSP, "8 bit raw midi",+ PW_KEY_PORT_NAME, port_name.data(),+ nullptr),+ nullptr, 0);+ // clang-format on+ if (!port)+ return std::errc::invalid_argument;+ return stdx::error{};+ }++ void set_port_buffer(int bytes)+ {+ uint8_t buffer[1024];+ struct spa_pod_builder builder;+ spa_pod_builder_init(&builder, buffer, sizeof(buffer));++ // clang-format off+ const struct spa_pod* params[1] = {+ (spa_pod*) spa_pod_builder_add_object(+ &builder,+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 32),+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(bytes, 4096, INT32_MAX),+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)+ )+ };+ // clang-format on++ pw.filter_update_params(this->filter, this->port, params, 1);+ }++ stdx::error remove_port()+ {+ assert(this->port);+ int ret = pw.filter_remove_port(this->port);+ this->port = nullptr;+ return from_errc(ret);+ }++ stdx::error rename_port(std::string_view port_name)+ {+ if (this->port)+ {+ spa_dict_item items[1] = {+ SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name.data()),+ };++ auto properties = SPA_DICT_INIT(items, 1);+ int ret = pw.filter_update_properties(this->filter, this->port, &properties);+ return from_errc(ret);+ }+ else+ {+ return std::errc::not_connected;+ }+ }++ [[nodiscard]] stdx::error start_filter()+ {+ if (int ret = pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0); ret < 0)+ {+ return from_errc(ret);+ }+ else+ {+ return stdx::error{};+ }+ }++ uint32_t filter_node_id() { return this->loop->pw.filter_get_node_id(this->filter); }++ void synchronize_node()+ {+ this->loop->synchronize();+ int k = 0;+ auto node_id = filter_node_id();+ while (node_id == 4294967295)+ {+ this->loop->synchronize();+ node_id = filter_node_id();++ if (k++; k > 100)+ return;+ }+ }+ void synchronize_ports(const pipewire_context::node& this_node)+ {+ // Leave some time to resolve the ports+ int k = 0;+ const auto num_local_ins = 1;+ const auto num_local_outs = 0;+ while (this_node.inputs.size() < num_local_ins || this_node.outputs.size() < num_local_outs)+ {+ this->loop->synchronize();+ if (k++; k > 100)+ return;+ }+ }+};+}++#pragma GCC diagnostic pop
+ vendor/libremidi/backends/pipewire/helpers.hpp view
@@ -0,0 +1,473 @@+#pragma once++#include <libremidi/backends/linux/helpers.hpp>+#include <libremidi/backends/linux/pipewire.hpp>+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/context.hpp>+#include <libremidi/detail/memory.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/semaphore.hpp>++#include <atomic>+#include <semaphore>+#include <stop_token>+#include <thread>++namespace libremidi+{+struct pipewire_helpers+{+ struct port+ {+ void* data{};+ };++ // All pipewire operations have to happen in the same thread+ // - and pipewire checks that internally.+ std::jthread main_loop_thread;+ const libpipewire& pw = libpipewire::instance();+ std::shared_ptr<pipewire_instance> global_instance;+ std::shared_ptr<pipewire_context> global_context;+ std::unique_ptr<pipewire_filter> filter;+ pw_proxy* link{};++ int64_t this_instance{};++ eventfd_notifier termination_event{};+ pollfd fds[2]{};++ semaphore_pair_lock thread_lock;+ std::shared_ptr<void> canary = std::make_shared<int>();++ enum poll_state+ {+ start_poll,+ in_poll,+ not_in_poll+ };+ std::atomic<poll_state> current_state{not_in_poll};++ pipewire_helpers()+ {+ static std::atomic_int64_t instance{};+ this_instance = ++instance;++ fds[1] = termination_event;+ }++ template <typename Self>+ stdx::error create_filter(Self& self)+ {+ if (this->filter)+ return stdx::error{};++ auto& configuration = self.configuration;+ if (configuration.context && configuration.filter && configuration.set_process_func)+ {+ this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);++ pipewire_callback cbs{+ .token = this_instance,+ .callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void {+ if (auto pt = p.lock())+ self.process(nf);++ self.thread_lock.check_client_released();+ }};+ configuration.set_process_func(cbs);+ }+ else+ {+ this->filter = std::make_unique<pipewire_filter>(this->global_context);+#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"+ static constexpr struct pw_filter_events filter_events+ = {.version = PW_VERSION_FILTER_EVENTS,+ .process = +[](void* _data, struct spa_io_position* position) -> void {+ // FIXME likely we need the thread_lock check here too+ Self& self = *static_cast<Self*>(_data);+ self.process(position);+ }};+#pragma GCC diagnostic pop++ this->filter->create_filter(self.configuration.client_name, filter_events, &self);+ return this->filter->start_filter();+ }+ return stdx::error{};+ }++ template <typename Self>+ void destroy_filter(Self& self)+ {+ assert(global_context);+ if (!global_context->owns_main_loop)+ {+ if (self.configuration.clear_process_func)+ {+ self.configuration.clear_process_func(this_instance);+ }+ }+ else+ {+ if (this->filter)+ {+ this->filter->destroy();+ }+ }++ this->filter.reset();+ }++ template <typename Self>+ stdx::error create_context(Self& self)+ {+ if (this->global_context)+ return stdx::error{};++ // Initialize PipeWire client+ auto& configuration = self.configuration;+ if (configuration.context)+ {+ this->global_context = std::make_shared<pipewire_context>(configuration.context);+ }+ else+ {+ this->global_instance = std::make_shared<pipewire_instance>();+ this->global_context = std::make_shared<pipewire_context>(this->global_instance);+ }+ if (!this->global_context->main_loop)+ return std::errc::connection_refused;++ return stdx::error{};+ }++ void destroy_context()+ {+ assert(this->global_context);+ this->global_context.reset();+ this->global_instance.reset();+ }++ void run_poll_loop()+ try+ {+ // Note: called from a std::jthread.+ assert(this->global_context);+ if (int fd = this->global_context->get_fd(); fd != -1)+ {+ fds[0] = {.fd = fd, .events = POLLIN, .revents = 0};+ current_state = poll_state::in_poll;++ for (;;)+ {+ if (int err = poll(fds, 2, -1); err < 0)+ {+ if (err == -EAGAIN)+ continue;+ else+ break;+ }++ // Check pipewire fd:+ if (fds[0].revents & POLLIN)+ {+ if (auto lp = this->global_context->lp)+ {+ int result = pw_loop_iterate(lp, 0);+ if (result < 0)+ {+ LIBREMIDI_LOG(spa_strerror(result));+ }+ }+ fds[0].revents = 0;+ }++ // Check exit fd:+ if (fds[1].revents & POLLIN)+ {+ break;+ }+ }+ }+ current_state = poll_state::not_in_poll;+ }+ catch (...)+ {+ current_state = poll_state::not_in_poll;+ }++ template <typename Self>+ stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction)+ {+ assert(this->global_context);+ assert(this->filter);++ if (portName.empty())+ portName = direction == SPA_DIRECTION_INPUT ? "i" : "o";++ if (!this->filter->port)+ {+ auto ret = this->filter->create_local_port(portName.data(), direction);+ if (ret != stdx::error{})+ {+ self.libremidi_handle_error(self.configuration, "error creating port");+ return ret;+ }+ }++ return stdx::error{};+ }++ void add_callbacks(const observer_configuration& conf)+ {+ assert(global_context);+ global_context->on_port_added = [&conf](const pipewire_context::port_info& port) {+ if (port.format.find("midi") == std::string::npos)+ return;++ bool unfiltered = conf.track_any;+ unfiltered |= (port.physical && conf.track_hardware);+ unfiltered |= (!port.physical && conf.track_virtual);+ if (unfiltered)+ {+ if (port.direction == SPA_DIRECTION_INPUT)+ {+ if (conf.output_added)+ conf.output_added(to_port_info<SPA_DIRECTION_INPUT>(port));+ }+ else+ {+ if (conf.input_added)+ conf.input_added(to_port_info<SPA_DIRECTION_OUTPUT>(port));+ }+ }+ };++ global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) {+ if (port.format.find("midi") == std::string::npos)+ return;++ bool unfiltered = conf.track_any;+ unfiltered |= (port.physical && conf.track_hardware);+ unfiltered |= (!port.physical && conf.track_virtual);+ if (unfiltered)+ {+ if (port.direction == SPA_DIRECTION_INPUT)+ {+ if (conf.output_removed)+ conf.output_removed(to_port_info<SPA_DIRECTION_INPUT>(port));+ }+ else+ {+ if (conf.input_removed)+ conf.input_removed(to_port_info<SPA_DIRECTION_OUTPUT>(port));+ }+ }+ };+ }++ void start_thread()+ {+ if (!this->global_context->owns_main_loop)+ return;++ current_state = poll_state::start_poll;+ main_loop_thread = std::jthread{[this]() { run_poll_loop(); }};+ }++ void stop_thread()+ {+ assert(this->global_context);+ if (!this->global_context->owns_main_loop)+ return;++ if (main_loop_thread.joinable() || current_state != poll_state::not_in_poll)+ {+ termination_event.notify();+ main_loop_thread.request_stop();++ termination_event.notify();+ for (int i = 0; i < 100; i++)+ {+ if (current_state == poll_state::not_in_poll)+ break;+ std::this_thread::sleep_for(std::chrono::milliseconds(10));+ termination_event.notify();+ }++ if (main_loop_thread.joinable())+ main_loop_thread.join();+ }+ }++ stdx::error do_close_port()+ {+ if (!this->filter)+ return stdx::error{};+ if (!this->filter->port)+ return stdx::error{};++ if (!this->global_context->owns_main_loop)+ {+ this->canary.reset();+ this->thread_lock.prepare_release_client();+ }++ unlink_ports();+ return this->filter->remove_port();+ }++ stdx::error rename_port(std::string_view port_name)+ {+ if (this->filter)+ {+ return this->filter->rename_port(port_name);+ }+ else+ {+ return std::errc::not_connected;+ }+ }++ void unlink_ports()+ {+ if (link)+ {+ this->global_context->unlink_ports(link);+ link = nullptr;+ }+ }++ stdx::error link_ports(auto& self, const input_port& in_port)+ {+ // Wait for the pipewire server to send us back our node's info+ for (int i = 0; i < 1000; i++)+ this->filter->synchronize_node();++ auto this_node = this->filter->filter_node_id();+ auto& midi = this->global_context->current_graph.software_midi;+ auto node_it = midi.find(this_node);+ if (node_it == midi.end())+ return std::errc::invalid_argument;++ // Wait for the pipewire server to send us back our node's ports+ this->filter->synchronize_ports(node_it->second);++ if (node_it->second.inputs.empty())+ return std::errc::no_link;++ // Link ports+ const auto& p = node_it->second.inputs.front();+ link = this->global_context->link_ports(in_port.port, p.id);+ pw_loop_iterate(this->global_context->lp, 1);+ if (!link)+ {+ self.libremidi_handle_error(+ self.configuration,+ "could not connect to port: " + in_port.port_name + " -> " + p.port_name);+ return std::errc::no_link;+ }++ return stdx::error{};+ }++ stdx::error link_ports(auto& self, const output_port& out_port)+ {+ // Wait for the pipewire server to send us back our node's info+ for (int i = 0; i < 1000; i++)+ this->filter->synchronize_node();++ auto this_node = this->filter->filter_node_id();+ auto& midi = this->global_context->current_graph.software_midi;+ auto node_it = midi.find(this_node);+ if (node_it == midi.end())+ {+ return std::errc::invalid_argument;+ }++ // Wait for the pipewire server to send us back our node's ports+ this->filter->synchronize_ports(node_it->second);++ if (node_it->second.outputs.empty())+ {+ return std::errc::no_link;+ }++ // Link ports+ const auto& p = node_it->second.outputs.front();+ link = this->global_context->link_ports(p.id, out_port.port);+ pw_loop_iterate(this->global_context->lp, 1);+ if (!link)+ {+ self.libremidi_handle_error(+ self.configuration,+ "could not connect to port: " + p.port_name + " -> " + out_port.port_name);+ return std::errc::no_link;+ }++ return stdx::error{};+ }++ template <spa_direction Direction>+ static auto to_port_info(const pipewire_context::port_info& port)+ -> std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>+ {+ std::string device_name, port_name;+ auto name_colon = port.port_alias.find(':');+ if (name_colon != std::string::npos)+ {+ device_name = port.port_alias.substr(0, name_colon);+ port_name = port.port_alias.substr(name_colon + 1);+ }+ else+ {+ port_name = port.port_alias;+ }++ return {{+ .client = 0,+ .port = port.id,+ .manufacturer = "",+ .device_name = device_name,+ .port_name = port.port_name,+ .display_name = port_name,+ }};+ }++ // Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer)+ // is an "output" port from the point of view of pipewire as data will come out of it+ template <spa_direction Direction>+ static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept+ -> std::vector<+ std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>+ {+ std::vector<std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>+ ret;++ {+ std::lock_guard _{ctx.current_graph.mtx};+ if (conf.track_any || conf.track_hardware)+ for (auto& node : ctx.current_graph.physical_midi)+ {+ for (auto& port :+ (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))+ {+ ret.push_back(to_port_info<Direction>(port));+ }+ }++ if (conf.track_any || conf.track_virtual)+ for (auto& node : ctx.current_graph.software_midi)+ {+ for (auto& port :+ (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))+ {+ ret.push_back(to_port_info<Direction>(port));+ }+ }+ }++ return ret;+ }+};+}
+ vendor/libremidi/backends/pipewire/midi_in.hpp view
@@ -0,0 +1,131 @@+#pragma once+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{+class midi_in_pipewire final+ : public midi1::in_api+ , public pipewire_helpers+ , public error_handler+{+public:+ struct+ : input_configuration+ , pipewire_input_configuration+ {+ } configuration;++ explicit midi_in_pipewire(input_configuration&& conf, pipewire_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (auto ret = create_context(*this); ret != stdx::error{})+ {+ client_open_ = ret;+ return;+ }+ if (auto ret = create_filter(*this); ret != stdx::error{})+ {+ client_open_ = ret;+ return;+ }+ client_open_ = stdx::error{};+ }++ ~midi_in_pipewire() override+ {+ stop_thread();+ do_close_port();+ destroy_filter(*this);+ destroy_context();+ client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }++ stdx::error open_port(const input_port& in_port, std::string_view name) override+ {+ if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})+ return err;++ if (auto err = link_ports(*this, in_port); err != stdx::error{})+ return err;++ start_thread();+ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view name) override+ {+ if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})+ return err;++ start_thread();+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ stop_thread();+ return do_close_port();+ }++ stdx::error set_port_name(std::string_view port_name) override+ {+ return rename_port(port_name);+ }++ timestamp absolute_timestamp() const noexcept override { return system_ns(); }++ void process(struct spa_io_position* position)+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = true,+ .has_samples = true,+ };++ assert(this->filter);+ assert(this->filter->port);+ const auto b = pw.filter_dequeue_buffer(this->filter->port);+ if (!b)+ return;++ const auto buf = b->buffer;+ const auto d = &buf->datas[0];++ if (d->data == nullptr)+ return;++ const auto pod+ = (spa_pod*)spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size);+ if (!pod)+ return;+ if (!spa_pod_is_sequence(pod))+ return;++ struct spa_pod_control* c{};+ SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c)+ {+ if (c->type != SPA_CONTROL_Midi)+ continue;++ auto data = (uint8_t*)SPA_POD_BODY(&c->value);+ auto size = SPA_POD_BODY_SIZE(&c->value);++ const auto to_ns = [=, clk = position->clock] {+ return 1e9 * ((clk.position + c->offset) / (double)clk.rate.denom);+ };++ m_processing.on_bytes(+ {data, data + size}, m_processing.timestamp<timestamp_info>(to_ns, c->offset));+ }++ pw.filter_queue_buffer(this->filter->port, b);+ }++ midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/pipewire/midi_out.hpp view
@@ -0,0 +1,188 @@+#pragma once+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/detail/midi_out.hpp>++#include <readerwriterqueue.h>++#include <semaphore>++namespace libremidi+{+class midi_out_pipewire+ : public midi1::out_api+ , public pipewire_helpers+ , public error_handler+{+public:+ struct+ : output_configuration+ , pipewire_output_configuration+ {+ } configuration;++ midi_out_pipewire(output_configuration&& conf, pipewire_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (auto ret = create_context(*this); ret != stdx::error{})+ {+ client_open_ = ret;+ return;+ }+ if (auto ret = create_filter(*this); ret != stdx::error{})+ {+ client_open_ = ret;+ return;+ }+ client_open_ = stdx::error{};+ }++ ~midi_out_pipewire() override+ {+ stop_thread();+ do_close_port();+ destroy_filter(*this);+ destroy_context();+ client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }++ stdx::error open_port(const output_port& out_port, std::string_view name) override+ {+ if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})+ return err;++ this->filter->set_port_buffer(configuration.output_buffer_size);++ if (auto err = link_ports(*this, out_port); err != stdx::error{})+ return err;++ start_thread();+ return stdx::error{};+ }++ stdx::error open_virtual_port(std::string_view name) override+ {+ if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})+ return err;++ this->filter->set_port_buffer(configuration.output_buffer_size);++ start_thread();+ return stdx::error{};+ }++ stdx::error close_port() override+ {+ stop_thread();+ return do_close_port();+ }++ stdx::error set_port_name(std::string_view port_name) override+ {+ return rename_port(port_name);+ }++ int process(spa_io_position* pos)+ {+ m_process_clock.store(pos->clock.nsec, std::memory_order_relaxed);+ const auto b = pw.filter_dequeue_buffer(this->filter->port);+ if (!b)+ return 1;++ const auto buf = b->buffer;+ const auto d = &buf->datas[0];++ if (d->data == nullptr)+ return 1;++ spa_pod_builder build;+ spa_zero(build);+ spa_pod_builder_init(&build, d->data, d->maxsize);++ spa_pod_frame f;+ spa_pod_builder_push_sequence(&build, &f, 0);++ // for all events+ while (auto m_ptr = m_queue.peek())+ {+ auto& m = *m_ptr;+ if (m.empty())+ {+ m_queue.pop();+ continue;+ }++ // TODO why+ if (m.bytes[0] == 0xff)+ {+ m_queue.pop();+ continue;+ }++ spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi);+ int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size());++ // Try again next buffer+ if (res == -ENOSPC)+ break;++ // Recycle the memory+ m_gcqueue.enqueue(std::move(m));+ m_queue.pop();+ }+ spa_pod_builder_pop(&build, &f);++ int n_fill_frames = build.state.offset;+ if (n_fill_frames > 0)+ {+ d->chunk->offset = 0;+ d->chunk->stride = 1;+ d->chunk->size = n_fill_frames;+ b->size = n_fill_frames;++ pw.filter_queue_buffer(this->filter->port, b);+ return 0;+ }++ pw.filter_flush(this->filter->filter, true);++ return 0;+ }++ stdx::error send_message(const unsigned char* message, size_t size) override+ {+ libremidi::message m;+ m_gcqueue.try_dequeue(m);+ m.bytes.assign(message, message + size);+ m.timestamp = 0;+ m_queue.enqueue(std::move(m));+ return stdx::error{};+ }++ int convert_timestamp(int64_t user) const noexcept+ {+ switch (configuration.timestamps)+ {+ case timestamp_mode::AudioFrame:+ return static_cast<int>(user);++ default:+ // TODO+ return 0;+ }+ }++ stdx::error schedule_message(int64_t ts, const unsigned char* message, size_t size) override+ {+ m_queue.enqueue(+ libremidi::message(midi_bytes{message, message + size}, convert_timestamp(ts)));+ return stdx::error{};+ }++ moodycamel::ReaderWriterQueue<libremidi::message> m_queue;+ moodycamel::ReaderWriterQueue<libremidi::message> m_gcqueue;+ std::atomic_int64_t m_process_clock = 0;+};+}
+ vendor/libremidi/backends/pipewire/observer.hpp view
@@ -0,0 +1,85 @@+#pragma once+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/pipewire/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <unordered_set>++namespace libremidi+{+class observer_pipewire final+ : public observer_api+ , private pipewire_helpers+ , private error_handler+{+public:+ struct+ : observer_configuration+ , pipewire_observer_configuration+ {+ } configuration;++ explicit observer_pipewire(observer_configuration&& conf, pipewire_observer_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ create_context(*this);++ // FIXME notify_in_constructor+ // FIXME port rename callback+#if 0+ // Initialize PipeWire client+ if (configuration.context)+ {+ this->client = configuration.context;+ set_callbacks();+ }+ else+#endif+ {+ this->add_callbacks(configuration);+ this->start_thread();+ }++ if (configuration.notify_in_constructor)+ {+ if (configuration.input_added)+ for (const auto& p : get_input_ports())+ configuration.input_added(p);++ if (configuration.output_added)+ for (const auto& p : get_output_ports())+ configuration.output_added(p);+ }+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }++ std::vector<libremidi::input_port> get_input_ports() const noexcept override+ {+ return get_ports<SPA_DIRECTION_OUTPUT>(this->configuration, *this->global_context);+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept override+ {+ return get_ports<SPA_DIRECTION_INPUT>(this->configuration, *this->global_context);+ }++ ~observer_pipewire()+ {+ stop_thread();+ destroy_context();+#if 0+ if (client && !configuration.context)+ {+ // If we own the client, deactivate it+ pipewire_deactivate(this->client);+ pipewire_client_close(this->client);+ this->client = nullptr;+ }+#endif+ }++ std::unordered_set<std::string> seen_input_ports;+ std::unordered_set<std::string> seen_output_ports;+};+}
@@ -0,0 +1,153 @@+#pragma once+#if __has_include(<boost/lockfree/spsc_queue.hpp>)+ #include <libremidi/backends/pipewire/config.hpp>+ #include <libremidi/backends/pipewire/helpers.hpp>+ #include <libremidi/shared_context.hpp>++ #include <boost/lockfree/spsc_queue.hpp>++ #include <variant>++namespace libremidi::pipewire+{++// Create a PipeWire client which will be shared across objects+struct shared_handler : public libremidi::shared_context+{+ explicit shared_handler(std::string_view v)+ {+ midiin_callbacks.reserve(64);+ midiout_callbacks.reserve(64);++ pipewire_status_t status{};+ client = pipewire_client_open(v.data(), PipewireNoStartServer, &status);+ assert(client);+ assert(status == 0);+ pipewire_set_process_callback(+ client,+ +[](pipewire_nframes_t cnt, void* ctx) -> int {+ ((shared_handler*)ctx)->pipewire_callback(cnt);+ return 0;+ },+ this);+ }++ virtual void start_processing() override { pipewire_activate(client); }+ virtual void stop_processing() override { pipewire_deactivate(client); }++ static shared_configurations make(std::string_view client_name)+ {+ auto clt = std::make_shared<shared_handler>(client_name);+ auto add_in_cb = [client = std::weak_ptr{clt}](libremidi::pipewire_callback cb) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::in_callback_added, std::move(cb)});+ };+ auto clear_in_cb = [client = std::weak_ptr{clt}](int64_t index) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::in_callback_removed, index});+ };+ auto add_out_cb = [client = std::weak_ptr{clt}](libremidi::pipewire_callback cb) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::out_callback_added, std::move(cb)});+ };+ auto clear_out_cb = [client = std::weak_ptr{clt}](int64_t index) {+ if (auto clt = client.lock())+ clt->events.push({shared_handler::event_type::out_callback_removed, index});+ };+ return {+ .context = clt,+ .observer = pipewire_observer_configuration{.context = clt->client},+ .in+ = pipewire_input_configuration{.context = clt->client, .set_process_func = add_in_cb, .clear_process_func = clear_in_cb},+ .out+ = pipewire_output_configuration{.context = clt->client, .set_process_func = add_out_cb, .clear_process_func = clear_out_cb},+ };+ }++ int pipewire_callback(pipewire_nframes_t cnt)+ {+ // 1. Process the events that will change the callback list+ event ev;+ while (events.pop(ev))+ {+ switch (ev.type)+ {+ case in_callback_added:+ midiin_callbacks.push_back(+ std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));+ break;+ case in_callback_removed: {+ auto idx = *std::get_if<int64_t>(&ev.payload);+ for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)+ {+ if (it->token == idx)+ {+ midiin_callbacks.erase(it);+ break;+ }+ else+ {+ ++it;+ }+ }+ break;+ }+ case out_callback_added:+ midiout_callbacks.push_back(+ std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));+ break;+ case out_callback_removed:+ auto idx = *std::get_if<int64_t>(&ev.payload);+ for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)+ {+ if (it->token == idx)+ {+ midiout_callbacks.erase(it);+ break;+ }+ else+ {+ ++it;+ }+ }+ break;+ }+ }++ for (auto& cb : midiin_callbacks)+ cb.callback(cnt);++ for (auto& cb : midiout_callbacks)+ cb.callback(cnt);++ return 0;+ }++ ~shared_handler()+ {+ pipewire_deactivate(client);+ pipewire_client_close(client);+ }++ pipewire_client_t* client{};++ enum event_type+ {+ in_callback_added,+ in_callback_removed,+ out_callback_added,+ out_callback_removed,+ };+ struct event+ {+ event_type type;+ std::variant<libremidi::pipewire_callback, int64_t> payload;+ };++ boost::lockfree::spsc_queue<event> events{16};++ std::vector<libremidi::pipewire_callback> midiin_callbacks;+ std::vector<libremidi::pipewire_callback> midiout_callbacks;+};+}+#endif
+ vendor/libremidi/backends/winmidi.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/winmidi/midi_in.hpp>+#include <libremidi/backends/winmidi/midi_out.hpp>+#include <libremidi/backends/winmidi/observer.hpp>++namespace libremidi::winmidi+{+struct backend+{+ using midi_in = midi_in_impl;+ using midi_out = midi_out_impl;+ using midi_observer = observer_impl;+ using midi_in_configuration = winmidi::input_configuration;+ using midi_out_configuration = winmidi::output_configuration;+ using midi_observer_configuration = winmidi::observer_configuration;+ static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES;+ static const constexpr auto name = "winmidi";+ static const constexpr auto display_name = "Windows MIDI Services";++ static constexpr inline bool available() noexcept { return true; }+};+} // namespace libremidi
+ vendor/libremidi/backends/winmidi/config.hpp view
@@ -0,0 +1,19 @@+#pragma once+#include <libremidi/config.hpp>++namespace libremidi::winmidi+{++struct input_configuration+{+};++struct output_configuration+{+};++struct observer_configuration+{+};++}
+ vendor/libremidi/backends/winmidi/helpers.hpp view
@@ -0,0 +1,60 @@+#pragma once+// clang-format off+#define NOMINMAX 1+#define WIN32_LEAN_AND_MEAN 1+#include <libremidi/detail/midi_api.hpp>++#include <mutex>+#include <cctype>+#include <string>+#include <thread>+#include <vector>+#include <guiddef.h>++#include <winrt/Windows.Foundation.h>+#include <winrt/Windows.Foundation.Collections.h>+#include <winrt/Windows.Devices.Enumeration.h>+#include <winrt/Windows.Devices.Midi2.h>+++namespace midi2 = winrt::Windows::Devices::Midi2;+namespace foundation = winrt::Windows::Foundation;+namespace collections = winrt::Windows::Foundation::Collections;+++// clang-format on++namespace libremidi::winmidi+{+using namespace winrt;+using namespace winrt::Windows::Foundation;+using namespace winrt::Windows::Devices::Enumeration;+using namespace winrt::Windows::Storage::Streams;+using namespace winrt::Windows::Devices::Midi2;+using namespace Windows::Devices::Enumeration;++inline bool ichar_equals(char a, char b)+{+ return std::tolower(static_cast<unsigned char>(a)) ==+ std::tolower(static_cast<unsigned char>(b));+}+inline bool iequals(std::string_view lhs, std::string_view rhs)+{+ return std::ranges::equal(lhs, rhs, ichar_equals);+}++inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation+get_port_by_name(const std::string& port) {+ auto eps = MidiEndpointDeviceInformation::FindAll();+ for (const auto& ep : eps)+ {+ auto str = to_string(ep.Id());+ if (str.empty())+ continue;++ if (iequals(str, port))+ return ep;+ }+ return {nullptr};+}+}
+ vendor/libremidi/backends/winmidi/midi_in.hpp view
@@ -0,0 +1,97 @@+#pragma once+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmidi/helpers.hpp>+#include <libremidi/backends/winmidi/observer.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++#include <iostream>++namespace libremidi::winmidi+{++class midi_in_impl final+ : public midi2::in_api+ , public error_handler+{+public:+ struct+ : libremidi::ump_input_configuration+ , winmidi::input_configuration+ {+ } configuration;++ explicit midi_in_impl(+ libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ , m_session{MidiSession::CreateSession(L"libremidi session")}+ {+ this->client_open_ = stdx::error{};+ }++ ~midi_in_impl() override+ {+ close_port();+ this->client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override+ {+ return libremidi::API::WINDOWS_MIDI_SERVICES;+ }++ stdx::error open_port(const input_port& port, std::string_view) override+ {+ auto ep = get_port_by_name(port.port_name);+ if (!ep)+ return std::errc::address_not_available;++ m_endpoint = m_session.CreateEndpointConnection(ep.Id());++ m_revoke_token = m_endpoint.MessageReceived(+ [&](const foundation::IInspectable& sender,+ const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {+ process_message(args);+ });++ m_endpoint.Open();++ return stdx::error{};+ }++ void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg)+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ const auto& ump = msg.GetMessagePacket();+ const auto& b = ump.GetAllWords();++ uint32_t ump_space[64];+ array_view<uint32_t> ref{ump_space};+ b.GetMany(0, ref);++ auto to_ns = [t = ump.Timestamp()] { return t; };+ m_processing.on_bytes(+ {ump_space, ump_space + b.Size()}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }++ stdx::error close_port() override+ {+ m_endpoint.MessageReceived(m_revoke_token);+ m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());+ return stdx::error{};+ }++ virtual timestamp absolute_timestamp() const noexcept override { return {}; }++private:+ MidiSession m_session;+ winrt::event_token m_revoke_token{};+ winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};+ midi2::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/winmidi/midi_out.hpp view
@@ -0,0 +1,89 @@+#pragma once+#include <libremidi/detail/ump_stream.hpp>+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmidi/helpers.hpp>+#include <libremidi/backends/winmidi/observer.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi::winmidi+{++class midi_out_impl final+ : public midi2::out_api+ , public error_handler+{+public:+ struct+ : libremidi::output_configuration+ , winmidi::output_configuration+ {+ } configuration;++ midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ , m_session{MidiSession::CreateSession(L"libremidi session")}+ {+ this->client_open_ = stdx::error{};+ }++ ~midi_out_impl() override { close_port(); }++ libremidi::API get_current_api() const noexcept override+ {+ return libremidi::API::WINDOWS_MIDI_SERVICES;+ }++ stdx::error open_port(const output_port& port, std::string_view) override+ {+ auto ep = get_port_by_name(port.port_name);+ if (!ep)+ return std::errc::address_not_available;++ m_endpoint = m_session.CreateEndpointConnection(ep.Id());+ m_endpoint.Open();++ return stdx::error{};+ }++ stdx::error close_port() override+ {+ m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());+ return stdx::error{};+ }++ stdx::error send_ump(const uint32_t* message, size_t size) override+ {+ auto write_func = [this](const uint32_t* ump, int64_t bytes) -> std::errc {+ MidiSendMessageResults ret{};+ switch(bytes / 4)+ {+ case 1:+ ret = m_endpoint.SendSingleMessagePacket(MidiMessage32(0, ump[0]));+ break;+ case 2:+ ret = m_endpoint.SendSingleMessagePacket(MidiMessage64(0, ump[0], ump[1]));+ break;+ case 3:+ ret = m_endpoint.SendSingleMessagePacket(MidiMessage96(0, ump[0], ump[1], ump[2]));+ break;+ case 4:+ ret = m_endpoint.SendSingleMessagePacket(MidiMessage128(0, ump[0], ump[1], ump[2], ump[3]));+ break;+ default:+ return std::errc::bad_message;+ }++ if(ret != MidiSendMessageResults::Succeeded)+ return std::errc::bad_message;+ return std::errc{0};+ };++ return segment_ump_stream(message, size, write_func, []() {});+ }++private:+ MidiSession m_session;+ winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};+};++}
+ vendor/libremidi/backends/winmidi/observer.hpp view
@@ -0,0 +1,148 @@+#pragma once+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmidi/helpers.hpp>+#include <libremidi/detail/observer.hpp>+;+namespace libremidi::winmidi+{+struct port_info+{+ hstring id;+ hstring name;+};++class observer_impl final : public observer_api+{+public:+ struct+ : libremidi::observer_configuration+ , winmidi::observer_configuration+ {+ } configuration;+++ explicit observer_impl(+ libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ , session{MidiSession::CreateSession(L"libremidi session")}+ {+ if (!configuration.has_callbacks())+ return;++ if (configuration.notify_in_constructor)+ {+ if (configuration.input_added)+ for (const auto& p : get_input_ports())+ configuration.input_added(p);++ if (configuration.output_added)+ for (const auto& p : get_output_ports())+ configuration.output_added(p);+ }++ /*+ evTokenOnInputAdded_+ = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); });+ evTokenOnInputRemoved_+ = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); });+ evTokenOnOutputAdded_+ = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); });+ evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved(+ [this](const port_info& p) { on_output_removed(p); });+*/+ }++ ~observer_impl()+ {+ if (!configuration.has_callbacks())+ return;+ // internalInPortObserver_.PortAdded(evTokenOnInputAdded_);+ // internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);+ // internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);+ // internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);+ }++ libremidi::API get_current_api() const noexcept override+ {+ return libremidi::API::WINDOWS_MIDI_SERVICES;+ }++ template <bool Input>+ auto to_port_info(const DeviceInformation& p) const noexcept+ -> std::conditional_t<Input, input_port, output_port>+ {+ return {+ {.client = 0,+ .port = 0,+ .manufacturer = "",+ .device_name = "",+ .port_name = to_string(p.Id()),+ .display_name = to_string(p.Name())}};+ }++ std::vector<libremidi::input_port> get_input_ports() const noexcept override+ {+ std::vector<libremidi::input_port> ret;++ auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();+ auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();+ for (const auto& ep : endpointDevices)+ {+ if(ep.Name().starts_with(L"Diagnostics")) {+ continue;+ }+ // FIXME if(has input...)++ ret.emplace_back(to_port_info<true>(ep));+ }++ return ret;+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept override+ {+ std::vector<libremidi::output_port> ret;++ auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();+ auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();+ for (const auto& ep : endpointDevices)+ {+ if(ep.Name().starts_with(L"Diagnostics")) {+ continue;+ }+ // FIXME if(has output...)+ ret.emplace_back(to_port_info<false>(ep));+ }++ return ret;+ }++ void on_input_added(const DeviceInformation& name)+ {+ if (configuration.input_added)+ configuration.input_added(to_port_info<true>(name));+ }++ void on_input_removed(const DeviceInformation& name)+ {+ if (configuration.input_removed)+ configuration.input_removed(to_port_info<true>(name));+ }++ void on_output_added(const DeviceInformation& name)+ {+ if (configuration.output_added)+ configuration.output_added(to_port_info<false>(name));+ }++ void on_output_removed(const DeviceInformation& name)+ {+ if (configuration.output_removed)+ configuration.output_removed(to_port_info<false>(name));+ }++private:+ MidiSession session;+};++}
+ vendor/libremidi/backends/winmm.hpp view
@@ -0,0 +1,36 @@+#pragma once+#include <libremidi/backends/winmm/midi_in.hpp>+#include <libremidi/backends/winmm/midi_out.hpp>+#include <libremidi/backends/winmm/observer.hpp>++// Default for Windows is to add an identifier to the port names; this+// flag can be defined (e.g. in your project file) to disable this behaviour.+// #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES++//*********************************************************************//+// API: Windows Multimedia Library (MM)+//*********************************************************************//++// API information deciphered from:+// -+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp++// Thanks to Jean-Baptiste Berruchon for the sysex code.+namespace libremidi+{++struct winmm_backend+{+ using midi_in = midi_in_winmm;+ using midi_out = midi_out_winmm;+ using midi_observer = observer_winmm;+ using midi_in_configuration = winmm_input_configuration;+ using midi_out_configuration = winmm_output_configuration;+ using midi_observer_configuration = winmm_observer_configuration;+ static const constexpr auto API = libremidi::API::WINDOWS_MM;+ static const constexpr auto name = "winmm";+ static const constexpr auto display_name = "Windows Multimedia";++ static constexpr inline bool available() noexcept { return true; }+};+}
+ vendor/libremidi/backends/winmm/config.hpp view
@@ -0,0 +1,31 @@+#pragma once+#include <libremidi/config.hpp>++#include <chrono>+#include <functional>++namespace libremidi+{++struct winmm_input_configuration+{+ int sysex_buffer_size = 1024;+ int sysex_buffer_count = 4;+};++struct winmm_output_configuration+{+};++struct poll_parameters+{+ std::function<void()> callback;+};++struct winmm_observer_configuration+{+ std::chrono::milliseconds poll_period{100};+ std::function<void(const poll_parameters&)> manual_poll;+};++}
+ vendor/libremidi/backends/winmm/error_domain.hpp view
@@ -0,0 +1,70 @@+#pragma once+// clang-format off+#define NOMINMAX 1+#define WIN32_LEAN_AND_MEAN 1++#include <windows.h>+#include <mmsystem.h>+// clang-format on++#include <libremidi/error.hpp>++namespace libremidi+{++struct winmm_error_domain : public stdx::error_domain+{+public:+ constexpr winmm_error_domain() noexcept+ : error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}+ {+ }++ stdx::string_ref name() const noexcept override { return "winmm"; }++ bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override+ {+ if (lhs.domain() == rhs.domain())+ return error_cast<int>(lhs) == error_cast<int>(rhs);++ return false;+ }++ stdx::string_ref message(const stdx::error& e) const noexcept override+ {+ switch (error_cast<int>(e))+ {+ case MMSYSERR_NOERROR: return "No error";+ case MMSYSERR_ERROR: return "Error";+ case MMSYSERR_BADDEVICEID: return "Bad device ID";+ case MMSYSERR_NOTENABLED: return "Not enabled";+ case MMSYSERR_ALLOCATED: return "Allocated";+ case MMSYSERR_INVALHANDLE: return "Invalid handle";+ case MMSYSERR_NODRIVER: return "No driver";+ case MMSYSERR_NOMEM: return "No memory";+ case MMSYSERR_NOTSUPPORTED: return "Not supported";+ case MMSYSERR_BADERRNUM: return "Bad errnum";+ case MMSYSERR_INVALFLAG: return "Invalid flag";+ case MMSYSERR_INVALPARAM: return "Invalid parameter";+ case MMSYSERR_HANDLEBUSY: return "Handle busy";+ case MMSYSERR_INVALIDALIAS: return "Invalid alias";+ case MMSYSERR_BADDB: return "Bad database";+ case MMSYSERR_KEYNOTFOUND: return "Key not found";+ case MMSYSERR_READERROR: return "Read error";+ case MMSYSERR_WRITEERROR: return "Write error";+ case MMSYSERR_DELETEERROR: return "Delete error";+ case MMSYSERR_VALNOTFOUND: return "Value not found";+ case MMSYSERR_NODRIVERCB: return "No driver callback";+ case MMSYSERR_MOREDATA: return "More data";+ }+ return "Unknown error code";+ }+};++inline stdx::error from_mmerr(int ret) noexcept+{+ static constexpr winmm_error_domain domain;+ return {ret, domain};+}++}
+ vendor/libremidi/backends/winmm/helpers.hpp view
@@ -0,0 +1,74 @@+#pragma once+#include <libremidi/backends/winmm/error_domain.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <algorithm>++namespace libremidi+{++// Convert a nullptr-terminated wide string or ANSI-encoded string to UTF-8.+inline std::string ConvertToUTF8(const TCHAR* str)+{+ std::string u8str;+ const WCHAR* wstr = L"";+#if defined(UNICODE) || defined(_UNICODE)+ wstr = str;+#else+ // Convert from ANSI encoding to wide string+ int wlength = MultiByteToWideChar(CP_ACP, 0, str, -1, nullptr, 0);+ std::wstring wstrtemp;+ if (wlength)+ {+ wstrtemp.assign(wlength - 1, 0);+ MultiByteToWideChar(CP_ACP, 0, str, -1, &wstrtemp[0], wlength);+ wstr = &wstrtemp[0];+ }+#endif+ // Convert from wide string to UTF-8+ int length = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);+ if (length)+ {+ u8str.assign(static_cast<std::string::size_type>(length - 1), 0);+ /*length =*/WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &u8str[0], length, nullptr, nullptr);+ }+ return u8str;+}++// Next functions add the portNumber to the name so that+// the device's names are sure to be listed with individual names+// even when they have the same brand name+inline void MakeUniqueInPortName(std::string& deviceName, std::size_t portNumber)+{+ int x = 1;+ for (std::size_t i = 0; i < portNumber; i++)+ {+ MIDIINCAPS deviceCaps;+ midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS));+ auto stringName = ConvertToUTF8(deviceCaps.szPname);+ if (deviceName == stringName)+ {+ x++;+ }+ }+ deviceName += " ";+ deviceName += std::to_string(x);+}++inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumber)+{+ int x = 1;+ for (std::size_t i = 0; i < portNumber; i++)+ {+ MIDIOUTCAPS deviceCaps;+ midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS));+ auto stringName = ConvertToUTF8(deviceCaps.szPname);+ if (deviceName == stringName)+ {+ x++;+ }+ }+ deviceName += " ";+ deviceName += std::to_string(x);+}++}
+ vendor/libremidi/backends/winmm/midi_in.hpp view
@@ -0,0 +1,292 @@+#pragma once+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winmm/helpers.hpp>+#include <libremidi/backends/winmm/observer.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{++class midi_in_winmm final+ : public midi1::in_api+ , public error_handler+{+public:+ struct+ : input_configuration+ , winmm_input_configuration+ {+ } configuration;++ explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400))+ {+ libremidi_handle_error(+ configuration,+ "InitializeCriticalSectionAndSpinCount failed.");++ this->client_open_ = std::errc::too_many_files_open;+ return;+ }++ this->client_open_ = stdx::error{};+ }++ ~midi_in_winmm() override+ {+ // Close a connection if it exists.+ midi_in_winmm::close_port();++ if(this->client_open_ == stdx::error{})+ DeleteCriticalSection(&(this->_mutex));+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }++ stdx::error do_open(std::size_t portNumber)+ {+ MMRESULT result = midiInOpen(+ &this->inHandle, portNumber, std::bit_cast<DWORD_PTR>(&midiInputCallback),+ std::bit_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);+ if (result != MMSYSERR_NOERROR)+ {+ libremidi_handle_error(+ configuration, "error creating Windows MM MIDI input port.");+ return from_mmerr(result);+ }++ // Allocate and init the sysex buffers.+ const auto bufferCount = static_cast<std::size_t>(configuration.sysex_buffer_count);+ this->sysexBuffer.resize(bufferCount);+ for (std::size_t i = 0; i < bufferCount; ++i)+ {+ this->sysexBuffer[i] = new MIDIHDR;+ this->sysexBuffer[i]->lpData = new char[configuration.sysex_buffer_size];+ this->sysexBuffer[i]->dwBufferLength = static_cast<DWORD>(configuration.sysex_buffer_size);+ this->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator+ this->sysexBuffer[i]->dwFlags = 0;++ result = midiInPrepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));+ if (result != MMSYSERR_NOERROR)+ {+ midiInClose(this->inHandle);+ this->inHandle = nullptr;+ libremidi_handle_error(+ configuration,+ "error starting Windows MM MIDI input port "+ "(PrepareHeader).");+ return from_mmerr(result);+ }++ // Register the buffer.+ result = midiInAddBuffer(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));+ if (result != MMSYSERR_NOERROR)+ {+ midiInClose(this->inHandle);+ this->inHandle = nullptr;+ libremidi_handle_error(+ configuration,+ "error starting Windows MM MIDI input port "+ "(AddBuffer).");+ return from_mmerr(result);+ }+ }++ result = midiInStart(this->inHandle);+ midi_start_timestamp = std::chrono::steady_clock::now();+ if (result != MMSYSERR_NOERROR)+ {+ midiInClose(this->inHandle);+ this->inHandle = nullptr;+ libremidi_handle_error(+ configuration, "error starting Windows MM MIDI input port.");+ return from_mmerr(result);+ }++ return stdx::error{};+ }++ stdx::error open_port(const input_port& p, std::string_view) override+ {+ observer_winmm obs{{}, winmm_observer_configuration{}};+ auto ports = obs.get_input_ports();++ // First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2+ for (auto& port : ports)+ {+ if (p.display_name == port.display_name)+ return do_open(port.port);+ }+ // If nothing is found, try to check with the raw name+ for (auto& port : ports)+ {+ if (p.port_name == port.port_name)+ return do_open(port.port);+ }+ libremidi_handle_error(+ configuration, "port not found: " + p.port_name);+ return std::errc::invalid_argument;+ }++ stdx::error close_port() override+ {+ if (connected_)+ {+ EnterCriticalSection(&(this->_mutex));+ midiInReset(this->inHandle);+ midiInStop(this->inHandle);++ for (std::size_t i = 0; i < static_cast<std::size_t>(configuration.sysex_buffer_count); ++i)+ {+ MMRESULT res{};++ int wait_count = 5;+ while (+ ((res = midiInUnprepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR)))+ == MIDIERR_STILLPLAYING)+ && wait_count-- >= 0)+ {+ Sleep(1);+ }++ if (res != MMSYSERR_NOERROR)+ {+ libremidi_handle_warning(+ configuration,+ "error closing Windows MM MIDI input "+ "port (midiInUnprepareHeader).");+ continue;+ }+ else+ {+ delete[] this->sysexBuffer[i]->lpData;+ delete[] this->sysexBuffer[i];+ }+ }++ midiInClose(this->inHandle);+ this->inHandle = nullptr;+ LeaveCriticalSection(&(this->_mutex));+ }+ return stdx::error{};+ }++private:+ timestamp absolute_timestamp() const noexcept override+ {+ return std::chrono::duration_cast<std::chrono::nanoseconds>(+ std::chrono::steady_clock::now() - midi_start_timestamp)+ .count();+ }++ static constexpr int bytes_for_message(uint8_t status)+ {+ if (status < 0xC0)+ return 3;+ else if (status < 0xE0)+ return 2;+ else if (status < 0xF0)+ return 3;+ else if (status == 0xF1)+ return 2;+ else if (status == 0xF2)+ return 3;+ else if (status == 0xF3)+ return 2;+ else if (status == 0xF8)+ return 1;+ else if (status == 0xFE)+ return 1;+ else+ return 0;+ }++ static void CALLBACK midiInputCallback(+ HMIDIIN /*hmin*/, UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage,+ DWORD_PTR timestamp)+ {+ if (inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR)+ return;++ auto& self = *reinterpret_cast<midi_in_winmm*>(instancePtr);+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ const auto to_ns = [timestamp] { return timestamp * 1'000'000; };++ if (inputStatus == MIM_DATA)+ {+ // Channel or system message+ uint8_t message[sizeof(DWORD_PTR)];+ memcpy(message, &midiMessage, sizeof(DWORD_PTR));++ // Make sure the first byte is a status byte.+ if (message[0] & 0x80)+ {+ self.m_processing.on_bytes(+ {message, message + bytes_for_message(message[0])},+ self.m_processing.timestamp<timestamp_info>(to_ns, 0));+ }+ }+ else+ {+ // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )+ const auto* sysex = reinterpret_cast<MIDIHDR*>(midiMessage);+ if(inputStatus == MIM_LONGERROR)+ {+ self.m_processing.reset();+ }+ else if (!self.configuration.ignore_sysex)+ {+ if(sysex->dwBytesRecorded > 0)+ {+ const auto sysex_bytes = reinterpret_cast<uint8_t*>(sysex->lpData);++ self.m_processing.on_bytes(+ {sysex_bytes, sysex_bytes + sysex->dwBytesRecorded},+ self.m_processing.timestamp<timestamp_info>(to_ns, 0));+ }+ }++ // The WinMM API requires that the sysex buffer be requeued after+ // input of each sysex message. Even if we are ignoring sysex+ // messages, we still need to requeue the buffer in case the user+ // decides to not ignore sysex messages in the future. However,+ // it seems that WinMM calls this function with an empty sysex+ // buffer when an application closes and in this case, we should+ // avoid requeueing it, else the computer suddenly reboots after+ // one or two minutes.+ if (self.sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0)+ {+ EnterCriticalSection(&(self._mutex));+ MMRESULT result+ = midiInAddBuffer(self.inHandle, self.sysexBuffer[sysex->dwUser], sizeof(MIDIHDR));+ LeaveCriticalSection(&(self._mutex));+ if (result != MMSYSERR_NOERROR)+ {+ LIBREMIDI_LOG(+ "error sending sysex to "+ "Midi device!!");+ }+ }+ }+ }++ HMIDIIN inHandle; // Handle to Midi Input Device++ std::vector<LPMIDIHDR> sysexBuffer;+ // [Patrice] see+ // https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo+ CRITICAL_SECTION _mutex;+ std::chrono::steady_clock::time_point midi_start_timestamp;++ midi1::input_state_machine m_processing{this->configuration};+};++}
+ vendor/libremidi/backends/winmm/midi_out.hpp view
@@ -0,0 +1,164 @@+#pragma once+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winmm/helpers.hpp>+#include <libremidi/backends/winmm/observer.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{++class midi_out_winmm final+ : public midi1::out_api+ , public error_handler+{+public:+ struct+ : output_configuration+ , winmm_output_configuration+ {+ } configuration;++ midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ this->client_open_ = stdx::error{};+ }++ ~midi_out_winmm() override+ {+ // Close a connection if it exists.+ midi_out_winmm::close_port();+ this->client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }++ [[nodiscard]] stdx::error do_open(unsigned int portNumber)+ {+ MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL);+ if (result != MMSYSERR_NOERROR)+ {+ libremidi_handle_error(+ configuration,+ "error creating Windows MM MIDI output "+ "port.");+ return from_mmerr(result);+ }++ return stdx::error{};+ }++ stdx::error open_port(const output_port& p, std::string_view) override+ {+ observer_winmm obs{{}, winmm_observer_configuration{}};+ auto ports = obs.get_output_ports();++ // First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2+ for (auto& port : ports)+ {+ if (p.display_name == port.display_name)+ return do_open(port.port);+ }+ // If nothing is found, try to check with the raw name+ for (auto& port : ports)+ {+ if (p.port_name == port.port_name)+ return do_open(port.port);+ }+ libremidi_handle_error(+ configuration, "port not found: " + p.port_name);+ return std::errc::invalid_argument;+ }++ stdx::error close_port() override+ {+ if (this->outHandle)+ midiOutClose(this->outHandle);++ this->outHandle = nullptr;+ connected_ = false;+ return stdx::error{};+ }++ stdx::error send_message(const unsigned char* message, size_t size) override+ {+ if (!connected_)+ return std::errc::not_connected;++ if (size == 0)+ {+ libremidi_handle_warning(configuration, "message argument is empty!");+ return std::errc::invalid_argument;+ }++ if (message[0] == 0xF0)+ { // Sysex message++ buffer.assign(message, message + size);++ // FIXME this can be made asynchronous... see Chrome source.+ // But need to know whe buffers are freed.++ // Create and prepare MIDIHDR structure.+ MIDIHDR sysex{};+ sysex.lpData = (LPSTR)buffer.data();+ sysex.dwBufferLength = size;+ sysex.dwFlags = 0;+ auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR));+ if (result != MMSYSERR_NOERROR)+ {+ libremidi_handle_error(+ configuration, "error preparing sysex header.");+ return from_mmerr(result);+ }++ // Send the message.+ result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR));+ if (result != MMSYSERR_NOERROR)+ {+ libremidi_handle_error(+ configuration, "error sending sysex message.");+ return from_mmerr(result);+ }++ // Unprepare the buffer and MIDIHDR.+ // FIXME yuck+ while (MIDIERR_STILLPLAYING+ == midiOutUnprepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)))+ Sleep(1);+ }+ else+ { // Channel or system message.++ // Make sure the message size isn't too big.+ if (size > 3)+ {+ libremidi_handle_warning(+ configuration,+ "message size is greater than 3 bytes "+ "(and not sysex)!");+ return std::errc::message_size;+ }++ // Pack MIDI bytes into double word.+ DWORD packet;+ std::copy_n(message, size, (unsigned char*)&packet);++ // Send the message immediately.+ auto result = midiOutShortMsg(this->outHandle, packet);+ if (result != MMSYSERR_NOERROR)+ {+ libremidi_handle_error(+ configuration, "error sending MIDI message.");+ return from_mmerr(result);+ }+ }+ return stdx::error{};+ }++private:+ HMIDIOUT outHandle; // Handle to Midi Output Device+ std::vector<char> buffer;+};++}
+ vendor/libremidi/backends/winmm/observer.hpp view
@@ -0,0 +1,271 @@+#pragma once+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winmm/helpers.hpp>+#include <libremidi/detail/observer.hpp>++#include <condition_variable>+#include <mutex>+#include <ranges>+#include <thread>++namespace libremidi+{++class observer_winmm : public observer_api+{+public:+ struct+ : observer_configuration+ , winmm_observer_configuration+ {+ } configuration;++ explicit observer_winmm(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (!configuration.has_callbacks())+ return;++ if (configuration.notify_in_constructor)+ check_new_ports<true>();+ else+ check_new_ports<false>();+ }++ ~observer_winmm() { }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }++ std::vector<input_port> get_input_ports() const noexcept override+ {+ return get_port_list<true>();+ }++ std::vector<output_port> get_output_ports() const noexcept override+ {+ return get_port_list<false>();+ }++protected:+ template <bool Notify>+ void check_new_ports()+ {+ auto currInputPortList = get_port_list<true>();++ if constexpr (Notify)+ {+ compare_port_lists_and_notify_clients(+ inputPortList, currInputPortList, configuration.input_added,+ configuration.input_removed);+ }+ inputPortList = std::move(currInputPortList);++ auto currOutputPortList = get_port_list<false>();++ if constexpr (Notify)+ {+ compare_port_lists_and_notify_clients(+ outputPortList, currOutputPortList, configuration.output_added,+ configuration.output_removed);+ }+ outputPortList = std::move(currOutputPortList);+ }++ void compare_port_lists_and_notify_clients(+ const auto& prevList, const auto& currList, const auto& portAddedFunc,+ const auto& portRemovedFunc)+ {+ if (portAddedFunc)+ {+ for (const auto& port : currList)+ {+ auto iter+ = std::ranges::find(prevList, port.display_name, &port_information::display_name);+ if (iter == prevList.end())+ portAddedFunc(port);+ }+ }++ if (portRemovedFunc)+ {+ for (const auto& port : prevList)+ {+ auto iter+ = std::ranges::find(currList, port.display_name, &port_information::display_name);+ if (iter == currList.end())+ portRemovedFunc(port);+ }+ }+ }++ input_port to_in_port_info(std::size_t index) const noexcept+ {+ MIDIINCAPS deviceCaps;+ midiInGetDevCaps(index, &deviceCaps, sizeof(MIDIINCAPS));++ auto rawName = ConvertToUTF8(deviceCaps.szPname);+ auto portName = rawName;+ MakeUniqueInPortName(portName, index);+ return {+ {.client = 0,+ .port = index,+ .manufacturer = "",+ .device_name = "",+ .port_name = rawName,+ .display_name = portName}};+ }++ output_port to_out_port_info(std::size_t index) const noexcept+ {+ MIDIOUTCAPS deviceCaps;+ midiOutGetDevCaps(index, &deviceCaps, sizeof(MIDIOUTCAPS));++ auto rawName = ConvertToUTF8(deviceCaps.szPname);+ auto portName = rawName;+ MakeUniqueOutPortName(portName, index);+ return {+ {.client = 0,+ .port = index,+ .manufacturer = "",+ .device_name = "",+ .port_name = rawName,+ .display_name = portName}};+ }++ template <bool Input>+ auto get_port_list() const noexcept+ -> std::vector<std::conditional_t<Input, input_port, output_port>>+ {+ std::vector<std::conditional_t<Input, input_port, output_port>> portList;++ if constexpr (Input)+ {+ std::size_t nDevices = midiInGetNumDevs();+ for (std::size_t i = 0; i < nDevices; ++i)+ {+ portList.push_back(to_in_port_info(i));+ }+ }+ else+ {+ std::size_t nDevices = midiOutGetNumDevs();+ for (std::size_t i = 0; i < nDevices; ++i)+ {+ portList.push_back(to_out_port_info(i));+ }+ }+ return portList;+ }++ static constexpr bool INPUT = true;+ static constexpr bool OUTPUT = false;++ std::vector<input_port> inputPortList;+ std::vector<output_port> outputPortList;+};+}++#if __has_include(<stop_token>) && __cpp_lib_jthread >= 201911L+ #include <stop_token>+namespace libremidi::winmm+{+class observer_threaded final : public observer_winmm+{+public:+ struct+ : observer_configuration+ , winmm_observer_configuration+ {+ } configuration;++ explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+ : observer_winmm{std::move(conf), std::move(apiconf)}+ {+ thread = std::jthread([this](std::stop_token tk) {+ while (!tk.stop_requested())+ {+ check_new_ports<true>();+ std::this_thread::sleep_for(this->configuration.poll_period);+ }+ });+ }++private:+ std::jthread thread;+};+}+#else+ #include <atomic>+ #include <semaphore>+namespace libremidi::winmm+{+class observer_threaded final : public observer_winmm+{+public:+ struct+ : observer_configuration+ , winmm_observer_configuration+ {+ } configuration;++ explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+ : observer_winmm{std::move(conf), std::move(apiconf)}+ , sema{0}+ {+ thread = std::thread([this] {+ while (!stop_flag.test(std::memory_order_acquire))+ {+ check_new_ports<true>();+ std::this_thread::sleep_for(this->configuration.poll_period);+ }+ sema.release();+ });+ }++ ~observer_threaded()+ {+ stop_flag.test_and_set();+ sema.acquire();+ thread.join();+ }++private:+ std::thread thread;+ std::atomic_flag stop_flag = ATOMIC_FLAG_INIT;+ std::binary_semaphore sema;+};+}+#endif++namespace libremidi::winmm+{+class observer_manual final : public observer_winmm+{+public:+ struct+ : observer_configuration+ , winmm_observer_configuration+ {+ } configuration;++ explicit observer_manual(observer_configuration&& conf, winmm_observer_configuration&& apiconf)+ : observer_winmm{std::move(conf), std::move(apiconf)}+ {+ this->configuration.manual_poll({.callback = [this] { this->check_new_ports<true>(); }});+ }++ ~observer_manual() { }+};+}+namespace libremidi+{+template <>+inline std::unique_ptr<observer_api> make<observer_winmm>(+ libremidi::observer_configuration&& conf, libremidi::winmm_observer_configuration&& api)+{+ if (api.manual_poll)+ return std::make_unique<winmm::observer_manual>(std::move(conf), std::move(api));+ else+ return std::make_unique<winmm::observer_threaded>(std::move(conf), std::move(api));+}+}
+ vendor/libremidi/backends/winuwp.hpp view
@@ -0,0 +1,22 @@+#pragma once+#include <libremidi/backends/winuwp/midi_in.hpp>+#include <libremidi/backends/winuwp/midi_out.hpp>+#include <libremidi/backends/winuwp/observer.hpp>++namespace libremidi+{+struct winuwp_backend+{+ using midi_in = midi_in_winuwp;+ using midi_out = midi_out_winuwp;+ using midi_observer = observer_winuwp;+ using midi_in_configuration = winuwp_input_configuration;+ using midi_out_configuration = winuwp_output_configuration;+ using midi_observer_configuration = winuwp_observer_configuration;+ static const constexpr auto API = libremidi::API::WINDOWS_UWP;+ static const constexpr auto name = "winuwp";+ static const constexpr auto display_name = "Windows UWP";++ static constexpr inline bool available() noexcept { return true; }+};+} // namespace libremidi
+ vendor/libremidi/backends/winuwp/config.hpp view
@@ -0,0 +1,19 @@+#pragma once+#include <libremidi/config.hpp>++namespace libremidi+{++struct winuwp_input_configuration+{+};++struct winuwp_output_configuration+{+};++struct winuwp_observer_configuration+{+};++}
+ vendor/libremidi/backends/winuwp/helpers.hpp view
@@ -0,0 +1,63 @@+#pragma once+#define NOMINMAX 1+#define WIN32_LEAN_AND_MEAN 1+#include <libremidi/detail/midi_api.hpp>++#include <mutex>+#include <string>+#include <thread>+#include <vector>++#include <winrt/Windows.Devices.Enumeration.h>+#include <winrt/Windows.Devices.Midi.h>+#include <winrt/Windows.Foundation.h>+#include <winrt/Windows.Storage.Streams.h>++namespace libremidi+{+inline void winrt_init()+{+ // init_apartment should only be called on the threads we own.+ // Since we're the library we don't own the threads we are called from,+ // so we should not perform this initialization ourselves.+ // winrt::init_apartment();+}++namespace+{+using namespace winrt;+using namespace winrt::Windows::Foundation;+using namespace winrt::Windows::Devices::Midi;+using namespace winrt::Windows::Devices::Enumeration;+using namespace winrt::Windows::Storage::Streams;++// Helper function to allow waiting for aynchronous operation completion+// from the thread in STA. The only benefit from it compared to the+// get() function from winrt is that we avoid an assertion if waiting+// from the STA thread.+template <typename T>+auto get(T const& async)+{+ if (async.Status() != AsyncStatus::Completed)+ {+ slim_mutex m;+ slim_condition_variable cv;+ bool completed = false;++ async.Completed([&](auto&&, auto&&) {+ {+ slim_lock_guard const guard(m);+ completed = true;+ }++ cv.notify_one();+ });++ slim_lock_guard guard(m);+ cv.wait(m, [&] { return completed; });+ }++ return async.GetResults();+}+}+}
+ vendor/libremidi/backends/winuwp/midi_in.hpp view
@@ -0,0 +1,97 @@+#pragma once+#include <libremidi/backends/winuwp/config.hpp>+#include <libremidi/backends/winuwp/helpers.hpp>+#include <libremidi/backends/winuwp/observer.hpp>+#include <libremidi/detail/midi_in.hpp>+#include <libremidi/detail/midi_stream_decoder.hpp>++namespace libremidi+{++class midi_in_winuwp final+ : public midi1::in_api+ , public error_handler+{+public:+ struct+ : input_configuration+ , winuwp_input_configuration+ {+ } configuration;++ explicit midi_in_winuwp(input_configuration&& conf, winuwp_input_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ winrt_init();+ this->client_open_ = stdx::error{};+ }++ ~midi_in_winuwp() override+ {+ close_port();+ this->client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }++ stdx::error open_port(const input_port& port, std::string_view) override+ {+ const auto id = winrt::to_hstring(port.port_name);+ if (id.empty())+ return std::errc::invalid_argument;++ port_ = get(MidiInPort::FromIdAsync(id));+ if (!port_)+ return std::errc::io_error;++ midi_start_timestamp = std::chrono::steady_clock::now();++ port_.MessageReceived(+ [=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort,+ const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) {+ this->process_message(args.Message());+ });++ return stdx::error{};+ }++ void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)+ {+ static constexpr timestamp_backend_info timestamp_info{+ .has_absolute_timestamps = true,+ .absolute_is_monotonic = false,+ .has_samples = false,+ };++ auto reader = DataReader::FromBuffer(msg.RawData());+ auto begin = msg.RawData().data();+ auto end = begin + msg.RawData().Length();++ const auto to_ns = [&msg] { return msg.Timestamp().count() * 1'000'000; };+ m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0));+ }++ stdx::error close_port() override+ {+ if (port_)+ {+ port_.Close();+ port_ = nullptr;+ }+ return stdx::error{};+ }++ timestamp absolute_timestamp() const noexcept override+ {+ return std::chrono::duration_cast<std::chrono::nanoseconds>(+ std::chrono::steady_clock::now() - midi_start_timestamp)+ .count();+ }++private:+ winrt::Windows::Devices::Midi::IMidiInPort port_{nullptr};+ std::chrono::steady_clock::time_point midi_start_timestamp;++ midi1::input_state_machine m_processing{this->configuration};+};+}
+ vendor/libremidi/backends/winuwp/midi_out.hpp view
@@ -0,0 +1,77 @@+#pragma once+#include <libremidi/backends/winuwp/config.hpp>+#include <libremidi/backends/winuwp/helpers.hpp>+#include <libremidi/backends/winuwp/observer.hpp>+#include <libremidi/detail/midi_out.hpp>++namespace libremidi+{++class midi_out_winuwp final+ : public midi1::out_api+ , public error_handler+{+public:+ struct+ : output_configuration+ , winuwp_output_configuration+ {+ } configuration;++ midi_out_winuwp(output_configuration&& conf, winuwp_output_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ winrt_init();+ this->client_open_ = stdx::error{};+ }++ ~midi_out_winuwp() override+ {+ close_port();+ this->client_open_ = std::errc::not_connected;+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }++ stdx::error open_port(const output_port& port, std::string_view) override+ {+ const auto id = winrt::to_hstring(port.port_name);+ if (id.empty())+ return std::errc::invalid_argument;++ port_ = get(MidiOutPort::FromIdAsync(id));+ if (!bool(port_))+ return std::errc::io_error;++ return stdx::error{};+ }++ stdx::error close_port() override+ {+ if (port_)+ {+ port_.Close();+ port_ = {};+ }+ return stdx::error{};+ }++ stdx::error send_message(const unsigned char* message, size_t size) override+ {+ if (!port_)+ return std::errc::not_connected;++ InMemoryRandomAccessStream str;+ DataWriter rb(str);+ rb.WriteBytes(+ winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size});+ port_.SendBuffer(rb.DetachBuffer());++ return stdx::error{};+ }++private:+ winrt::Windows::Devices::Midi::IMidiOutPort port_{nullptr};+};++}
+ vendor/libremidi/backends/winuwp/observer.hpp view
@@ -0,0 +1,296 @@+#pragma once+#include <libremidi/backends/winuwp/config.hpp>+#include <libremidi/detail/observer.hpp>++namespace libremidi+{+class observer_winuwp_internal+{+public:+ struct port_info+ {+ hstring id;+ hstring name;+ };+ struct callback+ {+ int token{};+ std::function<void(const port_info&)> function;+ };+ struct callbacks+ {+ std::vector<callback> cbs;+ int current_token{};+ void operator()(const port_info& p)+ {+ for (auto& cb : cbs)+ cb.function(p);+ }++ int add(std::function<void(const port_info&)> f)+ {+ int tk = current_token++;+ cbs.emplace_back(tk, f);+ return tk;+ }+ void remove(int tk)+ {+ auto it = std::remove_if(+ cbs.begin(), cbs.end(), [tk](const callback& c) { return c.token == tk; });+ auto r = std::distance(it, cbs.end());+ cbs.erase(it, cbs.end());+ }+ };++ explicit observer_winuwp_internal(hstring deviceSelector) { initialize(deviceSelector); }+ ~observer_winuwp_internal() { terminate(); }++ std::vector<port_info> get_ports() const+ {+ std::lock_guard<std::mutex> lock(portListMutex_);+ return portList_;+ }++ unsigned int get_port_count() const+ {+ std::lock_guard<std::mutex> lock(portListMutex_);+ return static_cast<unsigned int>(portList_.size());+ }++ bool get_port_info(unsigned int portNumber, port_info& portInfo) const+ {+ std::lock_guard<std::mutex> lock(portListMutex_);+ if (portNumber >= portList_.size())+ return false;+ portInfo = portList_[portNumber];+ return true;+ }++ hstring get_port_id(unsigned int portNumber) const+ {+ std::lock_guard<std::mutex> lock(portListMutex_);+ return portNumber < portList_.size() ? portList_[portNumber].id : hstring{};+ }++ std::string get_port_name(unsigned int portNumber) const+ {+ std::lock_guard<std::mutex> lock(portListMutex_);+ return portNumber < portList_.size() ? to_string(portList_[portNumber].name) : std::string{};+ }++ int PortAdded(const std::function<void(port_info)>& handler)+ {+ return portAddedEvent_.add(handler);+ }++ void PortAdded(int token) noexcept { portAddedEvent_.remove(token); }++ int PortRemoved(const std::function<void(port_info)>& handler)+ {+ return portRemovedEvent_.add(handler);+ }++ void PortRemoved(int token) noexcept { portRemovedEvent_.remove(token); }++private:+ observer_winuwp_internal(const observer_winuwp_internal&) = delete;+ observer_winuwp_internal& operator=(const observer_winuwp_internal&) = delete;++private:+ void initialize(hstring deviceSelector)+ {+ deviceWatcher_ = DeviceInformation::CreateWatcher(deviceSelector);++ evTokenOnDeviceAdded_+ = deviceWatcher_.Added({this, &observer_winuwp_internal::on_device_added});+ evTokenOnDeviceRemoved_+ = deviceWatcher_.Removed({this, &observer_winuwp_internal::on_device_removed});+ evTokenOnDeviceUpdated_+ = deviceWatcher_.Updated({this, &observer_winuwp_internal::on_device_updated});+ evTokenOnDeviceEnumerationCompleted_ = deviceWatcher_.EnumerationCompleted(+ {this, &observer_winuwp_internal::on_device_enumeration_completed});++ deviceWatcher_.Start();+ }++ void terminate()+ {+ deviceWatcher_.Stop();+ deviceWatcher_.EnumerationCompleted(evTokenOnDeviceEnumerationCompleted_);+ deviceWatcher_.Updated(evTokenOnDeviceUpdated_);+ deviceWatcher_.Removed(evTokenOnDeviceRemoved_);+ deviceWatcher_.Added(evTokenOnDeviceAdded_);+ }++ void on_device_added(DeviceWatcher sender, DeviceInformation deviceInfo)+ {+ port_info p;+ {+ std::lock_guard<std::mutex> lock(portListMutex_);+ p = port_info{deviceInfo.Id(), deviceInfo.Name()};+ portList_.push_back(p);+ }+ portAddedEvent_(p);+ }++ void on_device_removed(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate)+ {+ const auto id = deviceUpdate.Id();+ auto pred = [&id](const port_info& portInfo) { return portInfo.id == id; };+ std::optional<port_info> p;+ hstring name;+ {+ std::lock_guard<std::mutex> lock(portListMutex_);+ auto iter = std::find_if(portList_.begin(), portList_.end(), pred);+ if (iter != portList_.end())+ {+ p = *iter;+ portList_.erase(iter);+ }+ }+ if (p)+ portRemovedEvent_(*p);+ }++ void on_device_updated(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) { }++ void on_device_enumeration_completed(DeviceWatcher sender, IInspectable const&) { }++private:+ std::vector<port_info> portList_;+ mutable std::mutex portListMutex_;++ DeviceWatcher deviceWatcher_{nullptr};+ event_token evTokenOnDeviceAdded_;+ event_token evTokenOnDeviceRemoved_;+ event_token evTokenOnDeviceUpdated_;+ event_token evTokenOnDeviceEnumerationCompleted_;++ callbacks portAddedEvent_;+ callbacks portRemovedEvent_;+};++class observer_winuwp final : public observer_api+{+public:+ struct+ : observer_configuration+ , winuwp_observer_configuration+ {+ } configuration;++ using port_info = observer_winuwp_internal::port_info;+ explicit observer_winuwp(observer_configuration&& conf, winuwp_observer_configuration&& apiconf)+ : configuration{std::move(conf), std::move(apiconf)}+ {+ if (!configuration.has_callbacks())+ return;++ if (configuration.notify_in_constructor)+ {+ if (configuration.input_added)+ for (const auto& p : get_input_ports())+ configuration.input_added(p);++ if (configuration.output_added)+ for (const auto& p : get_output_ports())+ configuration.output_added(p);+ }++ evTokenOnInputAdded_+ = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); });+ evTokenOnInputRemoved_+ = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); });+ evTokenOnOutputAdded_+ = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); });+ evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved(+ [this](const port_info& p) { on_output_removed(p); });+ }++ ~observer_winuwp()+ {+ if (!configuration.has_callbacks())+ return;+ internalInPortObserver_.PortAdded(evTokenOnInputAdded_);+ internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);+ internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);+ internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);+ }++ libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }++ template <bool Input>+ auto to_port_info(const observer_winuwp_internal::port_info& p) const noexcept+ -> std::conditional_t<Input, input_port, output_port>+ {+ return {+ {.client = 0,+ .port = 0,+ .manufacturer = "",+ .device_name = "",+ .port_name = to_string(p.id),+ .display_name = to_string(p.name)}};+ }++ std::vector<libremidi::input_port> get_input_ports() const noexcept override+ {+ std::vector<libremidi::input_port> ret;+ for (auto& port : internalInPortObserver_.get_ports())+ ret.push_back(to_port_info<true>(port));+ return ret;+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept override+ {+ std::vector<libremidi::output_port> ret;+ for (auto& port : internalOutPortObserver_.get_ports())+ ret.push_back(to_port_info<false>(port));+ return ret;+ }++ static observer_winuwp_internal& get_internal_in_port_observer()+ {+ return internalInPortObserver_;+ }++ static observer_winuwp_internal& get_internal_out_port_observer()+ {+ return internalOutPortObserver_;+ }++ void on_input_added(const observer_winuwp_internal::port_info& name)+ {+ if (configuration.input_added)+ configuration.input_added(to_port_info<true>(name));+ }++ void on_input_removed(const observer_winuwp_internal::port_info& name)+ {+ if (configuration.input_removed)+ configuration.input_removed(to_port_info<true>(name));+ }++ void on_output_added(const observer_winuwp_internal::port_info& name)+ {+ if (configuration.output_added)+ configuration.output_added(to_port_info<false>(name));+ }++ void on_output_removed(const observer_winuwp_internal::port_info& name)+ {+ if (configuration.output_removed)+ configuration.output_removed(to_port_info<false>(name));+ }++private:+ static inline observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};+ static inline observer_winuwp_internal internalOutPortObserver_{+ MidiOutPort::GetDeviceSelector()};++ int evTokenOnInputAdded_{-1};+ int evTokenOnInputRemoved_{-1};+ int evTokenOnOutputAdded_{-1};+ int evTokenOnOutputRemoved_{-1};+};++}
+ vendor/libremidi/client.hpp view
@@ -0,0 +1,204 @@+#pragma once+#include <libremidi/configurations.hpp>+#include <libremidi/libremidi.hpp>+#include <libremidi/shared_context.hpp>++#include <map>++namespace libremidi::midi1+{+struct client_configuration+{+ libremidi::API api = libremidi::midi1::default_api();++ //! A client name, if the backend allows it (e.g. JACK, ALSA SEQ...)+ std::string_view client_name = "libremidi client";++ //! Set a callback function to be invoked for incoming MIDI messages.+ //! Mandatory!+ std::function<void(const libremidi::input_port&, message&&)> on_message+ = [](const libremidi::input_port& /*port*/, libremidi::message&&) {};++ //! Observation callbacks for when ports are added or removed+ input_port_callback input_added;+ input_port_callback input_removed;+ output_port_callback output_added;+ output_port_callback output_removed;++ //! Error callback function to be invoked when an error has occured.+ /*!+ The callback function will be called whenever an error has occured. It is+ best to set the error callback function before opening a port.+ */+ midi_error_callback on_error{};+ midi_warning_callback on_warning{};++ //! Poll period for observation polling operations, if relevant to the backend+ std::chrono::milliseconds poll_period{100};++ //! Specify whether certain MIDI message types should be queued or ignored+ //! during input.+ /*!+ By default, MIDI timing and active sensing messages are ignored+ during message input because of their relative high data rates.+ MIDI sysex messages are ignored by default as well. Variable+ values of "true" imply that the respective message type will be+ ignored.+ */+ uint32_t ignore_sysex : 1 = true;+ uint32_t ignore_timing : 1 = true;+ uint32_t ignore_sensing : 1 = true;+ uint32_t timestamps : 3 = timestamp_mode::Absolute;++ //! Observe hardware ports+ uint32_t track_hardware : 1 = true;++ //! Observe software (virtual) ports if the API provides it+ uint32_t track_virtual : 1 = false;+};++class client+{+public:+ explicit client(const client_configuration& conf)+ : client{conf, create_shared_context(conf.api, conf.client_name)}+ {+ }++ explicit client(const client_configuration& conf, shared_configurations ctx)+ : configuration{conf}+ , context{ctx}+ , m_observer{+ observer_configuration{+ .on_error = conf.on_error,+ .on_warning = conf.on_warning,++ .input_added = conf.input_added,+ .input_removed = conf.input_removed,+ .output_added = conf.output_added,+ .output_removed = conf.output_removed,++ .track_hardware = conf.track_hardware,+ .track_virtual = conf.track_virtual,+ .notify_in_constructor = false},+ context.observer}+ {+ if (context.context)+ context.context->start_processing();+ }++ ~client()+ {+ m_inputs.clear();+ m_outputs.clear();++ if (context.context)+ context.context->stop_processing();+ }++ std::vector<libremidi::input_port> get_input_ports() const noexcept+ {+ return m_observer.get_input_ports();+ }++ std::vector<libremidi::output_port> get_output_ports() const noexcept+ {+ return m_observer.get_output_ports();+ }++ void add_input(const input_port& port, std::string_view name)+ {+ if (m_inputs.find(port) != m_inputs.end())+ return;++ auto res = m_inputs.try_emplace(+ port,+ input_configuration{+ .on_message+ = [this,+ port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); },+ .on_raw_data = {},+ .get_timestamp = {},++ .on_error = configuration.on_error,+ .on_warning = configuration.on_warning,++ .ignore_sysex = configuration.ignore_sysex,+ .ignore_timing = configuration.ignore_timing,+ .ignore_sensing = configuration.ignore_sensing,++ .timestamps = configuration.timestamps},+ context.in);++ res.first->second.open_port(port, name);+ }++ void add_output(const output_port& port, std::string_view name)+ {+ if (m_outputs.find(port) != m_outputs.end())+ return;++ auto res = m_outputs.try_emplace(+ port,+ output_configuration{+ .on_error = configuration.on_error,+ .on_warning = configuration.on_warning,++ .timestamps = configuration.timestamps},+ context.out);++ res.first->second.open_port(port, name);+ }++ void remove_input(const input_port& port) { m_inputs.erase(port); }+ void remove_output(const output_port& port) { m_outputs.erase(port); }++ stdx::error send_message(const unsigned char* message, size_t size)+ {+ for (auto& [_, out] : m_outputs)+ {+ if (auto err = out.send_message(message, size); err != stdx::error{})+ return err;+ }+ return stdx::error{};+ }++ stdx::error send_ump(const uint32_t* message, size_t size)+ {+ for (auto& [_, out] : m_outputs)+ {+ if (auto err = out.send_ump(message, size); err != stdx::error{})+ return err;+ }+ return stdx::error{};+ }++ stdx::error send_message(const output_port& port, const unsigned char* message, size_t size)+ {+ if (auto it = m_outputs.find(port); it != m_outputs.end())+ return it->second.send_message(message, size);++ return stdx::error{};+ }++ stdx::error send_ump(const output_port& port, const uint32_t* message, size_t size)+ {+ if (auto it = m_outputs.find(port); it != m_outputs.end())+ return it->second.send_ump(message, size);++ return stdx::error{};+ }++private:+ client_configuration configuration;+ shared_configurations context;++ std::map<input_port, midi_in> m_inputs;+ std::map<output_port, midi_out> m_outputs;++ observer m_observer;+};+}+#if defined(LIBREMIDI_HEADER_ONLY)+ #include "client.cpp"+#endif
+ vendor/libremidi/cmidi2.hpp view
@@ -0,0 +1,3266 @@+#ifndef CMIDI2_H_INCLUDED+#define CMIDI2_H_INCLUDED++#include <memory.h>+#include <stdbool.h>+#include <stdint.h>++#define MIDI_2_0_RESERVED 0+#define JR_TIMESTAMP_TICKS_PER_SECOND 31250++#pragma GCC system_header+#pragma clang system_header++#ifdef __cplusplus+extern "C" {+#endif++enum cmidi2_status_code+{+ CMIDI2_STATUS_NOTE_OFF = 0x80,+ CMIDI2_STATUS_NOTE_ON = 0x90,+ CMIDI2_STATUS_PAF = 0xA0,+ CMIDI2_STATUS_CC = 0xB0,+ CMIDI2_STATUS_PROGRAM = 0xC0,+ CMIDI2_STATUS_CAF = 0xD0,+ CMIDI2_STATUS_PITCH_BEND = 0xE0,+ CMIDI2_STATUS_PER_NOTE_RCC = 0x00,+ CMIDI2_STATUS_PER_NOTE_ACC = 0x10,+ CMIDI2_STATUS_RPN = 0x20,+ CMIDI2_STATUS_NRPN = 0x30,+ CMIDI2_STATUS_RELATIVE_RPN = 0x40,+ CMIDI2_STATUS_RELATIVE_NRPN = 0x50,+ CMIDI2_STATUS_PER_NOTE_PITCH_BEND = 0x60,+ CMIDI2_STATUS_PER_NOTE_MANAGEMENT = 0xF0,+};++enum cmidi2_message_type+{+ CMIDI2_MESSAGE_TYPE_UTILITY = 0,+ CMIDI2_MESSAGE_TYPE_SYSTEM = 1,+ CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL = 2,+ CMIDI2_MESSAGE_TYPE_SYSEX7 = 3,+ CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL = 4,+ CMIDI2_MESSAGE_TYPE_SYSEX8_MDS = 5,+ CMIDI2_MESSAGE_TYPE_FLEX_DATA = 0xD,+ CMIDI2_MESSAGE_TYPE_UMP_STREAM = 0xF,+};++enum cmidi2_ci_protocol_bytes+{+ CMIDI2_PROTOCOL_BYTES_TYPE = 1,+ CMIDI2_PROTOCOL_BYTES_VERSION = 2,+ CMIDI2_PROTOCOL_BYTES_EXTENSIONS = 3,+};++enum cmidi2_ci_protocol_values+{+ CMIDI2_PROTOCOL_TYPE_MIDI1 = 1,+ CMIDI2_PROTOCOL_TYPE_MIDI2 = 2,+ CMIDI2_PROTOCOL_VERSION_MIDI1 = 0,+ CMIDI2_PROTOCOL_VERSION_MIDI2_V1 = 0,+};++enum cmidi2_ci_protocol_extensions+{+ CMIDI2_PROTOCOL_EXTENSIONS_JITTER = 1,+ CMIDI2_PROTOCOL_EXTENSIONS_LARGER = 2, // only for MIDI 1.0 compat UMP+};++typedef struct cmidi2_ci_protocol_tag+{+ uint8_t protocol_type; // cmidi2_ci_protocol_bytes+ uint8_t version; // cmidi2_ci_protocol_values+ uint8_t extensions; // cmidi2_ci_protocol_extensions (flags)+ uint8_t reserved1;+ uint8_t reserved2;+} cmidi2_ci_protocol;++enum cmidi2_cc+{+ CMIDI2_CC_BANK_SELECT = 0x00,+ CMIDI2_CC_MODULATION = 0x01,+ CMIDI2_CC_BREATH = 0x02,+ CMIDI2_CC_FOOT = 0x04,+ CMIDI2_CC_PORTAMENTO_TIME = 0x05,+ CMIDI2_CC_DTE_MSB = 0x06,+ CMIDI2_CC_VOLUME = 0x07,+ CMIDI2_CC_BALANCE = 0x08,+ CMIDI2_CC_PAN = 0x0A,+ CMIDI2_CC_EXPRESSION = 0x0B,+ CMIDI2_CC_EFFECT_CONTROL_1 = 0x0C,+ CMIDI2_CC_EFFECT_CONTROL_2 = 0x0D,+ CMIDI2_CC_GENERAL_1 = 0x10,+ CMIDI2_CC_GENERAL_2 = 0x11,+ CMIDI2_CC_GENERAL_3 = 0x12,+ CMIDI2_CC_GENERAL_4 = 0x13,+ CMIDI2_CC_BANK_SELECT_LSB = 0x20,+ CMIDI2_CC_MODULATION_LSB = 0x21,+ CMIDI2_CC_BREATH_LSB = 0x22,+ CMIDI2_CC_FOOT_LSB = 0x24,+ CMIDI2_CC_PORTAMENTO_TIME_LSB = 0x25,+ CMIDI2_CC_DTE_LSB = 0x26,+ CMIDI2_CC_VOLUME_LSB = 0x27,+ CMIDI2_CC_BALANCE_LSB = 0x28,+ CMIDI2_CC_PAN_LSB = 0x2A,+ CMIDI2_CC_EXPRESSION_LSB = 0x2B,+ CMIDI2_CC_EFFECT1_LSB = 0x2C,+ CMIDI2_CC_EFFECT2_LSB = 0x2D,+ CMIDI2_CC_GENERAL_1_LSB = 0x30,+ CMIDI2_CC_GENERAL_2_LSB = 0x31,+ CMIDI2_CC_GENERAL_3_LSB = 0x32,+ CMIDI2_CC_GENERAL_4_LSB = 0x33,+ CMIDI2_CC_HOLD = 0x40,+ CMIDI2_CC_PORTAMENTO_SWITCH = 0x41,+ CMIDI2_CC_SOSTENUTO = 0x42,+ CMIDI2_CC_SOFT_PEDAL = 0x43,+ CMIDI2_CC_LEGATO = 0x44,+ CMIDI2_CC_HOLD_2 = 0x45,+ CMIDI2_CC_SOUND_CONTROLLER_1 = 0x46,+ CMIDI2_CC_SOUND_CONTROLLER_2 = 0x47,+ CMIDI2_CC_SOUND_CONTROLLER_3 = 0x48,+ CMIDI2_CC_SOUND_CONTROLLER_4 = 0x49,+ CMIDI2_CC_SOUND_CONTROLLER_5 = 0x4A,+ CMIDI2_CC_SOUND_CONTROLLER_6 = 0x4B,+ CMIDI2_CC_SOUND_CONTROLLER_7 = 0x4C,+ CMIDI2_CC_SOUND_CONTROLLER_8 = 0x4D,+ CMIDI2_CC_SOUND_CONTROLLER_9 = 0x4E,+ CMIDI2_CC_SOUND_CONTROLLER_10 = 0x4F,+ CMIDI2_CC_GENERAL_5 = 0x50,+ CMIDI2_CC_GENERAL_6 = 0x51,+ CMIDI2_CC_GENERAL_7 = 0x52,+ CMIDI2_CC_GENERAL_8 = 0x53,+ CMIDI2_CC_PORTAMENTO_CONTROL = 0x54,+ CMIDI2_CC_RSD = 0x5B,+ CMIDI2_CC_EFFECT_1 = 0x5B,+ CMIDI2_CC_TREMOLO = 0x5C,+ CMIDI2_CC_EFFECT_2 = 0x5C,+ CMIDI2_CC_CSD = 0x5D,+ CMIDI2_CC_EFFECT_3 = 0x5D,+ CMIDI2_CC_CELESTE = 0x5E,+ CMIDI2_CC_EFFECT_4 = 0x5E,+ CMIDI2_CC_PHASER = 0x5F,+ CMIDI2_CC_EFFECT_5 = 0x5F,+ CMIDI2_CC_DTE_INCREMENT = 0x60,+ CMIDI2_CC_DTE_DECREMENT = 0x61,+ CMIDI2_CC_NRPN_LSB = 0x62,+ CMIDI2_CC_NRPN_MSB = 0x63,+ CMIDI2_CC_RPN_LSB = 0x64,+ CMIDI2_CC_RPN_MSB = 0x65,+ // CHANNEL MODE MESSAGES+ CMIDI2_CC_ALL_SOUND_OFF = 0x78,+ CMIDI2_CC_RESET_ALL_CONTROLLERS = 0x79,+ CMIDI2_CC_LOCAL_CONTROL = 0x7A,+ CMIDI2_CC_ALL_NOTES_OFF = 0x7B,+ CMIDI2_CC_OMNI_MODE_OFF = 0x7C,+ CMIDI2_CC_OMNI_MODE_ON = 0x7D,+ CMIDI2_CC_POLY_MODE_ON_OFF = 0x7E,+ CMIDI2_CC_POLY_MODE_ON = 0x7F,+};++enum cmidi2_rpn+{+ CMIDI2_RPN_PITCH_BEND_SENSITIVITY = 0,+ CMIDI2_RPN_FINE_TUNING = 1,+ CMIDI2_RPN_COARSE_TUNING = 2,+ CMIDI2_RPN_TUNING_PROGRAM = 3,+ CMIDI2_RPN_TUNING_BANK_SELECT = 4,+ CMIDI2_RPN_MODULATION_DEPTH = 5,+};++enum cmidi2_meta_event_type+{+ CMIDI2_META_SEQUENCE_NUMBER = 0X00,+ CMIDI2_META_TEXT = 0X01,+ CMIDI2_META_COPYRIGHT = 0X02,+ CMIDI2_META_TRACK_NAME = 0X03,+ CMIDI2_META_INSTRUMENT_NAME = 0X04,+ CMIDI2_META_LYRIC = 0X05,+ CMIDI2_META_MARKER = 0X06,+ CMIDI2_META_CUE = 0X07,+ CMIDI2_META_CHANNEL_PREFIX = 0X20,+ CMIDI2_META_END_OF_TRACK = 0X2F,+ CMIDI2_META_TEMPO = 0X51,+ CMIDI2_META_SMPTE_OFFSET = 0X54,+ CMIDI2_META_TIME_SIGNATURE = 0X58,+ CMIDI2_META_KEY_SIGNATURE = 0X59,+ CMIDI2_META_SEQUENCER_SPECIFIC = 0X7F,+};++enum cmidi2_per_note_management_flags+{+ CMIDI2_PER_NOTE_MANAGEMENT_RESET = 1,+ CMIDI2_PER_NOTE_MANAGEMENT_DETACH = 2,+};++enum cmidi2_note_attribute_type+{+ CMIDI2_ATTRIBUTE_TYPE_NONE = 0,+ CMIDI2_ATTRIBUTE_TYPE_MANUFACTURER = 1,+ CMIDI2_ATTRIBUTE_TYPE_PROFILE = 2,+ CMIDI2_ATTRIBUTE_TYPE_PITCH7_9 = 3,+};++enum cmidi2_program_change_option_flags+{+ CMIDI2_PROGRAM_CHANGE_OPTION_NONE = 0,+ CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID = 1,+};++enum cmidi2_sysex_status+{+ CMIDI2_SYSEX_IN_ONE_UMP = 0,+ CMIDI2_SYSEX_START = 0x10,+ CMIDI2_SYSEX_CONTINUE = 0x20,+ CMIDI2_SYSEX_END = 0x30,+};++enum cmidi2_mixed_data_set_status+{+ CMIDI2_MIXED_DATA_STATUS_HEADER = 0x80,+ CMIDI2_MIXED_DATA_STATUS_PAYLOAD = 0x90,+};++enum cmidi2_system_message_status+{+ CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE = 0xF1,+ CMIDI2_SYSTEM_STATUS_SONG_POSITION = 0xF2,+ CMIDI2_SYSTEM_STATUS_SONG_SELECT = 0xF3,+ CMIDI2_SYSTEM_STATUS_TUNE_REQUEST = 0xF6,+ CMIDI2_SYSTEM_STATUS_TIMING_CLOCK = 0xF8,+ CMIDI2_SYSTEM_STATUS_START = 0xFA,+ CMIDI2_SYSTEM_STATUS_CONTINUE = 0xFB,+ CMIDI2_SYSTEM_STATUS_STOP = 0xFC,+ CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING = 0xFE,+ CMIDI2_SYSTEM_STATUS_RESET = 0xFF,+};++// TODO: remove this. Use cmidi2_utility_message_status instead+enum cmidi2_jr_timestamp_status+{+ CMIDI2_JR_CLOCK = 0x10,+ CMIDI2_JR_TIMESTAMP = 0x20,+};++enum cmidi2_utility_message_status+{+ CMIDI2_UTILITY_STATUS_NOOP = 0,+ CMIDI2_UTILITY_STATUS_JR_CLOCK = 0x10,+ CMIDI2_UTILITY_STATUS_JR_TIMESTAMP = 0x20,+ CMIDI2_UTILITY_STATUS_DCTPQ = 0x30,+ CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP = 0x40,+};++enum cmidi2_flex_data_status_bank+{+ CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE = 0,+ CMIDI2_FLEX_DATA_BANK_METADATA_TEXT = 1,+ CMIDI2_FLEX_DATA_BANK_PERFORMANCE_TEXT = 2+};+enum cmidi2_flex_data_setup_status+{+ CMIDI2_FLEX_DATA_STATUS_SET_TEMPO = 0,+ CMIDI2_FLEX_DATA_STATUS_SET_TIME_SIGNATURE = 1,+ CMIDI2_FLEX_DATA_STATUS_SET_METRONOME = 2,+ CMIDI2_FLEX_DATA_STATUS_SET_KEY_SIGNATURE = 5,+ CMIDI2_FLEX_DATA_STATUS_SET_CHORD_NAME = 6+};+enum cmidi2_flex_data_metadata_text_status+{+ CMIDI2_FLEX_DATA_STATUS_UNKNOWN_METADATA_TEXT = 0,+ CMIDI2_FLEX_DATA_STATUS_PROJECT_NAME = 1,+ CMIDI2_FLEX_DATA_STATUS_SONG_NAME = 2,+ CMIDI2_FLEX_DATA_STATUS_CLIP_NAME = 3,+ CMIDI2_FLEX_DATA_STATUS_COPYRIGHT_NAME = 4,+ CMIDI2_FLEX_DATA_STATUS_COMPOSER_NAME = 5,+ CMIDI2_FLEX_DATA_STATUS_LYRICIST_NAME = 6,+ CMIDI2_FLEX_DATA_STATUS_ARRANGER_NAME = 7,+ CMIDI2_FLEX_DATA_STATUS_PUBLISHER_NAME = 8,+ CMIDI2_FLEX_DATA_STATUS_PRIMARY_PERFORMER_NAME = 9,+ CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMAER_NAME = 10,+ CMIDI2_FLEX_DATA_STATUS_RECORDING_DATE = 11,+ CMIDI2_FLEX_DATA_STATUS_RECORDING_LOCATION = 12+};+enum cmidi2_flex_data_performance_text_status+{+ CMIDI2_FLEX_DATA_STATUS_UNKNOWN_PERFORMANCE_TEXT = 0,+ CMIDI2_FLEX_DATA_STATUS_LYRICS = 1,+ CMIDI2_FLEX_DATA_STATUS_LYRICS_LANGUAGE = 2,+ CMIDI2_FLEX_DATA_STATUS_RUBY = 3,+ CMIDI2_FLEX_DATA_STATUS_RUBY_LANGUAGE = 4+};++enum cmidi2_ump_chord_name_sharps_flats+{+ CMIDI2_UMP_CHORD_NAME_DOUBLE_SHARP = 2,+ CMIDI2_UMP_CHORD_NAME_SHARP = 1,+ CMIDI2_UMP_CHORD_NAME_NATURAL = 0,+ CMIDI2_UMP_CHORD_NAME_FLAT = 0xF,+ CMIDI2_UMP_CHORD_NAME_DOUBLE_FLAT = 0xE,+};++enum cmidi2_ump_chord_name_tonic_note+{+ CMIDI2_UMP_CHORD_NAME_UNKNOWN = 0,+ CMIDI2_UMP_CHORD_NAME_A = 1,+ CMIDI2_UMP_CHORD_NAME_B = 2,+ CMIDI2_UMP_CHORD_NAME_C = 3,+ CMIDI2_UMP_CHORD_NAME_D = 4,+ CMIDI2_UMP_CHORD_NAME_E = 5,+ CMIDI2_UMP_CHORD_NAME_F = 6,+ CMIDI2_UMP_CHORD_NAME_G = 7,+};++enum cmidi2_ump_chor_name_chord_type+{+ CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0,+ CMIDI2_UMP_CHORD_TYPE_MAJOR = 1,+ CMIDI2_UMP_CHORD_TYPE_MAJOR_6TH = 2,+ CMIDI2_UMP_CHORD_TYPE_MAJOR_7TH = 3,+ CMIDI2_UMP_CHORD_TYPE_MAJOR_9TH = 4,+ CMIDI2_UMP_CHORD_TYPE_MAJOR_11TH = 5,+ CMIDI2_UMP_CHORD_TYPE_MAJOR_13TH = 6,+ CMIDI2_UMP_CHORD_TYPE_MINOR = 7,+ CMIDI2_UMP_CHORD_TYPE_MINOR_6TH = 8,+ CMIDI2_UMP_CHORD_TYPE_MINOR_7TH = 9,+ CMIDI2_UMP_CHORD_TYPE_MINOR_9TH = 10,+ CMIDI2_UMP_CHORD_TYPE_MINOR_11TH = 11,+ CMIDI2_UMP_CHORD_TYPE_MINOR_13TH = 12,+ CMIDI2_UMP_CHORD_TYPE_DOMINANT = 13,+ CMIDI2_UMP_CHORD_TYPE_DOMINANT_9TH = 14,+ CMIDI2_UMP_CHORD_TYPE_DOMINANT_11TH = 15,+ CMIDI2_UMP_CHORD_TYPE_DOMINANT_13TH = 16,+ CMIDI2_UMP_CHORD_TYPE_AUGMENTED = 17,+ CMIDI2_UMP_CHORD_TYPE_AUGMENTED_7TH = 18,+ CMIDI2_UMP_CHORD_TYPE_DIMINISHED = 19,+ CMIDI2_UMP_CHORD_TYPE_DIMINISHED_7TH = 20,+ CMIDI2_UMP_CHORD_TYPE_HALF_DIMINISHED = 21,+ CMIDI2_UMP_CHORD_TYPE_MAJOR_MINOR = 22,+ CMIDI2_UMP_CHORD_TYPE_MINOR_MAJOR = 22, // same+ CMIDI2_UMP_CHORD_TYPE_PEDAL = 23,+ CMIDI2_UMP_CHORD_TYPE_POWER = 24,+ CMIDI2_UMP_CHORD_TYPE_SUSPENDED_2ND = 25,+ CMIDI2_UMP_CHORD_TYPE_SUSPENDED_4TH = 26,+ CMIDI2_UMP_CHORD_TYPE_7_SUSPENDED_4TH = 27,+};++static inline uint8_t cmidi2_ump_get_num_bytes(uint32_t data)+{+ switch (((data & 0xF0000000) >> 28) & 0xF)+ {+ case CMIDI2_MESSAGE_TYPE_UTILITY:+ case CMIDI2_MESSAGE_TYPE_SYSTEM:+ case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:+ return 4;+ case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:+ case CMIDI2_MESSAGE_TYPE_SYSEX7:+ return 8;+ case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:+ return 16;+ }+ return 0xFF; /* wrong */+}++typedef struct cmidi2_ump128+{+ uint32_t p1;+ uint32_t p2;+ uint32_t p3;+ uint32_t p4;+} cmidi2_ump128_t;++typedef struct cmidi2_ump_version+{+ uint8_t major;+ uint8_t minor;+} cmidi2_ump_version_t;++enum cmidi2_ump_stream_status+{+ CMIDI2_UMP_STREAM_STATUS_ENDPOINT_DISCOVERY = 0,+ CMIDI2_UMP_STREAM_STATUS_ENDPOINT_INFO = 1,+ CMIDI2_UMP_STREAM_STATUS_DEVICE_IDENTITY = 2,+ CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME = 3,+ CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID = 4,+ CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_REQUEST = 5,+ CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_NOTIFICATION = 6,+ CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_DISCOVERY = 0x10,+ CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_INFO = 0x11,+ CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME = 0x12,+ CMIDI2_UMP_STREAM_STATUS_START_OF_CLIP = 0x20,+ CMIDI2_UMP_STREAM_STATUS_END_OF_CLIP = 0x21,+};++enum cmidi2_ump_endpoint_filter_flags+{+ CMIDI2_UMP_ENDPOINT_FILTER_ENDPOINT_INFO = 1,+ CMIDI2_UMP_ENDPOINT_FILTER_DEVICE_IDENTITY = 2,+ CMIDI2_UMP_ENDPOINT_FILTER_ENDPOINT_NAME = 4,+ CMIDI2_UMP_ENDPOINT_FILTER_PRODUCT_INSTANCE_ID = 8,+ CMIDI2_UMP_ENDPOINT_FILTER_STREAM_CONFIGURATION = 0x10,+};++enum cmidi2_ump_function_block_discovery_flags+{+ CMIDI2_UMP_FUNCTION_BLOCK_FILTER_INFO = 1,+ CMIDI2_UMP_FUNCTION_BLOCK_FILTER_NAME = 2,+};++// --------+// UMP generators++// 7.1 UMP Stream Messages++static inline cmidi2_ump128_t+cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap)+{+ cmidi2_ump128_t ret+ = {(uint32_t)((CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_ENDPOINT_DISCOVERY << 16) + (version.major << 8)+ + version.minor),+ (uint32_t)(filterBitmap & 0x1F), 0, 0};+ return ret;+}++static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification(+ cmidi2_ump_version_t version, bool isStaticFunctionBlock, uint8_t numFunctionBlocks,+ bool midi2Capable, bool midi1Capable, bool rxJR, bool txJR)+{+ cmidi2_ump128_t ret = {+ (uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_ENDPOINT_INFO << 16) + (version.major << 8) + version.minor,+ (uint32_t)(isStaticFunctionBlock ? 1 << 31 : 0) + (numFunctionBlocks << 24)+ + (midi2Capable ? 0x1000 : 0) + (midi1Capable ? 0x100 : 0) + (rxJR ? 2 : 0)+ + (txJR ? 1 : 0),+ 0, 0};+ return ret;+}++static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification(+ uint32_t manufacturerIdIn7bitArray, uint8_t deviceFamilyLSB, uint8_t deviceFamilyMSB,+ uint8_t deviceFamilyModelLSB, uint8_t deviceFamilyModelMSB,+ uint32_t softwareRevisionIn7bitArray)+{+ (void)deviceFamilyModelMSB;+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_DEVICE_IDENTITY << 16),+ manufacturerIdIn7bitArray,+ (uint32_t)(deviceFamilyLSB << 24) + (deviceFamilyMSB << 16) + (deviceFamilyModelLSB << 8)+ + deviceFamilyMSB,+ softwareRevisionIn7bitArray};+ return ret;+}++static inline cmidi2_ump128_t+cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14])+{+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) + (statusCode << 16) + (name[0] << 8)+ + name[1],+ (uint32_t)(name[2] << 24) + (name[3] << 16) + (name[4] << 8) + name[5],+ (uint32_t)(name[6] << 24) + (name[7] << 16) + (name[8] << 8) + name[9],+ (uint32_t)(name[10] << 24) + (name[11] << 16) + (name[12] << 8) + name[13]};+ return ret;+}++static inline cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14])+{+ return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME, name);+}++static inline cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14])+{+ return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID, id);+}++static inline cmidi2_ump128_t+cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR)+{+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_REQUEST << 16) + (protocol << 8)+ + (rxJR ? 2 : 0) + (txJR ? 1 : 0),+ 0, 0, 0};+ return ret;+}++static inline cmidi2_ump128_t+cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool txJR)+{+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_NOTIFICATION << 16) + (protocol << 8)+ + (rxJR ? 2 : 0) + (txJR ? 1 : 0),+ 0, 0, 0};+ return ret;+}++static inline cmidi2_ump128_t+cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter)+{+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_DISCOVERY << 16) + (numFunctionBlocks << 8)+ + filter,+ 0, 0, 0};+ return ret;+}++static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification(+ bool active, uint8_t numFunctionBlocks, uint8_t uiHint, uint8_t midi1, uint8_t direction,+ uint8_t firstGroup, uint8_t numSpannedGroup, uint8_t midiCIVersionFormat,+ uint8_t maxNumSysEx8Streams)+{+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_INFO << 16) + (active ? 0x8000 : 0)+ + (numFunctionBlocks << 8) + (uiHint << 4) + (midi1 << 2) + direction,+ (uint32_t)(firstGroup << 24) + (numSpannedGroup << 16) + (midiCIVersionFormat << 8)+ + maxNumSysEx8Streams,+ 0, 0};+ return ret;+}++static inline cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14])+{+ return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME, name);+}++static inline cmidi2_ump128_t cmidi2_ump_start_of_clip()+{+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_START_OF_CLIP << 16),+ 0, 0, 0};+ return ret;+}++static inline cmidi2_ump128_t cmidi2_ump_end_of_clip()+{+ cmidi2_ump128_t ret+ = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)+ + (CMIDI2_UMP_STREAM_STATUS_END_OF_CLIP << 16),+ 0, 0, 0};+ return ret;+}++// 7.2 Utility Messages+static inline uint32_t cmidi2_ump_noop(uint8_t group)+{+ return (group & 0xF) << 24;+}++static inline uint32_t cmidi2_ump_jr_clock_direct(uint8_t group, uint32_t senderClockTime)+{+ return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime;+}++static inline uint32_t cmidi2_ump_jr_clock(uint8_t group, double senderClockTime)+{+ uint16_t value = (uint16_t)(senderClockTime * JR_TIMESTAMP_TICKS_PER_SECOND);+ return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value;+}++static inline uint32_t cmidi2_ump_jr_timestamp_direct(uint8_t group, uint16_t senderClockTimestamp)+{+ return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16)+ + senderClockTimestamp;+}++static inline uint32_t cmidi2_ump_jr_timestamp(uint8_t group, double senderClockTimestamp)+{+ uint16_t value = (uint16_t)(senderClockTimestamp * JR_TIMESTAMP_TICKS_PER_SECOND);+ return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value;+}++static inline uint32_t cmidi2_ump_dctpq(uint8_t group, uint32_t dctpq)+{+ return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq;+}++static inline uint32_t cmidi2_ump_dcs(uint8_t group, uint32_t ticks)+{+ // Note that unlike JR timestamp delta clockstamps accepts ticks up to 20 bits.+ return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16)+ + (ticks & 0xFFFFF);+}++// 7.6 System Common and System Real Time Messages+static inline int32_t+cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uint8_t midi1Byte3)+{+ return (CMIDI2_MESSAGE_TYPE_SYSTEM << 28) + ((group & 0xF) << 24) + (status << 16)+ + ((midi1Byte2 & 0x7F) << 8) + (midi1Byte3 & 0x7F);+}++// 7.3 MIDI 1.0 Channel Voice Messages+static inline int32_t cmidi2_ump_midi1_message(+ uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4)+{+ return (CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL << 28) + ((group & 0xF) << 24)+ + (((code & 0xF0) + (channel & 0xF)) << 16) + ((byte3 & 0x7F) << 8) + (byte4 & 0x7F);+}++static inline int32_t+cmidi2_ump_midi1_note_off(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity)+{+ return cmidi2_ump_midi1_message(+ group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, velocity & 0x7F);+}++static inline int32_t+cmidi2_ump_midi1_note_on(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity)+{+ return cmidi2_ump_midi1_message(+ group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, velocity & 0x7F);+}++static inline int32_t+cmidi2_ump_midi1_paf(uint8_t group, uint8_t channel, uint8_t note, uint8_t data)+{+ return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_PAF, channel, note & 0x7F, data & 0x7F);+}++static inline int32_t+cmidi2_ump_midi1_cc(uint8_t group, uint8_t channel, uint8_t index, uint8_t data)+{+ return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_CC, channel, index & 0x7F, data & 0x7F);+}++static inline int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program)+{+ return cmidi2_ump_midi1_message(+ group, CMIDI2_STATUS_PROGRAM, channel, program & 0x7F, MIDI_2_0_RESERVED);+}++static inline int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data)+{+ return cmidi2_ump_midi1_message(+ group, CMIDI2_STATUS_CAF, channel, data & 0x7F, MIDI_2_0_RESERVED);+}++static inline int32_t+cmidi2_ump_midi1_pitch_bend_direct(uint8_t group, uint8_t channel, uint16_t data)+{+ return cmidi2_ump_midi1_message(+ group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F);+}++static inline int32_t+cmidi2_ump_midi1_pitch_bend_split(uint8_t group, uint8_t channel, uint8_t dataLSB, uint8_t dataMSB)+{+ return cmidi2_ump_midi1_message(+ group, CMIDI2_STATUS_PITCH_BEND, channel, dataLSB & 0x7F, dataMSB & 0x7F);+}++static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data)+{+ data += 8192;+ return cmidi2_ump_midi1_message(+ group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F);+}++// 7.4 MIDI 2.0 Channel Voice Messages+static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16(+ uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint16_t short1,+ uint16_t short2)+{+ return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24)+ + (((code & 0xF0) + (channel & 0xF)) << 16) + (byte3 << 8) + byte4)+ << 32)+ + ((uint64_t)short1 << 16) + short2;+}++static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32(+ uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint32_t rest)+{+ return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24)+ + (((code & 0xF0) + (channel & 0xF)) << 16) + (byte3 << 8) + byte4)+ << 32)+ + rest;+}++static inline uint16_t cmidi2_ump_pitch_7_9(double semitone)+{+ double actual = semitone < 0.0 ? 0.0 : semitone >= 128.0 ? 128.0 : semitone;+ uint16_t dec = (uint16_t)actual;+ double microtone = actual - dec;+ return (dec << 9) + (int)(microtone * 512.0);+}++static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone)+{+ uint16_t ret = (uint16_t)(semitone & 0x7F) << 9;+ double actual = microtone < 0.0 ? 0.0 : microtone > 1.0 ? 1.0 : microtone;+ ret += (int)(actual * 512.0);+ return ret;+}++static inline int64_t cmidi2_ump_midi2_note_off(+ uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity,+ uint16_t attributeData)+{+ return cmidi2_ump_midi2_channel_message_8_8_16_16(+ group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, attributeType, velocity, attributeData);+}++static inline int64_t cmidi2_ump_midi2_note_on(+ uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity,+ uint16_t attributeData)+{+ return cmidi2_ump_midi2_channel_message_8_8_16_16(+ group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, attributeType, velocity, attributeData);+}++static inline int64_t+cmidi2_ump_midi2_paf(uint8_t group, uint8_t channel, uint8_t note, uint32_t data)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_PAF, channel, note & 0x7F, MIDI_2_0_RESERVED, data);+}++static inline int64_t cmidi2_ump_midi2_per_note_rcc(+ uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_PER_NOTE_RCC, channel, note & 0x7F, index, data);+}++static inline int64_t cmidi2_ump_midi2_per_note_acc(+ uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_PER_NOTE_ACC, channel, note & 0x7F, index, data);+}++static inline int64_t cmidi2_ump_midi2_per_note_management(+ uint8_t group, uint8_t channel, uint8_t note, uint8_t optionFlags)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_PER_NOTE_MANAGEMENT, channel, note & 0x7F, optionFlags & 3, 0);+}++static inline int64_t+cmidi2_ump_midi2_cc(uint8_t group, uint8_t channel, uint8_t index, uint32_t data)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_CC, channel, index & 0x7F, MIDI_2_0_RESERVED, data);+}++static inline int64_t cmidi2_ump_midi2_rpn(+ uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE);+}++static inline int64_t cmidi2_ump_midi2_nrpn(+ uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE);+}++static inline int64_t cmidi2_ump_midi2_relative_rpn(+ uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_RELATIVE_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F,+ dataAkaDTE);+}++static inline int64_t cmidi2_ump_midi2_relative_nrpn(+ uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_RELATIVE_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F,+ dataAkaDTE);+}++static inline int64_t cmidi2_ump_midi2_program(+ uint8_t group, uint8_t channel, uint8_t optionFlags, uint8_t program, uint8_t bankMSB,+ uint8_t bankLSB)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_PROGRAM, channel, MIDI_2_0_RESERVED, optionFlags & 1,+ ((program & 0x7F) << 24) + (bankMSB << 8) + bankLSB);+}++static inline int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_CAF, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED, data);+}++static inline int64_t+cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsignedData)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_PITCH_BEND, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED,+ unsignedData);+}++static inline int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data)+{+ return cmidi2_ump_midi2_pitch_bend_direct(group, channel, 0x80000000 + data);+}++static inline int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct(+ uint8_t group, uint8_t channel, uint8_t note, uint32_t data)+{+ return cmidi2_ump_midi2_channel_message_8_8_32(+ group, CMIDI2_STATUS_PER_NOTE_PITCH_BEND, channel, note & 0x7F, MIDI_2_0_RESERVED, data);+}++static inline int64_t+cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t note, uint32_t data)+{+ return cmidi2_ump_midi2_per_note_pitch_bend_direct(group, channel, note, 0x80000000 + data);+}++// Common utility functions for sysex support++static inline uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index)+{+ return (uint8_t)(src >> ((7 - index) * 8) & 0xFF);+}++static inline uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index)+{+ return (uint8_t)(src >> ((7 - index) * 8) & 0xFF);+}++static inline uint8_t cmidi2_ump_sysex_get_num_packets(uint8_t numBytes, uint8_t radix)+{+ return numBytes <= radix ? 1 : numBytes / radix + (numBytes % radix ? 1 : 0);+}++static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence)+{+ const uint8_t* bytes = (const uint8_t*)sequence;+ uint32_t ret = 0;+ for (int i = 0; i < 4; i++)+ ret += ((uint32_t)bytes[i]) << (i * 8);+ return ret;+}++static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence)+{+ const uint8_t* bytes = (const uint8_t*)sequence;+ uint32_t ret = 0;+ for (int i = 0; i < 4; i++)+ ret += ((uint32_t)bytes[i]) << ((3 - i) * 8);+ return ret;+}++static inline bool cmidi2_util_is_platform_little_endian()+{+ int i = 1;+ return *(char*)&i;+}++static inline uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence)+{+ return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint32_bytes_le(sequence)+ : cmidi2_ump_read_uint32_bytes_be(sequence);+}++static inline uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence)+{+ return ((uint64_t)cmidi2_ump_read_uint32_bytes_le(sequence) << 32)+ + cmidi2_ump_read_uint32_bytes_le((const uint8_t*)sequence + 4);+}++static inline uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence)+{+ return ((uint64_t)cmidi2_ump_read_uint32_bytes_be(sequence) << 32)+ + cmidi2_ump_read_uint32_bytes_be((const uint8_t*)sequence + 4);+}++static inline uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence)+{+ return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint64_bytes_le(sequence)+ : cmidi2_ump_read_uint64_bytes_be(sequence);+}++static inline void cmidi2_ump_sysex_get_packet_of(+ uint64_t* result1, uint64_t* result2, uint8_t group, uint8_t numBytes, const void* srcData,+ int32_t index, enum cmidi2_message_type messageType, int radix, bool hasStreamId,+ uint8_t streamId)+{+ uint8_t dst8[16];+ memset(dst8, 0, 16);+ const uint8_t* src8 = (const uint8_t*)srcData;++ dst8[0] = (messageType << 4) + (group & 0xF);++ enum cmidi2_sysex_status status;+ uint8_t size;+ if (numBytes <= radix)+ {+ status = CMIDI2_SYSEX_IN_ONE_UMP;+ size = numBytes; // single packet message+ }+ else if (index == 0)+ {+ status = CMIDI2_SYSEX_START;+ size = radix;+ }+ else+ {+ uint8_t isEnd = index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1;+ if (isEnd)+ {+ size = numBytes % radix ? numBytes % radix : radix;+ status = CMIDI2_SYSEX_END;+ }+ else+ {+ size = radix;+ status = CMIDI2_SYSEX_CONTINUE;+ }+ }+ dst8[1] = status + size + (hasStreamId ? 1 : 0);++ if (hasStreamId)+ dst8[2] = streamId;++ uint8_t dstOffset = hasStreamId ? 3 : 2;+ for (uint8_t i = 0, j = index * radix; i < size; i++, j++)+ dst8[i + dstOffset] = src8[j];++ *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+ if (result2)+ *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++// 7.7 System Exclusive 7-Bit Messages+static inline uint64_t cmidi2_ump_sysex7_direct(+ uint8_t group, uint8_t status, uint8_t numBytes, uint8_t data1, uint8_t data2, uint8_t data3,+ uint8_t data4, uint8_t data5, uint8_t data6)+{+ return (((uint64_t)((CMIDI2_MESSAGE_TYPE_SYSEX7 << 28) + ((group & 0xF) << 24)+ + ((status + numBytes) << 16)))+ << 32)+ + ((uint64_t)data1 << 40) + ((uint64_t)data2 << 32) + (data3 << 24) + (data4 << 16)+ + (data5 << 8) + data6;+}++static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData)+{+ int i = 0;+ const uint8_t* csrc = (const uint8_t*)srcData;+ while (csrc[i] != 0xF7)+ i++;+ /* This function automatically detects if 0xF0 is prepended and reduce length if it is. */+ return i - (csrc[0] == 0xF0 ? 1 : 0);+}++static inline uint8_t cmidi2_ump_sysex7_get_num_packets(uint8_t numSysex7Bytes)+{+ return cmidi2_ump_sysex_get_num_packets(numSysex7Bytes, 6);+}++static inline uint64_t cmidi2_ump_sysex7_get_packet_of(+ uint8_t group, uint8_t numBytes, const void* srcData, int32_t index)+{+ uint64_t result;+ int srcOffset = numBytes > 0 && ((const uint8_t*)srcData)[0] == 0xF0 ? 1 : 0;+ cmidi2_ump_sysex_get_packet_of(+ &result, NULL, group, numBytes, (const uint8_t*)srcData + srcOffset, index,+ CMIDI2_MESSAGE_TYPE_SYSEX7, 6, false, 0);+ return result;+}++/* process() - more complicated function */++// This returns NULL for success, or anything else for failure.+typedef void* (*cmidi2_ump_handler_u64)(uint64_t data, void* context);++// This returns NULL for success, or anything else that `sendUMP` returns for failure.+static inline void* cmidi2_ump_sysex7_process(+ uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context)+{+ int32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex);+ int32_t numPackets = cmidi2_ump_sysex7_get_num_packets(length);+ for (int p = 0; p < numPackets; p++)+ {+ int64_t ump = cmidi2_ump_sysex7_get_packet_of(group, length, sysex, p);+ void* retCode = sendUMP(ump, context);+ if (retCode != 0)+ return retCode;+ }+ return NULL;+}++// 7.8 System Exclusive 8-Bit Messages++static inline int8_t cmidi2_ump_sysex8_get_num_packets(uint8_t numBytes)+{+ return cmidi2_ump_sysex_get_num_packets(numBytes, 13);+}++static inline void cmidi2_ump_sysex8_get_packet_of(+ uint8_t group, uint8_t streamId, uint8_t numBytes, const void* srcData, int32_t index,+ uint64_t* result1, uint64_t* result2)+{+ cmidi2_ump_sysex_get_packet_of(+ result1, result2, group, numBytes, srcData, index, CMIDI2_MESSAGE_TYPE_SYSEX8_MDS, 13, true,+ streamId);+}++/* process() - more complicated function */++// This returns NULL for success, or anything else for failure.+typedef void* (*cmidi2_ump_handler_u128)(+ uint64_t data1, uint64_t data2, size_t index, void* context);++// This returns NULL for success, or anything else that `sendUMP` returns for failure.+static inline void* cmidi2_ump_sysex8_process(+ uint8_t group, void* sysex, uint32_t length, uint8_t streamId, cmidi2_ump_handler_u128 sendUMP,+ void* context)+{+ uint32_t numPackets = cmidi2_ump_sysex8_get_num_packets(length);+ for (size_t p = 0; p < numPackets; p++)+ {+ uint64_t result1, result2;+ cmidi2_ump_sysex8_get_packet_of(group, streamId, length, sysex, p, &result1, &result2);+ void* retCode = sendUMP(result1, result2, p, context);+ if (retCode != 0)+ return retCode;+ }+ return NULL;+}++// 7.9 Mixed Data Set Message++static inline uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS)+{+ uint32_t radix = 14 * 0x10000;+ return numTotalBytesInMDS / radix + (numTotalBytesInMDS % radix ? 1 : 0);+}++// Returns -1 if input is out of range+static inline int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk)+{+ if (numTotalBytesinChunk > 14 * 65535)+ return -1;+ return numTotalBytesinChunk / 14 + (numTotalBytesinChunk % 14 ? 1 : 0);+}++static inline void cmidi2_ump_mds_get_header(+ uint8_t group, uint8_t mdsId, uint16_t numBytesInChunk, uint16_t numChunks,+ uint16_t chunkIndex, uint16_t manufacturerId, uint16_t deviceId, uint16_t subId,+ uint16_t subId2, uint64_t* result1, uint64_t* result2)+{+ uint8_t dst8[16];+ memset(dst8, 0, 16);++ dst8[0] = (CMIDI2_MESSAGE_TYPE_SYSEX8_MDS << 4) + (group & 0xF);+ dst8[1] = CMIDI2_MIXED_DATA_STATUS_HEADER + mdsId;+ *((uint16_t*)(void*)(dst8 + 2)) = numBytesInChunk;+ *((uint16_t*)(void*)(dst8 + 4)) = numChunks;+ *((uint16_t*)(void*)(dst8 + 6)) = chunkIndex;+ *((uint16_t*)(void*)(dst8 + 8)) = manufacturerId;+ *((uint16_t*)(void*)(dst8 + 10)) = deviceId;+ *((uint16_t*)(void*)(dst8 + 12)) = subId;+ *((uint16_t*)(void*)(dst8 + 14)) = subId2;++ *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+ if (result2)+ *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++// srcData points to exact start of the source data.+static inline void cmidi2_ump_mds_get_payload_of(+ uint8_t group, uint8_t mdsId, uint16_t numBytes, const void* srcData, uint64_t* result1,+ uint64_t* result2)+{+ uint8_t dst8[16];+ memset(dst8, 0, 16);+ const uint8_t* src8 = (const uint8_t*)srcData;++ dst8[0] = (CMIDI2_MESSAGE_TYPE_SYSEX8_MDS << 4) + (group & 0xF);+ dst8[1] = CMIDI2_MIXED_DATA_STATUS_PAYLOAD + mdsId;++ uint8_t radix = 14;+ uint8_t size = numBytes < radix ? numBytes % radix : radix;++ for (uint8_t i = 0; i < size; i++)+ dst8[i + 2] = src8[i];++ *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+ if (result2)+ *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++/* process() - more complicated function */+// This returns NULL for success, or anything else for failure.+typedef void* (*cmidi2_mds_handler)(+ uint64_t data1, uint64_t data2, size_t chunkId, size_t payloadId, void* context);++// This returns NULL for success, or anything else that `sendUMP` returns for failure.+static inline void* cmidi2_ump_mds_process(+ uint8_t group, uint8_t mdsId, void* data, uint32_t length, cmidi2_mds_handler sendUMP,+ void* context)+{+ int32_t numChunks = cmidi2_ump_mds_get_num_chunks(length);+ for (int c = 0; c < numChunks; c++)+ {+ int32_t maxChunkSize = 14 * 65535;+ int32_t chunkSize = c + 1 == numChunks ? (int32_t)(length % maxChunkSize) : maxChunkSize;+ int32_t numPayloads = cmidi2_ump_mds_get_num_payloads(chunkSize);+ for (int p = 0; p < numPayloads; p++)+ {+ uint64_t result1, result2;+ size_t offset = 14 * (65536 * c + p);+ cmidi2_ump_mds_get_payload_of(+ group, mdsId, chunkSize, (uint8_t*)data + offset, &result1, &result2);+ void* retCode = sendUMP(result1, result2, c, p, context);+ if (retCode)+ return retCode;+ }+ }+ return NULL;+}++// 7.5 Flex Data Message++static inline uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData)+{+ return numTotalBytesInFlexData / 12 + (numTotalBytesInFlexData % 12 ? 1 : 0);+}++static inline void cmidi2_ump_flex_data_complete_packet(+ uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode,+ uint32_t data1, uint32_t data2, uint32_t data3, uint64_t* result1, uint64_t* result2)+{+ uint8_t dst8[4];+ memset(dst8, 0, 4);+ dst8[0] = (CMIDI2_MESSAGE_TYPE_FLEX_DATA << 4) + (group & 0xF);+ dst8[1] = (CMIDI2_SYSEX_IN_ONE_UMP << 2) + ((addressing & 0x3) << 4) + (channel & 0xF);+ dst8[2] = statusBank;+ dst8[3] = statusCode;++ *result1 = ((uint64_t)cmidi2_ump_read_uint32_bytes_be(dst8) << 32) + data1;+ *result2 = ((uint64_t)data2 << 32) + data3;+}++static inline void cmidi2_ump_flex_data_get_packet_of(+ uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode,+ uint16_t numBytes, const void* srcData, int32_t currentPacket, uint64_t* result1,+ uint64_t* result2)+{+ uint8_t dst8[16];+ memset(dst8, 0, 16);+ const uint8_t* src8 = (const uint8_t*)srcData;++ dst8[0] = (CMIDI2_MESSAGE_TYPE_FLEX_DATA << 4) + (group & 0xF);++ const size_t radix = 12;+ enum cmidi2_sysex_status format;+ uint8_t size;+ if (numBytes <= radix)+ {+ format = CMIDI2_SYSEX_IN_ONE_UMP;+ size = numBytes; // single packet message+ }+ else if (currentPacket == 0)+ {+ format = CMIDI2_SYSEX_START;+ size = radix;+ }+ else+ {+ uint8_t isEnd = currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1;+ if (isEnd)+ {+ size = numBytes % radix ? numBytes % radix : radix;+ format = CMIDI2_SYSEX_END;+ }+ else+ {+ size = radix;+ format = CMIDI2_SYSEX_CONTINUE;+ }+ }+ dst8[1] = (format << 2) + ((addressing & 0x3) << 4) + (channel & 0xF);+ dst8[2] = statusBank;+ dst8[3] = statusCode;++ for (uint8_t i = 0; i < size; i++)+ dst8[i + 4] = src8[i];++ *result1 = cmidi2_ump_read_uint64_bytes_be(dst8);+ *result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);+}++/* process() - more complicated function */+// This returns NULL for success, or anything else for failure.+typedef void* (*cmidi2_flex_data_handler)(uint64_t data1, uint64_t data2, void* context);++// This returns NULL for success, or anything else that `sendUMP` returns for failure.+// `text` is usually a null-terminated text string, but it may contain `\0` as Melisma in lyricText. Hence we still need `length`.+static inline void* cmidi2_ump_flex_data_process(+ uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode,+ const char* text, uint32_t length, cmidi2_flex_data_handler sendUMP, void* context)+{+ int32_t numPackets = cmidi2_ump_flex_data_get_num_packets(length);+ for (int p = 0; p < numPackets; p++)+ {+ uint64_t result1, result2;+ cmidi2_ump_flex_data_get_packet_of(+ group, addressing, channel, statusBank, statusCode, length, text, p, &result1, &result2);+ void* retCode = sendUMP(result1, result2, context);+ if (retCode != 0)+ return retCode;+ }+ return NULL;+}++// individual flex data message generators++static inline void cmidi2_ump_flex_data_set_tempo_direct(+ uint8_t group, uint8_t channel, uint32_t tempoIn10NanosecondsPerQN, uint64_t* result1,+ uint64_t* result2)+{+ cmidi2_ump_flex_data_complete_packet(+ group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+ CMIDI2_FLEX_DATA_STATUS_SET_TEMPO, tempoIn10NanosecondsPerQN, 0, 0, result1, result2);+}++static inline void cmidi2_ump_flex_data_set_time_signature(+ uint8_t group, uint8_t channel, uint8_t numerator, uint8_t denominator,+ uint8_t numberOf32thNotes, uint64_t* result1, uint64_t* result2)+{+ cmidi2_ump_flex_data_complete_packet(+ group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+ CMIDI2_FLEX_DATA_STATUS_SET_TIME_SIGNATURE,+ (numerator << 24) + (denominator << 16) + (numberOf32thNotes << 8), 0, 0, result1, result2);+}++static inline void cmidi2_ump_flex_data_set_metronome(+ uint8_t group, uint8_t channel, uint8_t clocksPerPrimaryClick, uint8_t barAccent1,+ uint8_t barAccent2, uint8_t barAccent3, uint8_t subDivisionClicks1, uint8_t subDivisionClicks2,+ uint64_t* result1, uint64_t* result2)+{+ cmidi2_ump_flex_data_complete_packet(+ group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+ CMIDI2_FLEX_DATA_STATUS_SET_METRONOME,+ (clocksPerPrimaryClick << 24) + (barAccent1 << 16) + (barAccent2 << 8) + barAccent3,+ (subDivisionClicks1 << 24) + (subDivisionClicks2 << 16), 0, result1, result2);+}++static inline void cmidi2_ump_flex_data_set_key_signature(+ uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t tonicNote,+ uint64_t* result1, uint64_t* result2)+{+ cmidi2_ump_flex_data_complete_packet(+ group, addressing, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+ CMIDI2_FLEX_DATA_STATUS_SET_KEY_SIGNATURE, (sharpsFlats << 24) + (tonicNote << 16), 0, 0,+ result1, result2);+}++static inline void cmidi2_ump_flex_data_set_chord_name(+ uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t chordTonic,+ uint8_t chordType, uint8_t alter1Type, uint8_t alter1Degree, uint8_t alter2Type,+ uint8_t alter2Degree, uint8_t alter3Type, uint8_t alter3Degree, uint8_t alter4Type,+ uint8_t alter4Degree, uint8_t bassSharpsFlats, uint8_t bassNote, uint8_t bassChordType,+ uint8_t bassAlter1Type, uint8_t bassAlter1Degree, uint8_t bassAlter2Type,+ uint8_t bassAlter2Degree, uint64_t* result1, uint64_t* result2)+{+ cmidi2_ump_flex_data_complete_packet(+ group, addressing, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,+ CMIDI2_FLEX_DATA_STATUS_SET_CHORD_NAME,+ (sharpsFlats << 28) + (chordTonic << 24) + (chordType << 16) + (alter1Type << 12)+ + (alter1Degree << 8) + (alter2Type << 4) + alter2Degree,+ (alter3Type << 28) + (alter3Degree << 24) + (alter4Type << 20) + (alter4Degree << 16),+ (bassSharpsFlats << 28) + (bassNote << 24) + (bassChordType << 16) + (bassAlter1Type << 12)+ + (bassAlter1Degree << 8) + (bassAlter2Type << 4) + bassAlter2Degree,+ result1, result2);+}++// --------+// Strongly-typed(?) UMP.+// I kind of think those getters are overkill, so I would collect almost use `cmidi2_ump_get_xxx()`+// as those strongly typed functions, so that those who don't want them can safely ignore them.++typedef uint32_t cmidi2_ump;++static inline void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value)+{+ dst[0] = value;+}++static inline void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value)+{+ dst[0] = value >> 32;+ dst[1] = value & 0xFFFFFFFF;+}++static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2)+{+ dst[0] = value1 >> 32;+ dst[1] = value1 & 0xFFFFFFFF;+ dst[2] = value2 >> 32;+ dst[3] = value2 & 0xFFFFFFFF;+}++static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at)+{+ ump += at / 4;+ switch (at % 4)+ {+ case 0:+ return (*ump & 0xFF000000) >> 24;+ case 1:+ return (*ump & 0xFF0000) >> 16;+ case 2:+ return (*ump & 0xFF00) >> 8;+ case 3:+ return *ump & 0xFF;+ }+ return 0; // This is unexpected.+}++static inline uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump)+{+ return *ump >> 28;+}++static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump)+{+ switch (cmidi2_ump_get_message_type(ump))+ {+ case CMIDI2_MESSAGE_TYPE_UTILITY:+ case CMIDI2_MESSAGE_TYPE_SYSTEM:+ case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:+ return 4;+ case CMIDI2_MESSAGE_TYPE_SYSEX7:+ case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:+ return 8;+ case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:+ return 16;+ }+ return 0; // invalid+}++static inline uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump)+{+ return (*ump >> 24) & 0xF;+}++static inline uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump)+{+ return (*ump >> 16) & 0xF0;+}++static inline uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump)+{+ return (*ump >> 16) & 0xF;+}++static inline uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump)+{+ return *(ump + 1);+}++static inline uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);+}++static inline uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);+}++static inline uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump)+{+ return ((cmidi2_ump_get_byte_at(ump, 1) & 0xF) << 16) + (cmidi2_ump_get_byte_at(ump, 2) << 8)+ + cmidi2_ump_get_byte_at(ump, 3);+}++static inline uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}++static inline uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}++static inline uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2) + cmidi2_ump_get_byte_at(ump, 3) * 0x80;+}++static inline uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_channel(ump); // same bits+}++static inline uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 4) << 8) + (cmidi2_ump_get_byte_at(ump, 5));+}+static inline uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 6) << 8) + (cmidi2_ump_get_byte_at(ump, 7));+}+static inline uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+static inline uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+static inline uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+static inline uint32_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+// absolute RPN or relative RPN+static inline uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+// absolute RPN or relative RPN+static inline uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+// absolute RPN or relative RPN+static inline uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+// absolute NRPN or relative NRPN+static inline uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+// absolute NRPN or relative NRPN+static inline uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+// absolute NRPN or relative NRPN+static inline uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+static inline uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 4);+}+static inline uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 6));+}+static inline uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 7));+}+static inline uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+// either per-note or channel+static inline uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_32_to_64(ump);+}+static inline uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}++static inline uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_channel(ump); // same bits+}+static inline uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_byte_at(ump, 2);+}+static inline uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump)+{+ return cmidi2_ump_get_channel(ump); // same bits+}+static inline uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);+}+static inline uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 4) << 8) + cmidi2_ump_get_byte_at(ump, 5);+}+static inline uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 6) << 8) + cmidi2_ump_get_byte_at(ump, 7);+}+static inline uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 8) << 8) + cmidi2_ump_get_byte_at(ump, 9);+}+static inline uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 10) << 8) + cmidi2_ump_get_byte_at(ump, 11);+}+static inline uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 12) << 8) + cmidi2_ump_get_byte_at(ump, 13);+}+static inline uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump)+{+ return (cmidi2_ump_get_byte_at(ump, 14) << 8) + cmidi2_ump_get_byte_at(ump, 15);+}++// --------+// Realtime-safe SysEx8 binary reader+//+// The primary end-user facing function is `cmidi2_ump_get_sysex8_data()`, but the+// function signature needs some explanation as it requires some complicated processing.+//+// There can be multiple SysEx8 "streams" that simultaneously run within a UMP stream+// (e.g. sysex8 start packet for stream #1, sysex8 continue packet for stream #1,+// sysex8 start packet for stream #2, sysex8 continue packet for stream #1,+// sysex8 continue packet for stream #2, sysex8 complete packet for stream #3,+// sysex8 end packet for stream #2, sysex8 end packet for strean #1 ...).+//+// Those stream state must be preserved during UMP parsing. It is represented as+// `cmidi2_ump_binary_read_state` struct.+//+// There are handful of usage scenarios where we...+// - want to handle them all+// - want to handle only relevant ones+// - can control what we generate and determine that only one stream can appear so+// the context stream is always obvious+//+// To handle them all, we need "stream selector". There is a function type+// `cmidi2_ump_stream_selector_func`, and a function pointer is passed to the entry+// point `cmidi2_ump_get_sysex8_data()` function.+//+// Another control point is where we want to finish parsing sysex8. Whenever a relevant+// stream is parsed, `cmidi2_ump_get_syex8_data()` calls "continuity checker" function+// which is typed as `cmidi2_ump_binary_read_continuity_checker_func`.+// If this function returns true, then it completes parsing, returning the number of+// the parsed UMPs (in uint32_t length).+//+// See `testType5Messages_sysex8_reader_writer()` testcase for the actual usage example.+//++enum cmidi2_ump_binary_reader_result_code+{+ CMIDI2_BINARY_READER_RESULT_INCOMPLETE = 0,+ CMIDI2_BINARY_READER_RESULT_COMPLETE = 1,+ CMIDI2_BINARY_READER_RESULT_NO_SPACE = 2,+};++typedef struct cmidi2_ump_binary_read_state+{+ void* context;+ uint8_t* data;+ size_t dataCapacity;+ size_t dataSize;+ bool continueOnCompletion;+ enum cmidi2_ump_binary_reader_result_code resultCode;+} cmidi2_ump_binary_read_state;++static inline void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state)+{+ state->dataSize = 0;+ state->resultCode = CMIDI2_BINARY_READER_RESULT_INCOMPLETE;+}++static inline void cmidi2_ump_binary_read_state_init(+ cmidi2_ump_binary_read_state* state, void* context, uint8_t* dataBuffer, size_t dataCapacity,+ bool continueOnCompletion)+{+ state->context = context;+ state->data = dataBuffer;+ state->dataCapacity = dataCapacity;+ state->continueOnCompletion = continueOnCompletion;+ cmidi2_ump_binary_read_state_reset(state);+}++/// Binary stream selector for cmidi2_ump_get_sysex8_data() function.+/// If you do not support simultaneous streams, just return a reference to a fixed `cmidi2_ump_binary_read_state` instance.+/// If the targetSteramId does not indicate the streams the client handles, then it should return NULL.+/// Note that the resulting state must contain a non-null data and valid dataCapacity i.e. it must be already assigned.+/// Also note that the function implementation should not try to allocate a new instance of state+/// - otherwise it will break realtime safety.+typedef cmidi2_ump_binary_read_state* (*cmidi2_ump_stream_selector_func)(+ uint8_t targetStreamId, void* context);++/// Binary stream continuity checker for cmidi2_ump_get_sysex8_data() function.+/// If you do not support simultaneous streams, then it should reset stream state (dataSize etc.) when new stream starts.+/// Return true to continue parsing, otherwise return false. It is useful to determine whether it+/// should break or not when a stream is fully read (status code is 3 = CMIDI2_SYSEX_END).+/// `cmidi2_ump_get_sysex8_data()` can take NULL for this function pointer at `continuityChecker` argument,+/// then it falls back to the "default" behavior.+/// By default, it finishes parsing when a stream completed and `continueOnCompletion` was `false`.+typedef bool (*cmidi2_ump_binary_read_continuity_checker_func)(+ cmidi2_ump_binary_read_state* stream, cmidi2_ump* ump);++// it is a special copy function that only works with sysex8 memory state at `src` that can access beyond `sizeInBytes`.+static inline void cmidi2_internal_sysex8_copy_data_byte_swapping(+ uint8_t* dst, uint8_t srcHead, uint32_t* srcTail, size_t sizeInBytes)+{+ if (sizeInBytes == 0)+ return;++ // the first byte is at the lowest byte so it is always safe to copy as is.+ dst[0] = srcHead;+ uint8_t* d = dst + 1;+ uint32_t* s = srcTail;+ sizeInBytes--;++ // copy the rest+ size_t i = 0;+ while (i + 3 < sizeInBytes)+ {+ uint32_t i32 = *s;+ d[i++] = i32 >> 24;+ d[i++] = (i32 >> 16) & 0xFF;+ d[i++] = (i32 >> 8) & 0xFF;+ d[i++] = i32 & 0xFF;+ s++;+ }+ for (; i < sizeInBytes; i++)+ d[i] = s[i + 3 - i % 4];+}++/// Parse and store sysex8 binary, using some fine-tuned behavioral functions.+/// Return the number of 32-bit ints (number of `cmidi2_ump`s)+static inline size_t cmidi2_ump_get_sysex8_data(+ cmidi2_ump_stream_selector_func streamSelector, void* streamSelectorContext,+ cmidi2_ump_binary_read_continuity_checker_func continuityChecker, const cmidi2_ump* ump,+ const size_t umpCapacityInInt)+{++ cmidi2_ump *umpPtr = (cmidi2_ump*)ump, *umpEnd = (cmidi2_ump*)ump + umpCapacityInInt;+ for (; umpPtr < umpEnd; umpPtr += cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump))+ {+ if (cmidi2_ump_get_message_type(umpPtr) != CMIDI2_MESSAGE_TYPE_SYSEX8_MDS)+ continue;+ switch (cmidi2_ump_get_status_code(umpPtr))+ {+ case CMIDI2_SYSEX_IN_ONE_UMP:+ case CMIDI2_SYSEX_START:+ case CMIDI2_SYSEX_END:+ case CMIDI2_SYSEX_CONTINUE:+ break;+ default:+ continue; // the only expected value here is MDS (8 or 9)+ }++ cmidi2_ump_binary_read_state* state+ = streamSelector(cmidi2_ump_get_sysex8_stream_id(umpPtr), streamSelectorContext);+ if (state == NULL)+ continue;++ size_t copySize = cmidi2_ump_get_sysex8_num_bytes(umpPtr)+ - 1; // SysEx8 size field contains the size byte itself, hence -1.+ if (state->dataSize + copySize >= state->dataCapacity)+ {+ state->resultCode = CMIDI2_BINARY_READER_RESULT_NO_SPACE;+ return umpPtr - ump;+ }++ if (cmidi2_util_is_platform_little_endian())+ // We need to swap data along with byte order for resulting data...+ cmidi2_internal_sysex8_copy_data_byte_swapping(+ state->data + state->dataSize, (*umpPtr) & 0xFF, umpPtr + 1, copySize);+ else+ memcpy(state->data + state->dataSize, ((uint8_t*)(void*)umpPtr) + 3, copySize);++ state->dataSize += copySize;++ if (continuityChecker != NULL)+ if (continuityChecker(state, umpPtr))+ return umpPtr + cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump)+ - ump; // "break here" is indicated++ // otherwise default continuity checker+ switch (cmidi2_ump_get_status_code(umpPtr))+ {+ case CMIDI2_SYSEX_IN_ONE_UMP:+ case CMIDI2_SYSEX_END:+ state->resultCode = CMIDI2_BINARY_READER_RESULT_COMPLETE;+ if (state->continueOnCompletion)+ return umpPtr + cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump)+ - ump; // default "break here" condition.+ break;+ }+ }+ // finished parsing while no stream indicated "break here" for completion.+ return umpPtr - ump;+}++// --------+// sequence iterator++/* byte stream splitter */++static inline void* cmidi2_ump_sequence_next(const void* ptr)+{+ return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes(ptr));+}++// similar to LV2_ATOM Utilities API...+#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \+ for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \+ (iter) = (uint8_t*)cmidi2_ump_sequence_next(iter))++static inline void* cmidi2_ump_sequence_next_le(const void* ptr)+{+ return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_le(ptr));+}++#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \+ for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \+ (iter) = (uint8_t*)cmidi2_ump_sequence_next_le(iter))++static inline void* cmidi2_ump_sequence_next_be(const void* ptr)+{+ return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_be(ptr));+}++#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \+ for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \+ (iter) = (uint8_t*)cmidi2_ump_sequence_next_be(iter))++// --------+// MIDI CI support.++#define CMIDI2_CI_SUB_ID 0xD+#define CMIDI2_CI_SUB_ID_2_DISCOVERY_INQUIRY 0x70+#define CMIDI2_CI_SUB_ID_2_DISCOVERY_REPLY 0x71+#define CMIDI2_CI_SUB_ID_2_INVALIDATE_MUID 0x7E+#define CMIDI2_CI_SUB_ID_2_ACK 0x7F+#define CMIDI2_CI_SUB_ID_2_NAK 0x7F+#define CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_INQUIRY 0x10+#define CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_REPLY 0x11+#define CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL 0x12+#define CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_I2R 0x13+#define CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_R2I 0x14+#define CMIDI2_CI_SUB_ID_2_CONFIRM_NEW_PROTOCOL_ESTABLISHED 0x15+#define CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY 0x20+#define CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY_REPLY 0x21+#define CMIDI2_CI_SUB_ID_2_SET_PROFILE_ON 0x22+#define CMIDI2_CI_SUB_ID_2_SET_PROFILE_OFF 0x23+#define CMIDI2_CI_SUB_ID_2_PROFILE_ENABLED_REPORT 0x24+#define CMIDI2_CI_SUB_ID_2_PROFILE_DISABLED_REPORT 0x25+#define CMIDI2_CI_SUB_ID_2_PROFILE_SPECIFIC_DATA 0x2F+#define CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_INQUIRY 0x30+#define CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY 0x31+#define CMIDI2_CI_SUB_ID_2_PROPERTY_HAS_DATA 0x32+#define CMIDI2_CI_SUB_ID_2_PROPERTY_HAS_DATA_REPLY 0x33+#define CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA 0x34+#define CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA_REPLY 0x35+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA 0x36+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA_REPLY 0x37+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE 0x38+#define CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE_REPLY 0x39+#define CMIDI2_CI_SUB_ID_2_PROPERTY_NOTIFY 0x3F+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES 0x40+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES_REPLY 0x41+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT 0x42+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_REPLY 0x43+#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_END 0x44++#define CMIDI2_CI_PROTOCOL_NEGOTIATION_SUPPORTED 2+#define CMIDI2_CI_PROFILE_CONFIGURATION_SUPPORTED 4+#define CMIDI2_CI_PROPERTY_EXCHANGE_SUPPORTED 8++#define CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK 0x7F++typedef struct+{+ uint8_t type;+ uint8_t version;+ uint8_t extensions;+ uint8_t reserved1;+ uint8_t reserved2;+} cmidi2_ci_protocol_type_info;++typedef struct+{+ uint8_t fixed_7e; // 0x7E+ uint8_t bank;+ uint8_t number;+ uint8_t version;+ uint8_t level;+} cmidi2_profile_id;++// Assumes the input value is already 7-bit encoded if required.+static inline void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v)+{+ buf[0] = v & 0xFF;+ buf[1] = (v >> 8) & 0xFF;+}++// Assumes the input value is already 7-bit encoded if required.+static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v)+{+ buf[0] = v & 0xFF;+ buf[1] = (v >> 8) & 0xFF;+ buf[2] = (v >> 16) & 0xFF;+ buf[3] = (v >> 24) & 0xFF;+}++static inline void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v)+{+ buf[0] = v & 0x7F;+ buf[1] = (v >> 7) & 0x7F;+}++static inline void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v)+{+ buf[0] = v & 0x7F;+ buf[1] = (v >> 7) & 0x7F;+ buf[2] = (v >> 14) & 0x7F;+}++static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v)+{+ buf[0] = v & 0x7F;+ buf[1] = (v >> 7) & 0x7F;+ buf[2] = (v >> 14) & 0x7F;+ buf[3] = (v >> 21) & 0x7F;+}++static inline void cmidi2_ci_message_common(+ uint8_t* buf, uint8_t destination, uint8_t sysexSubId2, uint8_t versionAndFormat,+ uint32_t sourceMUID, uint32_t destinationMUID)+{+ buf[0] = 0x7E;+ buf[1] = destination;+ buf[2] = CMIDI2_CI_SUB_ID;+ buf[3] = sysexSubId2;+ buf[4] = versionAndFormat;+ cmidi2_ci_direct_uint32_at(buf + 5, sourceMUID);+ cmidi2_ci_direct_uint32_at(buf + 9, destinationMUID);+}++// Discovery++static inline void cmidi2_ci_discovery_common(+ uint8_t* buf, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint32_t deviceManufacturer3Bytes, uint16_t deviceFamily,+ uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported,+ uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId)+{+ cmidi2_ci_message_common(+ buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, sysexSubId2, versionAndFormat, sourceMUID,+ destinationMUID);+ cmidi2_ci_direct_uint32_at(+ buf + 13,+ deviceManufacturer3Bytes); // the last byte is extraneous, but will be overwritten next.+ cmidi2_ci_direct_uint16_at(buf + 16, deviceFamily);+ cmidi2_ci_direct_uint16_at(buf + 18, deviceFamilyModelNumber);+ // LAMESPEC: Software Revision Level does not mention in which endianness this field is stored.+ cmidi2_ci_direct_uint32_at(buf + 20, softwareRevisionLevel);+ buf[24] = ciCategorySupported;+ cmidi2_ci_direct_uint32_at(buf + 25, receivableMaxSysExSize);+ buf[29] = initiatorOutputPathId;+}++static inline void cmidi2_ci_discovery(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t deviceManufacturer,+ uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel,+ uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId)+{+ cmidi2_ci_discovery_common(+ buf, CMIDI2_CI_SUB_ID_2_DISCOVERY_INQUIRY, versionAndFormat, sourceMUID, 0x7F7F7F7F,+ deviceManufacturer, deviceFamily, deviceFamilyModelNumber, softwareRevisionLevel,+ ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId);+}++static inline void cmidi2_ci_discovery_reply(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber,+ uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize,+ uint8_t initiatorOutputPathId, uint8_t functionBlockOr7Fh)+{+ cmidi2_ci_discovery_common(+ buf, CMIDI2_CI_SUB_ID_2_DISCOVERY_REPLY, versionAndFormat, sourceMUID, destinationMUID,+ deviceManufacturer, deviceFamily, deviceFamilyModelNumber, softwareRevisionLevel,+ ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId);+ buf[30] = functionBlockOr7Fh;+}++static inline void cmidi2_ci_discovery_invalidate_muid(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t targetMUID)+{+ cmidi2_ci_message_common(+ buf, 0x7F, CMIDI2_CI_SUB_ID_2_INVALIDATE_MUID, versionAndFormat, sourceMUID, 0x7F7F7F7F);+ cmidi2_ci_direct_uint32_at(buf + 13, targetMUID);+}++static inline void cmidi2_ci_ack_nak_common(+ uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t statusCode,+ uint8_t statusData, uint8_t* details5Bytes, uint16_t messageLength, const char* messageText)+{+ cmidi2_ci_message_common(+ buf, deviceId, CMIDI2_CI_SUB_ID_2_ACK, versionAndFormat, sourceMUID, destinationMUID);+ buf[13] = originalTransactionSubID2Class;+ buf[14] = statusCode;+ buf[15] = statusData;+ memcpy(buf + 16, details5Bytes, 5);+ cmidi2_ci_direct_uint16_at(buf + 21, messageLength);+ memcpy(buf + 23, messageText, messageLength);+}++static inline void cmidi2_ci_ack(+ uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t ackStatusCode,+ uint8_t ackStatusData, uint8_t* ackDetails5Bytes, uint16_t messageLength,+ const char* messageText)+{+ cmidi2_ci_ack_nak_common(+ buf, deviceId, versionAndFormat, sourceMUID, destinationMUID, originalTransactionSubID2Class,+ ackStatusCode, ackStatusData, ackDetails5Bytes, messageLength, messageText);+}++static inline void cmidi2_ci_nak(+ uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t nakStatusCode,+ uint8_t nakStatusData, uint8_t* nakDetails5Bytes, uint16_t messageLength,+ const char* messageText)+{+ cmidi2_ci_ack_nak_common(+ buf, deviceId, versionAndFormat, sourceMUID, destinationMUID, originalTransactionSubID2Class,+ nakStatusCode, nakStatusData, nakDetails5Bytes, messageLength, messageText);+}++// Protocol Negotiation+// Note that it was removed in MIDI 2.0 specification June 2023 Updates (MIDI-CI 1.2).++static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info)+{+ buf[0] = info.type;+ buf[1] = info.version;+ buf[2] = info.extensions;+ buf[3] = info.reserved1;+ buf[4] = info.reserved2;+}++static inline void cmidi2_ci_protocols(+ uint8_t* buf, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes)+{+ buf[0] = numSupportedProtocols;+ for (int i = 0; i < numSupportedProtocols; i++)+ cmidi2_ci_protocol_info(buf + 1 + i * 5, protocolTypes[i]);+}++static inline void cmidi2_ci_protocol_negotiation(+ uint8_t* buf, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t authorityLevel, uint8_t numSupportedProtocols,+ cmidi2_ci_protocol_type_info* protocolTypes)+{+ cmidi2_ci_message_common(+ buf, 0x7F,+ isReply ? CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_REPLY+ : CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_INQUIRY,+ 1, sourceMUID, destinationMUID);+ buf[13] = authorityLevel;+ cmidi2_ci_protocols(buf + 14, numSupportedProtocols, protocolTypes);+}++static inline void cmidi2_ci_protocol_set(+ uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel,+ cmidi2_ci_protocol_type_info newProtocolType)+{+ cmidi2_ci_message_common(+ buf, 0x7F, CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL, 1, sourceMUID, destinationMUID);+ buf[13] = authorityLevel;+ cmidi2_ci_protocol_info(buf + 14, newProtocolType);+}++static inline void cmidi2_ci_protocol_test(+ uint8_t* buf, bool isInitiatorToResponder, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t authorityLevel, uint8_t* testData48Bytes)+{+ cmidi2_ci_message_common(+ buf, 0x7F,+ isInitiatorToResponder ? CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_I2R+ : CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_R2I,+ 1, sourceMUID, destinationMUID);+ buf[13] = authorityLevel;+ memcpy(buf + 14, testData48Bytes, 48);+}++static inline void cmidi2_ci_protocol_confirm_established(+ uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel)+{+ cmidi2_ci_message_common(+ buf, 0x7F, CMIDI2_CI_SUB_ID_2_CONFIRM_NEW_PROTOCOL_ESTABLISHED, 1, sourceMUID,+ destinationMUID);+ buf[13] = authorityLevel;+}++static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length)+{+ return (length != 19 || buf[0] != 0x7E || buf[1] != 0x7F || buf[2] != CMIDI2_CI_SUB_ID+ || buf[3] != CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL || buf[4] != 1)+ ? 0+ : buf[14];+}++// Profile Configuration++static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info)+{+ buf[0] = info.fixed_7e;+ buf[1] = info.bank;+ buf[2] = info.number;+ buf[3] = info.version;+ buf[4] = info.level;+}++static inline void cmidi2_ci_profile_inquiry(+ uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID)+{+ cmidi2_ci_message_common(+ buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY, 1, sourceMUID, destinationMUID);+}++static inline void cmidi2_ci_profile_inquiry_reply(+ uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t numEnabledProfiles, cmidi2_profile_id* enabledProfiles, uint8_t numDisabledProfiles,+ cmidi2_profile_id* disabledProfiles)+{+ cmidi2_ci_message_common(+ buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY_REPLY, 1, sourceMUID, destinationMUID);+ buf[13] = numEnabledProfiles;+ for (int i = 0; i < numEnabledProfiles; i++)+ cmidi2_ci_profile(buf + 14 + i * 5, enabledProfiles[i]);+ uint32_t pos = 14 + numEnabledProfiles * 5;+ buf[pos++] = numDisabledProfiles;+ for (int i = 0; i < numDisabledProfiles; i++)+ cmidi2_ci_profile(buf + pos + i * 5, disabledProfiles[i]);+}++static inline void cmidi2_ci_profile_set(+ uint8_t* buf, uint8_t destination, bool turnOn, uint32_t sourceMUID, uint32_t destinationMUID,+ cmidi2_profile_id profile)+{+ cmidi2_ci_message_common(+ buf, destination,+ turnOn ? CMIDI2_CI_SUB_ID_2_SET_PROFILE_ON : CMIDI2_CI_SUB_ID_2_SET_PROFILE_OFF, 1,+ sourceMUID, destinationMUID);+ cmidi2_ci_profile(buf + 13, profile);+}++static inline void cmidi2_ci_profile_report(+ uint8_t* buf, uint8_t source, bool isEnabledReport, uint32_t sourceMUID,+ cmidi2_profile_id profile)+{+ cmidi2_ci_message_common(+ buf, source,+ isEnabledReport ? CMIDI2_CI_SUB_ID_2_PROFILE_ENABLED_REPORT+ : CMIDI2_CI_SUB_ID_2_PROFILE_DISABLED_REPORT,+ 1, sourceMUID, 0x7F7F7F7F);+ cmidi2_ci_profile(buf + 13, profile);+}++static inline void cmidi2_ci_profile_specific_data(+ uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID,+ cmidi2_profile_id profile, uint32_t dataSize, void* data)+{+ cmidi2_ci_message_common(+ buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_SPECIFIC_DATA, 1, sourceMUID, destinationMUID);+ cmidi2_ci_profile(buf + 13, profile);+ cmidi2_ci_direct_uint32_at(buf + 18, dataSize);+ memcpy(buf + 22, data, dataSize);+}++// Property Exchange++static inline void cmidi2_ci_property_get_capabilities(+ uint8_t* buf, uint8_t destination, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t maxSupportedRequests)+{+ cmidi2_ci_message_common(+ buf, destination,+ isReply ? CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY+ : CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_INQUIRY,+ 1, sourceMUID, destinationMUID);+ buf[13] = maxSupportedRequests;+}++// common to all of: has data & reply, get data & reply, set data & reply, subscribe & reply, notify+static inline void cmidi2_ci_property_common(+ uint8_t* buf, uint8_t destination, uint8_t messageTypeSubId2, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t requestId, uint16_t headerSize, void* header,+ uint16_t numChunks, uint16_t chunkIndex, uint16_t dataSize, void* data)+{+ cmidi2_ci_message_common(buf, destination, messageTypeSubId2, 1, sourceMUID, destinationMUID);+ buf[13] = requestId;+ cmidi2_ci_direct_uint16_at(buf + 14, headerSize);+ memcpy(buf + 16, header, headerSize);+ cmidi2_ci_direct_uint16_at(buf + 16 + headerSize, numChunks);+ cmidi2_ci_direct_uint16_at(buf + 18 + headerSize, chunkIndex);+ cmidi2_ci_direct_uint16_at(buf + 20 + headerSize, dataSize);+ memcpy(buf + 22 + headerSize, data, dataSize);+}++static inline void cmidi2_ci_property_get_capabilities_reply(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t maxSupportedRequests, uint8_t peMajorVersion, uint8_t peMinorVersion)+{+ cmidi2_ci_message_common(+ buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK,+ CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY, versionAndFormat, sourceMUID,+ destinationMUID);+ buf[13] = maxSupportedRequests;+ buf[14] = peMajorVersion;+ buf[15] = peMinorVersion;+}++static inline void cmidi2_ci_property_data_common(+ uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData,+ uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength,+ const char* propertyData)+{+ cmidi2_ci_message_common(+ buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, subId2, versionAndFormat, sourceMUID,+ destinationMUID);+ buf[13] = requestId;+ buf[14] = headerSize % 0xFF;+ buf[15] = headerSize / 0xFF;+ memcpy(buf + 16, headerData, headerSize);+ cmidi2_ci_direct_uint16_at(buf + headerSize + 16, numChunksInMessage);+ cmidi2_ci_direct_uint16_at(buf + headerSize + 18, currentChunk);+ cmidi2_ci_direct_uint16_at(buf + headerSize + 20, propertyDataLength);+ if (propertyData)+ memcpy(buf + headerSize + 22, propertyData, propertyDataLength);+}++static inline void cmidi2_ci_property_get_data(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t requestId, size_t headerSize, const uint8_t* headerData)+{+ cmidi2_ci_property_data_common(+ buf, CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA, versionAndFormat, sourceMUID, destinationMUID,+ requestId, headerSize, headerData, 1, 1, 0, NULL);+}++static inline void cmidi2_ci_property_get_data_reply(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,+ uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)+{+ cmidi2_ci_property_data_common(+ buf, CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA_REPLY, versionAndFormat, sourceMUID,+ destinationMUID, requestId, headerSize, headerData, numChunksInMessage, currentChunk,+ propertyDataLength, propertyData);+}++static inline void cmidi2_ci_property_set_data(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,+ uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)+{+ cmidi2_ci_property_data_common(+ buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA, versionAndFormat, sourceMUID, destinationMUID,+ requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,+ propertyData);+}++static inline void cmidi2_ci_property_set_data_reply(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t requestId, size_t headerSize, const uint8_t* headerData)+{+ cmidi2_ci_property_data_common(+ buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA_REPLY, versionAndFormat, sourceMUID,+ destinationMUID, requestId, headerSize, headerData, 1, 1, 0, NULL);+}++static inline void cmidi2_ci_property_subscribe(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,+ uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)+{+ cmidi2_ci_property_data_common(+ buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE, versionAndFormat, sourceMUID, destinationMUID,+ requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,+ propertyData);+}++static inline void cmidi2_ci_property_subscribe_reply(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,+ uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)+{+ cmidi2_ci_property_data_common(+ buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE_REPLY, versionAndFormat, sourceMUID,+ destinationMUID, requestId, headerSize, headerData, numChunksInMessage, currentChunk,+ propertyDataLength, propertyData);+}++static inline void cmidi2_ci_property_notify(+ uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,+ uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,+ uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)+{+ cmidi2_ci_property_data_common(+ buf, CMIDI2_CI_SUB_ID_2_PROPERTY_NOTIFY, versionAndFormat, sourceMUID, destinationMUID,+ requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,+ propertyData);+}++// Process Inquiry++static inline void cmidi2_ci_process_get_capabilities(+ uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID)+{+ (void)subId2;+ cmidi2_ci_message_common(+ buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES,+ versionAndFormat, sourceMUID, destinationMUID);+}++static inline void cmidi2_ci_process_get_capabilities_reply(+ uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t processInquirySupportedFeatures)+{+ (void)subId2;+ cmidi2_ci_message_common(+ buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK,+ CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES_REPLY, versionAndFormat, sourceMUID,+ destinationMUID);+ buf[13] = processInquirySupportedFeatures;+}++static inline void cmidi2_ci_process_midi_report_common(+ uint8_t* buf, uint8_t deviceId, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages,+ uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages)+{+ (void)subId2;+ cmidi2_ci_message_common(+ buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES, versionAndFormat, sourceMUID,+ destinationMUID);+ buf[13] = messageDataControl;+ buf[14] = requestedSystemMessages;+ buf[15] = 0; // reserved+ buf[16] = requestedChannelControllerMessages;+ buf[17] = requestedNoteDataMessages;+}++static inline void cmidi2_ci_process_get_midi_report(+ uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages,+ uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages)+{+ cmidi2_ci_process_midi_report_common(+ buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT, versionAndFormat, sourceMUID,+ destinationMUID, messageDataControl, requestedSystemMessages,+ requestedChannelControllerMessages, requestedNoteDataMessages);+}++static inline void cmidi2_ci_process_get_midi_report_reply(+ uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages,+ uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages)+{+ cmidi2_ci_process_midi_report_common(+ buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_REPLY, versionAndFormat,+ sourceMUID, destinationMUID, messageDataControl, requestedSystemMessages,+ requestedChannelControllerMessages, requestedNoteDataMessages);+}++static inline void cmidi2_ci_process_get_midi_report_end(+ uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,+ uint32_t destinationMUID)+{+ cmidi2_ci_message_common(+ buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_END, versionAndFormat, sourceMUID,+ destinationMUID);+}++// Miscellaneous MIDI Utilities++/** Encodes `value` into `bytes` stream. Returns the length of the encoded value in bytes. */+static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value)+{+ uint8_t pos = 0;+ for (;; pos++)+ {+ bytes[pos] = value % 0x80;+ if (value >= 0x80)+ {+ value /= 0x80;+ bytes[pos] |= 0x80;+ }+ else+ return pos + 1;+ }+}++/** Returns the length of `value` when it would be encoded into a byte stream. */+static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value)+{+ for (uint8_t ret = 1;; ret++)+ {+ if (value >= 0x80)+ value /= 0x80;+ else+ return ret;+ }+ return 0;+}++/** Returns the 7-bit encoded value in `bytes` stream of `length` bytes. */+static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length)+{+ uint8_t* start = bytes;+ uint32_t value = 0;+ for (int digits = 0;; digits++)+ {+ if (bytes >= start + length)+ break;+ value += (0x7F & (*bytes)) << (digits * 7);+ if (*bytes < 0x80)+ break;+ bytes++;+ }+ bytes++;+ return value;+}++static inline uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length)+{+ uint32_t metaLength;+ uint8_t* start = bytes;+ uint8_t* end = bytes + length;+ switch (bytes[0])+ {+ case 0xF0:+ for (bytes++; bytes < end; bytes++)+ if (*bytes == 0xF7)+ break;+ bytes++;+ break;+ case 0xFF:+ bytes++;+ metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes);+ bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength);+ break;+ default:+ switch (bytes[0] & 0xF0)+ {+ case 0xC0:+ case 0xD0:+ bytes += 2;+ break;+ default:+ bytes += 3;+ break;+ }+ break;+ }+ return bytes - start;+}++// MIDI1 to UMP Translator++/* Should we define some structs like this to support conversion from MIDI1 UMP to MIDI2 UMP and complicate the API?+ I'm not sure if there are enough need, and it's better to avoid another cmidi2_convert_midi1_to_ump() (200-ish LoC).++typedef struct cmidi2_midi1_sequence_midi1_bytes {+ // input MIDI1 messages or SMF events.+ uint8_t *midi1;+ // size of the input stream to process in bytes+ size_t midi1_num_bytes;+ // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the MIDI1 stream.+ size_t midi1_proceeded_bytes;+} cmidi2_midi1_sequence_midi1_bytes;++typedef struct cmidi2_midi1_sequence_umps {+ // MIDI1 UMP stream.+ cmidi2_ump* ump;+ // size (capacity) of the output stream in bytes+ size_t ump_num_bytes;+ // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the UMP stream.+ size_t ump_proceeded_bytes;+} cmidi2_midi1_sequence_ump;+*/++enum cmidi2_translator_endianness+{+ CMIDI2_TRANSLATOR_DEFAULT_ENDIAN,+ CMIDI2_TRANSLATOR_BIG_ENDIAN,+ CMIDI2_TRANSLATOR_LITTLE_ENDIAN+};++// Conversion from MIDI1 message bytes or SMF event list to MIDI2 UMP stream+//+// The conversion requires some preserved context e.g. RPN/NRPN/DTE+// MSB/LSB, so we take this struct.+typedef struct cmidi2_midi_conversion_context+{+ // When it is true, it means the input MIDI1 stream contains delta time.+ // TODO: implement support for it+ bool is_midi1_smf;+ // Context tempo as in SMF specification defines.+ // If the MIDI1 stream is SMF and non-SMPTE delta time is used, then we have to calculate+ // the actual delta time length in SMPTE (then to JR timestamp).+ // TODO: implement support for it+ int32_t tempo;+ // input MIDI1 messages or SMF events.+ uint8_t* midi1;+ // size of the input stream to process in bytes+ size_t midi1_num_bytes;+ // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the MIDI1 stream.+ size_t midi1_proceeded_bytes;+ // output UMP stream.+ cmidi2_ump* ump;+ // size (capacity) of the output stream in bytes+ size_t ump_num_bytes;+ // it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the UMP stream.+ size_t ump_proceeded_bytes;+ // DTE conversion target.+ // cmidi2_convert_midi1_messages_to_ump() will return *_INVALID_RPN or *_INVALID_NRPN+ // for such invalid sequences, and to report that correctly we need to preserve CC status.+ // They are initialized to 0x8080 that implies both bank (MSB) and index (LSB) are invalid (> 0x7F).+ // When they are assigned valid values, then (context_[n]rpn & 0x8080) will become 0.+ // When cmidi2_convert_midi1_messages_to_ump() encountered DTE LSB, they are consumed+ // and reset to the initial value (0x8080).+ int32_t context_rpn;+ int32_t context_nrpn;+ int32_t context_dte;+ // MIDI 2.0 Defalult Translation (UMP specification Appendix D.3) accepts only DTE LSB+ // as the conversion terminator, but cmidi2 allows DTE LSB to come first,+ // if this flag is enabled.+ bool allow_reordered_dte;+ // Bank Select CC is preserved for the next program change.+ // The initial value is 0x8080, same as RPN/NRPN/DTE.+ // After program change is set, it is reset to the initial value.+ uint32_t context_bank;+ // Group can be specified.+ uint8_t group;+ // Destination protocol: can be MIDI1 UMP or MIDI2 UMP.+ enum cmidi2_ci_protocol_values midi_protocol;+ // Sysex conversion can be done to sysex8+ bool use_sysex8;+ // Determine whether delta time is skipped or not (inserted like SMF) in the output MIDI1 stream.+ bool skip_delta_time;+ // UMP serialization endianness+ int32_t ump_serialization_endianness;+} cmidi2_midi_conversion_context;++enum cmidi2_midi_conversion_result+{+ CMIDI2_CONVERSION_RESULT_OK = 0,+ CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE = 1,+ CMIDI2_CONVERSION_RESULT_INVALID_SYSEX = 0x10,+ CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE = 0x11,+ CMIDI2_CONVERSION_RESULT_INVALID_STATUS = 0x13,+ CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7 = 0x20,+ CMIDI2_CONVERSION_RESULT_INVALID_INPUT = 0x40,+};++static inline void+cmidi2_midi_conversion_context_initialize(cmidi2_midi_conversion_context* context)+{+ context->is_midi1_smf = false;+ context->tempo = 500000;+ context->midi1 = NULL;+ context->midi1_num_bytes = 0;+ context->midi1_proceeded_bytes = 0;+ context->ump = NULL;+ context->ump_num_bytes = 0;+ context->ump_proceeded_bytes = 0;+ context->context_rpn = 0x8080;+ context->context_nrpn = 0x8080;+ context->context_dte = 0x8080;+ context->context_bank = 0x8080;+ context->allow_reordered_dte = false;+ context->group = 0;+ context->midi_protocol = CMIDI2_PROTOCOL_TYPE_MIDI2;+ context->use_sysex8 = false;+ context->skip_delta_time = false;+ context->ump_serialization_endianness = CMIDI2_TRANSLATOR_DEFAULT_ENDIAN;+}++typedef struct cmidi2_convert_sysex_context+{+ cmidi2_midi_conversion_context* conversion_context;+ size_t dst_offset;+} cmidi2_convert_sysex_context;++static inline void*+cmidi2_internal_convert_add_midi1_sysex7_ump_to_list(uint64_t data, void* context)+{+ cmidi2_convert_sysex_context* s7ctx = (cmidi2_convert_sysex_context*)context;+ s7ctx->conversion_context->ump[s7ctx->dst_offset] = data >> 32;+ s7ctx->conversion_context->ump[s7ctx->dst_offset + 1] = data & 0xFFFFFFFF;+ s7ctx->conversion_context->ump_proceeded_bytes += 2;+ return NULL;+}++static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list(+ uint64_t data1, uint64_t data2, size_t index, void* context)+{+ (void)index;+ cmidi2_convert_sysex_context* s8ctx = (cmidi2_convert_sysex_context*)context;+ s8ctx->conversion_context->ump[s8ctx->dst_offset] = data1 >> 32;+ s8ctx->conversion_context->ump[s8ctx->dst_offset + 1] = data1 & 0xFFFFFFFF;+ s8ctx->conversion_context->ump[s8ctx->dst_offset + 2] = data2 >> 32;+ s8ctx->conversion_context->ump[s8ctx->dst_offset + 3] = data2 & 0xFFFFFFFF;+ s8ctx->conversion_context->ump_proceeded_bytes += 4;+ return NULL;+}++static inline uint64_t+cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context, uint8_t channel)+{+ bool isRpn = (context->context_rpn & 0x8080) == 0;+ uint8_t msb = (isRpn ? context->context_rpn : context->context_nrpn) >> 8;+ uint8_t lsb = (isRpn ? context->context_rpn : context->context_nrpn) & 0xFF;+ int32_t data = (context->context_dte >> 8 << 25) + ((context->context_dte & 0x7F) << 18);+ // reset RPN/NRPN/DTE status to the initial values.+ context->context_rpn = 0x8080;+ context->context_nrpn = 0x8080;+ context->context_dte = 0x8080;+ return isRpn ? cmidi2_ump_midi2_rpn(context->group, channel, msb, lsb, data)+ : cmidi2_ump_midi2_nrpn(context->group, channel, msb, lsb, data);+}++static inline uint32_t cmidi2_internal_swap_endian(uint32_t v)+{+ return ((v & 0xFF) << 24) + (((v >> 8) & 0xFF) << 16) + (((v >> 16) & 0xFF) << 8)+ + ((v >> 24) & 0xFF);+}++/** converts MIDI1 bytestream which can contain deltaTime in SMF, to MIDI2 UMP stream.+ * The resulting stream is native endianness.+ */+static enum cmidi2_midi_conversion_result+cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context)+{+ uint8_t* dst = (uint8_t*)context->ump;+ size_t sLen = context->midi1_num_bytes;+ size_t dLen = context->ump_num_bytes;+ uint8_t* sIdx = (uint8_t*)&context->midi1_proceeded_bytes;+ uint8_t* dIdx = (uint8_t*)&context->ump_proceeded_bytes;++ while (*sIdx < sLen)+ {+ // FIXME: implement deltaTime to JR Timestamp conversion.++ if (context->midi1[*sIdx] == 0xF0)+ {+ // sysex+ uint8_t* f7+ = (uint8_t*)memchr(context->midi1 + *sIdx, 0xF7, context->midi1_num_bytes - *sIdx);+ if (f7 == NULL)+ {+ return CMIDI2_CONVERSION_RESULT_INVALID_SYSEX; // error+ }+ size_t sysexSize = f7 - context->midi1 - *sIdx - 1; // excluding 0xF7+ size_t numPackets = context->use_sysex8 ? cmidi2_ump_sysex8_get_num_packets(sysexSize)+ : cmidi2_ump_sysex7_get_num_packets(sysexSize);+ if (dLen - *dIdx < numPackets)+ return CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE;+ cmidi2_convert_sysex_context sysExCtx;+ sysExCtx.conversion_context = context;+ sysExCtx.dst_offset = *dIdx;+ if (context->use_sysex8)+ {+ // ignoring the return code as it never returns non-NULL... (size is already verified)+ cmidi2_ump_sysex8_process(+ context->group, context->midi1 + *sIdx, sysexSize, 0,+ cmidi2_internal_convert_add_midi1_sysex8_ump_to_list, &sysExCtx);+ }+ else+ {+ // ignoring the return code as it never returns non-NULL... (size is already verified)+ cmidi2_ump_sysex7_process(+ context->group, context->midi1 + *sIdx,+ cmidi2_internal_convert_add_midi1_sysex7_ump_to_list, &sysExCtx);+ }+ *sIdx += sysexSize + 1; // +1 for 0xF7+ }+ else+ {+ // fixed sized message+ size_t remaining = sLen - *sIdx;+ size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, remaining);+ if (len > remaining)+ return CMIDI2_CONVERSION_RESULT_INVALID_INPUT;++ uint8_t byte2 = context->midi1[*sIdx + 1];+ uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0;+ uint8_t channel = context->midi1[*sIdx] & 0xF;+ if (context->midi_protocol == CMIDI2_PROTOCOL_TYPE_MIDI1)+ {+ // generate MIDI1 UMPs+ dst[*dIdx] = cmidi2_ump_midi1_message(+ context->group, context->midi1[*sIdx] & 0xF0, channel, byte2, byte3);+ *sIdx += len;+ *dIdx += 4;+ }+ else+ {+ // generate MIDI2 UMPs+ uint64_t m2;+ const int8_t NO_ATTRIBUTE_TYPE = 0;+ const int16_t NO_ATTRIBUTE_DATA = 0;+ bool bankValid, bankMsbValid, bankLsbValid;+ bool skipEmitUmp = false;+ switch (context->midi1[*sIdx] & 0xF0)+ {+ case CMIDI2_STATUS_NOTE_OFF:+ m2 = cmidi2_ump_midi2_note_off(+ context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA);+ break;+ case CMIDI2_STATUS_NOTE_ON:+ m2 = cmidi2_ump_midi2_note_on(+ context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA);+ break;+ case CMIDI2_STATUS_PAF:+ m2 = cmidi2_ump_midi2_paf(context->group, channel, byte2, byte3 << 25);+ break;+ case CMIDI2_STATUS_CC:+ switch (byte2)+ {+ case CMIDI2_CC_RPN_MSB:+ context->context_rpn = (context->context_rpn & 0xFF) | (byte3 << 8);+ skipEmitUmp = true;+ break;+ case CMIDI2_CC_RPN_LSB:+ context->context_rpn = (context->context_rpn & 0xFF00) | byte3;+ skipEmitUmp = true;+ break;+ case CMIDI2_CC_NRPN_MSB:+ context->context_nrpn = (context->context_nrpn & 0xFF) | byte3 << 8;+ skipEmitUmp = true;+ break;+ case CMIDI2_CC_NRPN_LSB:+ context->context_nrpn = (context->context_nrpn & 0xFF00) | byte3;+ skipEmitUmp = true;+ break;+ case CMIDI2_CC_DTE_MSB:+ context->context_dte = (context->context_dte & 0xFF) | (byte3 << 8);++ if (context->allow_reordered_dte && (context->context_dte & 0x8080) == 0)+ m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel);+ else+ skipEmitUmp = true;++ break;+ case CMIDI2_CC_DTE_LSB:+ context->context_dte = (context->context_dte & 0xFF00) | byte3;++ if ((context->context_dte & 0x8000) && !context->allow_reordered_dte)+ return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;+ if ((context->context_rpn & 0x8080) && (context->context_nrpn & 0x8080))+ return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;+ m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel);++ break;+ case CMIDI2_CC_BANK_SELECT:+ context->context_bank = (context->context_bank & 0xFF) | (byte3 << 8);+ skipEmitUmp = true;+ break;+ case CMIDI2_CC_BANK_SELECT_LSB:+ context->context_bank = (context->context_bank & 0xFF00) | byte3;+ skipEmitUmp = true;+ break;+ default:+ m2 = cmidi2_ump_midi2_cc(context->group, channel, byte2, byte3 << 25);+ break;+ }+ break;+ case CMIDI2_STATUS_PROGRAM:+ bankMsbValid = (context->context_bank & 0x8000) == 0;+ bankLsbValid = (context->context_bank & 0x80) == 0;+ bankValid = bankMsbValid || bankLsbValid;+ m2 = cmidi2_ump_midi2_program(+ context->group, channel,+ bankValid ? CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID+ : CMIDI2_PROGRAM_CHANGE_OPTION_NONE,+ byte2, bankMsbValid ? context->context_bank >> 8 : 0,+ bankLsbValid ? context->context_bank & 0x7F : 0);+ context->context_bank = 0x8080;+ break;+ case CMIDI2_STATUS_CAF:+ m2 = cmidi2_ump_midi2_caf(context->group, channel, byte2 << 25);+ break;+ case CMIDI2_STATUS_PITCH_BEND:+ // Note: Pitch Bend values in the MIDI 1.0 Protocol are presented as Little Endian.+ m2 = cmidi2_ump_midi2_pitch_bend_direct(+ context->group, channel, ((byte3 << 7) + byte2) << 18);+ break;+ default:+ return CMIDI2_CONVERSION_RESULT_INVALID_STATUS;+ }+ if (!skipEmitUmp)+ {+ int platEndian = cmidi2_util_is_platform_little_endian()+ ? CMIDI2_TRANSLATOR_LITTLE_ENDIAN+ : CMIDI2_TRANSLATOR_BIG_ENDIAN;+ int actualEndian+ = context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN+ ? context->ump_serialization_endianness+ : platEndian;+ if (platEndian != actualEndian)+ m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32)+ | cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF);+ *(uint32_t*)(dst + *dIdx) = m2 >> 32;+ *dIdx += 4;+ *(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF;+ *dIdx += 4;+ }+ *sIdx += len;+ }+ }+ }+ // incomplete UMP sequence will be reported as CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE,+ // so it is safe to judge that incomplete RPN/NRPN/DTE state at this state means invalid.+ if (context->context_rpn != 0x8080 || context->context_nrpn != 0x8080+ || context->context_dte != 0x8080)+ return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;++ return CMIDI2_CONVERSION_RESULT_OK;+}++// UMP to MIDI1 Translator++static int32_t cmidi2_internal_convert_jr_timestamp_to_timecode(+ int32_t deltaTime, cmidi2_midi_conversion_context* context)+{+ (void)context;+ // FIXME: implement+ return deltaTime;+}++static size_t cmidi2_internal_add_midi1_delta_time(+ uint8_t* dst, cmidi2_midi_conversion_context* context, int32_t deltaTime)+{+ if (!context || context->skip_delta_time)+ return 0;+ size_t* dIdx = (size_t*)&context->midi1_proceeded_bytes;+ int32_t len = cmidi2_midi1_get_7bit_encoded_int_length(deltaTime);+ cmidi2_midi1_write_7bit_encoded_int(dst, deltaTime);+ *dIdx += len;+ return len;+}++/// Convert one single UMP (without JR Timestamp) to MIDI 1.0 Message (without delta time)+/// It is a lengthy function, so it is recommended to wrap it in another non-inline function.+static inline size_t cmidi2_convert_single_ump_to_timed_midi1(+ uint8_t* dst, size_t maxBytes, cmidi2_ump* ump, int32_t deltaTime,+ cmidi2_midi_conversion_context* context, uint8_t* sysex7Buffer, size_t* sysex7BufferIndex)+{+ (void)maxBytes;+ size_t midiEventSize = 0;+ uint64_t sysex7U64;+ uint8_t sysex7NumBytesInUmp;++ uint8_t messageType = cmidi2_ump_get_message_type(ump);+ uint8_t statusCode = cmidi2_ump_get_status_code(ump); // may not apply, but won't break.++#define CMIDI2_INTERNAL_ADD_DELTA_TIME \+ dst += cmidi2_internal_add_midi1_delta_time(dst, context, deltaTime); \+ dst[0] = statusCode | cmidi2_ump_get_channel(ump);++ switch (messageType)+ {+ case CMIDI2_MESSAGE_TYPE_SYSTEM:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 1;+ switch (statusCode)+ {+ case 0xF1:+ case 0xF3:+ case 0xF9:+ dst[1] = cmidi2_ump_get_system_message_byte2(ump);+ midiEventSize = 2;+ break;+ }+ break;+ case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 3;+ dst[1] = cmidi2_ump_get_midi1_byte2(ump);+ switch (statusCode)+ {+ case 0xC0:+ case 0xD0:+ midiEventSize = 2;+ break;+ default:+ dst[2] = cmidi2_ump_get_midi1_byte3(ump);+ break;+ }+ break;+ case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:+ // FIXME: convert MIDI2 to MIDI1 as long as possible+ switch (statusCode)+ {+ case CMIDI2_STATUS_RPN:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 12;+ dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC;+ dst[1] = CMIDI2_CC_RPN_MSB;+ dst[2] = cmidi2_ump_get_midi2_rpn_msb(ump);+ dst[3] = dst[0]; // CC + channel+ dst[4] = CMIDI2_CC_RPN_LSB;+ dst[5] = cmidi2_ump_get_midi2_rpn_lsb(ump);+ dst[6] = dst[0]; // CC + channel+ dst[7] = CMIDI2_CC_DTE_MSB;+ dst[8] = (cmidi2_ump_get_midi2_rpn_data(ump) >> 25) & 0x7F;+ dst[9] = dst[0]; // CC + channel+ dst[10] = CMIDI2_CC_DTE_LSB;+ dst[11] = (cmidi2_ump_get_midi2_rpn_data(ump) >> 18) & 0x7F;+ break;+ case CMIDI2_STATUS_NRPN:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 12;+ dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC;+ dst[1] = CMIDI2_CC_NRPN_MSB;+ dst[2] = cmidi2_ump_get_midi2_nrpn_msb(ump);+ dst[3] = dst[0]; // CC + channel+ dst[4] = CMIDI2_CC_NRPN_LSB;+ dst[5] = cmidi2_ump_get_midi2_nrpn_lsb(ump);+ dst[6] = dst[0]; // CC + channel+ dst[7] = CMIDI2_CC_DTE_MSB;+ dst[8] = (cmidi2_ump_get_midi2_nrpn_data(ump) >> 25) & 0x7F;+ dst[9] = dst[0]; // CC + channel+ dst[10] = CMIDI2_CC_DTE_LSB;+ dst[11] = (cmidi2_ump_get_midi2_nrpn_data(ump) >> 18) & 0x7F;+ break;+ case CMIDI2_STATUS_NOTE_OFF:+ case CMIDI2_STATUS_NOTE_ON:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 3;+ dst[1] = cmidi2_ump_get_midi2_note_note(ump);+ dst[2] = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200;+ break;+ case CMIDI2_STATUS_PAF:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 3;+ dst[1] = cmidi2_ump_get_midi2_paf_note(ump);+ dst[2] = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000;+ break;+ case CMIDI2_STATUS_CC:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 3;+ dst[0] = statusCode | cmidi2_ump_get_channel(ump);+ dst[1] = cmidi2_ump_get_midi2_cc_index(ump);+ dst[2] = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000;+ break;+ case CMIDI2_STATUS_PROGRAM:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ dst[0] = statusCode | cmidi2_ump_get_channel(ump);+ if (cmidi2_ump_get_midi2_program_options(ump) & CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID)+ {+ midiEventSize = 8;+ dst[6] = dst[0]; // copy+ dst[7] = cmidi2_ump_get_midi2_program_program(ump);+ dst[0] = (dst[6] & 0xF) + CMIDI2_STATUS_CC;+ dst[1] = 0; // Bank MSB+ dst[2] = cmidi2_ump_get_midi2_program_bank_msb(ump);+ dst[3] = (dst[6] & 0xF) + CMIDI2_STATUS_CC;+ dst[4] = 32; // Bank LSB+ dst[5] = cmidi2_ump_get_midi2_program_bank_lsb(ump);+ }+ else+ {+ midiEventSize = 2;+ dst[1] = cmidi2_ump_get_midi2_program_program(ump);+ }+ break;+ case CMIDI2_STATUS_CAF:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 2;+ dst[1] = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000;+ break;+ case CMIDI2_STATUS_PITCH_BEND:+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 3;+ uint32_t pitchBendV1 = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000;+ dst[1] = pitchBendV1 % 0x80;+ dst[2] = pitchBendV1 / 0x80;+ break;+ // skip for other status bytes; we cannot support them.+ }+ break;+ case CMIDI2_MESSAGE_TYPE_SYSEX7:+ if (sysex7Buffer)+ {+ // sysex7 buffer is processed at cmidi2_convert_ump_to_midi1().+ midiEventSize = 0;+ sysex7U64 = cmidi2_ump_read_uint64_bytes(ump);+ sysex7NumBytesInUmp = cmidi2_ump_get_sysex7_num_bytes(ump);+ for (size_t i = 0; i < sysex7NumBytesInUmp; i++)+ sysex7Buffer[*sysex7BufferIndex + i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i);+ *sysex7BufferIndex += sysex7NumBytesInUmp;+ }+ else+ {+ // minimal implementation for single-byte sysex7+ CMIDI2_INTERNAL_ADD_DELTA_TIME+ midiEventSize = 1 + cmidi2_ump_get_sysex7_num_bytes(ump);+ sysex7U64 = cmidi2_ump_read_uint64_bytes(ump);+ for (size_t i = 0; i < midiEventSize - 1; i++)+ dst[i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i);+ }+ break;+ case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:+ // By the UMP specification they cannot be translated in Default Translation+ midiEventSize = 0;+ break;+ }+ return midiEventSize;+}++// Left for API backward compatibility.+static inline size_t+cmidi2_convert_single_ump_to_midi1(uint8_t* dst, size_t maxBytes, cmidi2_ump* ump)+{+ return cmidi2_convert_single_ump_to_timed_midi1(dst, maxBytes, ump, 0, NULL, NULL, 0);+}++#define IS_JR_TIMESTAMP(ump) \+ (cmidi2_ump_get_message_type(ump) == CMIDI2_MESSAGE_TYPE_UTILITY \+ && cmidi2_ump_get_status_code(ump) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)++static enum cmidi2_midi_conversion_result+cmidi2_convert_ump_to_midi1(cmidi2_midi_conversion_context* context)+{+ uint8_t* dst = (uint8_t*)context->midi1;+ size_t sLen = context->ump_num_bytes;+ size_t dLen = context->midi1_num_bytes;+ size_t* sIdx = (size_t*)&context->ump_proceeded_bytes;+ size_t* dIdx = (size_t*)&context->midi1_proceeded_bytes;+ uint8_t sysex7_buffer[1024];+ size_t sysex7_buffer_index = 0;++ while (*sIdx < sLen)+ {+ int32_t deltaTimeInJRTimestamp = 0;+ cmidi2_ump* ump;+ do+ {+ ump = (cmidi2_ump*)((uint8_t*)context->ump + *sIdx);+ if (IS_JR_TIMESTAMP(ump))+ {+ if (!context->skip_delta_time)+ deltaTimeInJRTimestamp += cmidi2_ump_get_jr_timestamp_timestamp(ump);+ }+ else+ break;+ *sIdx += 4; // 4 = sizeof JR Timestamp message+ } while (*sIdx < sLen);++ if (*sIdx >= sLen)+ break;++ int32_t deltaTime+ = cmidi2_internal_convert_jr_timestamp_to_timecode(deltaTimeInJRTimestamp, context);++ int32_t len = cmidi2_convert_single_ump_to_timed_midi1(+ dst + *dIdx, dLen - *dIdx, ump, deltaTime, context, sysex7_buffer, &sysex7_buffer_index);+ *dIdx += len;+ *sIdx += cmidi2_ump_get_num_bytes(*ump);++ if (cmidi2_ump_get_message_type(ump) == CMIDI2_MESSAGE_TYPE_SYSEX7)+ {+ switch (cmidi2_ump_get_status_code(ump))+ {+ case CMIDI2_SYSEX_END:+ case CMIDI2_SYSEX_IN_ONE_UMP:+ if (!context->skip_delta_time)+ cmidi2_internal_add_midi1_delta_time(dst + *dIdx, context, deltaTime);+ dst[*dIdx] = 0xF0;+ *dIdx += 1;+ memcpy(dst + *dIdx, sysex7_buffer, sysex7_buffer_index);+ *dIdx += sysex7_buffer_index;+ sysex7_buffer_index = 0;+ dst[*dIdx] = 0xF7;+ *dIdx += 1;+ break;+ }+ }+ deltaTime = 0;+ }++ return sysex7_buffer_index > 0 ? CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7+ : CMIDI2_CONVERSION_RESULT_OK;+}++// UMP forge (like LV2 Atom Forge, but flat and simpler)++typedef struct cmidi2_ump_forge+{+ cmidi2_ump* ump;+ size_t capacity;+ size_t offset;+} cmidi2_ump_forge;++static inline void+cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capacityInBytes)+{+ forge->ump = buffer;+ forge->capacity = capacityInBytes;+ forge->offset = 0;+}++static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump)+{+ int size = 4;+ if (forge->offset + size > forge->capacity)+ return false;+ uint8_t* p = (uint8_t*)forge->ump + forge->offset;+ cmidi2_ump_write32((cmidi2_ump*)p, ump);+ forge->offset += size;+ return true;+}++static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump)+{+ int size = 8;+ if (forge->offset + size > forge->capacity)+ return false;+ uint8_t* p = (uint8_t*)forge->ump + forge->offset;+ cmidi2_ump_write64((cmidi2_ump*)p, ump);+ forge->offset += size;+ return true;+}++static inline bool+cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t ump2)+{+ int size = 16;+ if (forge->offset + size > forge->capacity)+ return false;+ uint8_t* p = ((uint8_t*)forge->ump) + forge->offset;+ cmidi2_ump_write128((cmidi2_ump*)p, ump1, ump2);+ forge->offset += size;+ return true;+}++static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump)+{+ int size = cmidi2_ump_get_message_size_bytes(ump);+ if (forge->offset + size > forge->capacity)+ return false;+ memcpy((uint8_t*)forge->ump + forge->offset, ump, size);+ forge->offset += size;+ return true;+}++static inline bool+cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t size)+{+ if (forge->offset + size > forge->capacity)+ return false;+ memcpy((uint8_t*)forge->ump + forge->offset, ump, size);+ forge->offset += size;+ return true;+}++// UMP sequence merger++static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time(+ int32_t timestamp1, int32_t timestamp2, int32_t* lastTimestamp, void* dst, int32_t* dIdx,+ size_t dstSize)+{+ int32_t deltaTime = (timestamp1 <= timestamp2 ? timestamp1 : timestamp2) - *lastTimestamp;+ uint8_t jrTSSize = deltaTime / 0x10000 + (deltaTime % 0x10000 ? 1 : 0);+ if (*dIdx + jrTSSize * 4 > (int64_t)dstSize)+ return false;+ for (int32_t dt = deltaTime; dt > 0; dt -= 0x10000)+ {+ cmidi2_ump_write32(+ (cmidi2_ump*)((uint8_t*)dst + *dIdx),+ cmidi2_ump_jr_timestamp_direct(0, deltaTime > 0xFFFF ? 0xFFFF : deltaTime));+ *dIdx += 4;+ }+ *lastTimestamp += deltaTime;+ return true;+}++static inline size_t cmidi2_ump_merge_sequences(+ cmidi2_ump* dst, size_t dstCapacity, cmidi2_ump* seq1, size_t seq1Size, cmidi2_ump* seq2,+ size_t seq2Size)+{+ int32_t dIdx = 0, seq1Idx = 0, seq2Idx = 0;+ int32_t timestamp1 = 0, timestamp2 = 0, lastTimestamp = 0;+ while (true)+ {+ cmidi2_ump* s1 = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+ while (cmidi2_ump_get_message_type(s1) == CMIDI2_MESSAGE_TYPE_UTILITY+ && cmidi2_ump_get_status_code(s1) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)+ {+ timestamp1 += cmidi2_ump_get_jr_timestamp_timestamp(s1);+ seq1Idx += 4;+ if (seq1Idx >= (int64_t)seq1Size)+ break;+ s1 = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+ }+ cmidi2_ump* s2 = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+ while (cmidi2_ump_get_message_type(s2) == CMIDI2_MESSAGE_TYPE_UTILITY+ && cmidi2_ump_get_status_code(s2) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)+ {+ timestamp2 += cmidi2_ump_get_jr_timestamp_timestamp(s2);+ seq2Idx += 4;+ if (seq2Idx >= (int64_t)seq2Size)+ break;+ s2 = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+ }+ if (seq1Idx >= (int64_t)seq1Size || seq2Idx >= (int64_t)seq2Size)+ break;+ if (!cmidi2_internal_ump_merge_sequence_write_delta_time(+ timestamp1, timestamp2, &lastTimestamp, dst, &dIdx, dstCapacity))+ break;++ if (timestamp1 <= timestamp2)+ {+ cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+ uint8_t size = cmidi2_ump_get_message_size_bytes(sp);+ if (size == 0)+ return dIdx; // invalid bytes+ memcpy((uint8_t*)dst + dIdx, sp, size);+ seq1Idx += size;+ dIdx += size;+ }+ else+ {+ cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+ uint8_t size = cmidi2_ump_get_message_size_bytes(sp);+ if (size == 0)+ return dIdx; // invalid bytes+ memcpy((uint8_t*)dst + dIdx, sp, size);+ seq2Idx += size;+ dIdx += size;+ }+ }+ if (!cmidi2_internal_ump_merge_sequence_write_delta_time(+ timestamp1, timestamp2, &lastTimestamp, dst, &dIdx, dstCapacity))+ return dIdx;+ if (seq1Idx < (int64_t)seq1Size && dIdx + (int64_t)seq1Size - seq1Idx < (int64_t)dstCapacity)+ {+ cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);+ memcpy((uint8_t*)dst + dIdx, sp, seq1Size - seq1Idx);+ dIdx += seq1Size - seq1Idx;+ }+ if (seq2Idx < (int64_t)seq2Size && dIdx + (int64_t)seq2Size - seq2Idx < (int64_t)dstCapacity)+ {+ cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);+ memcpy((uint8_t*)dst + dIdx, sp, seq2Size - seq2Idx);+ dIdx += seq2Size - seq2Idx;+ }+ return dIdx;+}++#ifdef __cplusplus+}+#endif++#endif /* CMIDI2_H_INCLUDED */
+ vendor/libremidi/config.hpp view
@@ -0,0 +1,64 @@+#pragma once++#if defined(MSC_VER)+ #define NOMINMAX 1+ #define WIN32_LEAN_AND_MEAN+#endif++#if defined(LIBREMIDI_EXPORTS)+ #if defined(_MSC_VER)+ #define LIBREMIDI_EXPORT __declspec(dllexport)+ #elif defined(__GNUC__) || defined(__clang__)+ #define LIBREMIDI_EXPORT __attribute__((visibility("default")))+ #endif+#else+ #define LIBREMIDI_EXPORT+#endif++#define LIBREMIDI_VERSION "4.5.0"++#if defined(LIBREMIDI_USE_BOOST)+ #if !__has_include(<boost/container/small_vector.hpp>)+ #error \+ "Boost was used for building libremidi but is not found when using it. Add Boost to your include paths."+ #endif++ #if defined(LIBREMIDI_NO_BOOST)+ #error "Boost was used for building libremidi but LIBREMIDI_NO_BOOST is defined."+ #endif+#endif++#if __has_include(<boost/container/small_vector.hpp>) && !defined(LIBREMIDI_NO_BOOST)++ #if LIBREMIDI_SLIM_MESSAGE > 0+ #include <boost/container/static_vector.hpp>+namespace libremidi+{+using midi_bytes = boost::container::static_vector<unsigned char, LIBREMIDI_SLIM_MESSAGE>;+}+ #else+ #include <boost/container/small_vector.hpp>+namespace libremidi+{+static constexpr int small_vector_minimum_size+ = sizeof(boost::container::small_vector<unsigned char, 1>);+using midi_bytes = boost::container::small_vector<unsigned char, small_vector_minimum_size>;+}+ #endif+#else+ #include <vector>+namespace libremidi+{+using midi_bytes = std::vector<unsigned char>;+}+#endif++#if __has_include(<midi/universal_packet.h>) && defined(LIBREMIDI_USE_NI_MIDI2)+ #define LIBREMIDI_NI_MIDI2_COMPAT 1+#endif++#if defined(LIBREMIDI_HEADER_ONLY)+ #define LIBREMIDI_INLINE inline+#else+ #define LIBREMIDI_INLINE+#endif
+ vendor/libremidi/configurations.hpp view
@@ -0,0 +1,24 @@+#pragma once++#include <libremidi/backends/alsa_raw/config.hpp>+#include <libremidi/backends/alsa_raw_ump/config.hpp>+#include <libremidi/backends/alsa_seq/config.hpp>+#include <libremidi/backends/alsa_seq_ump/config.hpp>+#include <libremidi/backends/coremidi/config.hpp>+#include <libremidi/backends/coremidi_ump/config.hpp>+#include <libremidi/backends/emscripten/config.hpp>+#include <libremidi/backends/jack/config.hpp>+#include <libremidi/backends/keyboard/config.hpp>+#include <libremidi/backends/net/config.hpp>+#include <libremidi/backends/pipewire/config.hpp>+#include <libremidi/backends/winmidi/config.hpp>+#include <libremidi/backends/winmm/config.hpp>+#include <libremidi/backends/winuwp/config.hpp>++namespace libremidi+{++struct dummy_configuration+{+};+}
+ vendor/libremidi/defaults.hpp view
@@ -0,0 +1,98 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/output_configuration.hpp>++#include <any>+#include <optional>++namespace libremidi+{+class midi_in;+class midi_out;+class observer;++//! Get the default midi in configuration object for a given API+LIBREMIDI_EXPORT+std::any midi_in_configuration_for(libremidi::API);++//! Get the default midi out configuration object for a given API+LIBREMIDI_EXPORT+std::any midi_out_configuration_for(libremidi::API);++//! Get the default observer configuration object for a given API+LIBREMIDI_EXPORT+std::any observer_configuration_for(libremidi::API);++//! Get a matching midi in configuration object for a given observer instance.+LIBREMIDI_EXPORT+std::any midi_in_configuration_for(const libremidi::observer&);+//! Get a matching midi out configuration object for a given observer instance.+LIBREMIDI_EXPORT+std::any midi_out_configuration_for(const libremidi::observer&);++//! Get the default port for midi input (if any) in a given API.+//! Note that this will only return a hardware / physical port.+LIBREMIDI_EXPORT+std::optional<input_port> in_default_port(libremidi::API api) noexcept;++//! Get the default port for midi output (if any) in a given API.+//! Note that this will only return a hardware / physical port.+LIBREMIDI_EXPORT+std::optional<output_port> out_default_port(libremidi::API api) noexcept;++namespace midi1+{+//! Get a default configuration for creating a MIDI 1 input+LIBREMIDI_EXPORT+std::any in_default_configuration();++//! Get a default configuration for creating a MIDI 1 output+LIBREMIDI_EXPORT+std::any out_default_configuration();++//! Get a default configuration for creating a MIDI 1 observer+LIBREMIDI_EXPORT+std::any observer_default_configuration();++//! Get a default MIDI 1 input port for the default API of the system+inline std::optional<input_port> in_default_port() noexcept+{+ return libremidi::in_default_port(default_api());+}++//! Get a default MIDI 1 output port for the default API of the system+inline std::optional<output_port> out_default_port() noexcept+{+ return libremidi::out_default_port(default_api());+}+}++namespace midi2+{+//! Get a default configuration for creating a MIDI 2 input+LIBREMIDI_EXPORT+std::any in_default_configuration();++//! Get a default configuration for creating a MIDI 2 output+LIBREMIDI_EXPORT+std::any out_default_configuration();++//! Get a default configuration for creating a MIDI 2 observer+LIBREMIDI_EXPORT+std::any observer_default_configuration();++//! Get a default MIDI 2 input port for the default API of the system+inline std::optional<input_port> in_default_port() noexcept+{+ return libremidi::in_default_port(default_api());+}+//! Get a default MIDI 2 output port for the default API of the system+inline std::optional<output_port> out_default_port() noexcept+{+ return libremidi::out_default_port(default_api());+}+}+}
+ vendor/libremidi/detail/conversion.hpp view
@@ -0,0 +1,59 @@+#pragma once+// clang-format off+#include <libremidi/config.hpp>+// clang-format on++#include <libremidi/cmidi2.hpp>+#include <libremidi/error.hpp>+#include <libremidi/message.hpp>+#include <libremidi/ump.hpp>+namespace libremidi+{+struct midi1_to_midi2+{+ stdx::error+ convert(const unsigned char* message, std::size_t size, int64_t timestamp, auto on_ump)+ {+ context.midi1 = const_cast<unsigned char*>(message);+ context.midi1_num_bytes = size;+ context.midi1_proceeded_bytes = 0;+ context.ump = ump;+ context.ump_num_bytes = sizeof(ump);+ context.ump_proceeded_bytes = 0;++ if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK)+ return std::errc::invalid_argument;++ return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp);+ }++ cmidi2_midi_conversion_context context = [] {+ cmidi2_midi_conversion_context tmp;+ cmidi2_midi_conversion_context_initialize(&tmp);+ return tmp;+ }();+ uint32_t ump[65536 / 4];+};++struct midi2_to_midi1+{+ stdx::error+ convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi)+ {+ auto n+ = cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));+ if (n > 0)+ return on_midi(midi, n, timestamp);+ else+ return std::errc::no_buffer_space;+ }++ cmidi2_midi_conversion_context context = [] {+ cmidi2_midi_conversion_context tmp;+ cmidi2_midi_conversion_context_initialize(&tmp);+ return tmp;+ }();+ uint8_t midi[65536];+};++}
+ vendor/libremidi/detail/memory.hpp view
@@ -0,0 +1,40 @@+#pragma once++#include <memory>+#include <mutex>++namespace libremidi+{+template <auto func>+struct deleter+{+ template <typename U>+ void operator()(U* x) noexcept(noexcept(func(x)))+ {+ func(x);+ }+};++template <typename T, auto func>+using unique_handle = std::unique_ptr<T, deleter<func>>;++template <typename T>+std::shared_ptr<T> instance()+{+ static std::mutex mut;+ static std::weak_ptr<T> cache;++ std::lock_guard _{mut};++ if (auto ptr = cache.lock())+ {+ return ptr;+ }+ else+ {+ auto shared = std::make_shared<T>();+ cache = shared;+ return shared;+ }+}+}
+ vendor/libremidi/detail/midi_api.hpp view
@@ -0,0 +1,48 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>++#include <string_view>++namespace libremidi+{+class midi_api+{+public:+ midi_api() = default;+ virtual ~midi_api() = default;+ midi_api(const midi_api&) = delete;+ midi_api(midi_api&&) = delete;+ midi_api& operator=(const midi_api&) = delete;+ midi_api& operator=(midi_api&&) = delete;++ [[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0;++ [[nodiscard]] virtual stdx::error open_virtual_port(std::string_view)+ {+ return std::errc::function_not_supported;+ }+ virtual stdx::error set_client_name(std::string_view)+ {+ return std::errc::function_not_supported;+ }+ virtual stdx::error set_port_name(std::string_view)+ {+ return std::errc::function_not_supported;+ }++ virtual stdx::error close_port() = 0;++ stdx::error is_client_open() const noexcept { return client_open_; }+ bool is_port_open() const noexcept { return port_open_; }+ bool is_port_connected() const noexcept { return connected_; }++protected:+ friend class midi_in;+ friend class midi_out;+ stdx::error client_open_{std::errc::not_connected};+ bool port_open_{};+ bool connected_{};+};+}
+ vendor/libremidi/detail/midi_in.hpp view
@@ -0,0 +1,56 @@+#pragma once+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/error_handler.hpp>++namespace libremidi+{+class midi_in_api : public midi_api+{+public:+ midi_in_api() = default;+ ~midi_in_api() override = default;+ midi_in_api(const midi_in_api&) = delete;+ midi_in_api(midi_in_api&&) = delete;+ midi_in_api& operator=(const midi_in_api&) = delete;+ midi_in_api& operator=(midi_in_api&&) = delete;++ [[nodiscard]] virtual stdx::error+ open_port(const input_port& pt, std::string_view local_port_name)+ = 0;+ [[nodiscard]] virtual timestamp absolute_timestamp() const noexcept = 0;+};++namespace midi1+{+class in_api : public midi_in_api+{+public:+ using midi_in_api::midi_in_api;+};+}++namespace midi2+{+class in_api : public midi_in_api+{+public:+ using midi_in_api::midi_in_api;++ bool firstMessage{true};+};+}++template <typename T, typename Arg>+std::unique_ptr<midi_in_api> make(libremidi::input_configuration&& conf, Arg&& arg)+{+ return std::make_unique<T>(std::move(conf), std::move(arg));+}++template <typename T, typename Arg>+std::unique_ptr<midi_in_api> make(libremidi::ump_input_configuration&& conf, Arg&& arg)+{+ return std::make_unique<T>(std::move(conf), std::move(arg));+}+}
+ vendor/libremidi/detail/midi_out.hpp view
@@ -0,0 +1,89 @@+#pragma once+#include <libremidi/detail/conversion.hpp>+#include <libremidi/detail/midi_api.hpp>+#include <libremidi/error_handler.hpp>+#include <libremidi/output_configuration.hpp>++#include <string_view>++namespace libremidi+{++class midi_out_api : public midi_api+{+public:+ midi_out_api() = default;+ ~midi_out_api() override = default;+ midi_out_api(const midi_out_api&) = delete;+ midi_out_api(midi_out_api&&) = delete;+ midi_out_api& operator=(const midi_out_api&) = delete;+ midi_out_api& operator=(midi_out_api&&) = delete;++ [[nodiscard]] virtual stdx::error+ open_port(const output_port& pt, std::string_view local_port_name)+ = 0;++ [[nodiscard]] virtual int64_t current_time() const noexcept { return 0; }++ virtual stdx::error send_message(const unsigned char* message, std::size_t size) = 0;+ virtual stdx::error+ schedule_message(int64_t /*ts*/, const unsigned char* message, std::size_t size)+ {+ return send_message(message, size);+ }++ virtual stdx::error send_ump(const uint32_t* message, std::size_t size) = 0;+ virtual stdx::error schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t size)+ {+ return send_ump(ump, size);+ }+};++namespace midi1+{+class out_api : public midi_out_api+{+ friend struct midi_stream_decoder;++public:+ using midi_out_api::midi_out_api;++ stdx::error send_ump(const uint32_t* message, std::size_t size)+ {+ return converter.convert(+ message, size, 0, [this](const unsigned char* midi, std::size_t n, int64_t /* ts */) {+ return send_message(midi, n);+ });+ }++ midi2_to_midi1 converter;+};+}++namespace midi2+{+class out_api : public midi_out_api+{+ friend struct midi_stream_decoder;++public:+ using midi_out_api::midi_out_api;++ stdx::error send_message(const unsigned char* message, std::size_t size)+ {+ return converter.convert(+ message, size, 0, [this](const uint32_t* ump, std::size_t count, int64_t /* ts */) {+ return send_ump(ump, count);+ });+ }++ midi1_to_midi2 converter;+};+}++template <typename T, typename Arg>+std::unique_ptr<midi_out_api> make(libremidi::output_configuration&& conf, Arg&& arg)+{+ return std::make_unique<T>(std::move(conf), std::forward<Arg>(arg));+}+}
+ vendor/libremidi/detail/midi_stream_decoder.hpp view
@@ -0,0 +1,465 @@+#pragma once++#include <libremidi/detail/midi_in.hpp>++#include <libremidi/cmidi2.hpp>+#include <chrono>+#include <cstdint>+#include <cinttypes>+#include <span>++namespace libremidi+{+static inline int64_t system_ns() noexcept+{+ namespace clk = std::chrono;+ return clk::duration_cast<clk::nanoseconds>(clk::steady_clock::now().time_since_epoch()).count();+}++struct timestamp_backend_info+{+ // The API provides some kind of timestamping+ bool has_absolute_timestamps{};++ // The provided timestamping is equivalent or more precise than+ // e.g. clock_gettime(CLOCK_MONOTONIC)+ bool absolute_is_monotonic{};++ // The API can provide samples in a buffer cycle (only PipeWire and JACK so far)+ bool has_samples{};+};++template<typename Configuration>+struct input_state_machine_base+{+ const Configuration& configuration;++ explicit input_state_machine_base(const Configuration& conf)+ : configuration{conf}+ {+ }++ template <timestamp_backend_info info>+ int64_t timestamp(auto to_ns, int64_t samples)+ {+ switch (configuration.timestamps)+ {+ default:+ case timestamp_mode::NoTimestamp:+ return 0;++ case timestamp_mode::Relative: {+ int64_t time_ns;++ if constexpr (info.has_absolute_timestamps)+ time_ns = to_ns();+ else+ time_ns = system_ns();++ int64_t res;+ if (first_message)+ {+ first_message = false;+ res = 0;+ }+ else+ {+ res = time_ns - last_time_ns;+ }++ last_time_ns = time_ns;+ return res;+ }++ case timestamp_mode::Absolute:+ if constexpr (info.has_absolute_timestamps)+ return to_ns();+ else+ return system_ns();++ case timestamp_mode::SystemMonotonic:+ if constexpr (info.absolute_is_monotonic)+ return to_ns();+ else+ return system_ns();++ case timestamp_mode::AudioFrame:+ if constexpr (info.has_samples)+ return samples;+ else+ return 0;++ case timestamp_mode::Custom:+ return configuration.get_timestamp(to_ns());+ }+ }+ int64_t last_time_ns = 0;+ bool first_message = true;+};++namespace midi1+{+struct input_state_machine : input_state_machine_base<input_configuration>+{+ using input_state_machine_base::input_state_machine_base;++ void reset()+ {+ message.bytes.clear();+ message.timestamp = {};+ state = main;+ }++ bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept+ {+ return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));+ }++ // Function to process a byte stream which may contain multiple successive+ // MIDI events (CoreMIDI, ALSA Sequencer can work like this)+ void on_bytes_multi(std::span<const uint8_t> bytes, int64_t timestamp)+ {+ if (this->configuration.on_message)+ on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);+ if (this->configuration.on_raw_data)+ this->configuration.on_raw_data(bytes, timestamp);+ }++ // Function to process bytes corresponding to at most one midi event+ // e.g. a midi channel event or a single sysex+ void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)+ {+ if (this->configuration.on_message)+ on_bytes_segmented(this->configuration.on_message, bytes, timestamp);+ if (this->configuration.on_raw_data)+ this->configuration.on_raw_data(bytes, timestamp);+ }++private:+ void on_bytes_multi_segmented(+ const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)+ {+ int64_t nBytes = bytes.size();+ int64_t iByte = 0;++ const bool finished_sysex = has_finished_sysex(bytes);+ switch (state)+ {+ case in_sysex: {+ return on_continue_sysex(cb, bytes, finished_sysex);+ }+ case main: {+ while (iByte < nBytes)+ {+ int64_t size = 1;+ // We are expecting that the next byte in the packet is a status+ // byte.+ const auto status = bytes[iByte];+ if (!(status & 0x80))+ break;++ // Determine the number of bytes in the MIDI message.+ if (status < 0xC0)+ size = 3;+ else if (status < 0xE0)+ size = 2;+ else if (status < 0xF0)+ size = 3;+ else if (status == 0xF0)+ {+ if (configuration.ignore_sysex)+ {+ size = 0;+ iByte = nBytes;+ }+ else+ {+ size = nBytes - iByte;+ }++ if (bytes[nBytes - 1] != 0xF7)+ {+ // We know per CoreMIDI API there can't be anything else in this packet+ state = in_sysex;+ message.assign(bytes.begin(), bytes.begin() + size);+ message.timestamp = timestamp;+ return;+ }+ }+ else if (status == 0xF1)+ {+ // A MIDI time code message+ if (configuration.ignore_timing)+ {+ size = 0;+ iByte += 2;+ }+ else+ {+ size = 2;+ }+ }+ else if (status == 0xF2)+ size = 3;+ else if (status == 0xF3)+ size = 2;+ else if (status == 0xF8)+ {+ // A MIDI timing tick message+ if (configuration.ignore_timing)+ {+ size = 0;+ iByte += 1;+ }+ else+ {+ size = 1;+ }+ }+ else if (status == 0xFE)+ {+ // A MIDI active sensing message+ if (configuration.ignore_sensing)+ {+ size = 0;+ iByte += 1;+ }+ else+ {+ size = 1;+ }+ }+ else+ {+ // Remaining real-time messages+ size = 1;+ }++ // Now process the actual bytes of the message+ if (size > 0)+ {+ auto begin = bytes.begin() + iByte;+ message.assign(begin, begin + size);+ message.timestamp = timestamp;++ cb(std::move(message));+ message.clear();++ iByte += size;+ }+ }+ }+ }+ }++ void on_continue_sysex(+ const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)+ {+ if (finished_sysex)+ state = main;++ if (configuration.ignore_sysex)+ {+ return;+ }+ else+ {+ message.insert(message.end(), bytes.begin(), bytes.end());+ if (finished_sysex)+ {+ cb(std::move(message));+ message.clear();+ }+ }+ return;+ }++ void on_main(+ const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp,+ bool finished_sysex)+ {+ switch (bytes[0])+ {+ // SYSEX start+ case 0xF0: {+ if (!finished_sysex)+ state = in_sysex;++ if (!this->configuration.ignore_sysex)+ {+ message.assign(bytes.begin(), bytes.end());+ message.timestamp = timestamp;+ if (finished_sysex)+ {+ cb(std::move(message));+ message.clear();+ }+ }++ return;+ }++ case 0xF1:+ case 0xF8:+ if (this->configuration.ignore_timing)+ return;+ break;++ case 0xFE:+ if (this->configuration.ignore_sensing)+ return;+ break;++ default:+ break;+ }++ message.assign(bytes.begin(), bytes.end());+ message.timestamp = timestamp;++ cb(std::move(message));+ message.clear();+ }++ void+ on_bytes_segmented(const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)+ {+ if (bytes.empty())+ return;++ const bool finished_sysex = has_finished_sysex(bytes);+ switch (state)+ {+ case in_sysex:+ return on_continue_sysex(cb, bytes, finished_sysex);++ case main:+ return on_main(cb, bytes, timestamp, finished_sysex);+ }+ }++public:+ libremidi::message message;++private:+ enum+ {+ main,+ in_sysex+ } state{main};+};+}++namespace midi2+{+struct input_state_machine : input_state_machine_base<ump_input_configuration>+{+ using input_state_machine_base::input_state_machine_base;++public:+ void on_bytes_multi(std::span<const unsigned char> bytes, int64_t timestamp)+ {+ auto ptr = reinterpret_cast<const uint32_t*>(bytes.data());+ auto sz = bytes.size() / 4;+ return on_bytes_multi({ptr, sz}, timestamp);+ }++ void on_bytes_multi(std::span<const uint32_t> bytes, int64_t timestamp)+ {+ if (this->configuration.on_message)+ on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);+ if (this->configuration.on_raw_data)+ this->configuration.on_raw_data(bytes, timestamp);+ }++ void on_bytes(std::span<const uint32_t> bytes, int64_t timestamp)+ {+ if (this->configuration.on_message)+ on_bytes_segmented(this->configuration.on_message, bytes, timestamp);+ if (this->configuration.on_raw_data)+ this->configuration.on_raw_data(bytes, timestamp);+ }++private:+ // Function to process a byte stream which may contain multiple successive+ // MIDI events (CoreMIDI, ALSA Sequencer can work like this)+ void on_bytes_multi_segmented(+ const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)+ {+ auto count = bytes.size();+ auto ump_stream = bytes.data();+ while (count > 0)+ {+ // Handle NOOP (or padding)+ while (count > 0 && ump_stream[0] == 0)+ {+ count--;+ ump_stream++;+ }++ if (count == 0)+ break;++ const auto ump_uints = cmidi2_ump_get_num_bytes(ump_stream[0]) / 4;+ on_bytes_segmented(cb, {ump_stream, ump_stream + ump_uints}, timestamp);++ ump_stream += ump_uints;+ count -= ump_uints;+ }+ }++ // Function to process bytes corresponding to at most one midi event+ void+ on_bytes_segmented(const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)+ {+ // Filter according to message type+ switch(cmidi2_ump_get_message_type(bytes.data()))+ {+ case CMIDI2_MESSAGE_TYPE_UTILITY:+ {+ // All the utility messages are about timing+ if (this->configuration.ignore_timing)+ return;+ break;+ }++ case CMIDI2_MESSAGE_TYPE_SYSTEM:+ {+ if (this->configuration.ignore_timing)+ {+ auto status = cmidi2_ump_get_system_message_byte2(bytes.data());+ switch(status)+ {+ case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE:+ case CMIDI2_SYSTEM_STATUS_SONG_POSITION:+ case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK:+ return;+ }+ }++ if (this->configuration.ignore_sensing)+ {+ auto status = cmidi2_ump_get_system_message_byte2(bytes.data());+ if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING)+ return;+ }+ break;+ }++ case CMIDI2_MESSAGE_TYPE_SYSEX7:+ case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:+ {+ if (this->configuration.ignore_sysex)+ return;+ break;+ }+ }++ libremidi::ump msg;+ std::copy(bytes.begin(), bytes.end(), msg.data);+ msg.timestamp = timestamp;+ cb(std::move(msg));+ }+};+}+}
+ vendor/libremidi/detail/observer.hpp view
@@ -0,0 +1,27 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/error_handler.hpp>++#include <memory>+#include <vector>++namespace libremidi+{+class observer_api+{+public:+ virtual ~observer_api() = default;++ virtual libremidi::API get_current_api() const noexcept = 0;+ virtual std::vector<libremidi::input_port> get_input_ports() const noexcept = 0;+ virtual std::vector<libremidi::output_port> get_output_ports() const noexcept = 0;+};++template <typename T, typename Arg>+std::unique_ptr<observer_api> make(libremidi::observer_configuration&& conf, Arg&& arg)+{+ return std::make_unique<T>(std::move(conf), std::move(arg));+}+}
+ vendor/libremidi/detail/semaphore.hpp view
@@ -0,0 +1,24 @@+#pragma once++#include <chrono>+#include <semaphore>++struct semaphore_pair_lock+{+ std::binary_semaphore sem_cleanup{0};+ std::binary_semaphore sem_needpost{0};+ void prepare_release_client()+ {+ using namespace std::literals;++ // FIXME if jack is not running we can skip this+ this->sem_needpost.release();+ this->sem_cleanup.try_acquire_for(1s);+ }++ void check_client_released()+ {+ if (!this->sem_needpost.try_acquire())+ this->sem_cleanup.release();+ }+};
+ vendor/libremidi/detail/ump_stream.hpp view
@@ -0,0 +1,62 @@+#pragma once+#include <libremidi/cmidi2.hpp>+#include <libremidi/error.hpp>++#include <cinttypes>+#include <cstdint>++namespace libremidi+{+enum class segmentation_error+{+ no_error,+ need_space,+ other+};++/**+ * Utility function to segment an ump stream into individual messages.+ * Used to send a stream to APIs that work message-by-message.+ */+inline stdx::error+segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, auto realloc_func)+{+ while (count > 0)+ {+ // Handle NOOP (or padding)+ while (count > 0 && ump_stream[0] == 0)+ {+ count--;+ ump_stream++;+ }++ if (count == 0)+ break;++ const auto ump_bytes = cmidi2_ump_get_num_bytes(ump_stream[0]);++ // FIXME std::expected, propagate the error back to caller?+ switch (int err = static_cast<int>(write_func(ump_stream, ump_bytes)))+ {+ case 0:+ break;+ case -ENOMEM:+ case ENOMEM:+ // Try again if we didn't have enough space in the OS queue+ realloc_func();+ if (auto err = write_func(ump_stream, ump_bytes); err != std::errc{})+ return std::make_error_code(err);+ break;+ default:+ return from_errc(err);+ }++ const auto ump_uints = ump_bytes / 4;+ ump_stream += ump_uints;+ count -= ump_uints;+ }++ return stdx::error{};+}++}
+ vendor/libremidi/error.hpp view
@@ -0,0 +1,64 @@+#pragma once+#include <libremidi/config.hpp>++#pragma GCC diagnostic push+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"+#pragma GCC diagnostic ignored "-Wunused-parameter"+#include <libremidi/system_error2.hpp>+#pragma GCC diagnostic pop++#include <functional>+#include <string_view>+#include <version>++#if __has_include(<source_location>) && (__cpp_lib_source_location >= 201907L)+ #include <source_location>+namespace libremidi { using source_location = std::source_location; }+#else+namespace libremidi {+struct source_location {+ static source_location current() noexcept { return {}; }+ int line() const noexcept { return -1; }+ int offset() const noexcept { return -1; }+ const char* function_name() const noexcept { return "(unknown)"; }+ const char* file_name() const noexcept { return "(unknown)"; }+};+}+#endif++namespace libremidi+{+inline auto from_errc(int ret) noexcept+{+ return static_cast<std::errc>(-ret);+}++/*! \brief Error callback function+ \param type Type of error.+ \param errorText Error description.++ Note that class behaviour is undefined after a critical error (not+ a warning) is reported.+ */+using midi_error_callback = std::function<void(std::string_view errorText, const source_location&)>;+using midi_warning_callback = std::function<void(std::string_view errorText, const source_location&)>;+}++#if !defined(LIBREMIDI_LOG)+ #if !defined(__LIBREMIDI_DEBUG__)+ #define LIBREMIDI_LOG(...) \+ do \+ { \+ } while (0)+ #else+ #include <iostream>+ #define LIBREMIDI_LOG(...) \+ do \+ { \+ [](auto&&... args) { \+ (std::cerr << ... << args); \+ std::cerr << std::endl; \+ }(__VA_ARGS__); \+ } while (0)+ #endif+#endif
+ vendor/libremidi/error_handler.hpp view
@@ -0,0 +1,63 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/output_configuration.hpp>++#include <string_view>++namespace libremidi+{+struct error_handler+{+ //! Error reporting function for libremidi classes.+ void error_impl(+ const midi_error_callback& callback, std::string_view errorString,+ const source_location& location) const+ {+ if (callback)+ {+ if (first_error)+ return;++ first_error = true;+ callback(errorString, location);+ first_error = false;+ }+ else+ {+ LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");+ }+ }++ //! Warning reporting function for libremidi classes.+ void warning_impl(+ const midi_warning_callback& callback, std::string_view errorString,+ const source_location& location) const+ {+ if (callback)+ {+ if (first_warning)+ return;++ first_warning = true;+ callback(errorString, location);+ first_warning = false;+ }+ else+ {+ LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");+ }+ }++ // To prevent infinite error loops+ mutable bool first_error{};+ mutable bool first_warning{};+};++// Needed as apple still doesn't support source_location in xcode 15.3+#define libremidi_handle_error(config, str) error_impl(config.on_error, str, libremidi::source_location::current())+#define libremidi_handle_warning(config, str) warning_impl(config.on_warning, str, libremidi::source_location::current())+}
+ vendor/libremidi/input_configuration.hpp view
@@ -0,0 +1,127 @@+#pragma once+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>+#include <libremidi/message.hpp>+#include <libremidi/ump.hpp>++#include <functional>++namespace libremidi+{+//! Specify how timestamps are handled in the system+enum timestamp_mode+{+ //! No timestamping at all, all timestamps are zero+ NoTimestamp,++ //! In nanoseconds, timestamp is the time since the previous event (or zero)+ Relative,++ //! In nanoseconds, as per an arbitrary reference which may be provided by the host API,+ //! e.g. since the JACK cycle start, ALSA sequencer queue creation, through AudioHostTime on macOS.+ //! It offers the most precise ordering between events as it's the closest to the real timestamp of+ //! the event as provided by the host API.+ //! If the API does not provide any timing, it will be mapped to SystemMonotonic instead.+ Absolute,++ //! In nanoseconds, as per std::steady_clock::now() or equivalent (raw if possible).+ //! May be less precise than Absolute as timestamping is done within the library,+ //! but is more useful for system-wide synchronization.+ //! Note: depending on the backend, Absolute and SystemMonotonic may be the same.+ SystemMonotonic,++ //! For APIs which are based on audio process cycles such as JACK, timestamps will be in frames since+ //! the beginning of the current cycle's audio buffer+ AudioFrame,++ //! Will call the custom timestamping function provided by the user in the input configuration.+ Custom+};++using timestamp = int64_t;+using message_callback = std::function<void(message&& message)>;+using raw_callback = std::function<void(std::span<const uint8_t>, timestamp)>;+using timestamp_callback = std::function<timestamp(timestamp)>;+struct input_configuration+{+ //! Set a callback function to be invoked for incoming MIDI messages.+ //! Either this or on_raw_message must be set+ message_callback on_message{};++ //! Invoked for incoming MIDI bytes. No transformation, no filtering, no packetization,+ //! just the MIDI data straight from the source.+ raw_callback on_raw_data{};++ //! Set a custom callback function to be invoked for timestamping MIDI messages.+ //! Input: the API provided timestamp in nanoseconds, if available, for reference.+ //! (e.g. the same as "Absolute").+ //! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.+ timestamp_callback get_timestamp{};++ //! Set an error callback function to be invoked when an error has occured.+ /*!+ The callback function will be called whenever an error has occured. It is+ best to set the error callback function before opening a port.+ */+ midi_error_callback on_error{};+ midi_warning_callback on_warning{};++ //! Specify whether certain MIDI message types should be queued or ignored+ //! during input.+ /*!+ By default, MIDI timing and active sensing messages are ignored+ during message input because of their relative high data rates.+ MIDI sysex messages are ignored by default as well. Variable+ values of "true" imply that the respective message type will be+ ignored.+ */+ uint32_t ignore_sysex : 1 = true;+ uint32_t ignore_timing : 1 = true;+ uint32_t ignore_sensing : 1 = true;++ //! Timestamp mode. See @libremidi::timestamp_mode+ uint32_t timestamps : 3 = timestamp_mode::Absolute;+};++using ump_callback = std::function<void(ump&&)>;+using raw_ump_callback = std::function<void(std::span<const uint32_t>, timestamp)>;+struct ump_input_configuration+{+ //! Set a callback function to be invoked for incoming UMP messages.+ //! Either this or on_raw_message must be set+ ump_callback on_message{};++ //! Invoked for incoming UMP bytes. No transformation, no filtering, no packetization,+ //! just the UMP data straight from the source.+ raw_ump_callback on_raw_data{};++ //! Set a custom callback function to be invoked for timestamping MIDI messages.+ //! Input: the API provided timestamp in nanoseconds, if available, for reference.+ //! (e.g. the same as "Absolute").+ //! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.+ timestamp_callback get_timestamp{};++ //! Set an error callback function to be invoked when an error has occured.+ /*!+ The callback function will be called whenever an error has occured. It is+ best to set the error callback function before opening a port.+ */+ midi_error_callback on_error{};+ midi_warning_callback on_warning{};++ //! Specify whether certain MIDI message types should be queued or ignored+ //! during input.+ /*!+ By default, MIDI timing and active sensing messages are ignored+ during message input because of their relative high data rates.+ MIDI sysex messages are ignored by default as well. Variable+ values of "true" imply that the respective message type will be+ ignored.+ */+ uint32_t ignore_sysex : 1 = true;+ uint32_t ignore_timing : 1 = true;+ uint32_t ignore_sensing : 1 = true;++ uint32_t timestamps : 3 = timestamp_mode::Absolute;+};+}
+ vendor/libremidi/libremidi.hpp view
@@ -0,0 +1,279 @@+#pragma once+/* This software is based on the RtMidi and ModernMidi libraries.++ RtMidi WWW site: http://music.mcgill.ca/~gary/libremidi/++ RtMidi: realtime MIDI i/o C++ classes+ Copyright (c) 2003-2017 Gary P. Scavone++ Permission is hereby granted, free of charge, to any person+ obtaining a copy of this software and associated documentation files+ (the "Software"), to deal in the Software without restriction,+ including without limitation the rights to use, copy, modify, merge,+ publish, distribute, sublicense, and/or sell copies of the Software,+ and to permit persons to whom the Software is furnished to do so,+ subject to the following conditions:++ The above copyright notice and this permission notice shall be+ included in all copies or substantial portions of the Software.++ Any person wishing to distribute modifications to the Software is+ asked to send the modifications to the original developer so that+ they can be incorporated into the canonical version. This is,+ however, not a binding provision of this license.++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.++ ------------++ ModernMidi Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++ Redistribution and use in source and binary forms, with or without+ modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+ POSSIBILITY OF SUCH DAMAGE.+*/++#include <libremidi/api.hpp>+#include <libremidi/defaults.hpp>+#include <libremidi/input_configuration.hpp>+#include <libremidi/message.hpp>+#include <libremidi/observer_configuration.hpp>+#include <libremidi/output_configuration.hpp>++#if LIBREMIDI_NI_MIDI2_COMPAT+ #include <midi/sysex.h>+ #include <midi/universal_packet.h>+#endif+#include <any>++namespace libremidi+{+//! Main class for observing hotplug of MIDI 1.0 and 2.0 devices.+//! The callbacks will be called whenever a device is added or removed+//! for a given API.+class LIBREMIDI_EXPORT observer+{+public:+ //! Open an observer instance with the given configuration.+ //!+ //! * api_conf can be an instance of observer_configuration,+ //! such as jack_observer_configuration, winmm_observer_configuration, etc...+ //! * if no callbacks are passed, no secondary thread will be created unless absolutely necessary+ explicit observer(const observer_configuration& conf = {}) noexcept;+ explicit observer(observer_configuration conf, std::any api_conf);+ observer(const observer&) = delete;+ observer(observer&& other) noexcept;+ observer& operator=(const observer&) = delete;+ observer& operator=(observer&& other) noexcept;+ ~observer();++ [[nodiscard]] libremidi::API get_current_api() const noexcept;++ //! Return identifiers for the available MIDI ports+ [[nodiscard]] std::vector<libremidi::input_port> get_input_ports() const noexcept;+ [[nodiscard]] std::vector<libremidi::output_port> get_output_ports() const noexcept;++private:+ std::unique_ptr<class observer_api> impl_;+};++//! Main class for receiving MIDI 1.0 and 2.0 messages.+class LIBREMIDI_EXPORT midi_in+{+public:+ //! Construct a midi_in object with the default MIDI 1 back-end for the platform+ explicit midi_in(const input_configuration& conf) noexcept;++ //! Construct a midi_in object with a configuration object for a specific MIDI 1 back-end+ //! see configuration.hpp for the available configuration types.+ //! An exception will be thrown if the requested back-end cannot be opened.+ explicit midi_in(input_configuration conf, std::any api_conf);++ //! Construct a midi_in object with the default MIDI 2 back-end for the platform+ explicit midi_in(ump_input_configuration conf) noexcept;++ //! Construct a midi_in object with a configuration object for a specific MIDI 2 back-end+ //! see configuration.hpp for the available configuration types.+ //! An exception will be thrown if the requested back-end cannot be opened.+ explicit midi_in(ump_input_configuration conf, std::any api_conf);++ midi_in(const midi_in&) = delete;+ midi_in(midi_in&& other) noexcept;+ midi_in& operator=(const midi_in&) = delete;+ midi_in& operator=(midi_in&& other) noexcept;+ ~midi_in();++ //! Returns the MIDI API specifier for the current instance of midi_in.+ [[nodiscard]] libremidi::API get_current_api() const noexcept;++ //! Open a MIDI input connection+ stdx::error+ open_port(const input_port& pt, std::string_view local_port_name = "libremidi input");++ //! Create a virtual input port, with optional name, to allow software+ //! connections.+ //!+ //! \param portName An optional name for the application port that is+ //! used to connect to portId can be specified.+ stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port");++ stdx::error set_port_name(std::string_view portName);++ //! Close an open MIDI connection (if one exists).+ stdx::error close_port();++ //! Returns true if a port has been opened successfully with open_port or open_virtual_port+ [[nodiscard]] bool is_port_open() const noexcept;++ //! Returns true if a port is connected to another port.+ //! Never true for virtual ports.+ [[nodiscard]] bool is_port_connected() const noexcept;++ //! Returns the current timestamp for absolute ticks.+ timestamp absolute_timestamp() const noexcept;++private:+ std::unique_ptr<class midi_in_api> impl_;+};++//! Main class for sending MIDI 1.0 and 2.0 messages.+class LIBREMIDI_EXPORT midi_out+{+public:+ //! Construct a midi_out object with the default back-end for the platform+ explicit midi_out(const output_configuration& conf = {}) noexcept;++ //! Construct a midi_out object with a configuration object for a specific back-end+ //! see configuration.hpp for the available configuration types.+ //! An exception will be thrown if the requested back-end cannot be opened.+ explicit midi_out(output_configuration conf, std::any api_conf);++ midi_out(const midi_out&) = delete;+ midi_out(midi_out&& other) noexcept;+ midi_out& operator=(const midi_out&) = delete;+ midi_out& operator=(midi_out&& other) noexcept;+ ~midi_out();++ //! Returns the MIDI API specifier for the current instance of midi_out.+ [[nodiscard]] libremidi::API get_current_api() const noexcept;++ //! Open a MIDI output connection.+ stdx::error+ open_port(const output_port& pt, std::string_view local_port_name = "libremidi output") const;++ //! Close an open MIDI connection (if one exists).+ stdx::error close_port() const;++ //! Returns true if a port has been opened successfully with open_port or open_virtual_port+ [[nodiscard]] bool is_port_open() const noexcept;++ //! Returns true if a port is connected to another port.+ //! Never true for virtual ports.+ [[nodiscard]] bool is_port_connected() const noexcept;++ //! Create a virtual output port, with optional name, to allow software+ //! connections.+ //!+ //! \param portName An optional name for the application port that is+ //! used to connect to portId can be specified.+ stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port") const;++ stdx::error set_port_name(std::string_view portName) const;++ //! Immediately send a single message out an open MIDI output port.+ /*!+ An exception is thrown if an error occurs during output or an+ output connection was not previously established.+ */+ stdx::error send_message(const libremidi::message& message) const;++ //! Immediately send a single message to an open MIDI output port.+ stdx::error send_message(const unsigned char* message, size_t size) const;+ stdx::error send_message(std::span<const unsigned char>) const;+ stdx::error send_message(unsigned char b0) const;+ stdx::error send_message(unsigned char b0, unsigned char b1) const;+ stdx::error send_message(unsigned char b0, unsigned char b1, unsigned char b2) const;++ // Avoid silly mistakes:+ stdx::error send_message(auto* message) const noexcept = delete;+ stdx::error send_message(const auto* message) const noexcept = delete;++ //! Current time in the timestamp referential+ int64_t current_time();++ //! Try to schedule a message later in time if the underlying API supports it+ //! (currently not implemented anywhere)+ stdx::error schedule_message(int64_t timestamp, const unsigned char* message, size_t size) const;++ //! Immediately send a single UMP packet to an open MIDI output port.+ stdx::error send_ump(const uint32_t* message, size_t size) const;+ stdx::error send_ump(const libremidi::ump&) const;+ stdx::error send_ump(std::span<const uint32_t>) const;+ stdx::error send_ump(uint32_t b0) const;+ stdx::error send_ump(uint32_t b0, uint32_t b1) const;+ stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const;+ stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const;+ // Better compat with cmidi2+ stdx::error send_ump(int32_t b0) const;+ stdx::error send_ump(int64_t b01) const;+ stdx::error send_ump(uint64_t b01) const;++// Interop with ni-midi2+#if LIBREMIDI_NI_MIDI2_COMPAT+ stdx::error send_ump(const midi::universal_packet& pkt) const { send_ump(pkt.data, pkt.size()); }+ stdx::error send_ump(const midi::sysex7& msg, int group = 0)+ {+ midi::send_sysex7(msg, group, [&](const midi::sysex7_packet& x) { send_ump(x.data); });+ }+ stdx::error send_ump(const midi::sysex8& msg, int stream, int group = 0)+ {+ midi::send_sysex8(msg, stream, group, [&](const midi::sysex8_packet& x) { send_ump(x.data); });+ }+#endif++ //! Try to schedule an UMP packet later in time if the underlying API supports it+ //! (currently not implemented anywhere)+ stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const;++private:+ std::unique_ptr<class midi_out_api> impl_;+};+}++#if defined(LIBREMIDI_HEADER_ONLY)+ #include <libremidi/libremidi.cpp>+ #include <libremidi/midi_in.cpp>+ #include <libremidi/midi_out.cpp>+ #include <libremidi/observer.cpp>++ #if defined(__EMSCRIPTEN__)+ #include <libremidi/backends/emscripten/midi_access.cpp>+ #include <libremidi/backends/emscripten/midi_in.cpp>+ #include <libremidi/backends/emscripten/midi_out.cpp>+ #include <libremidi/backends/emscripten/observer.cpp>+ #endif+#endif
+ vendor/libremidi/message.hpp view
@@ -0,0 +1,301 @@+#pragma once+#include <libremidi/config.hpp>++#include <algorithm>+#include <cstdint>+#include <span>+#include <vector>+#if defined(__cpp_exceptions)+ #include <stdexcept>+#endif++namespace libremidi+{+enum class message_type : uint8_t+{+ INVALID = 0x0,+ // Standard Message+ NOTE_OFF = 0x80,+ NOTE_ON = 0x90,+ POLY_PRESSURE = 0xA0,+ CONTROL_CHANGE = 0xB0,+ PROGRAM_CHANGE = 0xC0,+ AFTERTOUCH = 0xD0,+ PITCH_BEND = 0xE0,++ // System Common Messages+ SYSTEM_EXCLUSIVE = 0xF0,+ TIME_CODE = 0xF1,+ SONG_POS_POINTER = 0xF2,+ SONG_SELECT = 0xF3,+ RESERVED1 = 0xF4,+ RESERVED2 = 0xF5,+ TUNE_REQUEST = 0xF6,+ EOX = 0xF7,++ // System Realtime Messages+ TIME_CLOCK = 0xF8,+ RESERVED3 = 0xF9,+ START = 0xFA,+ CONTINUE = 0xFB,+ STOP = 0xFC,+ RESERVED4 = 0xFD,+ ACTIVE_SENSING = 0xFE,+ SYSTEM_RESET = 0xFF+};++enum class meta_event_type : uint8_t+{+ SEQUENCE_NUMBER = 0x00,+ TEXT = 0x01,+ COPYRIGHT = 0x02,+ TRACK_NAME = 0x03,+ INSTRUMENT = 0x04,+ LYRIC = 0x05,+ MARKER = 0x06,+ CUE = 0x07,+ PATCH_NAME = 0x08,+ DEVICE_NAME = 0x09,+ CHANNEL_PREFIX = 0x20,+ MIDI_PORT = 0x21,+ END_OF_TRACK = 0x2F,+ TEMPO_CHANGE = 0x51,+ SMPTE_OFFSET = 0x54,+ TIME_SIGNATURE = 0x58,+ KEY_SIGNATURE = 0x59,+ PROPRIETARY = 0x7F,+ UNKNOWN = 0xFF+};++struct message+{+ midi_bytes bytes;+ int64_t timestamp{};++ message() noexcept = default;+ operator std::span<const unsigned char>() const noexcept { return {bytes.data(), bytes.size()}; }++ message(const midi_bytes& src_bytes, int64_t src_timestamp) noexcept+ : bytes(src_bytes)+ , timestamp(src_timestamp)+ {+ }+ message(std::initializer_list<unsigned char> args) noexcept+ : bytes{args}+ {+ }++ template <typename... Args>+ auto assign(Args&&... args)+ {+ return bytes.assign(std::forward<Args>(args)...);+ }+ template <typename... Args>+ auto insert(Args&&... args)+ {+ return bytes.insert(std::forward<Args>(args)...);+ }++ auto size() const noexcept { return bytes.size(); }+ auto empty() const noexcept { return bytes.empty(); }++ auto clear() noexcept { bytes.clear(); }++ auto& operator[](midi_bytes::size_type i) const noexcept { return bytes[i]; }+ auto& operator[](midi_bytes::size_type i) noexcept { return bytes[i]; }++ auto& front() const { return bytes.front(); }+ auto& back() const { return bytes.back(); }+ auto& front() { return bytes.front(); }+ auto& back() { return bytes.back(); }++ auto begin() const noexcept { return bytes.begin(); }+ auto end() const noexcept { return bytes.end(); }+ auto begin() noexcept { return bytes.begin(); }+ auto end() noexcept { return bytes.end(); }+ auto cbegin() const noexcept { return bytes.cbegin(); }+ auto cend() const noexcept { return bytes.cend(); }+ auto cbegin() noexcept { return bytes.cbegin(); }+ auto cend() noexcept { return bytes.cend(); }+ auto rbegin() const noexcept { return bytes.rbegin(); }+ auto rend() const noexcept { return bytes.rend(); }+ auto rbegin() noexcept { return bytes.rbegin(); }+ auto rend() noexcept { return bytes.rend(); }++ bool uses_channel(int channel) const+ {+#if defined(__cpp_exceptions)+ if (channel <= 0 || channel > 16)+ throw std::range_error("out of range");+#endif+ return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0);+ }++ int get_channel() const noexcept+ {+ if ((bytes[0] & 0xF0) != 0xF0)+ return (bytes[0] & 0xF) + 1;+ return 0;+ }++ bool is_meta_event() const noexcept { return bytes[0] == 0xFF; }++ meta_event_type get_meta_event_type() const noexcept+ {+ if (!is_meta_event())+ return meta_event_type::UNKNOWN;+ return static_cast<meta_event_type>(bytes[1]);+ }++ message_type get_message_type() const noexcept+ {+ if (bytes[0] >= static_cast<uint8_t>(message_type::SYSTEM_EXCLUSIVE))+ {+ return static_cast<message_type>(bytes[0] & 0xFF);+ }+ else+ {+ return static_cast<message_type>(bytes[0] & 0xF0);+ }+ }++ bool is_note_on_or_off() const noexcept+ {+ const auto status = get_message_type();+ return (status == message_type::NOTE_ON) || (status == message_type::NOTE_OFF);+ }+};++struct channel_events+{+ static constexpr uint8_t clamp_channel(int channel) noexcept+ {+ channel--;+ if (channel < 0)+ channel = 0;+ else if (channel > 15)+ channel = 15;+ return static_cast<uint8_t>(channel);+ }++ static uint8_t make_command(const message_type type, const int channel) noexcept+ {+ return static_cast<uint8_t>(static_cast<uint8_t>(type) | clamp_channel(channel));+ }++ static message note_on(uint8_t channel, uint8_t note, uint8_t velocity) noexcept+ {+ return {make_command(message_type::NOTE_ON, channel), note, velocity};+ }++ static message note_off(uint8_t channel, uint8_t note, uint8_t velocity) noexcept+ {+ return {make_command(message_type::NOTE_OFF, channel), note, velocity};+ }++ static message control_change(uint8_t channel, uint8_t control, uint8_t value) noexcept+ {+ return {make_command(message_type::CONTROL_CHANGE, channel), control, value};+ }++ static message program_change(uint8_t channel, uint8_t value) noexcept+ {+ return {make_command(message_type::PROGRAM_CHANGE, channel), value};+ }++ static message pitch_bend(uint8_t channel, int value) noexcept+ {+ return {+ make_command(message_type::PITCH_BEND, channel), static_cast<unsigned char>(value & 0x7F),+ static_cast<uint8_t>((value >> 7) & 0x7F)};+ }++ static message pitch_bend(uint8_t channel, uint8_t lsb, uint8_t msb) noexcept+ {+ return {make_command(message_type::PITCH_BEND, channel), lsb, msb};+ }++ static message poly_pressure(uint8_t channel, uint8_t note, uint8_t value) noexcept+ {+ return {make_command(message_type::POLY_PRESSURE, channel), note, value};+ }++ static message aftertouch(uint8_t channel, uint8_t value) noexcept+ {+ return {make_command(message_type::AFTERTOUCH, channel), value};+ }+};++struct meta_events+{+ static message end_of_track() noexcept { return {0xFF, 0x2F, 0}; }++ static message channel(int channel) noexcept+ {+ return {0xff, 0x20, 0x01, static_cast<uint8_t>(std::clamp(0, 0xff, channel - 1))};+ }++ static message tempo(int mpqn) noexcept+ {+ return {+ 0xff,+ 81,+ 3,+ static_cast<uint8_t>(mpqn >> 16),+ static_cast<uint8_t>(mpqn >> 8),+ static_cast<uint8_t>(mpqn)};+ }++ static message time_signature(+ int numerator, int denominator, int clocks_per_click = 1,+ int notated_32nd_notes_per_beat = 96)+ {+ int n = 1;+ int powTwo = 0;++ while (n < denominator)+ {+ n <<= 1;+ ++powTwo;+ }++ return {+ 0xff,+ 0x58,+ 0x04,+ static_cast<uint8_t>(numerator),+ static_cast<uint8_t>(powTwo),+ static_cast<uint8_t>(clocks_per_click),+ static_cast<uint8_t>(notated_32nd_notes_per_beat)};+ }++ // Where key index goes from -7 (7 flats, C♭ Major) to +7 (7 sharps, C♯+ // Major)+ static message key_signature(int keyIndex, bool isMinor)+ {+#if defined(__cpp_exceptions)+ if (keyIndex < -7 || keyIndex > 7)+ throw std::range_error("out of range");+#endif+ return {+ 0xff, 0x59, 0x02, static_cast<uint8_t>(keyIndex),+ isMinor ? static_cast<uint8_t>(1) : static_cast<uint8_t>(0)};+ }++ static message song_position(int positionInBeats) noexcept+ {+ return {+ 0xf2, static_cast<uint8_t>(positionInBeats & 127),+ static_cast<uint8_t>((positionInBeats >> 7) & 127)};+ }+};++struct track_event+{+ int tick = 0;+ int track = 0;+ message m;+};++typedef std::vector<track_event> midi_track;+}
+ vendor/libremidi/observer_configuration.hpp view
@@ -0,0 +1,86 @@+#pragma once+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>++#include <compare>+#include <string>++namespace libremidi+{+using client_handle = std::uint64_t;+using port_handle = std::uint64_t;++struct LIBREMIDI_EXPORT port_information+{+ // Handle to the client object:++ // ALSA Raw: unused+ // ALSA Seq: snd_seq_t*+ // CoreMIDI: MidiClientRef+ // WebMIDI: unused+ // JACK: jack_client_t*+ // PipeWire: unused+ // WinMM: unused+ // WinUWP: unused+ client_handle client = static_cast<client_handle>(-1);++ // ALSA Raw: { uint16_t card, device, sub, padding; }+ // ALSA Seq: { uint32_t client, uint32_t port; }+ // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t)+ // WebMIDI: unused+ // JACK: jack_port_id_t+ // PipeWire: port.id+ // WinMM: unset, identified by port_name+ // WinUWP: unused+ port_handle port = static_cast<port_handle>(-1);++ std::string manufacturer{};+ std::string device_name{};+ std::string port_name{};+ std::string display_name{};++ bool operator==(const port_information& other) const noexcept = default;+ std::strong_ordering operator<=>(const port_information& other) const noexcept = default;+};++struct input_port : port_information+{+ bool operator==(const input_port& other) const noexcept = default;+ std::strong_ordering operator<=>(const input_port& other) const noexcept = default;+};+struct output_port : port_information+{+ bool operator==(const output_port& other) const noexcept = default;+ std::strong_ordering operator<=>(const output_port& other) const noexcept = default;+};++using input_port_callback = std::function<void(const input_port&)>;+using output_port_callback = std::function<void(const output_port&)>;+struct observer_configuration+{+ midi_error_callback on_error{};+ midi_warning_callback on_warning{};++ input_port_callback input_added{};+ input_port_callback input_removed{};+ output_port_callback output_added{};+ output_port_callback output_removed{};++ // Observe hardware ports+ uint32_t track_hardware : 1 = true;++ // Observe software (virtual) ports if the API provides it+ uint32_t track_virtual : 1 = false;++ // Observe any port - some systems have other weird port types than hw / sw, this covers them+ uint32_t track_any : 1 = false;++ // Notify of the existing ports in the observer constructor+ uint32_t notify_in_constructor : 1 = true;++ bool has_callbacks() const noexcept+ {+ return input_added || input_removed || output_added || output_removed;+ }+};+}
+ vendor/libremidi/output_configuration.hpp view
@@ -0,0 +1,21 @@+#pragma once+#include <libremidi/config.hpp>+#include <libremidi/error.hpp>+#include <libremidi/input_configuration.hpp>++namespace libremidi+{+struct output_configuration+{+ //! Set an error callback function to be invoked when an error has occured.+ /*!+ The callback function will be called whenever an error has occured. It is+ best to set the error callback function before opening a port.+ */+ midi_error_callback on_error{};+ midi_warning_callback on_warning{};++ //! Timestamp mode for the timestamps passed to schedule_message+ uint32_t timestamps : 3 = timestamp_mode::Absolute;+};+}
+ vendor/libremidi/reader.hpp view
@@ -0,0 +1,75 @@+/*+Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+*/++#pragma once++#include <libremidi/message.hpp>++namespace libremidi+{+/**+ * @brief reads Standard MIDI files (SMF).+ *+ * Usage:++ * ```+ * libremidi::reader r;+ * auto res = r.parse(midi_bytes, num_bytes);+ * ```+ */+class LIBREMIDI_EXPORT reader+{+public:+ enum parse_result+ {+ invalid, //! Nothing could be parsed+ incomplete, //! Some of the data could be parsed, but not all: there may be missing events /+ //! tracks+ complete, //! All the data could be parsed but not necessarily validated+ validated //! The data could be parsed and conforms to SMF rules+ };+ explicit reader(bool useAbsolute = false);+ ~reader();++ parse_result parse(const uint8_t* data, std::size_t size) noexcept;+ parse_result parse(const std::vector<uint8_t>& buffer) noexcept;+ parse_result parse(std::span<uint8_t> buffer) noexcept;++ [[nodiscard]] double get_end_time() const noexcept;++ float ticksPerBeat{}; // precision (number of ticks distinguishable per second)+ float startingTempo{};+ int format{};++ std::vector<midi_track> tracks;++private:+ bool useAbsoluteTicks{};+};+}++#if defined(LIBREMIDI_HEADER_ONLY)+ #include <libremidi/reader.cpp>+#endif
@@ -0,0 +1,33 @@+#pragma once+#include <libremidi/api.hpp>+#include <libremidi/config.hpp>++#include <any>++namespace libremidi+{++class shared_context+{+public:+ shared_context() = default;+ virtual ~shared_context() = default;+ shared_context(const shared_context&) = delete;+ shared_context(shared_context&&) = delete;+ shared_context& operator=(const shared_context&) = delete;+ shared_context& operator=(shared_context&&) = delete;++ virtual void start_processing() = 0;+ virtual void stop_processing() = 0;+};++struct shared_configurations+{+ std::shared_ptr<shared_context> context;+ std::any observer, in, out;+};++LIBREMIDI_EXPORT+shared_configurations create_shared_context(libremidi::API api, std::string_view client_name);++}
+ vendor/libremidi/system_error2.hpp view
@@ -0,0 +1,3501 @@+#ifndef STDX_COMPILER_HPP+ #define STDX_COMPILER_HPP++ // Check compiler macros. Note that Clang defines __GNUC__ and other GNU macros as well,+ // but GNU does not define Clang macros, so we must check for Clang first.++ #if defined(__llvm__) || defined(__clang__)++ // -------- LLVM/Clang++ #define STDX_CLANG_COMPILER 1++ #if defined(__cpp_variable_templates) && (__cplusplus >= 201703L)+ #define STDX_VARIABLE_TEMPLATES 1+ #endif++ #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__)++ // -------- GNU G++++ #include <version>++ #define STDX_GCC_COMPILER 1++ #if (__GNUC__ >= 5) && (__cplusplus >= 201703L)+ #define STDX_VARIABLE_TEMPLATES 1+ #endif++ #if ((__GNUC__ > 7) || ((__GNUC__ == 7) && (__GNUC_MINOR__ >= 1)))+ #define STDX_TRIVIALLY_MOVE_CONSTRUCTIBLE 1+ #endif++ #endif++ #if defined(STDX_GCC_COMPILER)+ #if (__GNUC__ == 7) && ((__GNUC_MINOR__ >= 1) && (__GNUC_MINOR__ <= 3))+ #define STDX_GCC7_WORKAROUND_CONSTEXPR+ #else+ #define STDX_GCC7_WORKAROUND_CONSTEXPR constexpr+ #endif+ #else+ #define STDX_GCC7_WORKAROUND_CONSTEXPR constexpr+ #endif++ // Add a legacy constexpr macro for cases where GCC < 5 incorrectly applies the const+ // qualifier to constexpr member functions or does not support relaxed constexpr functions+ //+ #if defined(STDX_GCC_COMPILER) && (__GNUC__ < 5)+ #define STDX_LEGACY_CONSTEXPR+ #else+ #define STDX_LEGACY_CONSTEXPR constexpr+ #endif++ #if defined(_MSC_VER) && (_MSC_VER >= 1910)+ #define STDX_MSVC_EMPTY_BASE_CLASSES __declspec(empty_bases)+ #else+ #define STDX_MSVC_EMPTY_BASE_CLASSES+ #endif++ #if defined(__cpp_impl_trivially_relocatable)+ #define STDX_TRIVIALLY_RELOCATABLE [[trivially_relocatable]]+ #else+ #define STDX_TRIVIALLY_RELOCATABLE+ #endif++#endif // STDX_COMPILER_HPP+++#ifndef STDX_TYPE_TRAITS_HPP+ #define STDX_TYPE_TRAITS_HPP++ #include <type_traits>++namespace stdx {++// Implementation of std::void_t for use with pre-C++17 compilers.+//+namespace detail {++template <class... T>+struct void_t_impl+{+ using type = void;+};++} // end namespace detail++template <class... T>+using void_t = typename detail::void_t_impl<T...>::type;++template <class T, class U = T>+struct dependent_type+{+ using type = U;+};++template <class T, class U = T>+using dependent_type_t = typename dependent_type<T, U>::type;++template <class... B>+struct disjunction : std::false_type+{ };++template <class B1>+struct disjunction<B1> : B1+{ };++template<class B1, class... Bn>+struct disjunction<B1, Bn...>+ :+ std::conditional_t<+ bool(B1::value),+ B1,+ disjunction<Bn...>+ >+{ };++struct sentinel_type+{+ static constexpr bool value = true;+ using type = void;+};++template <bool B>+struct bool_constant : std::integral_constant<bool, B>+{ };++// Implementation of std::remove_cvref for use with pre-C++20 compilers.+//+template <class T>+struct remove_cvref+{+ using type = std::remove_cv_t<std::remove_reference_t<T>>;+};++template <class T>+using remove_cvref_t = typename remove_cvref<T>::type;++ #if defined(STDX_GCC_COMPILER)+ // Support missing functionality on older compilers (<= gcc 4.7)+ //+ #if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 7)+ // Map the old incorrect type-trait names to the newer correct ones+template <class T>+using is_trivially_copyable = std::is_trivial<T>;++template <class T>+using is_trivially_copy_constructible = std::has_trivial_copy_constructor<T>;++template <class T>+using is_trivially_destructible = std::has_trivial_destructor<T>;+ #elif (__GNUC__ < 5)+template <class T>+using is_trivially_copyable = std::is_trivial<T>;++template <class T>+using is_trivially_copy_constructible = std::has_trivial_copy_constructor<T>;++template <class T>+using is_trivially_destructible = std::is_trivially_destructible<T>;+ #else+using std::is_trivially_destructible;+using std::is_trivially_copyable;+using std::is_trivially_copy_constructible;+ #endif+ #else+using std::is_trivially_destructible;+using std::is_trivially_copyable;+using std::is_trivially_copy_constructible;+ #endif++ #if defined(STDX_TRIVIALLY_MOVE_CONSTRUCTIBLE)+template <class T>+using is_trivially_move_constructible = std::is_trivially_move_constructible<T>;+ #else+template <class T>+using is_trivially_move_constructible = is_trivially_copyable<T>;+ #endif++ #if defined(__cpp_lib_trivially_relocatable)+using std::is_trivially_relocatable;+ #elif defined(__has_builtin)+ #if __has_builtin(__is_trivially_relocatable)+template <class T>+struct is_trivially_relocatable : std::bool_constant<__is_trivially_relocatable(T)> { };+ #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+ #else+template <class T>+struct is_trivially_relocatable : is_trivially_copyable<T> { };+ #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+ #endif+ #else+template <class T>+struct is_trivially_relocatable : is_trivially_copyable<T> { };+ #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+ #endif++ #if __cplusplus >= 201703L+ #define STDX_LEGACY_INLINE_CONSTEXPR inline constexpr+ #else+ #define STDX_LEGACY_INLINE_CONSTEXPR constexpr+ #endif++} // end namespace stdx++#endif++++#ifndef STDX_BIT_CAST_HPP+ #define STDX_BIT_CAST_HPP++ #include <cstdint>+ #include <cstring>+++namespace stdx {++namespace detail {++template <class To, class From>+using use_static_cast = bool_constant<+ ((std::is_integral<To>::value || std::is_enum<To>::value)+ && (std::is_integral<From>::value || std::is_enum<From>::value))+ || (std::is_same<To, From>::value && std::is_copy_constructible<To>::value)+ >;++template <class T>+using is_integral_ptr_t = bool_constant<+ std::is_same<T, std::intptr_t>::value+ || std::is_same<T, std::uintptr_t>::value+ >;++template <class To, class From>+using use_reinterpret_cast = bool_constant<+ !std::is_same<To, From>::value+ && ((+ std::is_pointer<To>::value+ && std::is_pointer<From>::value+ && std::is_convertible<From, To>::value+ )+ || (std::is_pointer<To>::value && is_integral_ptr_t<From>::value)+ || (std::is_pointer<From>::value && is_integral_ptr_t<To>::value)+ )+ >;++ #if defined(STDX_GCC_COMPILER)+template <class To, class From>+using use_union_type_punning = bool_constant<+ !use_static_cast<To, From>::value+ && !use_reinterpret_cast<To, From>::value+ && !std::is_array<To>::value+ && !std::is_array<From>::value+ >;++template <class To, class From>+union bit_cast_union+{+ From from;+ To to;+};+ #else+template <class To, class From>+using use_union_type_punning = std::false_type;+ #endif++template <class To, class From>+using can_bit_cast = bool_constant<+ (sizeof(To) == sizeof(From))+ && is_trivially_copyable<From>::value+ && is_trivially_copyable<To>::value+ >;++} // end namespace detail++template <+ class To,+ class From,+ class = std::enable_if_t<+ detail::can_bit_cast<To, From>::value+ && detail::use_static_cast<To, From>::value+ >+ >+constexpr To bit_cast(const From& from) noexcept+{+ return static_cast<To>(from);+}++template <+ class To,+ class From,+ class = std::enable_if_t<+ detail::can_bit_cast<To, From>::value+ && detail::use_reinterpret_cast<To, From>::value+ >,+ int = 0+ >+constexpr To bit_cast(const From& from) noexcept+{+ return reinterpret_cast<To>(from);+}++ #if defined(STDX_GCC_COMPILER) // GCC allows union type punning+template <+ class To,+ class From,+ class = std::enable_if_t<+ detail::can_bit_cast<To, From>::value+ && detail::use_union_type_punning<To, From>::value+ >,+ class = void+ >+constexpr To bit_cast(const From& from) noexcept+{+ return detail::bit_cast_union<To, From>{from}.to;+}+ #elif defined(STDX_CLANG_COMPILER)+ #if __has_builtin(__builtin_bit_cast)+template <+ class To,+ class From,+ class = std::enable_if_t<+ detail::can_bit_cast<To, From>::value+ && !detail::use_static_cast<To, From>::value+ && !detail::use_reinterpret_cast<To, From>::value+ >,+ class = void+ >+constexpr To bit_cast(const From& from) noexcept+{+ return __builtin_bit_cast(To, from);+}+ #else+template <+ class To,+ class From,+ class = std::enable_if_t<+ detail::can_bit_cast<To, From>::value+ && !detail::use_static_cast<To, From>::value+ && !detail::use_reinterpret_cast<To, From>::value+ >,+ class = void+ >+To bit_cast(const From& from) noexcept+{+ To to;+ std::memcpy(&to, &from, sizeof(To));+ return to;+}+ #endif+ #endif // STDX_CLANG_COMPILER++template <class To, class From>+struct is_bit_castable+ :+ bool_constant<+ (sizeof(To) == sizeof(From))+ && is_trivially_copyable<From>::value+ && is_trivially_copyable<To>::value+ >+{ };++} // end namespace stdx++#endif++++#ifndef STDX_INTRUSIVE_POINTER_HPP+ #define STDX_INTRUSIVE_POINTER_HPP+++ #include <cstdint>+ #include <atomic>+ #include <memory>++namespace stdx {++struct default_intrusive_reference_count;+class default_intrusive_reference_control;++template <+ class T,+ class RefCountAccessor = default_intrusive_reference_count,+ class Deleter = std::default_delete<T>,+ class Pointer = T*+ >+class intrusive_ptr;++using ref_count_t = std::size_t;++struct enable_reference_count+{+protected:++ constexpr enable_reference_count() noexcept : m_reference_count(1)+ { }++public:++ std::atomic<ref_count_t>& shared_reference_count() noexcept+ {+ return m_reference_count;+ }++private:++ std::atomic<ref_count_t> m_reference_count;+};++struct default_intrusive_reference_count+{+ template <class Pointer>+ std::atomic<ref_count_t>& operator()(Pointer p) const noexcept+ {+ return p->shared_reference_count();+ }+};++namespace detail {++template <class Pointer>+struct pointer_wrapper+{+ constexpr pointer_wrapper() noexcept : shared_object(nullptr)+ { }++ constexpr explicit pointer_wrapper(Pointer p) noexcept : shared_object(p)+ { }++ constexpr pointer_wrapper(const pointer_wrapper& p) noexcept = default;++ pointer_wrapper(pointer_wrapper&& p) noexcept+ : shared_object(p.shared_object)+ {+ p.shared_object = nullptr;+ }++ pointer_wrapper& operator = (const pointer_wrapper&) noexcept = default;++ pointer_wrapper& operator = (pointer_wrapper&& p) noexcept+ {+ shared_object = p.shared_object;+ p.shared_object = nullptr;+ return *this;+ }++ void assign(Pointer ptr) noexcept+ {+ shared_object = ptr;+ }++ Pointer shared_object;+};++template <+ class T,+ class RefCountAccessor,+ class Deleter,+ class Pointer,+ class PointerImplementation+ >+class intrusive_ptr_base+{+protected:++ using pointer = Pointer;+ using count_type = ref_count_t;++ template <class, class, class, class>+ friend class reference_count_base;++ constexpr intrusive_ptr_base() = default;++ constexpr intrusive_ptr_base(pointer p) noexcept+ : m_impl(p)+ { }++ template <class RefCountAccessorForwardingReference>+ constexpr explicit intrusive_ptr_base(+ RefCountAccessorForwardingReference&& f,+ std::enable_if_t<+ std::is_constructible<+ RefCountAccessor,+ RefCountAccessorForwardingReference&&+ >::value+ >* = nullptr+ )+ : m_impl(std::forward<RefCountAccessorForwardingReference>(f))+ { }++ template <class RefCountAccessorForwardingReference>+ explicit intrusive_ptr_base(+ pointer ptr,+ RefCountAccessorForwardingReference&& f,+ std::enable_if_t<+ std::is_constructible<+ RefCountAccessor,+ RefCountAccessorForwardingReference&&+ >::value+ >* = nullptr+ )+ : m_impl(ptr, std::forward<RefCountAccessorForwardingReference>(f))+ { }++ template <+ class RefCountAccessorForwardingReference,+ class DeleterForwardingReference+ >+ constexpr explicit intrusive_ptr_base(+ RefCountAccessorForwardingReference&& f,+ DeleterForwardingReference&& d,+ typename std::enable_if<+ std::is_constructible<+ RefCountAccessor,+ RefCountAccessorForwardingReference&&+ >::value+ && std::is_constructible<+ Deleter,+ DeleterForwardingReference&&+ >::value+ >::type* = nullptr+ )+ :+ m_impl(+ std::forward<RefCountAccessorForwardingReference>(f),+ std::forward<DeleterForwardingReference>(d)+ )+ { }++ template <+ class RefCountAccessorForwardingReference,+ class DeleterForwardingReference+ >+ explicit intrusive_ptr_base(+ pointer ptr,+ RefCountAccessorForwardingReference&& f,+ DeleterForwardingReference&& d,+ std::enable_if_t<+ std::is_constructible<+ RefCountAccessor,+ RefCountAccessorForwardingReference&&+ >::value+ && std::is_constructible<+ Deleter,+ DeleterForwardingReference&&+ >::value+ >* = nullptr+ )+ :+ m_impl(+ ptr,+ std::forward<RefCountAccessorForwardingReference>(f),+ std::forward<DeleterForwardingReference>(d)+ )+ { }++ void assign(pointer ptr) noexcept+ {+ m_impl.assign(ptr);+ }++ template <+ class RefCountAccessorForwardingReference,+ class DeleterForwardingReference+ >+ void assign(+ pointer ptr,+ RefCountAccessorForwardingReference&& f,+ DeleterForwardingReference&& d+ )+ {+ m_impl.assign(+ ptr,+ std::forward<RefCountAccessorForwardingReference>(f),+ std::forward<DeleterForwardingReference>(d)+ );+ }++ void swap(intrusive_ptr_base& other)+ {+ m_impl.swap(other.m_impl);+ }++ struct STDX_MSVC_EMPTY_BASE_CLASSES impl+ :+ PointerImplementation,+ RefCountAccessor,+ Deleter+ {+ constexpr impl() = default;++ explicit impl(pointer ptr) noexcept : PointerImplementation(ptr)+ { }++ template <+ class RefCountAccess,+ class = std::enable_if_t<+ std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+ >+ >+ constexpr explicit impl(RefCountAccess&& f)+ : RefCountAccessor(std::forward<RefCountAccess>(f))+ { }++ template <+ class RefCountAccess,+ class = std::enable_if_t<+ std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+ >+ >+ impl(pointer ptr, RefCountAccess&& f)+ :+ PointerImplementation(ptr),+ RefCountAccessor(std::forward<RefCountAccessor>(f))+ { }++ template <+ class RefCountAccess,+ class D,+ class = std::enable_if_t<+ std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+ && std::is_constructible<Deleter, D&&>::value+ >+ >+ constexpr impl(RefCountAccess&& f, D&& d)+ :+ RefCountAccessor(std::forward<RefCountAccess>(f)),+ Deleter(std::forward<D>(d))+ { }++ template <+ class RefCountAccess,+ class D,+ class = std::enable_if_t<+ std::is_constructible<RefCountAccessor, RefCountAccess&&>::value+ && std::is_constructible<Deleter, D&&>::value+ >+ >+ impl(pointer ptr, RefCountAccess&& f, D&& d)+ :+ PointerImplementation(ptr),+ RefCountAccessor(std::forward<RefCountAccess>(f)),+ Deleter(std::forward<D>(d))+ { }++ impl(const impl&) = default;+ impl& operator = (const impl&) = default;+ impl(impl&&) = default;+ impl& operator = (impl&&) = default;++ Deleter& get_deleter() noexcept+ {+ return static_cast<Deleter&>(*this);+ }++ const Deleter& get_deleter() const noexcept+ {+ return static_cast<const Deleter&>(*this);+ }++ void assign(pointer ptr) noexcept+ {+ static_cast<PointerImplementation&>(*this).assign(ptr);+ }++ void assign(std::nullptr_t) noexcept+ {+ static_cast<PointerImplementation&>(*this).assign(nullptr);+ }++ void swap(impl& other)+ {+ std::swap(+ static_cast<PointerImplementation&>(*this),+ static_cast<PointerImplementation&>(other)+ );++ std::swap(static_cast<RefCountAccessor&>(*this), static_cast<RefCountAccessor&>(other));+ std::swap(+ static_cast<Deleter&>(this->get_deleter()),+ static_cast<Deleter&>(other.get_deleter())+ );+ }+ };++ impl m_impl;++ void increment_shared_reference_count(+ std::memory_order order = std::memory_order_relaxed+ ) const noexcept+ {+ if (ptr()) ref_count_func()(ptr()).fetch_add(1, order);+ }++ void decrement_shared_reference_count() noexcept+ {+ if (ptr())+ {+ if (ref_count_func()(ptr()).fetch_sub(1, std::memory_order_release) == 1)+ {+ std::atomic_thread_fence(std::memory_order_acquire);+ invoke_deleter(ptr());+ }+ }+ }++ // ----- accessors and modifiers++ pointer& ptr() noexcept+ {+ return static_cast<PointerImplementation&>(m_impl).shared_object;+ }++ constexpr pointer ptr() const noexcept+ {+ return static_cast<const PointerImplementation&>(m_impl).shared_object;+ }++ RefCountAccessor& ref_count_func() noexcept+ {+ return static_cast<RefCountAccessor&>(m_impl);+ }++ const RefCountAccessor& ref_count_func() const noexcept+ {+ return static_cast<const RefCountAccessor&>(m_impl);+ }++ Deleter& deleter() noexcept+ {+ return m_impl.get_deleter();+ }++ const Deleter& deleter() const noexcept+ {+ return m_impl.get_deleter();+ }++ intrusive_ptr<+ T,+ RefCountAccessor,+ Deleter,+ Pointer+ > make_intrusive_pointer(pointer p) const noexcept+ {+ return intrusive_ptr<T, RefCountAccessor, Deleter, Pointer>{+ p,+ ref_count_func(),+ deleter()+ };+ }++private:++ void invoke_deleter(pointer p)+ {+ m_impl.get_deleter()(p);+ }++ void invoke_deleter(pointer p) const+ {+ m_impl.get_deleter()(p);+ }++ template <class WeakReferenceCountDescriptor>+ void invoke_deleter(pointer p, WeakReferenceCountDescriptor* d)+ {+ maybe_delete_shared_object(p, m_impl.get_deleter(), d);+ }++ template <class WeakReferenceCountDescriptor>+ void invoke_deleter(pointer p, WeakReferenceCountDescriptor* d) const+ {+ maybe_delete_shared_object(p, m_impl.get_deleter(), d);+ }+};++} // end namespace detail++template <+ class T,+ class RefCountAccessor,+ class Deleter,+ class Pointer+ >+class STDX_TRIVIALLY_RELOCATABLE intrusive_ptr+ :+ public detail::intrusive_ptr_base<+ T,+ RefCountAccessor,+ Deleter,+ Pointer,+ detail::pointer_wrapper<Pointer>+ >+{+ using base_type = detail::intrusive_ptr_base<+ T,+ RefCountAccessor,+ Deleter,+ Pointer,+ detail::pointer_wrapper<Pointer>+ >;++public:++ using pointer = Pointer;+ using element_type = T;+ using ref_count_accessor = RefCountAccessor;+ using deleter_type = Deleter;+ using count_type = typename base_type::count_type;++ constexpr intrusive_ptr() noexcept : base_type()+ { }++ constexpr intrusive_ptr(std::nullptr_t) noexcept : base_type()+ { }++ template <class RefCountAccess>+ constexpr intrusive_ptr(std::nullptr_t, RefCountAccess&& f)+ : base_type(std::forward<RefCountAccess>(f))+ { }++ template <class RefCountAccess, class D>+ constexpr intrusive_ptr(std::nullptr_t, RefCountAccess&& f, D&& d)+ : base_type(std::forward<RefCountAccess>(f), std::forward<D>(d))+ { }++ constexpr explicit intrusive_ptr(Pointer ptr) noexcept+ : base_type(ptr)+ {+ // reference count must initially be >= 1+ }++ template <class RefCountAccess>+ intrusive_ptr(Pointer ptr, RefCountAccess&& f) noexcept+ : base_type(ptr, std::forward<RefCountAccess>(f))+ {+ // reference count must initially be >= 1+ }++ template <class RefCountAccess, class D>+ intrusive_ptr(Pointer ptr, RefCountAccess&& f, D&& d) noexcept+ :+ base_type(+ ptr,+ std::forward<RefCountAccess>(f),+ std::forward<D>(d)+ )+ {+ // reference count must initially be >= 1+ }++ // Copy constructor+ //+ intrusive_ptr(const intrusive_ptr& rhs) noexcept+ : base_type(rhs)+ {+ this->increment_shared_reference_count();+ }++ // Converting copy-constructor+ //+ template <+ class Y,+ class Ptr,+ class = std::enable_if_t<std::is_convertible<Ptr, pointer>::value>+ >+ intrusive_ptr(const intrusive_ptr<Y, RefCountAccessor, Deleter, Ptr>& rhs) noexcept+ :+ base_type(+ rhs.get(),+ rhs.ref_count_func(),+ rhs.get_deleter()+ )+ {+ this->increment_shared_reference_count();+ }++ // Move constructor+ //+ intrusive_ptr(intrusive_ptr&& rhs) noexcept+ : base_type(std::move(rhs))+ { }++ // Copy assignment+ //+ intrusive_ptr& operator = (const intrusive_ptr& rhs) noexcept+ {+ rhs.increment_shared_reference_count();+ this->decrement_shared_reference_count();++ static_cast<base_type&>(*this) = static_cast<const base_type&>(rhs);+ return *this;+ }++ // Move assignment+ //+ intrusive_ptr& operator = (intrusive_ptr&& rhs) noexcept+ {+ if (this != std::addressof(rhs))+ {+ this->decrement_shared_reference_count();+ static_cast<base_type&>(*this) = std::move(static_cast<base_type&>(rhs));+ }++ return *this;+ }++ ~intrusive_ptr() noexcept+ {+ this->decrement_shared_reference_count();+ }++ void reset() noexcept+ {+ this->decrement_shared_reference_count();+ this->assign(nullptr);+ }++ void reset(std::nullptr_t) noexcept+ {+ reset();+ }++ void reset(Pointer ptr) noexcept+ {+ if (this->ptr() != ptr)+ {+ this->decrement_shared_reference_count();+ this->assign(ptr);+ this->increment_shared_reference_count();+ }+ }++ void swap(intrusive_ptr& other) noexcept+ {+ if (this->get() != other.get())+ {+ base_type::swap(other);+ }+ }++ pointer get() const noexcept+ {+ return this->ptr();+ }++ element_type& operator * () const noexcept+ {+ return *this->get();+ }++ pointer operator -> () const noexcept+ {+ return this->get();+ }++ count_type use_count() const noexcept+ {+ return this->ref_count_func()(get()).load(std::memory_order_acquire);+ }++ explicit operator bool() const noexcept+ {+ return static_cast<bool>(this->get());+ }++ Deleter get_deleter() noexcept+ {+ return this->deleter();+ }++ const Deleter& get_deleter() const noexcept+ {+ return this->deleter();+ }++ RefCountAccessor ref_count_access() noexcept+ {+ return this->ref_count_func();+ }++ const RefCountAccessor& ref_count_access() const noexcept+ {+ return this->ref_count_func();+ }++private:++ template <class Y, class G, class D, class P>+ friend class intrusive_ptr;+};++// -------------- Global equality operators+//+template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator == (+ const intrusive_ptr<T, G1, D1, P1>& lhs,+ const intrusive_ptr<U, G2, D2, P2>& rhs+ ) noexcept+{+ return lhs.get() == rhs.get();+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator != (+ const intrusive_ptr<T, G1, D1, P1>& lhs,+ const intrusive_ptr<U, G2, D2, P2>& rhs+ ) noexcept+{+ return !(lhs == rhs);+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator < (+ const intrusive_ptr<T, G1, D1, P1>& lhs,+ const intrusive_ptr<U, G2, D2, P2>& rhs+ ) noexcept+{+ using pointer1 = typename intrusive_ptr<T, G1, D1, P1>::pointer;+ using pointer2 = typename intrusive_ptr<U, G2, D2, P2>::pointer;+ using common_type = typename std::common_type<pointer1, pointer2>::type;++ return std::less<common_type>{}(lhs.get(), rhs.get());+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator > (+ const intrusive_ptr<T, G1, D1, P1>& lhs,+ const intrusive_ptr<U, G2, D2, P2>& rhs+ ) noexcept+{+ return rhs < lhs;+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator <= (+ const intrusive_ptr<T, G1, D1, P1>& lhs,+ const intrusive_ptr<U, G2, D2, P2>& rhs+ ) noexcept+{+ return !(rhs < lhs);+}++template <class T, class G1, class D1, class P1, class U, class G2, class D2, class P2>+bool operator >= (+ const intrusive_ptr<T, G1, D1, P1>& lhs,+ const intrusive_ptr<U, G2, D2, P2>& rhs+ ) noexcept+{+ return !(lhs < rhs);+}++template <class T, class G1, class D1, class P1>+bool operator == (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+ return !lhs;+}++template <class T, class G1, class D1, class P1>+bool operator == (std::nullptr_t, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+ return !rhs;+}++template <class T, class G1, class D1, class P1>+bool operator != (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+ return bool(lhs);+}++template <class T, class G1, class D1, class P1>+bool operator != (std::nullptr_t, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+ return bool(rhs);+}++template <class T, class G1, class D1, class P1>+bool operator < (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+ using pointer = typename intrusive_ptr<T, G1, D1, P1>::pointer;+ return std::less<pointer>{}(lhs.get(), nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator < (std::nullptr_t, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+ using pointer = typename intrusive_ptr<T, G1, D1, P1>::pointer;+ return std::less<pointer>{}(nullptr, rhs.get());+}++template <class T, class G1, class D1, class P1>+bool operator > (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t) noexcept+{+ return (nullptr < lhs);+}++template <class T, class G1, class D1, class P1>+bool operator > (std::nullptr_t lhs, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+ return (rhs < nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator <= (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t rhs) noexcept+{+ return !(nullptr < lhs);+}++template <class T, class G1, class D1, class P1>+bool operator <= (std::nullptr_t lhs, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+ return !(rhs < nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator >= (const intrusive_ptr<T, G1, D1, P1>& lhs, std::nullptr_t rhs) noexcept+{+ return !(lhs < nullptr);+}++template <class T, class G1, class D1, class P1>+bool operator >= (std::nullptr_t lhs, const intrusive_ptr<T, G1, D1, P1>& rhs) noexcept+{+ return !(nullptr < rhs);+}++template <class T, class G, class D, class P>+void swap(intrusive_ptr<T, G, D, P>& lhs, intrusive_ptr<T, G, D, P>& rhs) noexcept+{+ lhs.swap(rhs);+}++ #ifdef STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+template <class Y, class G, class D, class P>+struct is_trivially_relocatable<intrusive_ptr<Y,G,D,P>> : std::true_type+{ };+ #endif++} // end namespace stdx++#endif // include guard++++#ifndef STDX_STRING_REF_HPP+ #define STDX_STRING_REF_HPP++ #include <cstring>+ #include <cstddef>+ #include <atomic>++namespace stdx {++class string_ref;++namespace detail {++constexpr const char* cstring_null_scan(const char* s) noexcept+{+ return *s ? cstring_null_scan(s + 1) : s;+}++} // end namespace detail++class string_ref+{+protected:++ struct state_type;++public:++ using value_type = const char;+ using size_type = std::size_t;+ using pointer = const char*;+ using const_pointer = const char*;+ using iterator = const char*;+ using const_iterator = const char*;++ struct resource_management+ {+ using copy_constructor = state_type(*)(const string_ref&);+ using move_constructor = state_type(*)(string_ref&&);+ using destructor = void(*)(string_ref&);++ constexpr resource_management() noexcept+ : copy{nullptr}, move{nullptr}, destroy{nullptr}+ { }++ constexpr resource_management(+ copy_constructor cctor,+ move_constructor mctor,+ destructor dtor+ ) noexcept+ : copy{cctor}, move{mctor}, destroy{dtor}+ { }++ copy_constructor copy;+ move_constructor move;+ destructor destroy;+ };++ constexpr string_ref() noexcept : m_begin(nullptr), m_end(nullptr), context{}+ { }++ constexpr string_ref(const char* beg) noexcept+ : m_begin(beg), m_end(detail::cstring_null_scan(beg)), context{}+ { }++ constexpr string_ref(const char* beg, const char* e) noexcept+ : m_begin(beg), m_end(e), context{}+ { }++ constexpr string_ref(const char* beg, resource_management rm) noexcept+ :+ m_begin(beg),+ m_end(detail::cstring_null_scan(beg)),+ m_resource_management(rm),+ context{}+ { }++ constexpr string_ref(const char* beg, const char* e, resource_management rm) noexcept+ : m_begin(beg), m_end(e), m_resource_management(rm), context{}+ { }++ constexpr string_ref(+ const char* beg,+ const char* e,+ resource_management rm,+ void* ctx+ ) noexcept+ : m_begin(beg), m_end(e), m_resource_management(rm), context{ctx}+ { }++ STDX_GCC7_WORKAROUND_CONSTEXPR string_ref(const string_ref& s)+ : string_ref{s.m_resource_management.copy ? s.m_resource_management.copy(s) : s.state()}+ { }++ STDX_GCC7_WORKAROUND_CONSTEXPR string_ref(string_ref&& s)+ :+ string_ref{+ s.m_resource_management.move ?+ s.m_resource_management.move(std::move(s)) : s.state()+ }+ { }++ string_ref& operator = (const string_ref& s)+ {+ string_ref tmp = s;+ *this = std::move(tmp);+ return *this;+ }++ string_ref& operator = (string_ref&& s)+ {+ if (this != &s)+ {+ if (m_resource_management.destroy) m_resource_management.destroy(*this);++ // This is legal because of the common initial sequence and the fact+ // that any type erased object must be trivially relocatable.+ *this = string_ref_state_union{std::move(s)}.state;+ }++ return *this;+ }++ ~string_ref() noexcept+ {+ if (m_resource_management.destroy) m_resource_management.destroy(*this);+ }++ bool empty() const noexcept { return m_begin == m_end; }++ size_type size() const noexcept { return m_end - m_begin; }++ const_pointer data() const noexcept { return m_begin; }++ iterator begin() noexcept { return m_begin; }++ iterator end() noexcept { return m_end; }++ const_iterator begin() const noexcept { return m_begin; }++ const_iterator end() const noexcept { return m_end; }++ const_iterator cbegin() const noexcept { return m_begin; }++ const_iterator cend() const noexcept { return m_end; }++protected:++ struct state_type+ {+ pointer m_begin;+ pointer m_end;+ resource_management m_resource_management;+ void* context;+ };++ state_type state() const noexcept+ {+ return state_type{m_begin, m_end, m_resource_management, context};+ }++ constexpr explicit string_ref(const state_type& s) noexcept+ :+ m_begin(s.m_begin),+ m_end(s.m_end),+ m_resource_management(s.m_resource_management),+ context(s.context)+ { }++ void clear() noexcept+ {+ m_begin = nullptr;+ m_end = nullptr;+ }++ template <class StringRef>+ union string_ref_state_union_type+ {+ explicit string_ref_state_union_type(StringRef&& s) : str(std::move(s))+ { }++ ~string_ref_state_union_type() noexcept {}++ StringRef str;+ state_type state;+ };++ using string_ref_state_union = string_ref_state_union_type<string_ref>;++ void operator = (const state_type& s) noexcept+ {+ m_begin = s.m_begin;+ m_end = s.m_end;+ m_resource_management = s.m_resource_management;+ context = s.context;+ }++ pointer m_begin;+ pointer m_end;+ resource_management m_resource_management;+ void* context;+};++inline bool operator == (const string_ref& lhs, const string_ref& rhs) noexcept+{+ return (lhs.size() == rhs.size()) && (std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0);+}++inline bool operator != (const string_ref& lhs, const string_ref& rhs) noexcept+{+ return !(lhs == rhs);+}++inline bool operator < (const string_ref& lhs, const string_ref& rhs) noexcept+{+ const std::size_t sz = (lhs.size() < rhs.size()) ? lhs.size() : rhs.size();+ int result = std::memcmp(lhs.data(), rhs.data(), sz);+ if (result == 0) return lhs.size() < rhs.size();+ return result < 0;+}++inline bool operator > (const string_ref& lhs, const string_ref& rhs) noexcept+{+ return rhs < lhs;+}++inline bool operator <= (const string_ref& lhs, const string_ref& rhs) noexcept+{+ return !(lhs > rhs);+}++inline bool operator >= (const string_ref& lhs, const string_ref& rhs) noexcept+{+ return !(lhs < rhs);+}++// Reference-counted allocated string+//+class shared_string_ref : public string_ref+{+ struct string_arena_base+ {+ mutable std::atomic<std::size_t> ref_count;+ std::size_t length;+ };++ struct string_arena : string_arena_base+ {+ constexpr explicit string_arena(std::size_t length) noexcept+ : string_arena_base{{1}, length}+ { }++ constexpr static std::size_t header_size() noexcept+ {+ return sizeof(string_arena);+ }++ char* data() noexcept+ {+ return reinterpret_cast<char*>(this) + header_size();+ }++ const char* data() const noexcept+ {+ return reinterpret_cast<const char*>(this) + header_size();+ }++ const char* begin() const noexcept { return data(); }++ const char* end() const noexcept { return data() + length; }+ };++ string_ref allocate_string_ref(const char* s, std::size_t length)+ {+ const std::size_t arena_size = string_arena::header_size() + length;+ char* buf = static_cast<char*>(::operator new(arena_size));+ string_arena* a = new (buf) string_arena{length};+ std::memcpy(a->data(), s, length);+ return shared_string_ref{a};+ }++ explicit shared_string_ref(string_arena* a) noexcept+ :+ string_ref{+ a->begin(),+ a->end(),+ string_ref::resource_management{©_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{+ ©_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 : ©_constructor<T>;+ }++ template <class T>+ constexpr static error_resource_management::move_constructor move() noexcept+ {+ return is_trivially_move_constructible<T>::value ?+ nullptr : &move_constructor<T>;+ }++ template <class T>+ constexpr static error_resource_management::destructor destroy() noexcept+ {+ return is_trivially_destructible<T>::value ?+ nullptr : &destructor<T>;+ }+};++} // end namespace detail++template <class T>+struct default_error_resource_management_t : error_resource_management+{+ constexpr default_error_resource_management_t() noexcept+ :+ error_resource_management{+ detail::default_error_constructors::copy<T>(),+ detail::default_error_constructors::move<T>(),+ detail::default_error_constructors::destroy<T>()+ }+ {}+};++ #if defined(STDX_VARIABLE_TEMPLATES)+template <class T>+inline constexpr default_error_resource_management_t<T> default_error_resource_management {};+ #endif++template <>+struct error_traits<std::errc>+{+ static std::exception_ptr to_exception(std::errc ec) noexcept+ {+ return std::make_exception_ptr(std::make_error_code(ec));+ }++ static error to_error(std::errc ec) noexcept+ {+ return error{error_value<std::errc>{ec}, generic_domain};+ }+};++namespace detail {++struct error_code_wrapper : enable_reference_count+{+ explicit error_code_wrapper(std::error_code ec) noexcept : code(ec)+ { }++ std::error_code code;+};++} // end namespace detail++// Error domain mapping to std::error_code+//+class error_code_error_domain : public error_domain+{+ using internal_value_type = intrusive_ptr<detail::error_code_wrapper>;++ friend struct error_traits<std::error_code>;++public:++ constexpr error_code_error_domain() noexcept+ :+ error_domain{+ {0x84e99cdcecae4443ULL, 0x9050179b713fd2afULL},+ default_error_resource_management_t<internal_value_type>{}+ }+ { }++ virtual string_ref name() const noexcept override+ {+ return "std::error_code error domain";+ }++ virtual bool equivalent(const error& lhs, const error& rhs) const noexcept override;++ virtual string_ref message(const error& e) const noexcept override;++ [[noreturn]] virtual void throw_exception(const error& e) const override;+};++STDX_LEGACY_INLINE_CONSTEXPR error_code_error_domain error_code_domain {};++template <>+struct error_traits<std::error_code>+{+ static std::error_code from_exception(std::exception_ptr e) noexcept+ {+ return error_code_from_exception(std::move(e));+ }++ static std::exception_ptr to_exception(std::error_code ec) noexcept+ {+ return std::make_exception_ptr(std::system_error{ec});+ }++ static error to_error(std::error_code ec) noexcept;+};++namespace detail {++template <class Ptr, bool = (sizeof(Ptr) <= sizeof(std::intptr_t))>+struct exception_ptr_wrapper_impl+{+ struct control_block : enable_reference_count+ {+ explicit control_block(Ptr p) noexcept : ptr_(std::move(p))+ { }++ Ptr ptr_;+ };++ explicit exception_ptr_wrapper_impl(Ptr p) : ptr{new control_block{std::move(p)}}+ { }++ Ptr get() noexcept { return ptr ? ptr->ptr_ : Ptr{}; }++ intrusive_ptr<control_block> ptr;+};++template <class Ptr>+struct exception_ptr_wrapper_impl<Ptr, true>+{+ explicit exception_ptr_wrapper_impl(Ptr p) : ptr{std::move(p)}+ { }++ Ptr get() noexcept { return ptr; }++ Ptr ptr;+};++using exception_ptr_wrapper = exception_ptr_wrapper_impl<std::exception_ptr>;++static_assert(sizeof(exception_ptr_wrapper) == sizeof(std::intptr_t), "Internal library error");++} // end namespace detail++ #ifdef STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE+template <>+struct is_trivially_relocatable<detail::exception_ptr_wrapper> : std::true_type+{ };+ #endif++// Error domain mapping to std::exception_ptr+//+class dynamic_exception_error_domain : public error_domain+{+public:++ constexpr dynamic_exception_error_domain() noexcept+ :+ error_domain{+ {0x3c223c0aa3cf45e5ULL, 0x80dac24345cfb9fcULL},+ default_error_resource_management_t<detail::exception_ptr_wrapper>{}+ }+ { }++ virtual string_ref name() const noexcept override+ {+ return "dynamic exception domain";+ }++ virtual bool equivalent(const error& lhs, const error& rhs) const noexcept override;++ virtual string_ref message(const error&) const noexcept override;++ [[noreturn]] virtual void throw_exception(const error& e) const override+ {+ assert(e.domain() == *this);+ std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());+ }+};++STDX_LEGACY_INLINE_CONSTEXPR dynamic_exception_error_domain dynamic_exception_domain {};++// Error domain mapping to dynamic_exception_errc+//+class dynamic_exception_code_error_domain : public error_domain+{+public:++ constexpr dynamic_exception_code_error_domain() noexcept+ : error_domain{{0xa242506c26484677ULL, 0x82365303df25e338ULL}}+ { }++ virtual string_ref name() const noexcept override+ {+ return "dynamic exception code domain";+ }++ virtual bool equivalent(const error& lhs, const error& rhs) const noexcept override;++ virtual string_ref message(const error&) const noexcept override;+};++STDX_LEGACY_INLINE_CONSTEXPR dynamic_exception_code_error_domain dynamic_exception_code_domain {};++inline error make_error(dynamic_exception_errc code) noexcept+{+ return error{error_value<dynamic_exception_errc>{code}, dynamic_exception_code_domain};+}++struct thrown_dynamic_exception : std::exception+{+ explicit thrown_dynamic_exception(stdx::error e) noexcept : m_error(e)+ { }++ stdx::error error() const noexcept+ {+ return m_error;+ }++private:++ stdx::error m_error;+};++template <>+struct error_traits<std::exception_ptr>+{+ static std::exception_ptr from_exception(std::exception_ptr e) noexcept+ {+ return e;+ }++ static std::exception_ptr to_exception(std::exception_ptr e) noexcept+ {+ return e;+ }++ static error to_error(std::exception_ptr e) noexcept+ {+ return error{+ error_value<detail::exception_ptr_wrapper>{detail::exception_ptr_wrapper{e}},+ dynamic_exception_domain+ };+ }+};++} // end namespace stdx++namespace std {++template<>+struct is_error_code_enum<stdx::dynamic_exception_errc> : std::true_type+{ };++} // end namespace std++#endif++++#if __cplusplus >= 201703L+ #include <any>+ #include <variant>+ #include <optional>+#endif++#include <functional>++namespace stdx {++namespace {++inline const char* dynamic_exception_errc_str(unsigned ev) noexcept+{+ constexpr const char* msg[] =+ {+ "Success",+ "std::runtime_error",+ "std::domain_error",+ "std::invalid_argument",+ "std::length_error",+ "std::out_of_range",+ "std::logic_error",+ "std::range_error",+ "std::overflow_error",+ "std::underflow_error",+ "std::bad_alloc",+ "std::bad_array_new_length",+ "std::bad_optional_access",+ "std::bad_typeid",+ "std::bad_any_cast",+ "std::bad_cast",+ "std::bad_weak_ptr",+ "std::bad_function_call",+ "std::bad_exception",+ "std::bad_variant_access",+ "unspecified dynamic exception"+ };++ assert(ev < (sizeof(msg) / sizeof(const char*)));+ return msg[ev];+}++class dynamic_exception_error_category : public std::error_category+{+public:++ const char* name() const noexcept override+ {+ return "dynamic_exception";+ }++ std::string message(int code) const override+ {+ return dynamic_exception_errc_str(code);+ }++ bool equivalent(int code, const std::error_condition& cond) const noexcept override+ {+ switch (static_cast<dynamic_exception_errc>(code))+ {+ case dynamic_exception_errc::domain_error:+ return (cond == std::errc::argument_out_of_domain);+ case dynamic_exception_errc::invalid_argument:+ return (cond == std::errc::invalid_argument);+ case dynamic_exception_errc::length_error:+ return (cond == std::errc::value_too_large);+ case dynamic_exception_errc::out_of_range:+ case dynamic_exception_errc::range_error:+ case dynamic_exception_errc::underflow_error:+ return (cond == std::errc::result_out_of_range);+ case dynamic_exception_errc::overflow_error:+ return (cond == std::errc::value_too_large);+ case dynamic_exception_errc::bad_alloc:+ case dynamic_exception_errc::bad_array_new_length:+ return (cond == std::errc::not_enough_memory);+ default:;+ }+ return false;+ }+};++inline const std::error_category& dynamic_exception_category() noexcept+{+ static const dynamic_exception_error_category dynamic_exception_error_category_instance;+ return dynamic_exception_error_category_instance;+}++} // end anonymous namespace++inline std::error_code make_error_code(dynamic_exception_errc code) noexcept+{+ return std::error_code{static_cast<int>(code), dynamic_exception_category()};+}++inline std::error_code error_code_from_exception(std::exception_ptr eptr) noexcept+{+ if (!eptr) return make_error_code(dynamic_exception_errc::bad_exception);++ try+ {+ std::rethrow_exception(eptr);+ }+ catch (const std::domain_error&)+ {+ return make_error_code(dynamic_exception_errc::domain_error);+ }+ catch (const std::invalid_argument&)+ {+ return make_error_code(dynamic_exception_errc::invalid_argument);+ }+ catch (const std::length_error&)+ {+ return make_error_code(dynamic_exception_errc::length_error);+ }+ catch (const std::out_of_range&)+ {+ return make_error_code(dynamic_exception_errc::out_of_range);+ }+ catch (const std::logic_error&)+ {+ return make_error_code(dynamic_exception_errc::logic_error);+ }+ catch (const std::range_error&)+ {+ return make_error_code(dynamic_exception_errc::range_error);+ }+ catch (const std::overflow_error&)+ {+ return make_error_code(dynamic_exception_errc::overflow_error);+ }+ catch (const std::underflow_error&)+ {+ return make_error_code(dynamic_exception_errc::underflow_error);+ }+ catch (const std::system_error& e)+ {+ return e.code();+ }+ catch (const std::runtime_error&)+ {+ return make_error_code(dynamic_exception_errc::runtime_error);+ }+ catch (const std::bad_array_new_length&)+ {+ return make_error_code(dynamic_exception_errc::bad_array_new_length);+ }+ catch (const std::bad_alloc&)+ {+ return make_error_code(dynamic_exception_errc::bad_alloc);+ }+ catch (const std::bad_typeid&)+ {+ return make_error_code(dynamic_exception_errc::bad_typeid);+ }+#if __cplusplus >= 201703L+ catch (const std::bad_optional_access&)+ {+ return make_error_code(dynamic_exception_errc::bad_optional_access);+ }+ catch (const std::bad_any_cast&)+ {+ return make_error_code(dynamic_exception_errc::bad_any_cast);+ }+ catch (const std::bad_variant_access&)+ {+ return make_error_code(dynamic_exception_errc::bad_variant_access);+ }+#endif+ catch (const std::bad_cast&)+ {+ return make_error_code(dynamic_exception_errc::bad_cast);+ }+ catch (const std::bad_weak_ptr&)+ {+ return make_error_code(dynamic_exception_errc::bad_weak_ptr);+ }+ catch (const std::bad_function_call&)+ {+ return make_error_code(dynamic_exception_errc::bad_function_call);+ }+ catch (const std::bad_exception&)+ {+ return make_error_code(dynamic_exception_errc::bad_exception);+ }+ catch (...)+ { }++ return make_error_code(dynamic_exception_errc::unspecified_exception);+}++inline error error_from_exception(std::exception_ptr eptr) noexcept+{+ if (!eptr) return make_error(dynamic_exception_errc::bad_exception);++ try+ {+ std::rethrow_exception(eptr);+ }+ catch (const std::domain_error&)+ {+ return make_error(dynamic_exception_errc::domain_error);+ }+ catch (const std::invalid_argument&)+ {+ return make_error(dynamic_exception_errc::invalid_argument);+ }+ catch (const std::length_error&)+ {+ return make_error(dynamic_exception_errc::length_error);+ }+ catch (const std::out_of_range&)+ {+ return make_error(dynamic_exception_errc::out_of_range);+ }+ catch (const std::logic_error&)+ {+ return make_error(dynamic_exception_errc::logic_error);+ }+ catch (const std::range_error&)+ {+ return make_error(dynamic_exception_errc::range_error);+ }+ catch (const std::overflow_error&)+ {+ return make_error(dynamic_exception_errc::overflow_error);+ }+ catch (const std::underflow_error&)+ {+ return make_error(dynamic_exception_errc::underflow_error);+ }+ catch (const std::system_error& e)+ {+ return error{e.code()};+ }+ catch (const std::runtime_error&)+ {+ return make_error(dynamic_exception_errc::runtime_error);+ }+ catch (const std::bad_array_new_length&)+ {+ return make_error(dynamic_exception_errc::bad_array_new_length);+ }+ catch (const std::bad_alloc&)+ {+ return make_error(dynamic_exception_errc::bad_alloc);+ }+ catch (const std::bad_typeid&)+ {+ return make_error(dynamic_exception_errc::bad_typeid);+ }+#if __cplusplus >= 201703L+ catch (const std::bad_optional_access&)+ {+ return make_error(dynamic_exception_errc::bad_optional_access);+ }+ catch (const std::bad_any_cast&)+ {+ return make_error(dynamic_exception_errc::bad_any_cast);+ }+ catch (const std::bad_variant_access&)+ {+ return make_error(dynamic_exception_errc::bad_variant_access);+ }+#endif+ catch (const std::bad_cast&)+ {+ return make_error(dynamic_exception_errc::bad_cast);+ }+ catch (const std::bad_weak_ptr&)+ {+ return make_error(dynamic_exception_errc::bad_weak_ptr);+ }+ catch (const std::bad_function_call&)+ {+ return make_error(dynamic_exception_errc::bad_function_call);+ }+ catch (const std::bad_exception&)+ {+ return make_error(dynamic_exception_errc::bad_exception);+ }+ catch (...)+ { }++ return make_error(dynamic_exception_errc::unspecified_exception);+}++// ---------- ErrorDomain (abstract base class)+//+inline void error_domain::throw_exception(const error& e) const+{+ throw thrown_dynamic_exception{e};+}++// ---------- GenericErrorDomain+//+inline bool generic_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept+{+ assert(lhs.domain() == *this);+ if (lhs.domain() == rhs.domain())+ {+ return error_cast<std::errc>(lhs) == error_cast<std::errc>(rhs);+ }++ return false;+}++namespace {++string_ref generic_error_code_message(std::errc code) noexcept+{+ switch (code)+ {+ case std::errc::address_family_not_supported:+ return "Address family not supported by protocol";+ case std::errc::address_in_use:+ return "Address already in use";+ case std::errc::address_not_available:+ return "Cannot assign requested address";+ case std::errc::already_connected:+ return "Transport endpoint is already connected";+ case std::errc::argument_list_too_long:+ return "Argument list too long";+ case std::errc::argument_out_of_domain:+ return "Numerical argument out of domain";+ case std::errc::bad_address:+ return "Bad address";+ case std::errc::bad_file_descriptor:+ return "Bad file descriptor";+ case std::errc::bad_message:+ return "Bad message";+ case std::errc::broken_pipe:+ return "Broken pipe";+ case std::errc::connection_aborted:+ return "Software caused connection abort";+ case std::errc::connection_already_in_progress:+ return "Operation already in progress";+ case std::errc::connection_refused:+ return "Connection refused";+ case std::errc::connection_reset:+ return "Connection reset by peer";+ case std::errc::cross_device_link:+ return "Invalid cross-device link";+ case std::errc::destination_address_required:+ return "Destination address required";+ case std::errc::device_or_resource_busy:+ return "Device or resource busy";+ case std::errc::directory_not_empty:+ return "Directory not empty";+ case std::errc::executable_format_error:+ return "Exec format error";+ case std::errc::file_exists:+ return "File exists";+ case std::errc::file_too_large:+ return "File too large";+ case std::errc::filename_too_long:+ return "File name too long";+ case std::errc::function_not_supported:+ return "Function not implemented";+ case std::errc::host_unreachable:+ return "No route to host";+ case std::errc::identifier_removed:+ return "Identifier removed";+ case std::errc::illegal_byte_sequence:+ return "Invalid or incomplete multibyte or wide character";+ case std::errc::inappropriate_io_control_operation:+ return "Inappropriate ioctl for device";+ case std::errc::interrupted:+ return "Interrupted system call";+ case std::errc::invalid_argument:+ return "Invalid argument";+ case std::errc::invalid_seek:+ return "Illegal seek";+ case std::errc::io_error:+ return "Input/output error";+ case std::errc::is_a_directory:+ return "Is a directory";+ case std::errc::message_size:+ return "Message too long";+ case std::errc::network_down:+ return "Network is down";+ case std::errc::network_reset:+ return "Network dropped connection on reset";+ case std::errc::network_unreachable:+ return "Network is unreachable";+ case std::errc::no_buffer_space:+ return "No buffer space available";+ case std::errc::no_child_process:+ return "No child processes";+ case std::errc::no_link:+ return "Link has been severed";+ case std::errc::no_lock_available:+ return "No locks available";+ case std::errc::no_message:+ return "No message of desired type";+ case std::errc::no_protocol_option:+ return "Protocol not available";+ case std::errc::no_space_on_device:+ return "No space left on device";+ case std::errc::no_stream_resources:+ return "Out of streams resources";+ case std::errc::no_such_device_or_address:+ return "No such device or address";+ case std::errc::no_such_device:+ return "No such device";+ case std::errc::no_such_file_or_directory:+ return "No such file or directory";+ case std::errc::no_such_process:+ return "No such process";+ case std::errc::not_a_directory:+ return "Not a directory";+ case std::errc::not_a_socket:+ return "Socket operation on non-socket";+ case std::errc::not_a_stream:+ return "Device not a stream";+ case std::errc::not_connected:+ return "Transport endpoint is not connected";+ case std::errc::not_enough_memory:+ return "Cannot allocate memory";+#if ENOTSUP != EOPNOTSUPP+ case std::errc::not_supported:+ return "Operation not supported";+#endif+ case std::errc::operation_canceled:+ return "Operation canceled";+ case std::errc::operation_in_progress:+ return "Operation now in progress";+ case std::errc::operation_not_permitted:+ return "Operation not permitted";+ case std::errc::operation_not_supported:+ return "Operation not supported";+#if EAGAIN != EWOULDBLOCK+ case std::errc::operation_would_block:+ return "Resource temporarily unavailable";+#endif+ case std::errc::owner_dead:+ return "Owner died";+ case std::errc::permission_denied:+ return "Permission denied";+ case std::errc::protocol_error:+ return "Protocol error";+ case std::errc::protocol_not_supported:+ return "Protocol not supported";+ case std::errc::read_only_file_system:+ return "Read-only file system";+ case std::errc::resource_deadlock_would_occur:+ return "Resource deadlock avoided";+ case std::errc::resource_unavailable_try_again:+ return "Resource temporarily unavailable";+ case std::errc::result_out_of_range:+ return "Numerical result out of range";+ case std::errc::state_not_recoverable:+ return "State not recoverable";+ case std::errc::stream_timeout:+ return "Timer expired";+ case std::errc::text_file_busy:+ return "Text file busy";+ case std::errc::timed_out:+ return "Connection timed out";+ case std::errc::too_many_files_open_in_system:+ return "Too many open files in system";+ case std::errc::too_many_files_open:+ return "Too many open files";+ case std::errc::too_many_links:+ return "Too many links";+ case std::errc::too_many_symbolic_link_levels:+ return "Too many levels of symbolic links";+ case std::errc::value_too_large:+ return "Value too large for defined data type";+ case std::errc::wrong_protocol_type:+ return "Protocol wrong type for socket";+ default:+ return "Unspecified error";+ }+}++} // end anonymous namespace++inline string_ref generic_error_domain::message(const error& e) const noexcept+{+ assert(e.domain() == *this);+ return generic_error_code_message(error_cast<std::errc>(e));+}++// ---------- ErrorCodeErrorDomain+//+inline string_ref error_code_error_domain::message(const error& e) const noexcept+{+ assert(e.domain() == *this);++ auto ptr = error_cast<internal_value_type>(e);+ if (ptr)+ {+ std::string msg = ptr->code.message();+ return shared_string_ref{msg.c_str(), msg.c_str() + msg.size()};+ }++ return string_ref{"Bad error code"};+}++inline void error_code_error_domain::throw_exception(const error& e) const+{+ assert(e.domain() == *this);++ std::error_code code;+ auto ptr = error_cast<internal_value_type>(e);+ if (ptr) code = ptr->code;+ throw std::system_error{code};+}++inline bool error_code_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept+{+ assert(lhs.domain() == *this);++ if (lhs.domain() == rhs.domain())+ {+ auto ptr1 = error_cast<internal_value_type>(lhs);+ auto ptr2 = error_cast<internal_value_type>(rhs);+ if (ptr1 && ptr2) return ptr1->code == ptr2->code.default_error_condition();+ return false;+ }++ if (rhs.domain() == generic_domain)+ {+ auto ptr1 = error_cast<internal_value_type>(lhs);+ if (ptr1) return ptr1->code == error_cast<std::errc>(rhs);+ }++ return false;+}++inline stdx::error error_traits<std::error_code>::to_error(std::error_code ec) noexcept+{+ using internal_value_type = error_code_error_domain::internal_value_type;++ if (ec.category() == std::generic_category())+ {+ return error{+ error_value<std::errc>{static_cast<std::errc>(ec.default_error_condition().value())},+ generic_domain+ };+ }++ return error{+ error_value<internal_value_type>{internal_value_type{new detail::error_code_wrapper{ec}}},+ error_code_domain+ };+}++// ---------- DynamicExceptionErrorDomain+//+inline string_ref dynamic_exception_error_domain::message(const error& e) const noexcept+{+ assert(e.domain() == *this);++ std::exception_ptr eptr = error_cast<detail::exception_ptr_wrapper>(e).get();++ try+ {+ std::rethrow_exception(eptr);+ }+ catch (const std::exception& ex)+ {+ return shared_string_ref{ex.what()};+ }+ catch (...) {}++ return string_ref{"Unknown dynamic exception"};+}++namespace {++std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept+{+ switch (code)+ {+ case dynamic_exception_errc::domain_error:+ return std::errc::argument_out_of_domain;+ case dynamic_exception_errc::invalid_argument:+ return std::errc::invalid_argument;+ case dynamic_exception_errc::length_error:+ return std::errc::value_too_large;+ case dynamic_exception_errc::out_of_range:+ case dynamic_exception_errc::range_error:+ case dynamic_exception_errc::underflow_error:+ return std::errc::result_out_of_range;+ case dynamic_exception_errc::overflow_error:+ return std::errc::value_too_large;+ case dynamic_exception_errc::bad_alloc:+ case dynamic_exception_errc::bad_array_new_length:+ return std::errc::not_enough_memory;+ default:;+ }+ return std::errc{};+}++} // end anonymous namespace++inline bool dynamic_exception_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept+{+ assert(lhs.domain() == *this);++ std::exception_ptr eptr = error_cast<detail::exception_ptr_wrapper>(lhs).get();++ if (rhs.domain() == *this)+ {+ std::exception_ptr eptr2 = error_cast<detail::exception_ptr_wrapper>(rhs).get();+ if (eptr == eptr2) return true;++ error e1 = error_from_exception(eptr);+ error e2 = error_from_exception(eptr2);+ return e1.domain().equivalent(e1, e2);+ }+ else if (rhs.domain() == error_code_domain)+ {+ std::error_code ec = error_code_from_exception(eptr);+ return error_code_domain.equivalent(rhs, error{ec});+ }++ error e = error_from_exception(eptr);+ return e.domain().equivalent(e, rhs);+}++// ---------- DynamicExceptionCodeErrorDomain+//+inline bool dynamic_exception_code_error_domain::equivalent(+ const error& lhs,+ const error& rhs+ ) const noexcept+{+ assert(lhs.domain() == *this);++ const dynamic_exception_errc code = error_cast<dynamic_exception_errc>(lhs);++ if (rhs.domain() == *this)+ {+ return code == error_cast<dynamic_exception_errc>(rhs);+ }+ else if (rhs.domain() == error_code_domain)+ {+ return error_code_domain.equivalent(rhs, make_error_code(code));+ }+ else if (rhs.domain() == generic_domain)+ {+ std::errc generic_code = dynamic_exception_code_to_generic_code(code);+ return generic_domain.equivalent(rhs, generic_code);+ }++ return false;+}++inline string_ref dynamic_exception_code_error_domain::message(const error& e) const noexcept+{+ assert(e.domain() == *this);+ return string_ref{+ dynamic_exception_errc_str(static_cast<unsigned>(error_cast<dynamic_exception_errc>(e)))+ };+}++} // end namespace stdx+++
+ vendor/libremidi/ump.hpp view
@@ -0,0 +1,113 @@+#pragma once+#include <libremidi/config.hpp>++#include <span>++#if LIBREMIDI_NI_MIDI2_COMPAT+ #include <midi/universal_packet.h>+#endif++namespace libremidi+{+struct ump+{+ alignas(4) uint32_t data[4] = {};+ int64_t timestamp{};++ constexpr ump() noexcept = default;+ constexpr ~ump() = default;++ explicit constexpr ump(uint32_t b0) noexcept+ : data{b0, 0, 0, 0}+ {+ }+ constexpr ump(uint32_t b0, uint32_t b1) noexcept+ : data{b0, b1, 0, 0}+ {+ }+ constexpr ump(uint32_t b0, uint32_t b1, uint32_t b2) noexcept+ : data{b0, b1, b2, 0}+ {+ }+ constexpr ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) noexcept+ : data{b0, b1, b2, b3}+ {+ }++ // Compatibility with ni-midi2:+#if LIBREMIDI_NI_MIDI2_COMPAT+ constexpr operator midi::universal_packet() const noexcept+ {+ return {data[0], data[1], data[2], data[3]};+ }+ explicit constexpr ump(midi::universal_packet b) noexcept+ : data{b.data[0], b.data[1], b.data[2], b.data[3]}+ {+ }+ constexpr ump& operator=(midi::universal_packet b) noexcept+ {+ data[0] = b.data[0];+ data[1] = b.data[1];+ data[2] = b.data[2];+ data[3] = b.data[3];+ return *this;+ }+#endif++ // Compatibility with cmidi2:+ operator uint32_t*() & noexcept { return data; }+ operator const uint32_t*() const& noexcept { return data; }+ operator uint32_t*() && noexcept = delete;+ operator const uint32_t*() const&& noexcept = delete;++ constexpr std::size_t size() const noexcept+ {+ // Imported from cmidi2+ enum midi2_message_type+ {+ // MIDI 2.0 UMP Section 3.+ UTILITY = 0,+ SYSTEM = 1,+ MIDI_1_CHANNEL = 2,+ SYSEX7 = 3,+ MIDI_2_CHANNEL = 4,+ SYSEX8_MDS = 5,+ };++ switch (((data[0] & 0xF0000000) >> 28) & 0xF)+ {+ case UTILITY:+ case SYSTEM:+ case MIDI_1_CHANNEL:+ return 1;+ case MIDI_2_CHANNEL:+ case SYSEX7:+ return 2;+ case SYSEX8_MDS:+ return 4;+ default:+ return 0;+ }+ }++ constexpr void clear() noexcept+ {+ data[0] = 0;+ data[1] = 0;+ data[2] = 0;+ data[3] = 0;+ }++ constexpr auto& operator[](int i) const noexcept { return data[i]; }+ constexpr auto& operator[](int i) noexcept { return data[i]; }++ constexpr auto begin() const noexcept { return data; }+ constexpr auto end() const noexcept { return data + size(); }+ constexpr auto begin() noexcept { return data; }+ constexpr auto end() noexcept { return data + size(); }+ constexpr auto cbegin() const noexcept { return data; }+ constexpr auto cend() const noexcept { return data + size(); }+ constexpr auto cbegin() noexcept { return data; }+ constexpr auto cend() noexcept { return data + size(); }+};+}
+ vendor/libremidi/writer.hpp view
@@ -0,0 +1,52 @@+/*+Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+*/++#pragma once+#include <libremidi/message.hpp>++#include <cstdint>+#include <iosfwd>+#include <vector>++namespace libremidi+{+struct LIBREMIDI_EXPORT writer+{+public:+ uint16_t ticksPerQuarterNote{120};+ std::vector<midi_track> tracks;++ void add_event(int tick, int track, const message& m);+ void add_event(int track, const track_event& m);++ void add_track();++ void write(std::ostream& out) const;+};+}++#if defined(LIBREMIDI_HEADER_ONLY)+ #include <libremidi/writer.cpp>+#endif
+ vendor/readerwriterqueue.h view
@@ -0,0 +1,979 @@+// ©2013-2020 Cameron Desrochers.+// Distributed under the simplified BSD license (see the license file that+// should have come with this header).++#pragma once++#include "atomicops.h"+#include <new>+#include <type_traits>+#include <utility>+#include <cassert>+#include <stdexcept>+#include <new>+#include <cstdint>+#include <cstdlib> // For malloc/free/abort & size_t+#include <memory>+#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012+#include <chrono>+#endif+++// A lock-free queue for a single-consumer, single-producer architecture.+// The queue is also wait-free in the common path (except if more memory+// needs to be allocated, in which case malloc is called).+// Allocates memory sparingly, and only once if the original maximum size+// estimate is never exceeded.+// Tested on x86/x64 processors, but semantics should be correct for all+// architectures (given the right implementations in atomicops.h), provided+// that aligned integer and pointer accesses are naturally atomic.+// Note that there should only be one consumer thread and producer thread;+// Switching roles of the threads, or using multiple consecutive threads for+// one role, is not safe unless properly synchronized.+// Using the queue exclusively from one thread is fine, though a bit silly.++#ifndef MOODYCAMEL_CACHE_LINE_SIZE+#define MOODYCAMEL_CACHE_LINE_SIZE 64+#endif++#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED+#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__))+#define MOODYCAMEL_EXCEPTIONS_ENABLED+#endif+#endif++#ifndef MOODYCAMEL_HAS_EMPLACE+#if !defined(_MSC_VER) || _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013+#define MOODYCAMEL_HAS_EMPLACE 1+#endif+#endif++#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE+#if defined (__APPLE__) && defined (__MACH__) && __cplusplus >= 201703L+// This is required to find out what deployment target we are using+#include <AvailabilityMacros.h>+#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || !defined(MAC_OS_X_VERSION_10_14) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14+// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we can't support over-alignment in this case+#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE+#endif+#endif+#endif++#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE+#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE)+#endif++#ifdef AE_VCPP+#pragma warning(push)+#pragma warning(disable: 4324) // structure was padded due to __declspec(align())+#pragma warning(disable: 4820) // padding was added+#pragma warning(disable: 4127) // conditional expression is constant+#endif++namespace moodycamel {++template<typename T, size_t MAX_BLOCK_SIZE = 512>+class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue+{+ // Design: Based on a queue-of-queues. The low-level queues are just+ // circular buffers with front and tail indices indicating where the+ // next element to dequeue is and where the next element can be enqueued,+ // respectively. Each low-level queue is called a "block". Each block+ // wastes exactly one element's worth of space to keep the design simple+ // (if front == tail then the queue is empty, and can't be full).+ // The high-level queue is a circular linked list of blocks; again there+ // is a front and tail, but this time they are pointers to the blocks.+ // The front block is where the next element to be dequeued is, provided+ // the block is not empty. The back block is where elements are to be+ // enqueued, provided the block is not full.+ // The producer thread owns all the tail indices/pointers. The consumer+ // thread owns all the front indices/pointers. Both threads read each+ // other's variables, but only the owning thread updates them. E.g. After+ // the consumer reads the producer's tail, the tail may change before the+ // consumer is done dequeuing an object, but the consumer knows the tail+ // will never go backwards, only forwards.+ // If there is no room to enqueue an object, an additional block (of+ // equal size to the last block) is added. Blocks are never removed.++public:+ typedef T value_type;++ // Constructs a queue that can hold at least `size` elements without further+ // allocations. If more than MAX_BLOCK_SIZE elements are requested,+ // then several blocks of MAX_BLOCK_SIZE each are reserved (including+ // at least one extra buffer block).+ AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15)+#ifndef NDEBUG+ : enqueuing(false)+ ,dequeuing(false)+#endif+ {+ assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && "MAX_BLOCK_SIZE must be a power of 2");+ assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2");+ + Block* firstBlock = nullptr;+ + largestBlockSize = ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block+ if (largestBlockSize > MAX_BLOCK_SIZE * 2) {+ // We need a spare block in case the producer is writing to a different block the consumer is reading from, and+ // wants to enqueue the maximum number of elements. We also need a spare element in each block to avoid the ambiguity+ // between front == tail meaning "empty" and "full".+ // So the effective number of slots that are guaranteed to be usable at any time is the block size - 1 times the+ // number of blocks - 1. Solving for size and applying a ceiling to the division gives us (after simplifying):+ size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1);+ largestBlockSize = MAX_BLOCK_SIZE;+ Block* lastBlock = nullptr;+ for (size_t i = 0; i != initialBlockCount; ++i) {+ auto block = make_block(largestBlockSize);+ if (block == nullptr) {+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED+ throw std::bad_alloc();+#else+ abort();+#endif+ }+ if (firstBlock == nullptr) {+ firstBlock = block;+ }+ else {+ lastBlock->next = block;+ }+ lastBlock = block;+ block->next = firstBlock;+ }+ }+ else {+ firstBlock = make_block(largestBlockSize);+ if (firstBlock == nullptr) {+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED+ throw std::bad_alloc();+#else+ abort();+#endif+ }+ firstBlock->next = firstBlock;+ }+ frontBlock = firstBlock;+ tailBlock = firstBlock;+ + // Make sure the reader/writer threads will have the initialized memory setup above:+ fence(memory_order_sync);+ }++ // Note: The queue should not be accessed concurrently while it's+ // being moved. It's up to the user to synchronize this.+ AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other)+ : frontBlock(other.frontBlock.load()),+ tailBlock(other.tailBlock.load()),+ largestBlockSize(other.largestBlockSize)+#ifndef NDEBUG+ ,enqueuing(false)+ ,dequeuing(false)+#endif+ {+ other.largestBlockSize = 32;+ Block* b = other.make_block(other.largestBlockSize);+ if (b == nullptr) {+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED+ throw std::bad_alloc();+#else+ abort();+#endif+ }+ b->next = b;+ other.frontBlock = b;+ other.tailBlock = b;+ }++ // Note: The queue should not be accessed concurrently while it's+ // being moved. It's up to the user to synchronize this.+ ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN+ {+ Block* b = frontBlock.load();+ frontBlock = other.frontBlock.load();+ other.frontBlock = b;+ b = tailBlock.load();+ tailBlock = other.tailBlock.load();+ other.tailBlock = b;+ std::swap(largestBlockSize, other.largestBlockSize);+ return *this;+ }++ // Note: The queue should not be accessed concurrently while it's+ // being deleted. It's up to the user to synchronize this.+ AE_NO_TSAN ~ReaderWriterQueue()+ {+ // Make sure we get the latest version of all variables from other CPUs:+ fence(memory_order_sync);++ // Destroy any remaining objects in queue and free memory+ Block* frontBlock_ = frontBlock;+ Block* block = frontBlock_;+ do {+ Block* nextBlock = block->next;+ size_t blockFront = block->front;+ size_t blockTail = block->tail;++ for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) {+ auto element = reinterpret_cast<T*>(block->data + i * sizeof(T));+ element->~T();+ (void)element;+ }+ + auto rawBlock = block->rawThis;+ block->~Block();+ std::free(rawBlock);+ block = nextBlock;+ } while (block != frontBlock_);+ }+++ // Enqueues a copy of element if there is room in the queue.+ // Returns true if the element was enqueued, false otherwise.+ // Does not allocate memory.+ AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN+ {+ return inner_enqueue<CannotAlloc>(element);+ }++ // Enqueues a moved copy of element if there is room in the queue.+ // Returns true if the element was enqueued, false otherwise.+ // Does not allocate memory.+ AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN+ {+ return inner_enqueue<CannotAlloc>(std::forward<T>(element));+ }++#if MOODYCAMEL_HAS_EMPLACE+ // Like try_enqueue() but with emplace semantics (i.e. construct-in-place).+ template<typename... Args>+ AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN+ {+ return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...);+ }+#endif++ // Enqueues a copy of element on the queue.+ // Allocates an additional block of memory if needed.+ // Only fails (returns false) if memory allocation fails.+ AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN+ {+ return inner_enqueue<CanAlloc>(element);+ }++ // Enqueues a moved copy of element on the queue.+ // Allocates an additional block of memory if needed.+ // Only fails (returns false) if memory allocation fails.+ AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN+ {+ return inner_enqueue<CanAlloc>(std::forward<T>(element));+ }++#if MOODYCAMEL_HAS_EMPLACE+ // Like enqueue() but with emplace semantics (i.e. construct-in-place).+ template<typename... Args>+ AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN+ {+ return inner_enqueue<CanAlloc>(std::forward<Args>(args)...);+ }+#endif++ // Attempts to dequeue an element; if the queue is empty,+ // returns false instead. If the queue has at least one element,+ // moves front to result using operator=, then returns true.+ template<typename U>+ bool try_dequeue(U& result) AE_NO_TSAN+ {+#ifndef NDEBUG+ ReentrantGuard guard(this->dequeuing);+#endif++ // High-level pseudocode:+ // Remember where the tail block is+ // If the front block has an element in it, dequeue it+ // Else+ // If front block was the tail block when we entered the function, return false+ // Else advance to next block and dequeue the item there++ // Note that we have to use the value of the tail block from before we check if the front+ // block is full or not, in case the front block is empty and then, before we check if the+ // tail block is at the front block or not, the producer fills up the front block *and+ // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently+ // reproducible in practice.+ // In order to avoid overhead in the common case, though, we do a double-checked pattern+ // where we have the fast path if the front block is not empty, then read the tail block,+ // then re-read the front block and check if it's not empty again, then check if the tail+ // block has advanced.+ + Block* frontBlock_ = frontBlock.load();+ size_t blockTail = frontBlock_->localTail;+ size_t blockFront = frontBlock_->front.load();+ + if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {+ fence(memory_order_acquire);+ + non_empty_front_block:+ // Front block not empty, dequeue from here+ auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));+ result = std::move(*element);+ element->~T();++ blockFront = (blockFront + 1) & frontBlock_->sizeMask;++ fence(memory_order_release);+ frontBlock_->front = blockFront;+ }+ else if (frontBlock_ != tailBlock.load()) {+ fence(memory_order_acquire);++ frontBlock_ = frontBlock.load();+ blockTail = frontBlock_->localTail = frontBlock_->tail.load();+ blockFront = frontBlock_->front.load();+ fence(memory_order_acquire);+ + if (blockFront != blockTail) {+ // Oh look, the front block isn't empty after all+ goto non_empty_front_block;+ }+ + // Front block is empty but there's another block ahead, advance to it+ Block* nextBlock = frontBlock_->next;+ // Don't need an acquire fence here since next can only ever be set on the tailBlock,+ // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock which+ // ensures next is up-to-date on this CPU in case we recently were at tailBlock.++ size_t nextBlockFront = nextBlock->front.load();+ size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();+ fence(memory_order_acquire);++ // Since the tailBlock is only ever advanced after being written to,+ // we know there's for sure an element to dequeue on it+ assert(nextBlockFront != nextBlockTail);+ AE_UNUSED(nextBlockTail);++ // We're done with this block, let the producer use it if it needs+ fence(memory_order_release); // Expose possibly pending changes to frontBlock->front from last dequeue+ frontBlock = frontBlock_ = nextBlock;++ compiler_fence(memory_order_release); // Not strictly needed++ auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));+ + result = std::move(*element);+ element->~T();++ nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;+ + fence(memory_order_release);+ frontBlock_->front = nextBlockFront;+ }+ else {+ // No elements in current block and no other block to advance to+ return false;+ }++ return true;+ }+++ // Returns a pointer to the front element in the queue (the one that+ // would be removed next by a call to `try_dequeue` or `pop`). If the+ // queue appears empty at the time the method is called, nullptr is+ // returned instead.+ // Must be called only from the consumer thread.+ T* peek() const AE_NO_TSAN+ {+#ifndef NDEBUG+ ReentrantGuard guard(this->dequeuing);+#endif+ // See try_dequeue() for reasoning++ Block* frontBlock_ = frontBlock.load();+ size_t blockTail = frontBlock_->localTail;+ size_t blockFront = frontBlock_->front.load();+ + if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {+ fence(memory_order_acquire);+ non_empty_front_block:+ return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));+ }+ else if (frontBlock_ != tailBlock.load()) {+ fence(memory_order_acquire);+ frontBlock_ = frontBlock.load();+ blockTail = frontBlock_->localTail = frontBlock_->tail.load();+ blockFront = frontBlock_->front.load();+ fence(memory_order_acquire);+ + if (blockFront != blockTail) {+ goto non_empty_front_block;+ }+ + Block* nextBlock = frontBlock_->next;+ + size_t nextBlockFront = nextBlock->front.load();+ fence(memory_order_acquire);++ assert(nextBlockFront != nextBlock->tail.load());+ return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T));+ }+ + return nullptr;+ }+ + // Removes the front element from the queue, if any, without returning it.+ // Returns true on success, or false if the queue appeared empty at the time+ // `pop` was called.+ bool pop() AE_NO_TSAN+ {+#ifndef NDEBUG+ ReentrantGuard guard(this->dequeuing);+#endif+ // See try_dequeue() for reasoning+ + Block* frontBlock_ = frontBlock.load();+ size_t blockTail = frontBlock_->localTail;+ size_t blockFront = frontBlock_->front.load();+ + if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {+ fence(memory_order_acquire);+ + non_empty_front_block:+ auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));+ element->~T();++ blockFront = (blockFront + 1) & frontBlock_->sizeMask;++ fence(memory_order_release);+ frontBlock_->front = blockFront;+ }+ else if (frontBlock_ != tailBlock.load()) {+ fence(memory_order_acquire);+ frontBlock_ = frontBlock.load();+ blockTail = frontBlock_->localTail = frontBlock_->tail.load();+ blockFront = frontBlock_->front.load();+ fence(memory_order_acquire);+ + if (blockFront != blockTail) {+ goto non_empty_front_block;+ }+ + // Front block is empty but there's another block ahead, advance to it+ Block* nextBlock = frontBlock_->next;+ + size_t nextBlockFront = nextBlock->front.load();+ size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();+ fence(memory_order_acquire);++ assert(nextBlockFront != nextBlockTail);+ AE_UNUSED(nextBlockTail);++ fence(memory_order_release);+ frontBlock = frontBlock_ = nextBlock;++ compiler_fence(memory_order_release);++ auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));+ element->~T();++ nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;+ + fence(memory_order_release);+ frontBlock_->front = nextBlockFront;+ }+ else {+ // No elements in current block and no other block to advance to+ return false;+ }++ return true;+ }+ + // Returns the approximate number of items currently in the queue.+ // Safe to call from both the producer and consumer threads.+ inline size_t size_approx() const AE_NO_TSAN+ {+ size_t result = 0;+ Block* frontBlock_ = frontBlock.load();+ Block* block = frontBlock_;+ do {+ fence(memory_order_acquire);+ size_t blockFront = block->front.load();+ size_t blockTail = block->tail.load();+ result += (blockTail - blockFront) & block->sizeMask;+ block = block->next.load();+ } while (block != frontBlock_);+ return result;+ }++ // Returns the total number of items that could be enqueued without incurring+ // an allocation when this queue is empty.+ // Safe to call from both the producer and consumer threads.+ //+ // NOTE: The actual capacity during usage may be different depending on the consumer.+ // If the consumer is removing elements concurrently, the producer cannot add to+ // the block the consumer is removing from until it's completely empty, except in+ // the case where the producer was writing to the same block the consumer was+ // reading from the whole time.+ inline size_t max_capacity() const {+ size_t result = 0;+ Block* frontBlock_ = frontBlock.load();+ Block* block = frontBlock_;+ do {+ fence(memory_order_acquire);+ result += block->sizeMask;+ block = block->next.load();+ } while (block != frontBlock_);+ return result;+ }+++private:+ enum AllocationMode { CanAlloc, CannotAlloc };++#if MOODYCAMEL_HAS_EMPLACE+ template<AllocationMode canAlloc, typename... Args>+ bool inner_enqueue(Args&&... args) AE_NO_TSAN+#else+ template<AllocationMode canAlloc, typename U>+ bool inner_enqueue(U&& element) AE_NO_TSAN+#endif+ {+#ifndef NDEBUG+ ReentrantGuard guard(this->enqueuing);+#endif++ // High-level pseudocode (assuming we're allowed to alloc a new block):+ // If room in tail block, add to tail+ // Else check next block+ // If next block is not the head block, enqueue on next block+ // Else create a new block and enqueue there+ // Advance tail to the block we just enqueued to++ Block* tailBlock_ = tailBlock.load();+ size_t blockFront = tailBlock_->localFront;+ size_t blockTail = tailBlock_->tail.load();++ size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask;+ if (nextBlockTail != blockFront || nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) {+ fence(memory_order_acquire);+ // This block has room for at least one more element+ char* location = tailBlock_->data + blockTail * sizeof(T);+#if MOODYCAMEL_HAS_EMPLACE+ new (location) T(std::forward<Args>(args)...);+#else+ new (location) T(std::forward<U>(element));+#endif++ fence(memory_order_release);+ tailBlock_->tail = nextBlockTail;+ }+ else {+ fence(memory_order_acquire);+ if (tailBlock_->next.load() != frontBlock) {+ // Note that the reason we can't advance to the frontBlock and start adding new entries there+ // is because if we did, then dequeue would stay in that block, eventually reading the new values,+ // instead of advancing to the next full block (whose values were enqueued first and so should be+ // consumed first).++ fence(memory_order_acquire); // Ensure we get latest writes if we got the latest frontBlock++ // tailBlock is full, but there's a free block ahead, use it+ Block* tailBlockNext = tailBlock_->next.load();+ size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load();+ nextBlockTail = tailBlockNext->tail.load();+ fence(memory_order_acquire);++ // This block must be empty since it's not the head block and we+ // go through the blocks in a circle+ assert(nextBlockFront == nextBlockTail);+ tailBlockNext->localFront = nextBlockFront;++ char* location = tailBlockNext->data + nextBlockTail * sizeof(T);+#if MOODYCAMEL_HAS_EMPLACE+ new (location) T(std::forward<Args>(args)...);+#else+ new (location) T(std::forward<U>(element));+#endif++ tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask;++ fence(memory_order_release);+ tailBlock = tailBlockNext;+ }+ else if (canAlloc == CanAlloc) {+ // tailBlock is full and there's no free block ahead; create a new block+ auto newBlockSize = largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2;+ auto newBlock = make_block(newBlockSize);+ if (newBlock == nullptr) {+ // Could not allocate a block!+ return false;+ }+ largestBlockSize = newBlockSize;++#if MOODYCAMEL_HAS_EMPLACE+ new (newBlock->data) T(std::forward<Args>(args)...);+#else+ new (newBlock->data) T(std::forward<U>(element));+#endif+ assert(newBlock->front == 0);+ newBlock->tail = newBlock->localTail = 1;++ newBlock->next = tailBlock_->next.load();+ tailBlock_->next = newBlock;++ // Might be possible for the dequeue thread to see the new tailBlock->next+ // *without* seeing the new tailBlock value, but this is OK since it can't+ // advance to the next block until tailBlock is set anyway (because the only+ // case where it could try to read the next is if it's already at the tailBlock,+ // and it won't advance past tailBlock in any circumstance).++ fence(memory_order_release);+ tailBlock = newBlock;+ }+ else if (canAlloc == CannotAlloc) {+ // Would have had to allocate a new block to enqueue, but not allowed+ return false;+ }+ else {+ assert(false && "Should be unreachable code");+ return false;+ }+ }++ return true;+ }+++ // Disable copying+ ReaderWriterQueue(ReaderWriterQueue const&) { }++ // Disable assignment+ ReaderWriterQueue& operator=(ReaderWriterQueue const&) { }+++ AE_FORCEINLINE static size_t ceilToPow2(size_t x)+ {+ // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2+ --x;+ x |= x >> 1;+ x |= x >> 2;+ x |= x >> 4;+ for (size_t i = 1; i < sizeof(size_t); i <<= 1) {+ x |= x >> (i << 3);+ }+ ++x;+ return x;+ }+ + template<typename U>+ static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN+ {+ const std::size_t alignment = std::alignment_of<U>::value;+ return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;+ }+private:+#ifndef NDEBUG+ struct ReentrantGuard+ {+ AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection)+ : inSection(_inSection)+ {+ assert(!inSection && "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one thread at a time may hold the producer or consumer role)");+ inSection = true;+ }++ AE_NO_TSAN ~ReentrantGuard() { inSection = false; }++ private:+ ReentrantGuard& operator=(ReentrantGuard const&);++ private:+ weak_atomic<bool>& inSection;+ };+#endif++ struct Block+ {+ // Avoid false-sharing by putting highly contended variables on their own cache lines+ weak_atomic<size_t> front; // (Atomic) Elements are read from here+ size_t localTail; // An uncontended shadow copy of tail, owned by the consumer+ + char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - sizeof(size_t)];+ weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here+ size_t localFront;+ + char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - sizeof(size_t)]; // next isn't very contended, but we don't want it on the same cache line as tail (which is)+ weak_atomic<Block*> next; // (Atomic)+ + char* data; // Contents (on heap) are aligned to T's alignment++ const size_t sizeMask;+++ // size must be a power of two (and greater than 0)+ AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data)+ : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), sizeMask(_size - 1), rawThis(_rawThis)+ {+ }++ private:+ // C4512 - Assignment operator could not be generated+ Block& operator=(Block const&);++ public:+ char* rawThis;+ };+ + + static Block* make_block(size_t capacity) AE_NO_TSAN+ {+ // Allocate enough memory for the block itself, as well as all the elements it will contain+ auto size = sizeof(Block) + std::alignment_of<Block>::value - 1;+ size += sizeof(T) * capacity + std::alignment_of<T>::value - 1;+ auto newBlockRaw = static_cast<char*>(std::malloc(size));+ if (newBlockRaw == nullptr) {+ return nullptr;+ }+ + auto newBlockAligned = align_for<Block>(newBlockRaw);+ auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block));+ return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData);+ }++private:+ weak_atomic<Block*> frontBlock; // (Atomic) Elements are dequeued from this block+ + char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)];+ weak_atomic<Block*> tailBlock; // (Atomic) Elements are enqueued to this block++ size_t largestBlockSize;++#ifndef NDEBUG+ weak_atomic<bool> enqueuing;+ mutable weak_atomic<bool> dequeuing;+#endif+};++// Like ReaderWriterQueue, but also providees blocking operations+template<typename T, size_t MAX_BLOCK_SIZE = 512>+class BlockingReaderWriterQueue+{+private:+ typedef ::moodycamel::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue;+ +public:+ explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN+ : inner(size), sema(new spsc_sema::LightweightSemaphore())+ { }++ BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN+ : inner(std::move(other.inner)), sema(std::move(other.sema))+ { }++ BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN+ {+ std::swap(sema, other.sema);+ std::swap(inner, other.inner);+ return *this;+ }+++ // Enqueues a copy of element if there is room in the queue.+ // Returns true if the element was enqueued, false otherwise.+ // Does not allocate memory.+ AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN+ {+ if (inner.try_enqueue(element)) {+ sema->signal();+ return true;+ }+ return false;+ }++ // Enqueues a moved copy of element if there is room in the queue.+ // Returns true if the element was enqueued, false otherwise.+ // Does not allocate memory.+ AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN+ {+ if (inner.try_enqueue(std::forward<T>(element))) {+ sema->signal();+ return true;+ }+ return false;+ }++#if MOODYCAMEL_HAS_EMPLACE+ // Like try_enqueue() but with emplace semantics (i.e. construct-in-place).+ template<typename... Args>+ AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN+ {+ if (inner.try_emplace(std::forward<Args>(args)...)) {+ sema->signal();+ return true;+ }+ return false;+ }+#endif+++ // Enqueues a copy of element on the queue.+ // Allocates an additional block of memory if needed.+ // Only fails (returns false) if memory allocation fails.+ AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN+ {+ if (inner.enqueue(element)) {+ sema->signal();+ return true;+ }+ return false;+ }++ // Enqueues a moved copy of element on the queue.+ // Allocates an additional block of memory if needed.+ // Only fails (returns false) if memory allocation fails.+ AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN+ {+ if (inner.enqueue(std::forward<T>(element))) {+ sema->signal();+ return true;+ }+ return false;+ }++#if MOODYCAMEL_HAS_EMPLACE+ // Like enqueue() but with emplace semantics (i.e. construct-in-place).+ template<typename... Args>+ AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN+ {+ if (inner.emplace(std::forward<Args>(args)...)) {+ sema->signal();+ return true;+ }+ return false;+ }+#endif+++ // Attempts to dequeue an element; if the queue is empty,+ // returns false instead. If the queue has at least one element,+ // moves front to result using operator=, then returns true.+ template<typename U>+ bool try_dequeue(U& result) AE_NO_TSAN+ {+ if (sema->tryWait()) {+ bool success = inner.try_dequeue(result);+ assert(success);+ AE_UNUSED(success);+ return true;+ }+ return false;+ }+ + + // Attempts to dequeue an element; if the queue is empty,+ // waits until an element is available, then dequeues it.+ template<typename U>+ void wait_dequeue(U& result) AE_NO_TSAN+ {+ while (!sema->wait());+ bool success = inner.try_dequeue(result);+ AE_UNUSED(result);+ assert(success);+ AE_UNUSED(success);+ }+++ // Attempts to dequeue an element; if the queue is empty,+ // waits until an element is available up to the specified timeout,+ // then dequeues it and returns true, or returns false if the timeout+ // expires before an element can be dequeued.+ // Using a negative timeout indicates an indefinite timeout,+ // and is thus functionally equivalent to calling wait_dequeue.+ template<typename U>+ bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN+ {+ if (!sema->wait(timeout_usecs)) {+ return false;+ }+ bool success = inner.try_dequeue(result);+ AE_UNUSED(result);+ assert(success);+ AE_UNUSED(success);+ return true;+ }+++#if __cplusplus > 199711L || _MSC_VER >= 1700+ // Attempts to dequeue an element; if the queue is empty,+ // waits until an element is available up to the specified timeout,+ // then dequeues it and returns true, or returns false if the timeout+ // expires before an element can be dequeued.+ // Using a negative timeout indicates an indefinite timeout,+ // and is thus functionally equivalent to calling wait_dequeue.+ template<typename U, typename Rep, typename Period>+ inline bool wait_dequeue_timed(U& result, std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN+ {+ return wait_dequeue_timed(result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());+ }+#endif+++ // Returns a pointer to the front element in the queue (the one that+ // would be removed next by a call to `try_dequeue` or `pop`). If the+ // queue appears empty at the time the method is called, nullptr is+ // returned instead.+ // Must be called only from the consumer thread.+ AE_FORCEINLINE T* peek() const AE_NO_TSAN+ {+ return inner.peek();+ }+ + // Removes the front element from the queue, if any, without returning it.+ // Returns true on success, or false if the queue appeared empty at the time+ // `pop` was called.+ AE_FORCEINLINE bool pop() AE_NO_TSAN+ {+ if (sema->tryWait()) {+ bool result = inner.pop();+ assert(result);+ AE_UNUSED(result);+ return true;+ }+ return false;+ }+ + // Returns the approximate number of items currently in the queue.+ // Safe to call from both the producer and consumer threads.+ AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN+ {+ return sema->availableApprox();+ }++ // Returns the total number of items that could be enqueued without incurring+ // an allocation when this queue is empty.+ // Safe to call from both the producer and consumer threads.+ //+ // NOTE: The actual capacity during usage may be different depending on the consumer.+ // If the consumer is removing elements concurrently, the producer cannot add to+ // the block the consumer is removing from until it's completely empty, except in+ // the case where the producer was writing to the same block the consumer was+ // reading from the whole time.+ AE_FORCEINLINE size_t max_capacity() const {+ return inner.max_capacity();+ }++private:+ // Disable copying & assignment+ BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) { }+ BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) { }+ +private:+ ReaderWriterQueue inner;+ std::unique_ptr<spsc_sema::LightweightSemaphore> sema;+};++} // end namespace moodycamel++#ifdef AE_VCPP+#pragma warning(pop)+#endif