diff --git a/MakefileMingW64 b/MakefileMingW64 new file mode 100644 index 0000000..2da2152 --- /dev/null +++ b/MakefileMingW64 @@ -0,0 +1,33 @@ +CXX = g++ +CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_WINUWP=1 +CXXINC = -Iinclude + +LDLIBS = -lraylibdll -pthread -lwindowsapp +LDFLAGS = -L./lib -L/usr/lib -L/usr/local/lib + +hdr_files = ${wildcard src/*.h} + +src_files = ${wildcard src/*.cpp} +src_files += ${wildcard include/libremidi/*.cpp} + +all: viseq + +viseq: $(src_files) $(hdr_files) + mkdir -p build + $(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS) + +$(src_files): %.cpp : %_src + $(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC) + +$(hdr_files): %.h : %_hdr + @echo "G++ >---------- $(*F).h -----------<" + +%_src: + @echo "G++ >---------- $(*F).cpp -----------<" + +%_hdr: + @echo "G++ >---------- $(*F).h -----------<" + +clean: + rm -f build/* + rm -f viseq diff --git a/include/libremidi/api-c.h b/include/libremidi/api-c.h new file mode 100644 index 0000000..db5f26a --- /dev/null +++ b/include/libremidi/api-c.h @@ -0,0 +1,39 @@ +#pragma once + +#if __cplusplus +extern "C" { +#endif + +//! MIDI API specifier arguments. +//! To get information on which feature is supported by each back-end, check their backend file +//! in e.g. backends/winmm.hpp, etc. +typedef enum libremidi_api +{ + UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */ + + // MIDI 1.0 APIs + COREMIDI = 0x1, /*!< macOS CoreMidi API. */ + ALSA_SEQ, /*!< Linux ALSA Sequencer API. */ + ALSA_RAW, /*!< Linux Raw ALSA API. */ + JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */ + WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */ + WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */ + WEBMIDI, /*!< Web MIDI API through Emscripten */ + PIPEWIRE, /*!< PipeWire */ + KEYBOARD, /*!< Computer keyboard input */ + NETWORK, /*!< MIDI over IP */ + + // MIDI 2.0 APIs + ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */ + ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */ + COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */ + WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */ + KEYBOARD_UMP, /*!< Computer keyboard input */ + NETWORK_UMP, /*!< MIDI2 over IP */ + + DUMMY = 0xFFFF /*!< A compilable but non-functional API. */ +} libremidi_api; + +#if __cplusplus +} +#endif diff --git a/include/libremidi/api.hpp b/include/libremidi/api.hpp index 7dc45ff..208fd9d 100644 --- a/include/libremidi/api.hpp +++ b/include/libremidi/api.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -9,28 +11,7 @@ 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. -enum class API -{ - UNSPECIFIED, /*!< Search for a working compiled API. */ - - // MIDI 1.0 APIs - COREMIDI, /*!< macOS CoreMidi API. */ - ALSA_SEQ, /*!< Linux ALSA Sequencer API. */ - ALSA_RAW, /*!< Linux Raw ALSA API. */ - JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */ - WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */ - WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */ - WEBMIDI, /*!< Web MIDI API through Emscripten */ - PIPEWIRE, /*!< PipeWire */ - - // MIDI 2.0 APIs - ALSA_RAW_UMP, /*!< Raw ALSA API for MIDI 2.0 */ - ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */ - COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */ - WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */ - - DUMMY /*!< A compilable but non-functional API. */ -}; +using API = libremidi_api; /** * \brief A function to determine the available compiled MIDI 1.0 APIs. @@ -50,6 +31,9 @@ LIBREMIDI_EXPORT std::vector available_apis() noexcept; */ LIBREMIDI_EXPORT std::vector available_ump_apis() noexcept; +LIBREMIDI_EXPORT +libremidi::API midi_api(const std::any& conf); + //! A static function to determine the current version. LIBREMIDI_EXPORT std::string_view get_version() noexcept; @@ -60,6 +44,17 @@ 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(api) >= 0x1 && static_cast(api) < 0x1000) + || api == libremidi::API::DUMMY; +} + +inline constexpr bool is_midi2(libremidi::API api) +{ + return static_cast(api) >= 0x1000; +} + namespace midi1 { //! Returns the default MIDI 1.0 backend to use for the target OS. diff --git a/include/libremidi/backends.hpp b/include/libremidi/backends.hpp index 3738d63..fb289eb 100644 --- a/include/libremidi/backends.hpp +++ b/include/libremidi/backends.hpp @@ -1,4 +1,7 @@ #pragma once +#include + +#include #include #if !__has_include() && !__has_include() @@ -44,9 +47,10 @@ #if defined(LIBREMIDI_WINUWP) #include - #if __has_include() - #include - #endif +#endif + +#if defined(LIBREMIDI_WINMIDI) + #include #endif #if defined(LIBREMIDI_EMSCRIPTEN) @@ -55,6 +59,15 @@ #include +#if defined(LIBREMIDI_KEYBOARD) + #include +#endif + +#if defined(LIBREMIDI_NETWORK) + #include + #include +#endif + namespace libremidi { // The order here will control the order of the API search in @@ -100,6 +113,14 @@ static constexpr auto available_backends = make_tl( #if defined(LIBREMIDI_PIPEWIRE) , pipewire::backend{} +#endif +#if defined(LIBREMIDI_KEYBOARD) + , + kbd_backend{} +#endif +#if defined(LIBREMIDI_NETWORK) + , + net::backend{} #endif , dummy_backend{}); @@ -135,15 +156,13 @@ static constexpr auto available_backends = make_tl( , coremidi_ump::backend{} #endif -#if defined(LIBREMIDI_JACK) -#endif -#if defined(LIBREMIDI_WINUWP) - #if __has_include() +#if defined(LIBREMIDI_WINMIDI) , winmidi::backend{} - #endif #endif -#if defined(LIBREMIDI_EMSCRIPTEN) +#if defined(LIBREMIDI_NETWORK) + , + net_ump::backend{} #endif , dummy_backend{}); @@ -183,5 +202,49 @@ auto for_backend(libremidi::API api, F&& f) midi2::for_backend(api, f); } +void for_input_configuration(auto f, std::any& api_conf) +{ + auto from_api = [&](T& /*backend*/) mutable { + if (auto conf = std::any_cast(&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 = [&](T& /*backend*/) mutable { + if (auto conf = std::any_cast(&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 = [&](T& /*backend*/) mutable { + if (auto conf = std::any_cast(&api_conf)) + { + f(*conf); + return true; + } + return false; + }; + if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends)) + return; + std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); +} } } diff --git a/include/libremidi/backends/alsa_raw/config.hpp b/include/libremidi/backends/alsa_raw/config.hpp index afe62eb..446ce9b 100644 --- a/include/libremidi/backends/alsa_raw/config.hpp +++ b/include/libremidi/backends/alsa_raw/config.hpp @@ -61,6 +61,7 @@ struct manual_poll_parameters struct alsa_raw_input_configuration { std::function manual_poll; + std::chrono::milliseconds poll_period{2}; }; struct alsa_raw_output_configuration diff --git a/include/libremidi/backends/alsa_raw/error_domain.hpp b/include/libremidi/backends/alsa_raw/error_domain.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/libremidi/backends/alsa_raw/helpers.hpp b/include/libremidi/backends/alsa_raw/helpers.hpp index 7aa23b7..1783a5a 100644 --- a/include/libremidi/backends/alsa_raw/helpers.hpp +++ b/include/libremidi/backends/alsa_raw/helpers.hpp @@ -3,14 +3,9 @@ #include #include -#include #include -#include -#include -#include #include #include -#include #include // Credits: greatly inspired from @@ -87,39 +82,16 @@ struct snd_ctl_wrapper struct enumerator { const libasound& snd = libasound::instance(); + const error_handler& handler; + const observer_configuration& configuration; std::vector inputs; std::vector outputs; - std::function error_callback; - std::function warn_callback; + explicit enumerator(const auto& self) + : handler{self} + , configuration{self.configuration} + { - template - void warning(Args&&... args) - { - std::string s; - ((s += args), ...); - if (warn_callback) - { - warn_callback(std::move(s)); - } - else - { - std::cerr << s << std::endl; - } - } - template - void error(Args&&... args) - { - std::string s; - ((s += args), ...); - if (error_callback) - { - error_callback(std::move(s)); - } - else - { - throw std::runtime_error(s.c_str()); - } } // 1: is an input / output @@ -141,9 +113,9 @@ struct enumerator } else if (status < 0 && status != -ENXIO) { - error( - "alsa_raw_helpers::enumerator::is: cannot get rawmidi information:", card, device, sub, - snd.strerror(status)); + handler.libremidi_handle_error( + configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub) + + " : " + snd.strerror(status)); return status; } else @@ -185,26 +157,23 @@ struct enumerator return s; } - void enumerate_cards() + stdx::error enumerate_cards() { + using namespace std::literals; int card = -1; int status = snd.card.next(&card); if (status < 0) { - error( - "alsa_raw_helpers::enumerator::enumerate_cards: " - "cannot determine card number: ", - snd.strerror(status)); - return; + handler.libremidi_handle_error( + configuration, "Cannot determine card number: "s + snd.strerror(status)); + return from_errc(status); } if (card < 0) { - error( - "alsa_raw_helpers::enumerator::enumerate_cards: " - "no sound cards found"); - return; + handler.libremidi_handle_error(configuration, "No sound cards found"); + return std::errc::no_such_device; } while (card >= 0) @@ -213,13 +182,12 @@ struct enumerator if ((status = snd.card.next(&card)) < 0) { - error( - "alsa_raw_helpers::enumerator::enumerate_cards: " - "cannot determine card number: ", - snd.strerror(status)); - break; + 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; @@ -228,20 +196,22 @@ struct enumerator 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.error( - "alsa_raw_helpers::enumerator::snd_ctl_wrapper: " - "cannot open control for card", - name, snd.strerror(status)); + 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); @@ -261,10 +231,8 @@ struct midi1_enumerator : enumerator if (status < 0) { - error( - "alsa_raw::midi1_enumerator::enumerate_devices: " - "cannot determine device number: ", - snd.strerror(status)); + handler.libremidi_handle_error( + configuration, "Cannot determine device number: "s + snd.strerror(status)); break; } diff --git a/include/libremidi/backends/alsa_raw/midi_in.hpp b/include/libremidi/backends/alsa_raw/midi_in.hpp index ec9018d..fda1b36 100644 --- a/include/libremidi/backends/alsa_raw/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw/midi_in.hpp @@ -33,83 +33,72 @@ public: ~midi_in_impl() override { } - bool open_virtual_port(std::string_view) override - { - warning(configuration, "midi_in_alsa_raw: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_alsa_raw: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_in_alsa_raw: set_port_name unsupported"); - } - libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; } // Must be a string such as: "hw:2,4,1" - [[nodiscard]] int do_init_port(const char* portname) + [[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) { - error(this->configuration, "midi_in_alsa_raw::open_port: cannot open device."); - return err; + 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 err; + return from_errc(err); if (const int err = snd.rawmidi.params_set_no_active_sensing(midiport_, params, 1); err < 0) - return err; + 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 err; + return from_errc(err); if (const int err = snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_NONE); err < 0) - return err; + return from_errc(err); } else { if (const int err = snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_TSTAMP); err < 0) - return err; + return from_errc(err); if (const int err = snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_MONOTONIC); err < 0) - return err; + return from_errc(err); } #endif if (const int err = snd.rawmidi.params(midiport_, params); err < 0) - return err; + return from_errc(err); return init_pollfd(); } - [[nodiscard]] int init_port(const port_information& p) + [[nodiscard]] stdx::error init_port(const port_information& p) { return do_init_port(raw_from_port_handle(p.port).to_string().c_str()); } - [[nodiscard]] int init_pollfd() + [[nodiscard]] stdx::error init_pollfd() { const int num_fds = snd.rawmidi.poll_descriptors_count(this->midiport_); this->fds_.clear(); this->fds_.resize(num_fds); - return snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), 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 fds) @@ -155,7 +144,8 @@ public: while ((err = snd.rawmidi.read(this->midiport_, bytes, nbytes)) > 0) { const auto to_ns = [this] { return absolute_timestamp(); }; - decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp(to_ns, 0)); + m_processing.on_bytes( + {bytes, bytes + err}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -180,7 +170,8 @@ public: const auto to_ns = [ts] { return static_cast(ts.tv_sec) * 1'000'000'000 + static_cast(ts.tv_nsec); }; - decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp(to_ns, 0)); + m_processing.on_bytes( + {bytes, bytes + err}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -188,18 +179,19 @@ public: ssize_t read_input_buffer_with_timestamps() { return read_input_buffer(); } #endif - void close_port() override + 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 fds_; - midi1::input_state_machine decoder_{this->configuration}; + midi1::input_state_machine m_processing{this->configuration}; }; class midi_in_alsa_raw_threaded : public midi_in_impl @@ -210,26 +202,33 @@ public: { if (this->termination_event < 0) { - error( - this->configuration, "midi_in_alsa::initialize: error creating eventfd."); + 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(this->configuration.poll_period) + .count(); for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), -1); + ssize_t err = poll(fds_.data(), fds_.size(), period); if (err == -EAGAIN) continue; else if (err < 0) @@ -245,7 +244,7 @@ private: } } - [[nodiscard]] ssize_t start_thread() + [[nodiscard]] stdx::error start_thread() { try { @@ -258,36 +257,37 @@ private: 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; - error( + libremidi_handle_error( this->configuration, - "midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what()); - return false; + "error starting MIDI input thread: "s + e.what()); + return e.code(); } - return true; + return stdx::error{}; } - bool open_port(const input_port& port, std::string_view /*name*/) override + stdx::error open_port(const input_port& port, std::string_view /*name*/) override { - if (const int err = midi_in_impl::init_port(port); err < 0) - return false; - if (!start_thread()) - return false; - return true; + 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{}; } - void close_port() override + stdx::error close_port() override { termination_event.notify(); if (thread_.joinable()) thread_.join(); termination_event.consume(); // Reset to zero - midi_in_impl::close_port(); + return midi_in_impl::close_port(); } std::thread thread_; @@ -297,12 +297,18 @@ private: class midi_in_alsa_raw_manual : public midi_in_impl { public: - using midi_in_impl::midi_in_impl; + 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: @@ -326,12 +332,12 @@ private: } } - bool open_port(const input_port& p, std::string_view /*name*/) override + stdx::error open_port(const input_port& p, std::string_view /*name*/) override { - if (midi_in_impl::init_port(p) < 0) - return false; + if (auto err = midi_in_impl::init_port(p); err != stdx::error{}) + return err; send_poll_callback(); - return true; + return stdx::error{}; } }; } diff --git a/include/libremidi/backends/alsa_raw/midi_out.hpp b/include/libremidi/backends/alsa_raw/midi_out.hpp index 0165f21..227b883 100644 --- a/include/libremidi/backends/alsa_raw/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw/midi_out.hpp @@ -26,83 +26,73 @@ public: 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; } - bool open_virtual_port(std::string_view) override - { - warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_alsa_raw: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_out_alsa_raw: set_port_name unsupported"); - } - - int connect_port(const char* portname) + 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) { - error( - this->configuration, "midi_out_alsa_raw::open_port: cannot open device."); - return status; + libremidi_handle_error( + this->configuration, "cannot open device."); + return from_errc(status); } - return status; + return stdx::error{}; } - bool open_port(const output_port& p, std::string_view) override + 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()) == 0; + return connect_port(raw_from_port_handle(p.port).to_string().c_str()); } - void close_port() override + stdx::error close_port() override { if (midiport_) snd.rawmidi.close(midiport_); midiport_ = nullptr; + + return stdx::error{}; } - void send_message(const unsigned char* message, size_t size) override + stdx::error send_message(const unsigned char* message, size_t size) override { if (!midiport_) - error( + libremidi_handle_error( this->configuration, - "midi_out_alsa_raw::send_message: trying to send a message without an open " + "trying to send a message without an open " "port."); if (!this->configuration.chunking) { - write(message, size); + return write(message, size); } else { - write_chunked(message, size); + return write_chunked(message, size); } } - bool write(const unsigned char* message, size_t size) + stdx::error write(const unsigned char* message, size_t size) { - if (snd.rawmidi.write(midiport_, message, size) < 0) + if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0) { - error( - this->configuration, "midi_out_alsa_raw::send_message: cannot write message."); - return false; + libremidi_handle_error( + this->configuration, "cannot write message."); + return from_errc(err); } - return true; + return stdx::error{}; } std::size_t get_chunk_size() const noexcept @@ -125,7 +115,7 @@ public: } // inspired from ALSA amidi.c source code - void write_chunked(const unsigned char* const begin, size_t size) + stdx::error write_chunked(const unsigned char* const begin, size_t size) { const unsigned char* data = begin; const unsigned char* end = begin + size; @@ -135,8 +125,8 @@ public: // Send the first buffer std::size_t len = chunk_size; - if (!write(data, len)) - return; + if (auto err = write(data, len); err != stdx::error{}) + return err; data += len; @@ -149,11 +139,11 @@ public: { if (!configuration.chunking->wait( std::chrono::microseconds((chunk_size - available) * 320), written_bytes)) - return; + return std::errc::protocol_error; }; if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes)) - return; + return std::errc::protocol_error; // Write more data len = end - data; @@ -165,11 +155,13 @@ public: if (len > chunk_size) len = chunk_size; - if (!write(data, len)) - return; + if (auto err = write(data, len); err != stdx::error{}) + return err; data += len; } + + return stdx::error{}; } snd_rawmidi_t* midiport_{}; diff --git a/include/libremidi/backends/alsa_raw/observer.hpp b/include/libremidi/backends/alsa_raw/observer.hpp index 460a767..68f2afb 100644 --- a/include/libremidi/backends/alsa_raw/observer.hpp +++ b/include/libremidi/backends/alsa_raw/observer.hpp @@ -8,12 +8,12 @@ #include #include - #include - namespace libremidi::alsa_raw { template -class observer_impl_base : public observer_api +class observer_impl_base + : public observer_api + , public error_handler { public: struct @@ -54,7 +54,7 @@ public: std::vector get_input_ports() const noexcept override { std::vector ret; - Enumerator new_devs; + Enumerator new_devs{*this}; new_devs.enumerate_cards(); for (auto& d : new_devs.inputs) @@ -67,7 +67,7 @@ public: std::vector get_output_ports() const noexcept override { std::vector ret; - Enumerator new_devs; + Enumerator new_devs{*this}; new_devs.enumerate_cards(); for (auto& d : new_devs.outputs) @@ -147,7 +147,7 @@ private: void check_devices() { - Enumerator new_devs; + Enumerator new_devs{*this}; new_devs.enumerate_cards(); diff --git a/include/libremidi/backends/alsa_raw_ump/config.hpp b/include/libremidi/backends/alsa_raw_ump/config.hpp index 6165eba..a89ccaf 100644 --- a/include/libremidi/backends/alsa_raw_ump/config.hpp +++ b/include/libremidi/backends/alsa_raw_ump/config.hpp @@ -6,6 +6,7 @@ namespace libremidi::alsa_raw_ump struct input_configuration { std::function manual_poll; + std::chrono::milliseconds poll_period{2}; }; struct output_configuration diff --git a/include/libremidi/backends/alsa_raw_ump/helpers.hpp b/include/libremidi/backends/alsa_raw_ump/helpers.hpp index c7af4d7..5b16ca9 100644 --- a/include/libremidi/backends/alsa_raw_ump/helpers.hpp +++ b/include/libremidi/backends/alsa_raw_ump/helpers.hpp @@ -7,8 +7,10 @@ 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); @@ -28,10 +30,8 @@ struct midi2_enumerator : alsa_raw::enumerator if (status < 0) { - error( - "alsa_raw_ump::midi2_enumerator::enumerate_devices: " - "cannot determine device number: ", - snd.strerror(status)); + handler.libremidi_handle_error( + configuration, "Cannot determine device number: "s + snd.strerror(status)); break; } diff --git a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp index 9bc8fb5..e4b1640 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp @@ -35,32 +35,17 @@ public: ~midi_in_impl() override { } - bool open_virtual_port(std::string_view) override - { - warning(configuration, "alsa_raw_ump::ump: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "alsa_raw_ump::ump: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "alsa_raw_ump::ump: set_port_name unsupported"); - } - 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]] int do_init_port(const char* portname) + [[nodiscard]] stdx::error do_init_port(const char* portname) { constexpr int mode = 0; - SND_RAWMIDI_NONBLOCK; + SND_RAWMIDI_NONBLOCK; // fixme if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0) { - error( - this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); - return err; + libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); + return from_errc(err); } snd_rawmidi_params_t* params{}; @@ -69,49 +54,52 @@ public: auto rawmidi = snd.ump.rawmidi(midiport_); if (int err = snd.ump.rawmidi_params_current(midiport_, params); err < 0) - return err; + return from_errc(err); if (int err = snd.rawmidi.params_set_no_active_sensing(rawmidi, params, 1); err < 0) - return err; + 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 err; + return from_errc(err); if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_NONE); err < 0) - return err; + return from_errc(err); } else { if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_TSTAMP); err < 0) - return err; + return from_errc(err); if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_MONOTONIC); err < 0) - return err; + return from_errc(err); } if (int err = snd.ump.rawmidi_params(midiport_, params); err < 0) - return err; + return from_errc(err); return init_pollfd(); } - [[nodiscard]] int init_port(const port_information& p) + [[nodiscard]] stdx::error init_port(const port_information& p) { return do_init_port(raw_from_port_handle(p.port).to_string().c_str()); } - [[nodiscard]] int init_pollfd() + [[nodiscard]] stdx::error init_pollfd() { int num_fds = snd.ump.poll_descriptors_count(this->midiport_); this->fds_.clear(); this->fds_.resize(num_fds); - return snd.ump.poll_descriptors(this->midiport_, fds_.data(), 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 fds) @@ -143,95 +131,60 @@ public: ssize_t read_input_buffer() { - static const constexpr int nbytes = 1024; - - unsigned char bytes[nbytes]; + 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_, bytes, nbytes)) > 0) + while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0) { - std::cerr << "We read: " << nbytes << "bytes !!!!"; - return 1; - // err is the amount of bytes read - // decoder_.add_bytes(bytes, err); + const auto to_ns = [this] { return absolute_timestamp(); }; + m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } - void set_timestamp(struct timespec ts, timestamp& res) - { - static constexpr int64_t nanos = 1e9; - switch (configuration.timestamps) - { - // Unneeded here - case timestamp_mode::NoTimestamp: - break; - case timestamp_mode::Relative: { - const auto t = static_cast(ts.tv_sec) * nanos + static_cast(ts.tv_nsec); - if (firstMessage == true) - { - firstMessage = false; - res = 0; - } - else - { - res = t - last_time; - } - last_time = t; - break; - } - case timestamp_mode::Absolute: - case timestamp_mode::SystemMonotonic: - res = static_cast(ts.tv_sec) * nanos + static_cast(ts.tv_nsec); - break; - case timestamp_mode::Custom: - res = configuration.get_timestamp( - static_cast(ts.tv_sec) * nanos + static_cast(ts.tv_nsec)); - break; - } - } - ssize_t read_input_buffer_with_timestamps() { - static const 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]; + 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, bytes, nbytes)) > 0) + while ((err = snd.ump.tread(this->midiport_, &ts, words, nwords * 4)) > 0) { - // err is the amount of bytes read - int64_t ns{}; - set_timestamp(ts, ns); - std::cerr << "We read: " << nbytes << "bytes !!!!"; - return 1; - // decoder_.add_bytes(bytes, err, ns); + const auto to_ns = [ts] { + return static_cast(ts.tv_sec) * 1'000'000'000 + static_cast(ts.tv_nsec); + }; + m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } - void close_port() override + 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(); } - midi2_enumerator get_device_enumerator() const noexcept - { - midi2_enumerator device_list; - device_list.error_callback - = [this](std::string_view text) { this->error(this->configuration, text); }; - return device_list; - } - snd_ump_t* midiport_{}; std::vector fds_; - // midi_stream_decoder decoder_{this->configuration.on_message}; - int64_t last_time{}; + midi2::input_state_machine m_processing{this->configuration}; }; class midi_in_impl_threaded : public midi_in_impl @@ -243,26 +196,32 @@ public: { if (this->termination_event < 0) { - error( - this->configuration, "midi_in_alsa::initialize: error creating eventfd."); + 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(this->configuration.poll_period) + .count(); for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), -1); + ssize_t err = poll(fds_.data(), fds_.size(), period); if (err == -EAGAIN) continue; else if (err < 0) @@ -278,7 +237,7 @@ private: } } - [[nodiscard]] int start_thread() + [[nodiscard]] stdx::error start_thread() { try { @@ -291,36 +250,37 @@ private: 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; - error( + libremidi_handle_error( this->configuration, - "midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what()); - return false; + "error starting MIDI input thread: "s + e.what()); + return e.code(); } - return true; + return stdx::error{}; } - bool open_port(const input_port& port, [[maybe_unused]] std::string_view name) override + stdx::error open_port(const input_port& port, [[maybe_unused]] std::string_view name) override { - if (int err = midi_in_impl::init_port(port); err < 0) - return false; - if (!start_thread()) - return false; - return true; + 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{}; } - void close_port() override + stdx::error close_port() override { termination_event.notify(); if (thread_.joinable()) thread_.join(); termination_event.consume(); // Reset to zero - midi_in_impl::close_port(); + return midi_in_impl::close_port(); } std::thread thread_; @@ -330,12 +290,18 @@ private: class midi_in_impl_manual : public midi_in_impl { public: - using midi_in_impl::midi_in_impl; + 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: @@ -359,12 +325,12 @@ private: } } - bool open_port(const input_port& p, [[maybe_unused]] std::string_view name) override + stdx::error open_port(const input_port& p, [[maybe_unused]] std::string_view name) override { - if (midi_in_impl::init_port(p) < 0) - return false; + if (auto err = midi_in_impl::init_port(p); err != stdx::error{}) + return err; send_poll_callback(); - return true; + return stdx::error{}; } }; } diff --git a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp index a43d694..f350cde 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp @@ -29,76 +29,63 @@ public: : 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; } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; } - bool open_virtual_port(std::string_view) override - { - warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_alsa_raw: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_out_alsa_raw: set_port_name unsupported"); - } - - int connect_port(const char* portname) + stdx::error connect_port(const char* portname) { constexpr int mode = SND_RAWMIDI_SYNC; - int status = snd.ump.open(NULL, &midiport_, portname, mode); - if (status < 0) + int ret = snd.ump.open(NULL, &midiport_, portname, mode); + if (ret < 0) { - error( - this->configuration, "midi_out_alsa_raw::open_port: cannot open device."); - return status; + libremidi_handle_error(this->configuration, "cannot open device."); + return from_errc(ret); } - return status; + return stdx::error{}; } - bool open_port(const output_port& p, std::string_view) override + 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()) == 0; + return connect_port(raw_from_port_handle(p.port).to_string().c_str()); } - void close_port() override + stdx::error close_port() override { if (midiport_) snd.ump.close(midiport_); midiport_ = nullptr; + return stdx::error{}; } - void send_ump(const uint32_t* ump_stream, std::size_t count) override + stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override { if (!midiport_) - error( + libremidi_handle_error( this->configuration, - "midi_out_alsa_raw::send_message: trying to send a message without an open " + "trying to send a message without an open " "port."); - write(ump_stream, count * sizeof(uint32_t)); + return write(ump_stream, count * sizeof(uint32_t)); } - bool write(const uint32_t* ump_stream, size_t bytes) + stdx::error write(const uint32_t* ump_stream, size_t bytes) { - if (snd.ump.write(midiport_, ump_stream, bytes) < 0) + if (auto err = snd.ump.write(midiport_, ump_stream, bytes); err < 0) { - error( - this->configuration, "midi_out_alsa_raw::send_message: cannot write message."); - return false; + libremidi_handle_error(this->configuration, "cannot write message."); + return from_errc(err); } - return true; + return stdx::error{}; } snd_ump_t* midiport_{}; diff --git a/include/libremidi/backends/alsa_seq/config.hpp b/include/libremidi/backends/alsa_seq/config.hpp index b0fa758..8153fb3 100644 --- a/include/libremidi/backends/alsa_seq/config.hpp +++ b/include/libremidi/backends/alsa_seq/config.hpp @@ -38,6 +38,7 @@ struct input_configuration snd_seq_t* context{}; std::function manual_poll; std::function stop_poll; + std::chrono::milliseconds poll_period{2}; static constexpr int midi_version = 1; }; @@ -56,6 +57,7 @@ struct observer_configuration snd_seq_t* context{}; std::function manual_poll; std::function stop_poll; + std::chrono::milliseconds poll_period{100}; static constexpr int midi_version = 1; }; diff --git a/include/libremidi/backends/alsa_seq/helpers.hpp b/include/libremidi/backends/alsa_seq/helpers.hpp index 05f8ca4..0500a28 100644 --- a/include/libremidi/backends/alsa_seq/helpers.hpp +++ b/include/libremidi/backends/alsa_seq/helpers.hpp @@ -177,18 +177,20 @@ struct alsa_data } } - void set_client_name(std::string_view clientName) + stdx::error set_client_name(std::string_view clientName) { - snd.seq.set_client_name(seq, clientName.data()); + int ret = snd.seq.set_client_name(seq, clientName.data()); + return from_errc(ret); } - void set_port_name(std::string_view portName) + 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()); - snd.seq.set_port_info(seq, vport, pinfo); + int ret = snd.seq.set_port_info(seq, vport, pinfo); + return from_errc(ret); } unsigned int get_port_count(int caps) const @@ -209,9 +211,9 @@ struct alsa_data // snd.seq.port_info_set_port(src_pinfo, port); // { - // self.template error( + // self.libremidi_handle_error( // self.configuration, - // "alsa::get_port_info: invalid 'portNumber' argument: " + std::to_string(portNumber)); + // "invalid 'portNumber' argument: " + std::to_string(portNumber)); // return {}; // } snd_seq_addr_t addr; @@ -270,8 +272,8 @@ struct alsa_data // Make subscription if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0) { - self.template error( - self.configuration, "create_connection: ALSA error allocation port subscription."); + self.libremidi_handle_error( + self.configuration, "ALSA error allocation port subscription."); return err; } diff --git a/include/libremidi/backends/alsa_seq/midi_in.hpp b/include/libremidi/backends/alsa_seq/midi_in.hpp index 1855aec..8065097 100644 --- a/include/libremidi/backends/alsa_seq/midi_in.hpp +++ b/include/libremidi/backends/alsa_seq/midi_in.hpp @@ -17,7 +17,7 @@ using midi_in_base = std::conditional_t; template using midi_in_processing = std::conditional_t< - ConfigurationImpl::midi_version == 1, midi1::input_state_machine, dummy_processing>; + ConfigurationImpl::midi_version == 1, midi1::input_state_machine, midi2::input_state_machine>; template class midi_in_impl @@ -55,9 +55,9 @@ public: { if (init_client(configuration) < 0) { - error( + libremidi_handle_error( this->configuration, - "midi_in_alsa::initialize: error creating ALSA sequencer client " + "error creating ALSA sequencer client " "object."); return; } @@ -80,8 +80,7 @@ public: int result = snd.midi.event_new(0, &coder); if (result < 0) { - error( - this->configuration, "midi_in_alsa::initialize: error during snd_midi_event_new."); + libremidi_handle_error(this->configuration, "error during snd_midi_event_new."); return; } snd.midi.event_init(coder); @@ -105,7 +104,13 @@ public: snd.seq.close(this->seq); } - libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_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) { @@ -157,14 +162,13 @@ public: if (int ret = create_port(portName); ret < 0) { - error(configuration, "midi_in_alsa::create_port: ALSA error creating port."); + libremidi_handle_error(configuration, "ALSA error creating port."); return ret; } if (int ret = connect_port(*source); ret < 0) { - error( - configuration, "midi_in_alsa::create_port: ALSA error making port connection."); + libremidi_handle_error(configuration, "ALSA error making port connection."); return ret; } @@ -184,63 +188,21 @@ public: return 0; } - void close_port() override + stdx::error close_port() override { unsubscribe(); stop_queue(); + return stdx::error{}; } - void set_client_name(std::string_view clientName) override + stdx::error set_client_name(std::string_view clientName) override { - alsa_data::set_client_name(clientName); + return alsa_data::set_client_name(clientName); } - void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); } - -protected: - void set_timestamp(const auto& ev, auto& msg) noexcept + stdx::error set_port_name(std::string_view portName) override { - static constexpr int64_t nanos = 1e9; - switch (configuration.timestamps) - { - case timestamp_mode::NoTimestamp: - msg.timestamp = 0; - return; - case timestamp_mode::Relative: { - const auto t1 = static_cast(ev.time.time.tv_sec) * nanos - + static_cast(ev.time.time.tv_nsec); - const auto t0 = static_cast(last_time.tv_sec) * nanos - + static_cast(last_time.tv_nsec); - const auto time = t1 - t0; - - last_time = ev.time.time; - - if (this->firstMessage == true) - { - this->firstMessage = false; - msg.timestamp = 0; - } - else - { - msg.timestamp = time; - } - return; - } - case timestamp_mode::Absolute: { - msg.timestamp = ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec; - break; - } - case timestamp_mode::Custom: { - msg.timestamp - = configuration.get_timestamp(ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec); - break; - } - case timestamp_mode::SystemMonotonic: { - namespace clk = std::chrono; - msg.timestamp = system_ns(); - break; - } - } + return alsa_data::set_port_name(portName); } timestamp absolute_timestamp() const noexcept override @@ -303,11 +265,13 @@ protected: snd_seq_event_t* ev{}; event_handle handle{snd}; int result = 0; - while ((result = snd.seq.event_input(seq, &ev)) > 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; } @@ -346,11 +310,17 @@ protected: } } - // MIDI 2 : no decoder, we can just send the UMP data directly, yay - libremidi::ump ump; - std::memcpy(ump.data, ev.ump, sizeof(ev.ump)); - set_timestamp(ev, ump); - configuration.on_message(std::move(ump)); + 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(ts.tv_sec) * 1'000'000'000 + static_cast(ts.tv_nsec); + }; + + m_processing.on_bytes_multi( + {ev.ump, ev.ump + 4}, m_processing.template timestamp(to_ns, 0)); return 0; } @@ -370,7 +340,6 @@ protected: #endif int queue_id{}; // an input queue is needed to get timestamped events - snd_seq_real_time_t last_time{}; // Only needed for midi 1 std::vector decoding_buffer = std::vector(4096); @@ -386,62 +355,65 @@ public: { if (this->termination_event < 0) { - this->template error( - this->configuration, "midi_in_alsa::initialize: error creating eventfd."); + this->libremidi_handle_error(this->configuration, "error creating eventfd."); + return; } + + this->client_open_ = stdx::error{}; } - ~midi_in_alsa_threaded() { this->close_port(); } + ~midi_in_alsa_threaded() + { + this->close_port(); + this->client_open_ = std::errc::not_connected; + } private: - bool open_port(const input_port& pt, std::string_view local_port_name) override + 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 false; + return from_errc(err); - if (!start_thread()) - return false; - - return true; + return this->start_thread(); } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { if (int err = this->init_virtual_port(portName); err < 0) - return false; + return from_errc(err); - if (!this->start_thread()) - return false; - return true; + return this->start_thread(); } - void close_port() override + stdx::error close_port() override { - midi_in_impl::close_port(); + // FIXME shouldn't we always close the thread + auto err = midi_in_impl::close_port(); stop_thread(); + + return err; } - [[nodiscard]] int start_thread() + [[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->template error( - this->configuration, - "midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what()); - return false; + this->libremidi_handle_error( + this->configuration, "error starting MIDI input thread: "s + e.what()); + return e.code(); } - return true; } - void stop_thread() + stdx::error stop_thread() { termination_event.notify(); @@ -449,6 +421,7 @@ private: this->thread.join(); termination_event.consume(); + return stdx::error{}; } void thread_handler() @@ -458,12 +431,15 @@ private: 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(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, -1) >= 0) + if (poll(poll_fds, poll_fd_count, period) >= 0) { // We got our stop-thread signal if (termination_event.ready(poll_fds[0])) @@ -486,12 +462,8 @@ private: } #endif - (void)res; -#if defined(__LIBREMIDI_DEBUG__) if (res < 0) - std::cerr << "midi_in_alsa::thread_handler: MIDI input error: " << snd.strerror(res) - << "\n"; -#endif + LIBREMIDI_LOG("MIDI input error: ", this->snd.strerror(res)); } } @@ -527,31 +499,33 @@ public: ~midi_in_alsa_manual() { this->close_port(); } - bool open_port(const input_port& pt, std::string_view local_port_name) override + 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 false; + return from_errc(err); if (int err = init_callback(); err < 0) - return false; - return true; + return from_errc(err); + + return stdx::error{}; } - bool open_virtual_port(std::string_view name) override + stdx::error open_virtual_port(std::string_view name) override { if (int err = this->init_virtual_port(name); err < 0) - return false; + return from_errc(err); if (int err = init_callback(); err < 0) - return false; - return true; + return from_errc(err); + + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { this->configuration.stop_poll(this->vaddr); - midi_in_impl::close_port(); + return midi_in_impl::close_port(); } }; } diff --git a/include/libremidi/backends/alsa_seq/midi_out.hpp b/include/libremidi/backends/alsa_seq/midi_out.hpp index 147aaa5..0eea4ba 100644 --- a/include/libremidi/backends/alsa_seq/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq/midi_out.hpp @@ -23,22 +23,21 @@ public: { if (init_client(configuration) < 0) { - error( + libremidi_handle_error( this->configuration, - "midi_in_alsa::initialize: error creating ALSA sequencer client " + "error creating ALSA sequencer client " "object."); return; } if (snd.midi.event_new(this->bufferSize, &this->coder) < 0) { - error( - this->configuration, - "midi_out_alsa::initialize: error initializing MIDI event " - "parser."); + 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 @@ -54,6 +53,8 @@ public: 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; } @@ -65,55 +66,60 @@ public: SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt); } - bool open_port(const output_port& p, std::string_view portName) override + 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) { - error( - this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!"); - return false; + 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 false; + return std::errc::invalid_argument; if (int err = create_port(portName); err < 0) { - error(configuration, "midi_out_alsa::create_port: ALSA error creating port."); - return false; + 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) { - error( - configuration, "midi_out_alsa::create_port: ALSA error making port connection."); - return false; + libremidi_handle_error(configuration, "ALSA error making port connection."); + return from_errc(err); } - return true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { if (int err = create_port(portName); err < 0) - return false; - return true; + return from_errc(err); + return stdx::error{}; } - void close_port() override { unsubscribe(); } - - void set_client_name(std::string_view clientName) override + stdx::error close_port() override { - alsa_data::set_client_name(clientName); + unsubscribe(); + return stdx::error{}; } - void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); } + stdx::error set_client_name(std::string_view clientName) override + { + return alsa_data::set_client_name(clientName); + } - void send_message(const unsigned char* message, std::size_t size) override + 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) @@ -122,11 +128,11 @@ public: result = snd.midi.event_resize_buffer(this->coder, size); if (result != 0) { - error( + libremidi_handle_error( this->configuration, - "midi_out_alsa::send_message: ALSA error resizing MIDI event " + "ALSA error resizing MIDI event " "buffer."); - return; + return std::errc::no_buffer_space; } } @@ -144,14 +150,14 @@ public: result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev); if (result < 0) { - warning(this->configuration, "midi_out_alsa::send_message: event parsing error!"); - return; + libremidi_handle_warning(this->configuration, "event parsing error!"); + return std::errc::bad_message; } if (ev.type == SND_SEQ_EVENT_NONE) { - warning(this->configuration, "midi_out_alsa::send_message: incomplete message!"); - return; + libremidi_handle_warning(this->configuration, "incomplete message!"); + return std::errc::message_size; } offset += result; @@ -159,13 +165,12 @@ public: result = snd.seq.event_output(this->seq, &ev); if (result < 0) { - warning( - this->configuration, - "midi_out_alsa::send_message: error sending MIDI message to port."); - return; + 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: diff --git a/include/libremidi/backends/alsa_seq/observer.hpp b/include/libremidi/backends/alsa_seq/observer.hpp index 6722a12..fb101cb 100644 --- a/include/libremidi/backends/alsa_seq/observer.hpp +++ b/include/libremidi/backends/alsa_seq/observer.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,7 @@ template class observer_impl : public observer_api , public alsa_data + , public error_handler { public: struct @@ -40,7 +42,11 @@ public: using namespace std::literals; if (int err = init_client(configuration); err < 0) { - throw driver_error("observer_alsa: snd_seq_open failed"); + libremidi_handle_error( + this->configuration, + "error creating ALSA sequencer client " + "object."); + return; } if (!configuration.has_callbacks()) @@ -65,7 +71,8 @@ public: *this, "libremidi-observe", caps, SND_SEQ_PORT_TYPE_APPLICATION, false); if (err < 0) { - throw driver_error("observer: ALSA error creating port."); + libremidi_handle_error(this->configuration, "error creating ALSA sequencer port."); + return; } } @@ -75,7 +82,8 @@ public: = snd.seq.connect_from(seq, vport, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); if (err < 0) { - throw driver_error("observer_alsa: snd_seq_connect_from failed"); + libremidi_handle_error(this->configuration, "error connecting to ALSA sequencer."); + return; } } } @@ -98,9 +106,13 @@ public: 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 & SND_SEQ_PORT_TYPE_SOFTWARE) && this->configuration.track_virtual) + else if ((tp & virtual_port) && this->configuration.track_virtual) ok = true; if (!ok) return {}; @@ -271,9 +283,12 @@ public: // Start the listening thread thread = std::thread{[this] { auto& snd = alsa_data::snd; + const auto period + = std::chrono::duration_cast(this->configuration.poll_period) + .count(); for (;;) { - int err = poll(descriptors_.data(), descriptors_.size(), -1); + int err = poll(descriptors_.data(), descriptors_.size(), period); if (err >= 0) { // We got our stop-thread signal diff --git a/include/libremidi/backends/alsa_seq_ump/config.hpp b/include/libremidi/backends/alsa_seq_ump/config.hpp index 4ad4242..f57a306 100644 --- a/include/libremidi/backends/alsa_seq_ump/config.hpp +++ b/include/libremidi/backends/alsa_seq_ump/config.hpp @@ -28,6 +28,7 @@ struct input_configuration snd_seq_t* context{}; std::function manual_poll; std::function stop_poll; + std::chrono::milliseconds poll_period{2}; static constexpr int midi_version = 2; }; @@ -46,6 +47,7 @@ struct observer_configuration snd_seq_t* context{}; std::function manual_poll; std::function stop_poll; + std::chrono::milliseconds poll_period{100}; static constexpr int midi_version = 2; }; diff --git a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp index 87c1b39..46760d6 100644 --- a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp @@ -27,22 +27,14 @@ public: assert(snd.seq.ump.available); if (init_client(configuration) < 0) { - error( + libremidi_handle_error( this->configuration, - "midi_in_alsa::initialize: error creating ALSA sequencer client " + "error creating ALSA sequencer client " "object."); return; } - if (snd.midi.event_new(this->bufferSize, &this->coder) < 0) - { - error( - this->configuration, - "midi_out_alsa::initialize: error initializing MIDI event " - "parser."); - return; - } - snd.midi.event_init(this->coder); + this->client_open_ = stdx::error{}; } ~midi_out_impl() override @@ -53,14 +45,14 @@ public: // 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; } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ_UMP; } [[nodiscard]] int create_port(std::string_view portName) { @@ -70,55 +62,60 @@ public: SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt); } - bool open_port(const output_port& p, std::string_view portName) override + 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) { - error( - this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!"); - return false; + 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 false; + return std::errc::invalid_argument; if (int err = create_port(portName); err < 0) { - error(configuration, "midi_out_alsa::create_port: ALSA error creating port."); - return false; + 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) { - error( - configuration, "midi_out_alsa::create_port: ALSA error making port connection."); - return false; + libremidi_handle_error(configuration, "ALSA error making port connection."); + return from_errc(err); } - return true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { if (int err = create_port(portName); err < 0) - return false; - return true; + return from_errc(err); + return stdx::error{}; } - void close_port() override { unsubscribe(); } - - void set_client_name(std::string_view clientName) override + stdx::error close_port() override { - alsa_data::set_client_name(clientName); + unsubscribe(); + return stdx::error{}; } - void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); } + stdx::error set_client_name(std::string_view clientName) override + { + return alsa_data::set_client_name(clientName); + } - void send_ump(const uint32_t* ump_stream, std::size_t count) override + 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; @@ -128,24 +125,20 @@ public: snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); - auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) { + auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) -> std::errc { std::memcpy(ev.ump, ump, bytes); - const int result = snd.seq.ump.event_output_direct(this->seq, &ev); - if (result < 0) + const int ret = snd.seq.ump.event_output_direct(this->seq, &ev); + if (ret < 0) { - warning( - this->configuration, - "midi_out_alsa::send_message: error sending MIDI message to port."); - return libremidi::segmentation_error::other; + libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); + return static_cast(-ret); } - return libremidi::segmentation_error::no_error; + return std::errc{0}; }; segment_ump_stream(ump_stream, count, write_func, []() {}); snd.seq.drain_output(this->seq); + return stdx::error{}; } - -private: - unsigned int bufferSize{32}; }; } diff --git a/include/libremidi/backends/coremidi/config.hpp b/include/libremidi/backends/coremidi/config.hpp index f126f83..359dece 100644 --- a/include/libremidi/backends/coremidi/config.hpp +++ b/include/libremidi/backends/coremidi/config.hpp @@ -16,6 +16,7 @@ typedef UInt32 MIDIObjectRef; typedef MIDIObjectRef MIDIClientRef; #else using MIDIClientRef = uint32_t; +using MIDIObjectRef = uint32_t; #endif namespace libremidi { diff --git a/include/libremidi/backends/coremidi/error_domain.hpp b/include/libremidi/backends/coremidi/error_domain.hpp new file mode 100644 index 0000000..fb33d39 --- /dev/null +++ b/include/libremidi/backends/coremidi/error_domain.hpp @@ -0,0 +1,72 @@ +#pragma once +#include + +#include + +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(lhs) == error_cast(rhs); + + return false; + } + + stdx::string_ref message(const stdx::error& e) const noexcept override + { + switch (error_cast(e)) + { + case kMIDIInvalidClient: + return "Invalid Client"; + case kMIDIInvalidPort: + return "Invalid Port"; + case kMIDIWrongEndpointType: + return "Wrong EndpointT ype"; + case kMIDINoConnection: + return "No Connection"; + case kMIDIUnknownEndpoint: + return "Unknown Endpoint"; + case kMIDIUnknownProperty: + return "Unknown Property"; + case kMIDIWrongPropertyType: + return "Wrong Property Type"; + case kMIDINoCurrentSetup: + return "No Current Setup"; + case kMIDIMessageSendErr: + return "Message Send Error"; + case kMIDIServerStartErr: + return "Server Start Error"; + case kMIDISetupFormatErr: + return "Setup Format Error"; + case kMIDIWrongThread: + return "Wrong Thread"; + case kMIDIObjectNotFound: + return "Object Not Found"; + case kMIDIIDNotUnique: + return "ID Not Unique"; + case kMIDINotPermitted: + return "Not Permitted"; + case kMIDIUnknownError: + return "Unknown Error"; + } + return "Unknown error code"; + } +}; + +inline stdx::error from_osstatus(OSStatus ret) noexcept +{ + static constexpr coremidi_error_domain domain{}; + return {ret, domain}; +} +} diff --git a/include/libremidi/backends/coremidi/helpers.hpp b/include/libremidi/backends/coremidi/helpers.hpp index b3a53ad..30f5eea 100644 --- a/include/libremidi/backends/coremidi/helpers.hpp +++ b/include/libremidi/backends/coremidi/helpers.hpp @@ -1,7 +1,9 @@ #pragma once #include #include +#include #include +#include #include #include @@ -28,7 +30,8 @@ namespace static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept { CFStringRef res; - MIDIObjectGetStringProperty(object, property, &res); + if (MIDIObjectGetStringProperty(object, property, &res) || !res) + return {}; char name[256]; CFStringGetCString(res, name, sizeof(name), kCFStringEncodingUTF8); @@ -215,15 +218,15 @@ locate_object(auto& self, const port_information& info, MIDIObjectType requested auto ret = MIDIObjectFindByUniqueID(uid, &object, &type); if (ret != noErr) { - self.template error( - self.configuration, "coremidi::locate_object: cannot find port: " + info.port_name); + self.libremidi_handle_error( + self.configuration, "cannot find port: " + info.port_name); return 0; } if (type != requested_type || object == 0) { - self.template error( - self.configuration, "coremidi::locate_object: invalid object: " + info.port_name + " : " + self.libremidi_handle_error( + self.configuration, "invalid object: " + info.port_name + " : " + std::to_string(object)); return 0; } @@ -255,6 +258,13 @@ struct coremidi_data } } + 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) @@ -319,6 +329,23 @@ struct coremidi_data } } } + + stdx::error close_port() + { + if (this->endpoint) + { + MIDIEndpointDispose(this->endpoint); + this->endpoint = 0; + } + + if (this->port) + { + MIDIPortDispose(this->port); + this->port = 0; + } + + return stdx::error{}; + } }; } diff --git a/include/libremidi/backends/coremidi/midi_in.hpp b/include/libremidi/backends/coremidi/midi_in.hpp index 2207cbd..deaaf82 100644 --- a/include/libremidi/backends/coremidi/midi_in.hpp +++ b/include/libremidi/backends/coremidi/midi_in.hpp @@ -8,10 +8,11 @@ namespace libremidi { class midi_in_core final : public midi1::in_api - , private coremidi_data + , public coremidi_data , public error_handler { public: + using midi_api::client_open_; struct : input_configuration , coremidi_input_configuration @@ -23,11 +24,14 @@ public: { if (auto result = init_client(configuration); result != noErr) { - error( + libremidi_handle_error( this->configuration, - "midi_in_core: error creating MIDI client object: " + std::to_string(result)); + "error creating MIDI client object: " + std::to_string(result)); + client_open_ = from_osstatus(result); return; } + + client_open_ = stdx::error{}; } ~midi_in_core() override @@ -38,33 +42,18 @@ public: if (this->endpoint) MIDIEndpointDispose(this->endpoint); - close_client(); - } - - void close_client() - { - if (!configuration.context) - MIDIClientDispose(this->client); - } - - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_core: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_in_core: set_port_name unsupported"); + close_client(*this); } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; } - bool open_port(const input_port& info, std::string_view portName) override + 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 false; + return std::errc::invalid_argument; // Create our local sink MIDIPortRef port; @@ -73,29 +62,29 @@ public: if (result != noErr) { - close_client(); - error( - this->configuration, "midi_in_core::open_port: error creating macOS MIDI input port: " + close_client(*this); + libremidi_handle_error( + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); - return false; + return from_osstatus(result); } // Make the connection. if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr) { MIDIPortDispose(port); - close_client(); - error( - this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port."); - return false; + 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 true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { // Create a virtual MIDI input destination. MIDIEndpointRef endpoint; @@ -104,31 +93,21 @@ public: if (result != noErr) { - error( + libremidi_handle_error( this->configuration, - "midi_in_core::open_virtual_port: error creating virtual macOS MIDI " + "error creating virtual macOS MIDI " "destination."); - return false; + return from_osstatus(result); } // Save our api-specific connection information. this->endpoint = endpoint; - return true; + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { - if (this->endpoint) - { - MIDIEndpointDispose(this->endpoint); - this->endpoint = 0; - } - - if (this->port) - { - MIDIPortDispose(this->port); - this->port = 0; - } + return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override @@ -158,17 +137,14 @@ public: // MIDIPacketLists, they must be handled by multiple calls to this // function. - if (packet->length == 0) + if (packet->length > 0) { - packet = MIDIPacketNext(packet); - continue; + 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(to_ns, 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(to_ns, 0)); - packet = MIDIPacketNext(packet); } } diff --git a/include/libremidi/backends/coremidi/midi_out.hpp b/include/libremidi/backends/coremidi/midi_out.hpp index a9f139a..e260475 100644 --- a/include/libremidi/backends/coremidi/midi_out.hpp +++ b/include/libremidi/backends/coremidi/midi_out.hpp @@ -7,10 +7,11 @@ namespace libremidi { class midi_out_core final : public midi1::out_api - , private coremidi_data + , public coremidi_data , public error_handler { public: + using midi_api::client_open_; struct : output_configuration , coremidi_output_configuration @@ -22,11 +23,14 @@ public: { if (auto result = init_client(configuration); result != noErr) { - error( + libremidi_handle_error( this->configuration, - "midi_out_core: error creating MIDI client object: " + std::to_string(result)); + "error creating MIDI client object: " + std::to_string(result)); + client_open_ = from_osstatus(result); return; } + + client_open_ = stdx::error{}; } ~midi_out_core() @@ -36,112 +40,79 @@ public: if (this->endpoint) MIDIEndpointDispose(this->endpoint); - close_client(); - } - - void close_client() - { - if (!configuration.context) - MIDIClientDispose(this->client); - } - - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_core: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_out_core: set_port_name unsupported"); + close_client(*this); } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; } - bool open_port(const output_port& info, std::string_view portName) override + 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 false; + 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(); - error( - this->configuration, "midi_out_core::open_port: error creating macOS MIDI output port."); - return false; + 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 true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { - if (this->endpoint) - { - warning( - configuration, - "midi_out_core::open_virtual_port: a virtual output port already " - "exists!"); - return false; - } - // Create a virtual MIDI output source. MIDIEndpointRef endpoint; OSStatus result = MIDISourceCreate(this->client, toCFString(portName).get(), &endpoint); if (result != noErr) { - error( + libremidi_handle_error( this->configuration, - "midi_out_core::initialize: error creating macOS virtual MIDI source."); - return false; + "error creating macOS virtual MIDI source."); + + return from_osstatus(result); } // Save our api-specific connection information. this->endpoint = endpoint; - return true; + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { - if (this->endpoint) - { - MIDIEndpointDispose(this->endpoint); - this->endpoint = 0; - } - - if (this->port) - { - MIDIPortDispose(this->port); - this->port = 0; - } + return coremidi_data::close_port(); } - void send_message(const unsigned char* message, size_t size) override + stdx::error send_message(const unsigned char* message, size_t size) override { unsigned int nBytes = static_cast(size); if (nBytes == 0) { - warning(configuration, "midi_out_core::send_message: no data in message argument!"); - return; + libremidi_handle_warning(configuration, "no data in message argument!"); + return std::errc::invalid_argument; } if (message[0] != 0xF0 && nBytes > 3) { - warning( + libremidi_handle_warning( configuration, - "midi_out_core::send_message: message format problem ... not sysex but " + "message format problem ... not sysex but " "> 3 bytes?"); - return; + return std::errc::bad_message; } const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME(); @@ -166,9 +137,10 @@ public: if (!packet) { - error( - this->configuration, "midi_out_core::send_message: could not allocate packet list"); - return; + 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. @@ -177,10 +149,11 @@ public: auto result = MIDIReceived(this->endpoint, packetList); if (result != noErr) { - warning( + libremidi_handle_warning( this->configuration, - "midi_out_core::send_message: error sending MIDI to virtual " + "error sending MIDI to virtual " "destinations."); + return std::errc::io_error; } } @@ -190,12 +163,14 @@ public: auto result = MIDISend(this->port, this->destinationId, packetList); if (result != noErr) { - warning( + libremidi_handle_warning( this->configuration, - "midi_out_core::send_message: error sending MIDI message to port."); + "error sending MIDI message to port."); + return std::errc::io_error; } } } + return stdx::error{}; } MIDIEndpointRef destinationId{}; diff --git a/include/libremidi/backends/coremidi/observer.hpp b/include/libremidi/backends/coremidi/observer.hpp index defe623..833be51 100644 --- a/include/libremidi/backends/coremidi/observer.hpp +++ b/include/libremidi/backends/coremidi/observer.hpp @@ -38,9 +38,9 @@ public: if (result != noErr) { - error( + libremidi_handle_error( this->configuration, - "midi_in_core: error creating MIDI client object: " + std::to_string(result)); + "error creating MIDI client object: " + std::to_string(result)); return; } @@ -69,11 +69,11 @@ public: MIDIEndpointGetEntity(obj, &e); bool physical = bool(e); - bool ok = false; + bool ok = this->configuration.track_any; if (physical && this->configuration.track_hardware) - ok = true; + ok |= true; else if ((!physical) && this->configuration.track_virtual) - ok = true; + ok |= true; if (!ok) return {}; diff --git a/include/libremidi/backends/coremidi_ump.hpp b/include/libremidi/backends/coremidi_ump.hpp index f6a6af2..67f7728 100644 --- a/include/libremidi/backends/coremidi_ump.hpp +++ b/include/libremidi/backends/coremidi_ump.hpp @@ -10,9 +10,9 @@ struct backend using midi_in = midi_in_impl; using midi_out = midi_out_impl; using midi_observer = observer_impl; - using midi_in_configuration = coremidi_input_configuration; - using midi_out_configuration = coremidi_output_configuration; - using midi_observer_configuration = coremidi_observer_configuration; + 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"; diff --git a/include/libremidi/backends/coremidi_ump/config.hpp b/include/libremidi/backends/coremidi_ump/config.hpp index ff49822..dee80f9 100644 --- a/include/libremidi/backends/coremidi_ump/config.hpp +++ b/include/libremidi/backends/coremidi_ump/config.hpp @@ -3,7 +3,21 @@ namespace libremidi::coremidi_ump { -using input_configuration = coremidi_input_configuration; -using output_configuration = coremidi_output_configuration; -using observer_configuration = coremidi_observer_configuration; +struct input_configuration +{ + std::string client_name = "libremidi client"; + std::optional context{}; +}; + +struct output_configuration +{ + std::string client_name = "libremidi client"; + std::optional context{}; +}; + +struct observer_configuration +{ + std::string client_name = "libremidi client"; + std::function on_create_context{}; +}; } diff --git a/include/libremidi/backends/coremidi_ump/midi_in.hpp b/include/libremidi/backends/coremidi_ump/midi_in.hpp index a90a58c..fb94fb7 100644 --- a/include/libremidi/backends/coremidi_ump/midi_in.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_in.hpp @@ -2,32 +2,37 @@ #include #include #include +#include namespace libremidi::coremidi_ump { class midi_in_impl final : public midi2::in_api - , private coremidi_data + , 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_input_configuration&& apiconf) + 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) { - error( + libremidi_handle_error( this->configuration, - "midi_in_core: error creating MIDI client object: " + std::to_string(result)); + "error creating MIDI client object: " + std::to_string(result)); + client_open_ = from_osstatus(result); return; } + + client_open_ = stdx::error{}; } ~midi_in_impl() override @@ -38,33 +43,18 @@ public: if (this->endpoint) MIDIEndpointDispose(this->endpoint); - close_client(); - } - - void close_client() - { - if (!configuration.context) - MIDIClientDispose(this->client); - } - - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_core: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_in_core: set_port_name unsupported"); + close_client(*this); } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; } - bool open_port(const input_port& info, std::string_view portName) override + 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 false; + return std::errc::invalid_argument; // Create our local sink MIDIPortRef port; @@ -77,29 +67,29 @@ public: if (result != noErr) { - close_client(); - error( - this->configuration, "midi_in_core::open_port: error creating macOS MIDI input port: " + close_client(*this); + libremidi_handle_error( + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); - return false; + return from_osstatus(result); } // Make the connection. if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr) { MIDIPortDispose(port); - close_client(); - error( - this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port."); - return false; + 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 true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { // Create a virtual MIDI input destination. MIDIEndpointRef endpoint; @@ -111,37 +101,21 @@ public: if (result != noErr) { - error( + libremidi_handle_error( this->configuration, - "midi_in_core::open_virtual_port: error creating virtual macOS MIDI " + "error creating virtual macOS MIDI " "destination."); - return false; + return from_osstatus(result); } // Save our api-specific connection information. this->endpoint = endpoint; - return true; + return stdx::error{}; } - void close_port() override - { - if (this->endpoint) - { - MIDIEndpointDispose(this->endpoint); - this->endpoint = 0; - } - - if (this->port) - { - MIDIPortDispose(this->port); - this->port = 0; - } - } - - void set_timestamp( - [[maybe_unused]] const MIDIEventPacket& packet, - [[maybe_unused]] libremidi::ump& msg) noexcept + stdx::error close_port() override { + return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override @@ -151,33 +125,28 @@ public: void midiInputCallback(const MIDIEventList* list, void* /*srcRef*/) { - unsigned short nBytes{}; + 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) { - nBytes = packet->wordCount; - if (nBytes == 0) + if (packet->wordCount > 0) { - packet = MIDIEventPacketNext(packet); - continue; + auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); }; + m_processing.on_bytes_multi( + {packet->words, packet->words + packet->wordCount}, + m_processing.timestamp(to_ns, 0)); } - libremidi::ump msg; - coremidi_data::set_timestamp(*this, packet->timeStamp, msg.timestamp); - - if (packet->wordCount <= 4) - { - std::copy_n(packet->words, packet->wordCount, msg.data); - configuration.on_message(std::move(msg)); - } - last_time = time_in_nanos(packet->timeStamp); - packet = MIDIEventPacketNext(packet); } } - unsigned long long last_time{}; + midi2::input_state_machine m_processing{this->configuration}; }; } diff --git a/include/libremidi/backends/coremidi_ump/midi_out.hpp b/include/libremidi/backends/coremidi_ump/midi_out.hpp index 0431584..4ef9a67 100644 --- a/include/libremidi/backends/coremidi_ump/midi_out.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_out.hpp @@ -9,10 +9,11 @@ namespace libremidi::coremidi_ump { class midi_out_impl final : public midi2::out_api - , private coremidi_data + , public coremidi_data , public error_handler { public: + using midi_api::client_open_; struct : libremidi::output_configuration , coremidi_ump::output_configuration @@ -25,11 +26,14 @@ public: { if (auto result = init_client(configuration); result != noErr) { - error( + libremidi_handle_error( this->configuration, - "midi_out_impl: error creating MIDI client object: " + std::to_string(result)); + "error creating MIDI client object: " + std::to_string(result)); + client_open_ = from_osstatus(result); return; } + + client_open_ = stdx::error{}; } ~midi_out_impl() @@ -39,64 +43,40 @@ public: if (this->endpoint) MIDIEndpointDispose(this->endpoint); - close_client(); - } - - void close_client() - { - if (!configuration.context) - MIDIClientDispose(this->client); - } - - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_impl: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_out_impl: set_port_name unsupported"); + close_client(*this); } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; } - bool open_port(const output_port& info, std::string_view portName) override + 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 false; + 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(); - error( - this->configuration, "midi_out_impl::open_port: error creating macOS MIDI output port."); - return false; + 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 true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { - if (this->endpoint) - { - warning( - configuration, - "midi_out_impl::open_virtual_port: a virtual output port already " - "exists!"); - return false; - } - // Create a virtual MIDI output source. OSStatus result = MIDISourceCreateWithProtocol( this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &this->endpoint); @@ -104,42 +84,33 @@ public: if (result != noErr) { this->endpoint = 0; - error( + libremidi_handle_error( this->configuration, - "midi_out_impl::initialize: error creating macOS virtual MIDI source."); - return false; + "error creating macOS virtual MIDI source."); + + return from_osstatus(result); } - return true; + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { - if (this->endpoint) - { - MIDIEndpointDispose(this->endpoint); - this->endpoint = 0; - } - - if (this->port) - { - MIDIPortDispose(this->port); - this->port = 0; - } + return coremidi_data::close_port(); } - void send_ump(const uint32_t* ump_stream, std::size_t count) override + stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override { MIDIEventList* eventList = reinterpret_cast(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) { + 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 segmentation_error::no_error; + return std::errc{0}; else - return segmentation_error::need_space; + return std::errc::not_enough_memory; }; auto realloc_fun = [this, &packet, &eventList] { @@ -148,20 +119,21 @@ public: }; segment_ump_stream(ump_stream, count, write_fun, realloc_fun); - push_event_list(eventList); + return push_event_list(eventList); } - void push_event_list(MIDIEventList* eventList) + stdx::error push_event_list(MIDIEventList* eventList) { if (this->endpoint) { auto result = MIDIReceivedEventList(this->endpoint, eventList); if (result != noErr) { - warning( + libremidi_handle_warning( this->configuration, - "midi_out_core::send_message: error sending MIDI to virtual " + "error sending MIDI to virtual " "destinations."); + return std::errc::io_error; } } @@ -170,11 +142,13 @@ public: auto result = MIDISendEventList(this->port, this->destinationId, eventList); if (result != noErr) { - warning( + libremidi_handle_warning( this->configuration, - "midi_out_core::send_message: error sending MIDI message to port."); + "error sending MIDI message to port."); + return std::errc::io_error; } } + return stdx::error{}; } MIDIEndpointRef destinationId{}; diff --git a/include/libremidi/backends/coremidi_ump/observer.hpp b/include/libremidi/backends/coremidi_ump/observer.hpp index 5a28ce0..35a3f18 100644 --- a/include/libremidi/backends/coremidi_ump/observer.hpp +++ b/include/libremidi/backends/coremidi_ump/observer.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include namespace libremidi::coremidi_ump @@ -6,7 +7,13 @@ namespace libremidi::coremidi_ump class observer_impl final : public libremidi::observer_core { - using observer_core::observer_core; +public: + explicit observer_impl(libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) + : observer_core{std::move(conf), coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} + { + + } + libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; } }; diff --git a/include/libremidi/backends/dummy.hpp b/include/libremidi/backends/dummy.hpp index 7527a89..b3fb029 100644 --- a/include/libremidi/backends/dummy.hpp +++ b/include/libremidi/backends/dummy.hpp @@ -23,20 +23,33 @@ class midi_in_dummy final , public error_handler { public: - explicit midi_in_dummy(const auto& configuration, const auto&) + explicit midi_in_dummy(const input_configuration& configuration, std::any) { - warning(configuration, "midi_in_dummy: This class provides no functionality."); + 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; } - bool open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override + stdx::error open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override { - return true; + 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{}; } - bool open_virtual_port(std::string_view /*portName*/) override { return true; } - void close_port() override { } - void set_client_name(std::string_view /*clientName*/) override { } - void set_port_name(std::string_view /*portName*/) override { } timestamp absolute_timestamp() const noexcept override { return 0; } }; @@ -45,22 +58,34 @@ class midi_out_dummy final , public error_handler { public: - explicit midi_out_dummy(const auto& configuration, const auto&) + explicit midi_out_dummy(const output_configuration& configuration, std::any) { - warning(configuration, "midi_out_dummy: This class provides no functionality."); + libremidi_handle_warning(configuration, "This class provides no functionality."); } libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; } - bool open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override + stdx::error open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override { - return true; + return stdx::error{}; + } + stdx::error open_virtual_port(std::string_view /*portName*/) override + { + return stdx::error{}; } - bool open_virtual_port(std::string_view /*portName*/) override { return true; } - void close_port() override { } - void set_client_name(std::string_view /*clientName*/) override { } - void set_port_name(std::string_view /*portName*/) override { } - void send_message(const unsigned char* /*message*/, size_t /*size*/) override { } + 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 diff --git a/include/libremidi/backends/emscripten/midi_access.hpp b/include/libremidi/backends/emscripten/midi_access.hpp index 90de5ef..3ca9f9e 100644 --- a/include/libremidi/backends/emscripten/midi_access.hpp +++ b/include/libremidi/backends/emscripten/midi_access.hpp @@ -204,7 +204,7 @@ public: } } - void send_message(int port_index, const char* bytes, int len) + stdx::error send_message(int port_index, const char* bytes, int len) { const auto& id = m_current_outputs[port_index].id; EM_ASM( diff --git a/include/libremidi/backends/emscripten/midi_in.cpp b/include/libremidi/backends/emscripten/midi_in.cpp index 0cb6746..3b919d3 100644 --- a/include/libremidi/backends/emscripten/midi_in.cpp +++ b/include/libremidi/backends/emscripten/midi_in.cpp @@ -11,12 +11,14 @@ LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten( input_configuration&& conf, emscripten_input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { + client_open_ = stdx::error{}; } LIBREMIDI_INLINE midi_in_emscripten::~midi_in_emscripten() { // Close a connection if it exists. midi_in_emscripten::close_port(); + client_open_ = std::errc::not_connected; } LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noexcept @@ -24,49 +26,35 @@ LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noex return libremidi::API::WEBMIDI; } -LIBREMIDI_INLINE bool midi_in_emscripten::open_port(int portNumber, std::string_view) +LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std::string_view) { auto& midi = webmidi_helpers::midi_access_emscripten::instance(); if (portNumber < 0 || portNumber >= midi.input_count()) { - error( - this->configuration, "midi_in_emscripten::open_port: no MIDI output sources found."); - return false; + libremidi_handle_error( + this->configuration, "no MIDI output sources found."); + return std::errc::invalid_argument; } midi.open_input(portNumber, *this); portNumber_ = portNumber; - return true; + return stdx::error{}; } -LIBREMIDI_INLINE bool +LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(const libremidi::input_port& p, std::string_view nm) { return open_port(p.port, nm); } -LIBREMIDI_INLINE bool midi_in_emscripten::open_virtual_port(std::string_view) -{ - warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported."); - return false; -} - -LIBREMIDI_INLINE void midi_in_emscripten::close_port() +LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port() { auto& midi = webmidi_helpers::midi_access_emscripten::instance(); midi.close_input(portNumber_, *this); -} -LIBREMIDI_INLINE void midi_in_emscripten::set_client_name(std::string_view) -{ - warning(configuration, "midi_in_emscripten::set_client_name: unsupported."); -} - -LIBREMIDI_INLINE void midi_in_emscripten::set_port_name(std::string_view) -{ - warning(configuration, "midi_in_emscripten::set_port_name: unsupported."); + return stdx::error{}; } LIBREMIDI_INLINE int64_t midi_in_emscripten::absolute_timestamp() const noexcept diff --git a/include/libremidi/backends/emscripten/midi_in.hpp b/include/libremidi/backends/emscripten/midi_in.hpp index 32e7cd7..4833903 100644 --- a/include/libremidi/backends/emscripten/midi_in.hpp +++ b/include/libremidi/backends/emscripten/midi_in.hpp @@ -22,13 +22,10 @@ public: libremidi::API get_current_api() const noexcept override; - bool open_port(int portNumber, std::string_view); - bool open_port(const input_port& p, std::string_view) override; - bool open_virtual_port(std::string_view) override; - void close_port() 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; - void set_client_name(std::string_view clientName) override; - void set_port_name(std::string_view portName) override; timestamp absolute_timestamp() const noexcept override; void on_input(double ts, unsigned char* begin, unsigned char* end); diff --git a/include/libremidi/backends/emscripten/midi_out.cpp b/include/libremidi/backends/emscripten/midi_out.cpp index feaadd8..e2bf024 100644 --- a/include/libremidi/backends/emscripten/midi_out.cpp +++ b/include/libremidi/backends/emscripten/midi_out.cpp @@ -8,12 +8,14 @@ LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten( output_configuration&& conf, emscripten_output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { + client_open_ = stdx::error{}; } LIBREMIDI_INLINE midi_out_emscripten::~midi_out_emscripten() { // Close a connection if it exists. midi_out_emscripten::close_port(); + client_open_ = std::errc::not_connected; } LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noexcept @@ -21,54 +23,41 @@ LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noe return libremidi::API::WEBMIDI; } -LIBREMIDI_INLINE bool midi_out_emscripten::open_port(unsigned int portNumber, std::string_view) +LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std::string_view) { auto& midi = webmidi_helpers::midi_access_emscripten::instance(); if (portNumber >= midi.output_count()) { - error( - this->configuration, "midi_out_emscripten::open_port: no MIDI output sources found."); - return false; + libremidi_handle_error( + this->configuration, "no MIDI output sources found."); + return std::errc::invalid_argument; } portNumber_ = portNumber; - return true; + return stdx::error{}; } -LIBREMIDI_INLINE bool midi_out_emscripten::open_port(const output_port& p, std::string_view nm) +LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(const output_port& p, std::string_view nm) { return open_port(p.port, nm); } -LIBREMIDI_INLINE void midi_out_emscripten::close_port() { } - -LIBREMIDI_INLINE void midi_out_emscripten::set_client_name(std::string_view clientName) -{ - warning(configuration, "midi_out_emscripten::set_client_name: unsupported."); +LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() { + return stdx::error{}; } -LIBREMIDI_INLINE void midi_out_emscripten::set_port_name(std::string_view portName) -{ - warning(configuration, "midi_out_emscripten::set_port_name: unsupported."); -} - -LIBREMIDI_INLINE bool midi_out_emscripten::open_virtual_port(std::string_view) -{ - warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported."); - return false; -} - -LIBREMIDI_INLINE void midi_out_emscripten::send_message(const unsigned char* message, size_t size) +LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size) { if (portNumber_ < 0) - error( + libremidi_handle_error( this->configuration, - "midi_out_emscripten::send_message: trying to send a message without an open " + "trying to send a message without an open " "port."); webmidi_helpers::midi_access_emscripten::instance().send_message( portNumber_, reinterpret_cast(message), size); + return stdx::error{}; } } #endif diff --git a/include/libremidi/backends/emscripten/midi_out.hpp b/include/libremidi/backends/emscripten/midi_out.hpp index 584e1cb..f6f2137 100644 --- a/include/libremidi/backends/emscripten/midi_out.hpp +++ b/include/libremidi/backends/emscripten/midi_out.hpp @@ -21,15 +21,11 @@ public: libremidi::API get_current_api() const noexcept override; - bool open_port(unsigned int portNumber, std::string_view); - bool open_port(const output_port& p, std::string_view) override; - bool open_virtual_port(std::string_view) override; - void close_port() 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; - void set_client_name(std::string_view clientName) override; - void set_port_name(std::string_view portName) override; - - void send_message(const unsigned char* message, size_t size) override; + stdx::error send_message(const unsigned char* message, size_t size) override; private: int portNumber_{-1}; diff --git a/include/libremidi/backends/jack/config.hpp b/include/libremidi/backends/jack/config.hpp index 1e406e7..d99ed92 100644 --- a/include/libremidi/backends/jack/config.hpp +++ b/include/libremidi/backends/jack/config.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -25,8 +24,8 @@ struct jack_input_configuration std::string client_name = "libremidi client"; jack_client_t* context{}; - std::function set_process_func; - std::function clear_process_func; + std::function set_process_func{}; + std::function clear_process_func{}; }; struct jack_output_configuration @@ -34,8 +33,8 @@ struct jack_output_configuration std::string client_name = "libremidi client"; jack_client_t* context{}; - std::function set_process_func; - std::function clear_process_func; + std::function set_process_func{}; + std::function clear_process_func{}; int32_t ringbuffer_size = 16384; bool direct = false; diff --git a/include/libremidi/backends/jack/error_domain.hpp b/include/libremidi/backends/jack/error_domain.hpp new file mode 100644 index 0000000..f63f27f --- /dev/null +++ b/include/libremidi/backends/jack/error_domain.hpp @@ -0,0 +1,78 @@ +#pragma once +#include + +#if __has_include() + #include +#elif __has_include() + #include +#elif __has_include( ) + #include + #include + #include +#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(lhs) == error_cast(rhs); + + return false; + } + + stdx::string_ref message(const stdx::error& e) const noexcept override + { + const auto status = error_cast(e); + if(status == jack_status_t{}) + return "Success"; + + if(status & JackInvalidOption) + return "The operation contained an invalid or unsupported option"; + if(status & JackServerFailed) + return "Unable to connect to the JACK server"; + if(status & JackServerError) + return "Communication error with the JACK server"; + if(status & JackNoSuchClient) + return "Requested client does not exist"; + if(status & JackLoadFailure) + return "Unable to load internal client"; + if(status & JackInitFailure) + return "Unable to initialize client"; + if(status & JackShmFailure) + return "Unable to access shared memory"; + if(status & JackVersionError) + return "Client's protocol version does not match"; + if(status & JackBackendError) + return "Backend error"; + if(status & JackClientZombie) + return "Client zombified failure"; + if(status & JackFailure) + return "Failure"; + if(status & JackNameNotUnique) + return "The desired client name was not unique"; + + // Can't happen in libremidi as we set JackNoStartServer + if(status & JackServerStarted) + return "Server was started"; + + return "Unknown JACK status code"; + } +}; + +inline stdx::error from_jack_status(jack_status_t ret) noexcept +{ + static constexpr jack_error_domain domain{}; + return {ret, domain}; +} +} diff --git a/include/libremidi/backends/jack/helpers.hpp b/include/libremidi/backends/jack/helpers.hpp index 0fcfeaf..60eee90 100644 --- a/include/libremidi/backends/jack/helpers.hpp +++ b/include/libremidi/backends/jack/helpers.hpp @@ -1,14 +1,6 @@ #pragma once -#if __has_include() - #include -#elif __has_include() - #include -#elif __has_include( ) - #include - #include - #include -#endif +#include #include #include @@ -157,6 +149,10 @@ struct jack_helpers : jack_client = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); if (this->client != nullptr) { + if(status & JackNameNotUnique) { + self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed."); + } + jack_set_process_callback( this->client, +[](jack_nframes_t nf, void* ctx) -> int { @@ -192,9 +188,12 @@ struct jack_helpers : jack_client if (this->client && !self.configuration.context) jack_client_close(this->client); + + self.client_open_ = std::errc::not_connected; } - bool create_local_port(const auto& self, std::string_view portName, JackPortFlags flags) + stdx::error + create_local_port(const auto& self, std::string_view portName, JackPortFlags flags) { // full name: "client_name:port_name\0" if (portName.empty()) @@ -203,9 +202,9 @@ struct jack_helpers : jack_client if (self.configuration.client_name.size() + portName.size() + 2u >= static_cast(jack_port_name_size())) { - self.template error( - self.configuration, "JACK: port name length limit exceeded"); - return false; + self.libremidi_handle_error( + self.configuration, "port name length limit exceeded"); + return std::errc::invalid_argument; } if (!this->port) @@ -216,16 +215,16 @@ struct jack_helpers : jack_client if (!this->port) { - self.template error(self.configuration, "JACK: error creating port"); - return false; + self.libremidi_handle_error(self.configuration, "error creating port"); + return std::errc::operation_not_supported; } - return true; + return stdx::error{}; } - void do_close_port() + stdx::error do_close_port() { if (this->port == nullptr) - return; + 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(); @@ -235,7 +234,8 @@ struct jack_helpers : jack_client this->thread_lock.prepare_release_client(); // 3. Now we are sure that the client is not going to use the port anymore - jack_port_unregister(this->client, port_ptr); + int err = jack_port_unregister(this->client, port_ptr); + return from_errc(err); } }; } diff --git a/include/libremidi/backends/jack/midi_in.hpp b/include/libremidi/backends/jack/midi_in.hpp index 97e2e39..c51b9b6 100644 --- a/include/libremidi/backends/jack/midi_in.hpp +++ b/include/libremidi/backends/jack/midi_in.hpp @@ -14,6 +14,7 @@ class midi_in_jack final , public error_handler { public: + using midi_api::client_open_; struct : input_configuration , jack_input_configuration @@ -24,8 +25,14 @@ public: : configuration{std::move(conf), std::move(apiconf)} { auto status = connect(*this); - if (status != jack_status_t{}) - warning(configuration, "midi_in_jack: " + std::to_string((int)jack_status_t{})); + 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 @@ -35,39 +42,35 @@ public: disconnect(*this); } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_jack: set_client_name unsupported"); - } - libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; } - bool open_port(const input_port& port, std::string_view portName) override + stdx::error open_port(const input_port& port, std::string_view portName) override { - if (!create_local_port(*this, portName, JackPortIsInput)) - return false; + if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{}) + return err; - if (auto ret = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); - ret != 0) + if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); + err != 0 && err != EEXIST) { - error( - configuration, "JACK: could not connect to port: " + port.port_name + " -> " + libremidi_handle_error( + configuration, "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port)); - return false; + return from_errc(err); } - return true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { return create_local_port(*this, portName, JackPortIsInput); } - void close_port() override { return do_close_port(); } + stdx::error close_port() override { return do_close_port(); } - void set_port_name(std::string_view portName) override + stdx::error set_port_name(std::string_view portName) override { - jack_port_rename(this->client, this->port, portName.data()); + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); } timestamp absolute_timestamp() const noexcept override diff --git a/include/libremidi/backends/jack/midi_out.hpp b/include/libremidi/backends/jack/midi_out.hpp index 2f77cc4..7077541 100644 --- a/include/libremidi/backends/jack/midi_out.hpp +++ b/include/libremidi/backends/jack/midi_out.hpp @@ -37,16 +37,18 @@ public: jack_ringbuffer_free(ringbuffer); } - void write(const unsigned char* data, int64_t sz) const noexcept + stdx::error write(const unsigned char* data, int64_t sz) const noexcept { if (static_cast(sz + size_sz) > ringbuffer_space) - return; + return std::errc::no_buffer_space; while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) sched_yield(); jack_ringbuffer_write(ringbuffer, reinterpret_cast(&sz), size_sz); jack_ringbuffer_write(ringbuffer, reinterpret_cast(data), sz); + + return stdx::error{}; } void read(void* jack_events) const noexcept @@ -74,6 +76,7 @@ class midi_out_jack , public error_handler { public: + using midi_api::client_open_; struct : output_configuration , jack_output_configuration @@ -87,39 +90,36 @@ public: ~midi_out_jack() override { } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_jack: set_client_name unsupported"); - } - libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; } - bool open_port(const output_port& port, std::string_view portName) override + stdx::error open_port(const output_port& port, std::string_view portName) override { - if (!create_local_port(*this, portName, JackPortIsOutput)) - return false; + if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{}) + return err; // Connecting to the output - if (jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()) != 0) + if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); + err != 0 && err != EEXIST) { - error( - configuration, "JACK: could not connect to port" + port.port_name); - return false; + libremidi_handle_error( + configuration, "could not connect to port" + port.port_name); + return from_errc(err); } - return true; + return stdx::error{}; } - bool open_virtual_port(std::string_view portName) override + stdx::error open_virtual_port(std::string_view portName) override { return create_local_port(*this, portName, JackPortIsOutput); } - void close_port() override { return do_close_port(); } + stdx::error close_port() override { return do_close_port(); } - void set_port_name(std::string_view portName) override + stdx::error set_port_name(std::string_view portName) override { - jack_port_rename(this->client, this->port, portName.data()); + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); } }; @@ -131,8 +131,14 @@ public: , queue{configuration.ringbuffer_size} { auto status = connect(*this); - if (status != jack_status_t{}) - warning(configuration, "midi_out_jack_queued: " + std::to_string((int)jack_status_t{})); + 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 @@ -142,9 +148,9 @@ public: disconnect(*this); } - void send_message(const unsigned char* message, std::size_t size) override + stdx::error send_message(const unsigned char* message, std::size_t size) override { - queue.write(message, size); + return queue.write(message, size); } int process(jack_nframes_t nframes) @@ -168,10 +174,15 @@ public: : midi_out_jack{std::move(conf), std::move(apiconf)} { auto status = connect(*this); - if (status != jack_status_t{}) - warning(configuration, "midi_out_jack_direct: " + std::to_string((int)jack_status_t{})); + 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 @@ -188,10 +199,11 @@ public: return 0; } - void send_message(const unsigned char* message, size_t size) override + stdx::error send_message(const unsigned char* message, size_t size) override { void* buff = jack_port_get_buffer(this->port, buffer_size); - jack_midi_event_write(buff, 0, message, size); + int ret = jack_midi_event_write(buff, 0, message, size); + return from_errc(ret); } int convert_timestamp(int64_t user) const noexcept @@ -207,10 +219,11 @@ public: } } - void schedule_message(int64_t ts, const unsigned char* message, size_t size) override + 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); - jack_midi_event_write(buff, convert_timestamp(ts), message, size); + int ret = jack_midi_event_write(buff, convert_timestamp(ts), message, size); + return from_errc(ret); } int buffer_size{}; diff --git a/include/libremidi/backends/jack/observer.hpp b/include/libremidi/backends/jack/observer.hpp index f3c6a73..01226b1 100644 --- a/include/libremidi/backends/jack/observer.hpp +++ b/include/libremidi/backends/jack/observer.hpp @@ -34,7 +34,7 @@ public: this->client = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); if (status != jack_status_t{}) - warning(configuration, "observer_jack: " + std::to_string((int)jack_status_t{})); + libremidi_handle_error(configuration, std::to_string((int)status)); if (this->client != nullptr) { diff --git a/include/libremidi/backends/keyboard.hpp b/include/libremidi/backends/keyboard.hpp new file mode 100644 index 0000000..4674341 --- /dev/null +++ b/include/libremidi/backends/keyboard.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +namespace libremidi +{ +struct kbd_backend +{ + using midi_in = midi_in_kbd; + using midi_in_configuration = kbd_input_configuration; + using midi_out = midi_out_dummy; + using midi_observer = observer_dummy; + using midi_out_configuration = dummy_configuration; + using midi_observer_configuration = dummy_configuration; + static const constexpr auto API = libremidi::API::KEYBOARD; + static const constexpr auto name = "keyboard"; + static const constexpr auto display_name = "Computer keyboard"; + + static inline bool available() noexcept { return true; } +}; +} diff --git a/include/libremidi/backends/keyboard/config.hpp b/include/libremidi/backends/keyboard/config.hpp new file mode 100644 index 0000000..109122e --- /dev/null +++ b/include/libremidi/backends/keyboard/config.hpp @@ -0,0 +1,92 @@ +#pragma once +#include + +#include +#include + +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; + // First argument is on key press, second on key release + std::function 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 scancode_map{ + { 0x1E, NOTE_0}, // C0 + { 0x11, NOTE_0 + 1}, + { 0x1F, NOTE_0 + 2}, + { 0x12, NOTE_0 + 3}, + { 0x20, NOTE_0 + 4}, + { 0x21, NOTE_0 + 5}, + { 0x14, NOTE_0 + 6}, + { 0x22, NOTE_0 + 7}, + { 0x15, NOTE_0 + 8}, + { 0x23, NOTE_0 + 9}, + { 0x16, NOTE_0 + 10}, + { 0x24, NOTE_0 + 11}, + { 0x25, NOTE_0 + 12}, // C1 + { 0x18, NOTE_0 + 13}, + { 0x26, NOTE_0 + 14}, + { 0x19, NOTE_0 + 15}, + { 0x27, NOTE_0 + 16}, + { 0x28, NOTE_0 + 17}, + { 0x1B, NOTE_0 + 18}, + { 0x2B, NOTE_0 + 19}, + + { 0x29, VEL_0 + int(0)}, + { 0x02, VEL_0 + int(1 * 127 / 12)}, + { 0x03, VEL_0 + int(2 * 127 / 12)}, + { 0x04, VEL_0 + int(3 * 127 / 12)}, + { 0x05, VEL_0 + int(4 * 127 / 12)}, + { 0x06, VEL_0 + int(5 * 127 / 12)}, + { 0x07, VEL_0 + int(6 * 127 / 12)}, + { 0x08, VEL_0 + int(7 * 127 / 12)}, + { 0x09, VEL_0 + int(8 * 127 / 12)}, + { 0x0A, VEL_0 + int(9 * 127 / 12)}, + { 0x0B, VEL_0 + int(10 * 127 / 12)}, + { 0x0C, VEL_0 + int(11 * 127 / 12)}, + { 0x0D, VEL_0 + int(127)}, + + { 0x2C, OCTAVE_MINUS }, + { 0x2D, OCTAVE_PLUS }, + { 0x2E, VELOCITY_MINUS }, + { 0x2F, VELOCITY_PLUS}, + }; + // clang-format on +}; + +} diff --git a/include/libremidi/backends/keyboard/midi_in.hpp b/include/libremidi/backends/keyboard/midi_in.hpp new file mode 100644 index 0000000..5b11124 --- /dev/null +++ b/include/libremidi/backends/keyboard/midi_in.hpp @@ -0,0 +1,112 @@ +#pragma once +#include +#include + +#include +#include + +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 m_current_notes_scancodes; +}; +} diff --git a/include/libremidi/backends/linux/alsa.hpp b/include/libremidi/backends/linux/alsa.hpp index 6741f39..2820d5e 100644 --- a/include/libremidi/backends/linux/alsa.hpp +++ b/include/libremidi/backends/linux/alsa.hpp @@ -57,13 +57,13 @@ struct libasound return; } - LIBREMIDI_SYMBOL_INIT(snd_card, get_name); - LIBREMIDI_SYMBOL_INIT(snd_card, next); + 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); + LIBREMIDI_SYMBOL_DEF(snd_card, get_name) + LIBREMIDI_SYMBOL_DEF(snd_card, next) } card{library}; struct ctl_t @@ -80,13 +80,13 @@ struct libasound return; } - LIBREMIDI_SYMBOL_INIT(snd_ctl, close); - LIBREMIDI_SYMBOL_INIT(snd_ctl, open); + 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); + LIBREMIDI_SYMBOL_DEF(snd_ctl, close) + LIBREMIDI_SYMBOL_DEF(snd_ctl, open) struct rawmidi_t { @@ -98,12 +98,12 @@ struct libasound return; } - LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info); - LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device); + 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); + LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info) + LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device) } rawmidi; #if LIBREMIDI_ALSA_HAS_UMP @@ -117,14 +117,14 @@ struct libasound 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); + 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); + 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}; @@ -139,23 +139,23 @@ struct libasound 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); + 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); + 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 @@ -168,59 +168,59 @@ struct libasound 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); + 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); + 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 @@ -237,118 +237,118 @@ struct libasound 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); + 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, 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); + 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 @@ -360,18 +360,18 @@ struct libasound available = false; return; } - LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version); - LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input); - LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output); - LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct); + 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); + LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version) + LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input) + LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output) + LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct) } ump; #endif } seq{library}; @@ -387,39 +387,39 @@ struct libasound return; } - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name); - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof); - LIBREMIDI_SYMBOL_INIT(snd_ump, close); - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name); - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof); - 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); + 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); + 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 }; diff --git a/include/libremidi/backends/linux/udev.hpp b/include/libremidi/backends/linux/udev.hpp index 2d03767..48e26cb 100644 --- a/include/libremidi/backends/linux/udev.hpp +++ b/include/libremidi/backends/linux/udev.hpp @@ -23,16 +23,16 @@ struct libudev 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); + 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() @@ -44,16 +44,16 @@ struct libudev 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); + 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 diff --git a/include/libremidi/backends/net/config.hpp b/include/libremidi/backends/net/config.hpp new file mode 100644 index 0000000..8365b50 --- /dev/null +++ b/include/libremidi/backends/net/config.hpp @@ -0,0 +1,86 @@ +#pragma once +#include + +#include +namespace boost::asio +{ +struct io_context; +} + +namespace libremidi::net +{ +enum class protocol +{ + OSC_MIDI, +}; + +struct dgram_input_configuration +{ + std::string client_name = "libremidi client"; + + enum protocol protocol = protocol::OSC_MIDI; + std::string accept = "0.0.0.0"; + int port{}; + + boost::asio::io_context* io_context{}; +}; + +struct dgram_output_configuration +{ + std::string client_name = "libremidi client"; + + enum protocol protocol = protocol::OSC_MIDI; + std::string host = "127.0.0.1"; + int port{}; + bool broadcast{}; + + boost::asio::io_context* io_context{}; +}; + +struct dgram_observer_configuration +{ + std::string client_name = "libremidi client"; + + boost::asio::io_context* io_context{}; +}; + +} + +namespace libremidi::net_ump +{ +enum class protocol +{ + OSC_MIDI2, +}; + +struct dgram_input_configuration +{ + std::string client_name = "libremidi client"; + + enum protocol protocol = protocol::OSC_MIDI2; + std::string accept = "0.0.0.0"; + int port{}; + + boost::asio::io_context* io_context{}; +}; + +struct dgram_output_configuration +{ + std::string client_name = "libremidi client"; + + enum protocol protocol = protocol::OSC_MIDI2; + std::string host = "127.0.0.1"; + int port{}; + bool broadcast{}; + + boost::asio::io_context* io_context{}; +}; + +struct dgram_observer_configuration +{ + std::string client_name = "libremidi client"; + + boost::asio::io_context* io_context{}; +}; + +} diff --git a/include/libremidi/backends/net/helpers.hpp b/include/libremidi/backends/net/helpers.hpp new file mode 100644 index 0000000..54d4879 --- /dev/null +++ b/include/libremidi/backends/net/helpers.hpp @@ -0,0 +1,91 @@ +#pragma once +#include + +#include + +#include +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{}} + { + } + + 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(e).get()); + } +}; +*/ +} + +namespace libremidi +{ +template +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(reinterpret_cast(storage.object)); + ownership = owned; + } + } + + ~optionally_owned() + { + if (is_owned()) + std::destroy_at(reinterpret_cast(storage.object)); + } + + T& get() noexcept { return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); } + const T& get() const noexcept + { + return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); + } + + optionally_owned(const optionally_owned&) = delete; + optionally_owned(optionally_owned&&) noexcept = delete; + optionally_owned& operator=(const optionally_owned&) = delete; + optionally_owned& operator=(optionally_owned&&) noexcept = delete; + + bool is_owned() const noexcept { return ownership == owned; } + +private: + union + { + alignas(T) unsigned char object[sizeof(T)]; + T* ref; + } storage; + enum + { + owned, + unowned + } ownership{}; +}; +} diff --git a/include/libremidi/backends/net/midi_in.hpp b/include/libremidi/backends/net/midi_in.hpp new file mode 100644 index 0000000..abf5c67 --- /dev/null +++ b/include/libremidi/backends/net/midi_in.hpp @@ -0,0 +1,440 @@ +#pragma once +#include +#include +#include +#include + +namespace libremidi +{ +template +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(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(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 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::steady_clock::now().time_since_epoch()) + .count(); + }; + + auto ptr = reinterpret_cast(data); + m_processing.on_bytes({ptr, ptr + 3}, m_processing.timestamp(to_ns, 0)); + } + + midi1::input_state_machine m_processing{this->configuration}; + + libremidi::optionally_owned 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(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(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(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 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::steady_clock::now().time_since_epoch()) + .count(); + }; + + m_processing.on_bytes({ump, sz}, m_processing.timestamp(to_ns, 0)); + } + + midi2::input_state_machine m_processing{this->configuration}; + + libremidi::optionally_owned m_ctx; + boost::asio::ip::udp::endpoint m_endpoint; + boost::asio::ip::udp::socket m_socket; + + std::jthread m_thread; + + std::string m_portname; + alignas(16) unsigned char m_data[65535]; +}; +} diff --git a/include/libremidi/backends/net/midi_out.hpp b/include/libremidi/backends/net/midi_out.hpp new file mode 100644 index 0000000..c77f2f1 --- /dev/null +++ b/include/libremidi/backends/net/midi_out.hpp @@ -0,0 +1,288 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +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 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(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 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 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(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 ctx; + boost::asio::ip::udp::endpoint m_endpoint; + boost::asio::ip::udp::socket m_socket; + + osc_midi2_packet pkt; +}; + +} diff --git a/include/libremidi/backends/net/observer.hpp b/include/libremidi/backends/net/observer.hpp new file mode 100644 index 0000000..d2b5516 --- /dev/null +++ b/include/libremidi/backends/net/observer.hpp @@ -0,0 +1,14 @@ +#pragma once +#include + +namespace libremidi::net +{ +using net_observer_configuration = libremidi::dummy_configuration; +using observer = libremidi::observer_dummy; +} + +namespace libremidi::net_ump +{ +using net_observer_configuration = libremidi::dummy_configuration; +using observer = libremidi::observer_dummy; +} diff --git a/include/libremidi/backends/network.hpp b/include/libremidi/backends/network.hpp new file mode 100644 index 0000000..42930e5 --- /dev/null +++ b/include/libremidi/backends/network.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace libremidi::net +{ +struct backend +{ + using midi_in = libremidi::net::midi_in; + using midi_out = libremidi::net::midi_out; + using midi_observer = libremidi::net::observer; + using midi_in_configuration = libremidi::net::dgram_input_configuration; + using midi_out_configuration = libremidi::net::dgram_output_configuration; + using midi_observer_configuration = libremidi::net::net_observer_configuration; + static const constexpr auto API = libremidi::API::NETWORK; + static const constexpr auto name = "network"; + static const constexpr auto display_name = "Network"; + + static inline bool available() noexcept { return true; } +}; +} diff --git a/include/libremidi/backends/network_ump.hpp b/include/libremidi/backends/network_ump.hpp new file mode 100644 index 0000000..7b12562 --- /dev/null +++ b/include/libremidi/backends/network_ump.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include +#include + +namespace libremidi::net_ump +{ +struct backend +{ + using midi_in = libremidi::net_ump::midi_in; + using midi_out = libremidi::net_ump::midi_out; + using midi_observer = libremidi::net_ump::observer; + using midi_in_configuration = libremidi::net_ump::dgram_input_configuration; + using midi_out_configuration = libremidi::net_ump::dgram_output_configuration; + using midi_observer_configuration = libremidi::net_ump::net_observer_configuration; + static const constexpr auto API = libremidi::API::NETWORK_UMP; + static const constexpr auto name = "network (UMP)"; + static const constexpr auto display_name = "Network (UMP)"; + + static inline bool available() noexcept { return true; } +}; +} diff --git a/include/libremidi/backends/pipewire/config.hpp b/include/libremidi/backends/pipewire/config.hpp index 3881324..efb5c25 100644 --- a/include/libremidi/backends/pipewire/config.hpp +++ b/include/libremidi/backends/pipewire/config.hpp @@ -27,8 +27,8 @@ struct pipewire_input_configuration pw_main_loop* context{}; pw_filter* filter{}; - std::function set_process_func; - std::function clear_process_func; + std::function set_process_func{}; + std::function clear_process_func{}; }; struct pipewire_output_configuration @@ -37,8 +37,8 @@ struct pipewire_output_configuration pw_main_loop* context{}; pw_filter* filter{}; - std::function set_process_func; - std::function clear_process_func; + std::function set_process_func{}; + std::function clear_process_func{}; int64_t output_buffer_size{65536}; }; diff --git a/include/libremidi/backends/pipewire/context.hpp b/include/libremidi/backends/pipewire/context.hpp index 849a94f..b9a8635 100644 --- a/include/libremidi/backends/pipewire/context.hpp +++ b/include/libremidi/backends/pipewire/context.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include @@ -128,7 +129,7 @@ struct pipewire_context this->main_loop = pw.main_loop_new(nullptr); if (!this->main_loop) { - // libremidi::logger().error("PipeWire: main_loop_new failed!"); + // libremidi::logger().libremidi_handle_error("main_loop_new failed!"); return; } initialize(); @@ -139,28 +140,28 @@ struct pipewire_context this->lp = pw.main_loop_get_loop(this->main_loop); if (!lp) { - // libremidi::logger().error("PipeWire: main_loop_get_loop failed!"); + // libremidi::logger().libremidi_handle_error("main_loop_get_loop failed!"); return; } this->context = pw.context_new(lp, nullptr, 0); if (!this->context) { - // libremidi::logger().error("PipeWire: context_new failed!"); + // libremidi::logger().libremidi_handle_error("context_new failed!"); return; } this->core = pw.context_connect(this->context, nullptr, 0); if (!this->core) { - // libremidi::logger().error("PipeWire: context_connect failed!"); + // 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().error("PipeWire: core_get_registry failed!"); + // libremidi::logger().libremidi_handle_error("core_get_registry failed!"); return; } @@ -297,7 +298,6 @@ struct pipewire_context if (!proxy) { - std::cerr << "PipeWire: could not allocate link\n"; pw.properties_free(props); return nullptr; } @@ -476,7 +476,7 @@ struct pipewire_filter PW_KEY_MEDIA_TYPE, "Midi", PW_KEY_MEDIA_CATEGORY, "Filter", PW_KEY_MEDIA_ROLE, "DSP", - PW_KEY_MEDIA_NAME, "libremidi", + PW_KEY_MEDIA_NAME, "", #if defined(PW_KEY_NODE_LOCK_RATE) PW_KEY_NODE_LOCK_RATE, "true", #endif @@ -498,7 +498,7 @@ struct pipewire_filter pw.filter_destroy(this->filter); } - void create_local_port(std::string_view port_name, spa_direction direction) + stdx::error create_local_port(std::string_view port_name, spa_direction direction) { // clang-format off this->port = (struct port*)pw.filter_add_port( @@ -512,7 +512,9 @@ struct pipewire_filter nullptr), nullptr, 0); // clang-format on - assert(port); + if (!port) + return std::errc::invalid_argument; + return stdx::error{}; } void set_port_buffer(int bytes) @@ -537,31 +539,41 @@ struct pipewire_filter pw.filter_update_params(this->filter, this->port, params, 1); } - void remove_port() + stdx::error remove_port() { assert(this->port); - pw.filter_remove_port(this->port); + int ret = pw.filter_remove_port(this->port); this->port = nullptr; + return from_errc(ret); } - void rename_port(std::string_view port_name) + stdx::error rename_port(std::string_view port_name) { - assert(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); - pw.filter_update_properties(this->filter, this->port, &properties); - this->port = nullptr; - } - - void start_filter() - { - if (pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0) + if (this->port) { - std::cerr << "can't connect\n"; - return; + 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{}; } } diff --git a/include/libremidi/backends/pipewire/helpers.hpp b/include/libremidi/backends/pipewire/helpers.hpp index 5f43f4c..8c2970c 100644 --- a/include/libremidi/backends/pipewire/helpers.hpp +++ b/include/libremidi/backends/pipewire/helpers.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -55,10 +56,10 @@ struct pipewire_helpers } template - void create_filter(Self& self) + stdx::error create_filter(Self& self) { if (this->filter) - return; + return stdx::error{}; auto& configuration = self.configuration; if (configuration.context && configuration.filter && configuration.set_process_func) @@ -90,8 +91,9 @@ struct pipewire_helpers #pragma GCC diagnostic pop this->filter->create_filter(self.configuration.client_name, filter_events, &self); - this->filter->start_filter(); + return this->filter->start_filter(); } + return stdx::error{}; } template @@ -117,10 +119,10 @@ struct pipewire_helpers } template - int create_context(Self& self) + stdx::error create_context(Self& self) { if (this->global_context) - return 0; + return stdx::error{}; // Initialize PipeWire client auto& configuration = self.configuration; @@ -133,7 +135,10 @@ struct pipewire_helpers this->global_instance = std::make_shared(); this->global_context = std::make_shared(this->global_instance); } - return 0; + if (!this->global_context->main_loop) + return std::errc::connection_refused; + + return stdx::error{}; } void destroy_context() @@ -170,7 +175,9 @@ struct pipewire_helpers { int result = pw_loop_iterate(lp, 0); if (result < 0) - std::cerr << "pw_loop_iterate: " << spa_strerror(result) << "\n"; + { + LIBREMIDI_LOG(spa_strerror(result)); + } } fds[0].revents = 0; } @@ -190,7 +197,7 @@ struct pipewire_helpers } template - bool create_local_port(Self& self, std::string_view portName, spa_direction direction) + stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction) { assert(this->global_context); assert(this->filter); @@ -200,15 +207,15 @@ struct pipewire_helpers if (!this->filter->port) { - this->filter->create_local_port(portName.data(), direction); + 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; + } } - if (!this->filter->port) - { - self.template error(self.configuration, "PipeWire: error creating port"); - return false; - } - return true; + return stdx::error{}; } void add_callbacks(const observer_configuration& conf) @@ -293,12 +300,12 @@ struct pipewire_helpers } } - void do_close_port() + stdx::error do_close_port() { if (!this->filter) - return; + return stdx::error{}; if (!this->filter->port) - return; + return stdx::error{}; if (!this->global_context->owns_main_loop) { @@ -307,13 +314,19 @@ struct pipewire_helpers } unlink_ports(); - this->filter->remove_port(); + return this->filter->remove_port(); } - void rename_port(std::string_view port_name) + stdx::error rename_port(std::string_view port_name) { if (this->filter) - this->filter->rename_port(port_name); + { + return this->filter->rename_port(port_name); + } + else + { + return std::errc::not_connected; + } } void unlink_ports() @@ -325,7 +338,7 @@ struct pipewire_helpers } } - bool link_ports(auto& self, const input_port& in_port) + 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++) @@ -335,19 +348,13 @@ struct pipewire_helpers auto& midi = this->global_context->current_graph.software_midi; auto node_it = midi.find(this_node); if (node_it == midi.end()) - { - std::cerr << "Node " << this_node << " not found! \n"; - return false; - } + 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()) - { - std::cerr << "Node " << this_node << " has no ports! \n"; - return false; - } + return std::errc::no_link; // Link ports const auto& p = node_it->second.inputs.front(); @@ -355,16 +362,16 @@ struct pipewire_helpers pw_loop_iterate(this->global_context->lp, 1); if (!link) { - self.template error( + self.libremidi_handle_error( self.configuration, - "PipeWire: could not connect to port: " + in_port.port_name + " -> " + p.port_name); - return false; + "could not connect to port: " + in_port.port_name + " -> " + p.port_name); + return std::errc::no_link; } - return true; + return stdx::error{}; } - bool link_ports(auto& self, const output_port& out_port) + 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++) @@ -375,8 +382,7 @@ struct pipewire_helpers auto node_it = midi.find(this_node); if (node_it == midi.end()) { - std::cerr << "Node " << this_node << " not found! \n"; - return false; + return std::errc::invalid_argument; } // Wait for the pipewire server to send us back our node's ports @@ -384,8 +390,7 @@ struct pipewire_helpers if (node_it->second.outputs.empty()) { - std::cerr << "Node " << this_node << " has no ports! \n"; - return false; + return std::errc::no_link; } // Link ports @@ -394,13 +399,13 @@ struct pipewire_helpers pw_loop_iterate(this->global_context->lp, 1); if (!link) { - self.template error( + self.libremidi_handle_error( self.configuration, - "PipeWire: could not connect to port: " + p.port_name + " -> " + out_port.port_name); - return false; + "could not connect to port: " + p.port_name + " -> " + out_port.port_name); + return std::errc::no_link; } - return true; + return stdx::error{}; } template @@ -432,30 +437,34 @@ struct pipewire_helpers // Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer) // is an "output" port from the point of view of pipewire as data will come out of it template - static auto get_ports(const pipewire_context& ctx) noexcept -> std::vector< - std::conditional_t> + static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept + -> std::vector< + std::conditional_t> { std::vector> ret; { std::lock_guard _{ctx.current_graph.mtx}; - for (auto& node : ctx.current_graph.physical_midi) - { - for (auto& p : - (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) + if (conf.track_any || conf.track_hardware) + for (auto& node : ctx.current_graph.physical_midi) { - ret.push_back(to_port_info(p)); + for (auto& port : + (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) + { + ret.push_back(to_port_info(port)); + } } - } - for (auto& node : ctx.current_graph.software_midi) - { - for (auto& p : - (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) + + if (conf.track_any || conf.track_virtual) + for (auto& node : ctx.current_graph.software_midi) { - ret.push_back(to_port_info(p)); + for (auto& port : + (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) + { + ret.push_back(to_port_info(port)); + } } - } } return ret; diff --git a/include/libremidi/backends/pipewire/midi_in.hpp b/include/libremidi/backends/pipewire/midi_in.hpp index 05680e5..a584b2d 100644 --- a/include/libremidi/backends/pipewire/midi_in.hpp +++ b/include/libremidi/backends/pipewire/midi_in.hpp @@ -4,8 +4,6 @@ #include #include -#include - namespace libremidi { class midi_in_pipewire final @@ -23,8 +21,17 @@ public: explicit midi_in_pipewire(input_configuration&& conf, pipewire_input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { - create_context(*this); - create_filter(*this); + 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 @@ -33,43 +40,42 @@ public: do_close_port(); destroy_filter(*this); destroy_context(); - } - - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_pipewire: set_client_name unsupported"); + client_open_ = std::errc::not_connected; } libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; } - bool open_port(const input_port& in_port, std::string_view name) override + stdx::error open_port(const input_port& in_port, std::string_view name) override { - if (!create_local_port(*this, name, SPA_DIRECTION_INPUT)) - return false; + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + return err; - if (!link_ports(*this, in_port)) - return false; + if (auto err = link_ports(*this, in_port); err != stdx::error{}) + return err; start_thread(); - return true; + return stdx::error{}; } - bool open_virtual_port(std::string_view name) override + stdx::error open_virtual_port(std::string_view name) override { - if (!create_local_port(*this, name, SPA_DIRECTION_INPUT)) - return false; + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + return err; start_thread(); - return true; + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { stop_thread(); - do_close_port(); + return do_close_port(); } - void set_port_name(std::string_view port_name) override { rename_port(port_name); } + stdx::error set_port_name(std::string_view port_name) override + { + return rename_port(port_name); + } timestamp absolute_timestamp() const noexcept override { return system_ns(); } diff --git a/include/libremidi/backends/pipewire/midi_out.hpp b/include/libremidi/backends/pipewire/midi_out.hpp index 00e83ff..e8ae5c5 100644 --- a/include/libremidi/backends/pipewire/midi_out.hpp +++ b/include/libremidi/backends/pipewire/midi_out.hpp @@ -24,8 +24,17 @@ public: midi_out_pipewire(output_configuration&& conf, pipewire_output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { - create_context(*this); - create_filter(*this); + 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 @@ -34,47 +43,46 @@ public: do_close_port(); destroy_filter(*this); destroy_context(); - } - - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_pipewire: set_client_name unsupported"); + client_open_ = std::errc::not_connected; } libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; } - bool open_port(const output_port& out_port, std::string_view name) override + stdx::error open_port(const output_port& out_port, std::string_view name) override { - if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT)) - return false; + 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 (!link_ports(*this, out_port)) - return false; + if (auto err = link_ports(*this, out_port); err != stdx::error{}) + return err; start_thread(); - return true; + return stdx::error{}; } - bool open_virtual_port(std::string_view name) override + stdx::error open_virtual_port(std::string_view name) override { - if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT)) - return false; + 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 true; + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { stop_thread(); - do_close_port(); + return do_close_port(); } - void set_port_name(std::string_view port_name) override { rename_port(port_name); } + stdx::error set_port_name(std::string_view port_name) override + { + return rename_port(port_name); + } int process(spa_io_position* pos) { @@ -120,6 +128,8 @@ public: if (res == -ENOSPC) break; + // Recycle the memory + m_gcqueue.enqueue(std::move(m)); m_queue.pop(); } spa_pod_builder_pop(&build, &f); @@ -141,9 +151,14 @@ public: return 0; } - void send_message(const unsigned char* message, size_t size) override + stdx::error send_message(const unsigned char* message, size_t size) override { - m_queue.enqueue(libremidi::message(midi_bytes{message, message + size}, 0)); + 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 @@ -159,13 +174,15 @@ public: } } - void schedule_message(int64_t ts, const unsigned char* message, size_t size) override + 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 m_queue; + moodycamel::ReaderWriterQueue m_gcqueue; std::atomic_int64_t m_process_clock = 0; }; } diff --git a/include/libremidi/backends/pipewire/observer.hpp b/include/libremidi/backends/pipewire/observer.hpp index 7a1e0a0..66eba74 100644 --- a/include/libremidi/backends/pipewire/observer.hpp +++ b/include/libremidi/backends/pipewire/observer.hpp @@ -39,18 +39,29 @@ public: 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 get_input_ports() const noexcept override { - return get_ports(*this->global_context); + return get_ports(this->configuration, *this->global_context); } std::vector get_output_ports() const noexcept override { - return get_ports(*this->global_context); + return get_ports(this->configuration, *this->global_context); } ~observer_pipewire() diff --git a/include/libremidi/backends/winmidi/helpers.hpp b/include/libremidi/backends/winmidi/helpers.hpp index 6a0046a..f8218a5 100644 --- a/include/libremidi/backends/winmidi/helpers.hpp +++ b/include/libremidi/backends/winmidi/helpers.hpp @@ -5,15 +5,23 @@ #include #include +#include #include #include #include #include + #include #include #include +#include + + +namespace midi2 = winrt::Windows::Devices::Midi2; +namespace foundation = winrt::Windows::Foundation; +namespace collections = winrt::Windows::Foundation::Collections; + -#include // clang-format on namespace libremidi::winmidi @@ -22,6 +30,31 @@ using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Storage::Streams; -using namespace Microsoft::Devices::Midi2; +using namespace winrt::Windows::Devices::Midi2; using namespace Windows::Devices::Enumeration; + +inline bool ichar_equals(char a, char b) +{ + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); +} +inline bool iequals(std::string_view lhs, std::string_view rhs) +{ + return std::ranges::equal(lhs, rhs, ichar_equals); +} + +inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation +get_port_by_name(const std::string& port) { + auto eps = MidiEndpointDeviceInformation::FindAll(); + for (const auto& ep : eps) + { + auto str = to_string(ep.Id()); + if (str.empty()) + continue; + + if (iequals(str, port)) + return ep; + } + return {nullptr}; +} } diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index 84bb44f..041060e 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -3,6 +3,9 @@ #include #include #include +#include + +#include namespace libremidi::winmidi { @@ -21,23 +24,15 @@ public: 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(); } - - bool open_virtual_port(std::string_view) override + ~midi_in_impl() override { - warning(configuration, "midi_in_winmidi: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_winmidi: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_in_winmidi: set_port_name unsupported"); + close_port(); + this->client_open_ = std::errc::not_connected; } libremidi::API get_current_api() const noexcept override @@ -45,50 +40,58 @@ public: return libremidi::API::WINDOWS_MIDI_SERVICES; } - bool open_port(const input_port& port, std::string_view) override + stdx::error open_port(const input_port& port, std::string_view) override { -#if 0 - const auto id = winrt::to_hstring(port.port_name); - if (id.empty()) - return false; + auto ep = get_port_by_name(port.port_name); + if (!ep) + return std::errc::address_not_available; - port_ = get(MidiInPort::FromIdAsync(id)); - if (!port_) - return false; - port_.MessageReceived( - [=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort, - const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) { - this->process_message(args.Message()); + 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); }); -#endif - return true; + m_endpoint.Open(); + + return stdx::error{}; } -#if 0 - void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg) + void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) { - auto reader = DataReader::FromBuffer(msg.RawData()); - auto begin = msg.RawData().data(); - auto end = begin + msg.RawData().Length(); + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = false, + .has_samples = false, + }; - auto t = msg.Timestamp().count(); - this->configuration.on_message(libremidi::message{{begin, end}, t}); + const auto& ump = msg.GetMessagePacket(); + const auto& b = ump.GetAllWords(); + + uint32_t ump_space[64]; + array_view 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(to_ns, 0)); } -#endif - void close_port() override + stdx::error close_port() override { -#if 0 - if (port_) - { - port_.Close(); - port_ = nullptr; - } -#endif + m_endpoint.MessageReceived(m_revoke_token); + m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); + return stdx::error{}; } + virtual timestamp absolute_timestamp() const noexcept override { return {}; } + private: - // winrt::Microsoft::Devices::Midi2::IMidiInPort port_{nullptr}; + MidiSession m_session; + winrt::event_token m_revoke_token{}; + winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; + midi2::input_state_machine m_processing{this->configuration}; }; } diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index d10f560..3a95546 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -20,72 +21,69 @@ public: midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} + , m_session{MidiSession::CreateSession(L"libremidi session")} { + this->client_open_ = stdx::error{}; } ~midi_out_impl() override { close_port(); } - bool open_virtual_port(std::string_view) override - { - warning(configuration, "midi_out_winmidi: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_winmidi: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_out_winmidi: set_port_name unsupported"); - } - libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MIDI_SERVICES; } - bool open_port(const output_port& port, std::string_view) override + stdx::error open_port(const output_port& port, std::string_view) override { -#if 0 - const auto id = winrt::to_hstring(port.port_name); - if (id.empty()) - return false; + auto ep = get_port_by_name(port.port_name); + if (!ep) + return std::errc::address_not_available; - port_ = get(MidiOutPort::FromIdAsync(id)); - return bool(port_); -#endif - return true; + m_endpoint = m_session.CreateEndpointConnection(ep.Id()); + m_endpoint.Open(); + + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { -#if 0 - if (port_) - { - port_.Close(); - port_ = {}; - } -#endif + m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); + return stdx::error{}; } - void send_ump(const uint32_t* message, size_t size) override + stdx::error send_ump(const uint32_t* message, size_t size) override { -#if 0 - if (!port_) - return; + 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; + } - InMemoryRandomAccessStream str; - DataWriter rb(str); - rb.WriteBytes( - winrt::array_view{(const uint8_t*)message, (const uint8_t*)message + size}); - port_.SendBuffer(rb.DetachBuffer()); -#endif + if(ret != MidiSendMessageResults::Succeeded) + return std::errc::bad_message; + return std::errc{0}; + }; + + return segment_ump_stream(message, size, write_func, []() {}); } private: -#if 0 -winrt::Windows::Devices::Midi2::IMidiOutPort port_{nullptr}; -#endif + MidiSession m_session; + winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; }; } diff --git a/include/libremidi/backends/winmidi/observer.hpp b/include/libremidi/backends/winmidi/observer.hpp index bc81f68..3642324 100644 --- a/include/libremidi/backends/winmidi/observer.hpp +++ b/include/libremidi/backends/winmidi/observer.hpp @@ -20,13 +20,11 @@ public: { } configuration; - MidiSession session; explicit observer_impl( libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , session{ - MidiSession::CreateNewSession(L"libremidi session", MidiSessionSettings::Default())} + , session{MidiSession::CreateSession(L"libremidi session")} { if (!configuration.has_callbacks()) return; @@ -36,6 +34,7 @@ public: 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); @@ -87,10 +86,14 @@ public: auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& selectedEndpointInformation : endpointDevices) + for (const auto& ep : endpointDevices) { + if(ep.Name().starts_with(L"Diagnostics")) { + continue; + } // FIXME if(has input...) - ret.emplace_back(to_port_info(selectedEndpointInformation)); + + ret.emplace_back(to_port_info(ep)); } return ret; @@ -102,10 +105,13 @@ public: auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& selectedEndpointInformation : endpointDevices) + for (const auto& ep : endpointDevices) { + if(ep.Name().starts_with(L"Diagnostics")) { + continue; + } // FIXME if(has output...) - ret.emplace_back(to_port_info(selectedEndpointInformation)); + ret.emplace_back(to_port_info(ep)); } return ret; @@ -114,38 +120,29 @@ public: void on_input_added(const DeviceInformation& name) { if (configuration.input_added) - configuration.input_added(to_port_info(name)); + configuration.input_added(to_port_info(name)); } void on_input_removed(const DeviceInformation& name) { if (configuration.input_removed) - configuration.input_removed(to_port_info(name)); + configuration.input_removed(to_port_info(name)); } void on_output_added(const DeviceInformation& name) { if (configuration.output_added) - configuration.output_added(to_port_info(name)); + configuration.output_added(to_port_info(name)); } void on_output_removed(const DeviceInformation& name) { if (configuration.output_removed) - configuration.output_removed(to_port_info(name)); + configuration.output_removed(to_port_info(name)); } private: -#if 0 - static inline observer_winmidi_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()}; - static inline observer_winmidi_internal internalOutPortObserver_{ - MidiOutPort::GetDeviceSelector()}; - - int evTokenOnInputAdded_{-1}; - int evTokenOnInputRemoved_{-1}; - int evTokenOnOutputAdded_{-1}; - int evTokenOnOutputRemoved_{-1}; -#endif + MidiSession session; }; } diff --git a/include/libremidi/backends/winmm/error_domain.hpp b/include/libremidi/backends/winmm/error_domain.hpp new file mode 100644 index 0000000..f1bf49c --- /dev/null +++ b/include/libremidi/backends/winmm/error_domain.hpp @@ -0,0 +1,70 @@ +#pragma once +// clang-format off +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN 1 + +#include +#include +// clang-format on + +#include + +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(lhs) == error_cast(rhs); + + return false; + } + + stdx::string_ref message(const stdx::error& e) const noexcept override + { + switch (error_cast(e)) + { + case MMSYSERR_NOERROR: return "No error"; + case MMSYSERR_ERROR: return "Error"; + case MMSYSERR_BADDEVICEID: return "Bad device ID"; + case MMSYSERR_NOTENABLED: return "Not enabled"; + case MMSYSERR_ALLOCATED: return "Allocated"; + case MMSYSERR_INVALHANDLE: return "Invalid handle"; + case MMSYSERR_NODRIVER: return "No driver"; + case MMSYSERR_NOMEM: return "No memory"; + case MMSYSERR_NOTSUPPORTED: return "Not supported"; + case MMSYSERR_BADERRNUM: return "Bad errnum"; + case MMSYSERR_INVALFLAG: return "Invalid flag"; + case MMSYSERR_INVALPARAM: return "Invalid parameter"; + case MMSYSERR_HANDLEBUSY: return "Handle busy"; + case MMSYSERR_INVALIDALIAS: return "Invalid alias"; + case MMSYSERR_BADDB: return "Bad database"; + case MMSYSERR_KEYNOTFOUND: return "Key not found"; + case MMSYSERR_READERROR: return "Read error"; + case MMSYSERR_WRITEERROR: return "Write error"; + case MMSYSERR_DELETEERROR: return "Delete error"; + case MMSYSERR_VALNOTFOUND: return "Value not found"; + case MMSYSERR_NODRIVERCB: return "No driver callback"; + case MMSYSERR_MOREDATA: return "More data"; + } + return "Unknown error code"; + } +}; + +inline stdx::error from_mmerr(int ret) noexcept +{ + static constexpr winmm_error_domain domain; + return {ret, domain}; +} + +} diff --git a/include/libremidi/backends/winmm/helpers.hpp b/include/libremidi/backends/winmm/helpers.hpp index 63584d2..ee75550 100644 --- a/include/libremidi/backends/winmm/helpers.hpp +++ b/include/libremidi/backends/winmm/helpers.hpp @@ -1,15 +1,8 @@ #pragma once -#define NOMINMAX 1 -#define WIN32_LEAN_AND_MEAN 1 +#include #include - #include -// clang-format off -#include -#include -// clang-format on - namespace libremidi { @@ -44,10 +37,10 @@ inline std::string ConvertToUTF8(const TCHAR* str) // 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, unsigned int portNumber) +inline void MakeUniqueInPortName(std::string& deviceName, std::size_t portNumber) { int x = 1; - for (unsigned int i = 0; i < portNumber; i++) + for (std::size_t i = 0; i < portNumber; i++) { MIDIINCAPS deviceCaps; midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS)); @@ -61,10 +54,10 @@ inline void MakeUniqueInPortName(std::string& deviceName, unsigned int portNumbe deviceName += std::to_string(x); } -inline void MakeUniqueOutPortName(std::string& deviceName, unsigned int portNumber) +inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumber) { int x = 1; - for (unsigned int i = 0; i < portNumber; i++) + for (std::size_t i = 0; i < portNumber; i++) { MIDIOUTCAPS deviceCaps; midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS)); diff --git a/include/libremidi/backends/winmm/midi_in.hpp b/include/libremidi/backends/winmm/midi_in.hpp index 02a67d5..f04d57e 100644 --- a/include/libremidi/backends/winmm/midi_in.hpp +++ b/include/libremidi/backends/winmm/midi_in.hpp @@ -22,20 +22,17 @@ public: explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { - // We'll issue a warning here if no devices are available but not - // throw an error since the user can plugin something later. - if (midiInGetNumDevs() == 0) - { - warning( - configuration, "midi_in_winmm::initialize: no MIDI input devices currently available."); - } - if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400)) { - warning( + libremidi_handle_error( configuration, - "midi_in_winmm::initialize: InitializeCriticalSectionAndSpinCount failed."); + "InitializeCriticalSectionAndSpinCount failed."); + + this->client_open_ = std::errc::too_many_files_open; + return; } + + this->client_open_ = stdx::error{}; } ~midi_in_winmm() override @@ -43,35 +40,22 @@ public: // Close a connection if it exists. midi_in_winmm::close_port(); - DeleteCriticalSection(&(this->_mutex)); - } - - bool open_virtual_port(std::string_view) override - { - warning(configuration, "midi_in_winmm: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_winmm: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_in_winmm: set_port_name unsupported"); + if(this->client_open_ == stdx::error{}) + DeleteCriticalSection(&(this->_mutex)); } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; } - bool do_open(unsigned int portNumber) + stdx::error do_open(std::size_t portNumber) { MMRESULT result = midiInOpen( &this->inHandle, portNumber, std::bit_cast(&midiInputCallback), std::bit_cast(this), CALLBACK_FUNCTION); if (result != MMSYSERR_NOERROR) { - error( - configuration, "midi_in_winmm::open_port: error creating Windows MM MIDI input port."); - return false; + libremidi_handle_error( + configuration, "error creating Windows MM MIDI input port."); + return from_mmerr(result); } // Allocate and init the sysex buffers. @@ -90,11 +74,11 @@ public: { midiInClose(this->inHandle); this->inHandle = nullptr; - error( + libremidi_handle_error( configuration, - "midi_in_winmm::open_port: error starting Windows MM MIDI input port " + "error starting Windows MM MIDI input port " "(PrepareHeader)."); - return false; + return from_mmerr(result); } // Register the buffer. @@ -103,11 +87,11 @@ public: { midiInClose(this->inHandle); this->inHandle = nullptr; - error( + libremidi_handle_error( configuration, - "midi_in_winmm::open_port: error starting Windows MM MIDI input port " + "error starting Windows MM MIDI input port " "(AddBuffer)."); - return false; + return from_mmerr(result); } } @@ -117,15 +101,15 @@ public: { midiInClose(this->inHandle); this->inHandle = nullptr; - error( - configuration, "midi_in_winmm::open_port: error starting Windows MM MIDI input port."); - return false; + libremidi_handle_error( + configuration, "error starting Windows MM MIDI input port."); + return from_mmerr(result); } - return true; + return stdx::error{}; } - bool open_port(const input_port& p, std::string_view) override + stdx::error open_port(const input_port& p, std::string_view) override { observer_winmm obs{{}, winmm_observer_configuration{}}; auto ports = obs.get_input_ports(); @@ -142,12 +126,12 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - error( - configuration, "midi_in_winmm::open_port: port not found: " + p.port_name); - return false; + libremidi_handle_error( + configuration, "port not found: " + p.port_name); + return std::errc::invalid_argument; } - void close_port() override + stdx::error close_port() override { if (connected_) { @@ -170,9 +154,9 @@ public: if (res != MMSYSERR_NOERROR) { - warning( + libremidi_handle_warning( configuration, - "midi_in_winmm::open_port: error closing Windows MM MIDI input " + "error closing Windows MM MIDI input " "port (midiInUnprepareHeader)."); continue; } @@ -187,6 +171,7 @@ public: this->inHandle = nullptr; LeaveCriticalSection(&(this->_mutex)); } + return stdx::error{}; } private: @@ -255,8 +240,7 @@ private: const auto* sysex = reinterpret_cast(midiMessage); if(inputStatus == MIM_LONGERROR) { - self.m_processing.message.bytes.clear(); - self.m_processing.state = self.m_processing.main; + self.m_processing.reset(); } else if (!self.configuration.ignore_sysex) { @@ -286,10 +270,9 @@ private: LeaveCriticalSection(&(self._mutex)); if (result != MMSYSERR_NOERROR) { -#if defined(__LIBREMIDI_DEBUG__) - std::cerr << "\nmidi_in::midiInputCallback: error sending sysex to " - "Midi device!!\n\n"; -#endif + LIBREMIDI_LOG( + "error sending sysex to " + "Midi device!!"); } } } diff --git a/include/libremidi/backends/winmm/midi_out.hpp b/include/libremidi/backends/winmm/midi_out.hpp index 8735c91..3f8b596 100644 --- a/include/libremidi/backends/winmm/midi_out.hpp +++ b/include/libremidi/backends/winmm/midi_out.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include namespace libremidi @@ -20,55 +21,34 @@ public: midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { - // We'll issue a warning here if no devices are available but not - // throw an error since the user can plug something in later. - if (midiOutGetNumDevs() == 0) - { - warning( - configuration, - "midi_out_winmm::initialize: no MIDI output devices currently " - "available."); - } + this->client_open_ = stdx::error{}; } ~midi_out_winmm() override { // Close a connection if it exists. midi_out_winmm::close_port(); - } - - bool open_virtual_port(std::string_view) override - { - warning(configuration, "midi_out_winmm: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_winmm: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_out_winmm: set_port_name unsupported"); + this->client_open_ = std::errc::not_connected; } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; } - [[nodiscard]] bool do_open(unsigned int portNumber) + [[nodiscard]] stdx::error do_open(unsigned int portNumber) { MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL); if (result != MMSYSERR_NOERROR) { - error( + libremidi_handle_error( configuration, - "midi_out_winmm::open_port: error creating Windows MM MIDI output " + "error creating Windows MM MIDI output " "port."); - return false; + return from_mmerr(result); } - return true; + return stdx::error{}; } - bool open_port(const output_port& p, std::string_view) override + stdx::error open_port(const output_port& p, std::string_view) override { observer_winmm obs{{}, winmm_observer_configuration{}}; auto ports = obs.get_output_ports(); @@ -85,29 +65,30 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - error( - configuration, "midi_out_winmm::open_port: port not found: " + p.port_name); - return false; + libremidi_handle_error( + configuration, "port not found: " + p.port_name); + return std::errc::invalid_argument; } - void close_port() override + stdx::error close_port() override { if (this->outHandle) midiOutClose(this->outHandle); this->outHandle = nullptr; connected_ = false; + return stdx::error{}; } - void send_message(const unsigned char* message, size_t size) override + stdx::error send_message(const unsigned char* message, size_t size) override { if (!connected_) - return; + return std::errc::not_connected; if (size == 0) { - warning(configuration, "midi_out_winmm::send_message: message argument is empty!"); - return; + libremidi_handle_warning(configuration, "message argument is empty!"); + return std::errc::invalid_argument; } if (message[0] == 0xF0) @@ -126,18 +107,18 @@ public: auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { - error( - configuration, "midi_out_winmm::send_message: error preparing sysex header."); - return; + 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) { - error( - configuration, "midi_out_winmm::send_message: error sending sysex message."); - return; + libremidi_handle_error( + configuration, "error sending sysex message."); + return from_mmerr(result); } // Unprepare the buffer and MIDIHDR. @@ -152,11 +133,11 @@ public: // Make sure the message size isn't too big. if (size > 3) { - warning( + libremidi_handle_warning( configuration, - "midi_out_winmm::send_message: message size is greater than 3 bytes " + "message size is greater than 3 bytes " "(and not sysex)!"); - return; + return std::errc::message_size; } // Pack MIDI bytes into double word. @@ -167,10 +148,12 @@ public: auto result = midiOutShortMsg(this->outHandle, packet); if (result != MMSYSERR_NOERROR) { - error( - configuration, "midi_out_winmm::send_message: error sending MIDI message."); + libremidi_handle_error( + configuration, "error sending MIDI message."); + return from_mmerr(result); } } + return stdx::error{}; } private: diff --git a/include/libremidi/backends/winuwp/midi_in.hpp b/include/libremidi/backends/winuwp/midi_in.hpp index fa198db..0ccfbc5 100644 --- a/include/libremidi/backends/winuwp/midi_in.hpp +++ b/include/libremidi/backends/winuwp/midi_in.hpp @@ -21,38 +21,28 @@ public: 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(); } - - bool open_virtual_port(std::string_view) override + ~midi_in_winuwp() override { - warning(configuration, "midi_in_winuwp: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_in_winuwp: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_in_winuwp: set_port_name unsupported"); + close_port(); + this->client_open_ = std::errc::not_connected; } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; } - bool open_port(const input_port& port, std::string_view) override + 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 false; + return std::errc::invalid_argument; port_ = get(MidiInPort::FromIdAsync(id)); if (!port_) - return false; + return std::errc::io_error; midi_start_timestamp = std::chrono::steady_clock::now(); @@ -62,7 +52,7 @@ public: this->process_message(args.Message()); }); - return true; + return stdx::error{}; } void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg) @@ -81,13 +71,14 @@ public: m_processing.on_bytes({begin, end}, m_processing.timestamp(to_ns, 0)); } - void close_port() override + stdx::error close_port() override { if (port_) { port_.Close(); port_ = nullptr; } + return stdx::error{}; } timestamp absolute_timestamp() const noexcept override diff --git a/include/libremidi/backends/winuwp/midi_out.hpp b/include/libremidi/backends/winuwp/midi_out.hpp index 6a74619..af7d2e7 100644 --- a/include/libremidi/backends/winuwp/midi_out.hpp +++ b/include/libremidi/backends/winuwp/midi_out.hpp @@ -22,55 +22,52 @@ public: : configuration{std::move(conf), std::move(apiconf)} { winrt_init(); + this->client_open_ = stdx::error{}; } - ~midi_out_winuwp() override { close_port(); } - - bool open_virtual_port(std::string_view) override + ~midi_out_winuwp() override { - warning(configuration, "midi_out_winuwp: open_virtual_port unsupported"); - return false; - } - void set_client_name(std::string_view) override - { - warning(configuration, "midi_out_winuwp: set_client_name unsupported"); - } - void set_port_name(std::string_view) override - { - warning(configuration, "midi_out_winuwp: set_port_name unsupported"); + close_port(); + this->client_open_ = std::errc::not_connected; } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; } - bool open_port(const output_port& port, std::string_view) override + 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 false; + return std::errc::invalid_argument; port_ = get(MidiOutPort::FromIdAsync(id)); - return bool(port_); + if (!bool(port_)) + return std::errc::io_error; + + return stdx::error{}; } - void close_port() override + stdx::error close_port() override { if (port_) { port_.Close(); port_ = {}; } + return stdx::error{}; } - void send_message(const unsigned char* message, size_t size) override + stdx::error send_message(const unsigned char* message, size_t size) override { if (!port_) - return; + return std::errc::not_connected; InMemoryRandomAccessStream str; DataWriter rb(str); rb.WriteBytes( winrt::array_view{(const uint8_t*)message, (const uint8_t*)message + size}); port_.SendBuffer(rb.DetachBuffer()); + + return stdx::error{}; } private: diff --git a/include/libremidi/client.hpp b/include/libremidi/client.hpp index 8acb082..068c496 100644 --- a/include/libremidi/client.hpp +++ b/include/libremidi/client.hpp @@ -31,7 +31,7 @@ struct client_configuration best to set the error callback function before opening a port. */ midi_error_callback on_error{}; - midi_error_callback on_warning{}; + midi_warning_callback on_warning{}; //! Poll period for observation polling operations, if relevant to the backend std::chrono::milliseconds poll_period{100}; @@ -115,9 +115,9 @@ public: port, input_configuration{ .on_message - = [this, port](libremidi::message&& m) { - configuration.on_message(port, std::move(m)); - }, + = [this, + port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); }, + .on_raw_data = {}, .get_timestamp = {}, .on_error = configuration.on_error, @@ -153,32 +153,40 @@ public: void remove_input(const input_port& port) { m_inputs.erase(port); } void remove_output(const output_port& port) { m_outputs.erase(port); } - void send_message(const unsigned char* message, size_t size) + stdx::error send_message(const unsigned char* message, size_t size) { for (auto& [_, out] : m_outputs) { - out.send_message(message, size); + if (auto err = out.send_message(message, size); err != stdx::error{}) + return err; } + return stdx::error{}; } - void send_ump(const uint32_t* message, size_t size) + stdx::error send_ump(const uint32_t* message, size_t size) { for (auto& [_, out] : m_outputs) { - out.send_ump(message, size); + if (auto err = out.send_ump(message, size); err != stdx::error{}) + return err; } + return stdx::error{}; } - void send_message(const output_port& port, const unsigned char* message, size_t size) + 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()) - it->second.send_message(message, size); + return it->second.send_message(message, size); + + return stdx::error{}; } - void send_ump(const output_port& port, const uint32_t* message, size_t size) + 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()) - it->second.send_ump(message, size); + return it->second.send_ump(message, size); + + return stdx::error{}; } private: diff --git a/include/libremidi/cmidi2.hpp b/include/libremidi/cmidi2.hpp index 39a347a..534e2b6 100644 --- a/include/libremidi/cmidi2.hpp +++ b/include/libremidi/cmidi2.hpp @@ -2559,6 +2559,7 @@ enum cmidi2_midi_conversion_result 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 @@ -2686,7 +2687,11 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) else { // fixed sized message - size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, sLen - *sIdx); + 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; diff --git a/include/libremidi/config.hpp b/include/libremidi/config.hpp index 47ac916..e845d4b 100644 --- a/include/libremidi/config.hpp +++ b/include/libremidi/config.hpp @@ -4,11 +4,6 @@ #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN #endif -#include -#include -#include -#include -#include #if defined(LIBREMIDI_EXPORTS) #if defined(_MSC_VER) @@ -51,6 +46,7 @@ using midi_bytes = boost::container::small_vector namespace libremidi { using midi_bytes = std::vector; diff --git a/include/libremidi/configurations.hpp b/include/libremidi/configurations.hpp index c905fea..b872f41 100644 --- a/include/libremidi/configurations.hpp +++ b/include/libremidi/configurations.hpp @@ -1,29 +1,19 @@ #pragma once -#if defined(__linux__) - #include - #include - #include - #include - #include -#endif - -#if defined(__APPLE__) - #include - #include -#endif - -#if defined(_WIN32) - #include - #include - #include -#endif - -#if defined(__EMSCRIPTEN__) - #include -#endif - +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include namespace libremidi { @@ -31,5 +21,4 @@ namespace libremidi struct dummy_configuration { }; - } diff --git a/include/libremidi/detail/conversion.hpp b/include/libremidi/detail/conversion.hpp new file mode 100644 index 0000000..d627c30 --- /dev/null +++ b/include/libremidi/detail/conversion.hpp @@ -0,0 +1,59 @@ +#pragma once +// clang-format off +#include +// clang-format on + +#include +#include +#include +#include +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(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(message)); + if (n > 0) + return on_midi(midi, n, timestamp); + else + return std::errc::no_buffer_space; + } + + cmidi2_midi_conversion_context context = [] { + cmidi2_midi_conversion_context tmp; + cmidi2_midi_conversion_context_initialize(&tmp); + return tmp; + }(); + uint8_t midi[65536]; +}; + +} diff --git a/include/libremidi/detail/midi_api.hpp b/include/libremidi/detail/midi_api.hpp index 1bf8965..2e5ccab 100644 --- a/include/libremidi/detail/midi_api.hpp +++ b/include/libremidi/detail/midi_api.hpp @@ -2,61 +2,11 @@ #include #include #include -#include -#include #include namespace libremidi { -struct error_handler -{ - //! Error reporting function for libremidi classes. Throws. - template - void error(auto& configuration, std::string_view errorString) const - { - if (configuration.on_error) - { - if (first_error) - return; - - first_error = true; - configuration.on_error(Error_T::code, errorString); - first_error = false; - } - else - { -#if defined(__LIBREMIDI_DEBUG__) - std::cerr << '\n' << errorString << "\n\n"; -#endif - throw Error_T{errorString.data()}; - } - } - - //! Warning reporting function for libremidi classes. - void warning(auto& configuration, std::string_view errorString) const - { - if (configuration.on_warning) - { - if (first_warning) - return; - - first_warning = true; - configuration.on_warning(midi_error::WARNING, errorString); - first_warning = false; - return; - } - -#if defined(__LIBREMIDI_DEBUG__) - std::cerr << '\n' << errorString << "\n\n"; -#endif - } - - // To prevent infinite error loops - mutable bool first_error{}; - mutable bool first_warning{}; -}; - class midi_api { public: @@ -69,18 +19,29 @@ public: [[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0; - [[nodiscard]] virtual bool open_virtual_port(std::string_view) = 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 void close_port() = 0; - virtual void set_client_name(std::string_view) = 0; - virtual void set_port_name(std::string_view) = 0; + virtual stdx::error close_port() = 0; - bool is_port_open() const noexcept { return bool(port_open_); } - bool is_port_connected() const noexcept { return bool(connected_); } + stdx::error is_client_open() const noexcept { return client_open_; } + bool is_port_open() const noexcept { return port_open_; } + bool is_port_connected() const noexcept { return connected_; } protected: friend class midi_in; friend class midi_out; + stdx::error client_open_{std::errc::not_connected}; bool port_open_{}; bool connected_{}; }; diff --git a/include/libremidi/detail/midi_in.hpp b/include/libremidi/detail/midi_in.hpp index 797b0da..29f7307 100644 --- a/include/libremidi/detail/midi_in.hpp +++ b/include/libremidi/detail/midi_in.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include #include +#include namespace libremidi { @@ -14,7 +16,9 @@ public: midi_in_api& operator=(const midi_in_api&) = delete; midi_in_api& operator=(midi_in_api&&) = delete; - [[nodiscard]] virtual bool open_port(const input_port& pt, std::string_view local_port_name) = 0; + [[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; }; diff --git a/include/libremidi/detail/midi_out.hpp b/include/libremidi/detail/midi_out.hpp index e3ae1c3..7966a14 100644 --- a/include/libremidi/detail/midi_out.hpp +++ b/include/libremidi/detail/midi_out.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include #include +#include #include #include @@ -18,19 +19,21 @@ public: midi_out_api& operator=(const midi_out_api&) = delete; midi_out_api& operator=(midi_out_api&&) = delete; - [[nodiscard]] virtual bool open_port(const output_port& pt, std::string_view local_port_name) + [[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 void send_message(const unsigned char* message, std::size_t size) = 0; - virtual void schedule_message(int64_t /*ts*/, const unsigned char* message, std::size_t size) + 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 void send_ump(const uint32_t* message, std::size_t size) = 0; - virtual void schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t 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); } @@ -45,14 +48,15 @@ class out_api : public midi_out_api public: using midi_out_api::midi_out_api; - void send_ump(const uint32_t* message, std::size_t /*size*/) + stdx::error send_ump(const uint32_t* message, std::size_t size) { - uint8_t midi[65536]; - const auto n - = cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast(message)); - if (n > 0) - send_message(midi, n); + 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; }; } @@ -65,25 +69,15 @@ class out_api : public midi_out_api public: using midi_out_api::midi_out_api; - void send_message(const unsigned char* message, std::size_t size) + stdx::error send_message(const unsigned char* message, std::size_t size) { - cmidi2_midi_conversion_context context{}; - cmidi2_midi_conversion_context_initialize(&context); - - uint32_t ump[65536 / 4]; - - context.midi1 = const_cast(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; - - send_ump(context.ump, context.ump_proceeded_bytes / 4); + 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; }; } diff --git a/include/libremidi/detail/midi_stream_decoder.hpp b/include/libremidi/detail/midi_stream_decoder.hpp index 2b48dab..b69fc6b 100644 --- a/include/libremidi/detail/midi_stream_decoder.hpp +++ b/include/libremidi/detail/midi_stream_decoder.hpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -28,16 +29,87 @@ struct timestamp_backend_info bool has_samples{}; }; -namespace midi1 +template +struct input_state_machine_base { -struct input_state_machine -{ - const input_configuration& configuration; - explicit input_state_machine(const input_configuration& conf) + const Configuration& configuration; + + explicit input_state_machine_base(const Configuration& conf) : configuration{conf} { } + template + 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 +{ + using input_state_machine_base::input_state_machine_base; + + void reset() + { + message.bytes.clear(); + message.timestamp = {}; + state = main; + } + bool has_finished_sysex(std::span bytes) const noexcept { return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7)); @@ -46,6 +118,26 @@ struct input_state_machine // 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 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 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 bytes, int64_t timestamp) { int64_t nBytes = bytes.size(); int64_t iByte = 0; @@ -54,7 +146,7 @@ struct input_state_machine switch (state) { case in_sysex: { - return on_continue_sysex(bytes, finished_sysex); + return on_continue_sysex(cb, bytes, finished_sysex); } case main: { while (iByte < nBytes) @@ -150,7 +242,7 @@ struct input_state_machine message.assign(begin, begin + size); message.timestamp = timestamp; - this->configuration.on_message(std::move(message)); + cb(std::move(message)); message.clear(); iByte += size; @@ -160,7 +252,8 @@ struct input_state_machine } } - void on_continue_sysex(std::span bytes, bool finished_sysex) + void on_continue_sysex( + const message_callback& cb, std::span bytes, bool finished_sysex) { if (finished_sysex) state = main; @@ -174,14 +267,16 @@ struct input_state_machine message.insert(message.end(), bytes.begin(), bytes.end()); if (finished_sysex) { - this->configuration.on_message(std::move(message)); + cb(std::move(message)); message.clear(); } } return; } - void on_main(std::span bytes, int64_t timestamp, bool finished_sysex) + void on_main( + const message_callback& cb, std::span bytes, int64_t timestamp, + bool finished_sysex) { switch (bytes[0]) { @@ -196,7 +291,7 @@ struct input_state_machine message.timestamp = timestamp; if (finished_sysex) { - this->configuration.on_message(std::move(message)); + cb(std::move(message)); message.clear(); } } @@ -222,13 +317,12 @@ struct input_state_machine message.assign(bytes.begin(), bytes.end()); message.timestamp = timestamp; - this->configuration.on_message(std::move(message)); + cb(std::move(message)); message.clear(); } - // 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 bytes, int64_t timestamp) + void + on_bytes_segmented(const message_callback& cb, std::span bytes, int64_t timestamp) { if (bytes.empty()) return; @@ -237,78 +331,135 @@ struct input_state_machine switch (state) { case in_sysex: - return on_continue_sysex(bytes, finished_sysex); + return on_continue_sysex(cb, bytes, finished_sysex); case main: - return on_main(bytes, timestamp, finished_sysex); - } - } - - template - 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()); + return on_main(cb, bytes, timestamp, finished_sysex); } } +public: libremidi::message message; - int64_t last_time_ns = 0; +private: enum { main, in_sysex } state{main}; +}; +} - bool first_message = true; +namespace midi2 +{ +struct input_state_machine : input_state_machine_base +{ + using input_state_machine_base::input_state_machine_base; + +public: + void on_bytes_multi(std::span bytes, int64_t timestamp) + { + auto ptr = reinterpret_cast(bytes.data()); + auto sz = bytes.size() / 4; + return on_bytes_multi({ptr, sz}, timestamp); + } + + void on_bytes_multi(std::span 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 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 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 bytes, int64_t timestamp) + { + // Filter according to message type + switch(cmidi2_ump_get_message_type(bytes.data())) + { + case CMIDI2_MESSAGE_TYPE_UTILITY: + { + // All the utility messages are about timing + if (this->configuration.ignore_timing) + return; + break; + } + + case CMIDI2_MESSAGE_TYPE_SYSTEM: + { + if (this->configuration.ignore_timing) + { + auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); + switch(status) + { + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK: + return; + } + } + + if (this->configuration.ignore_sensing) + { + auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); + if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING) + return; + } + break; + } + + case CMIDI2_MESSAGE_TYPE_SYSEX7: + case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS: + { + if (this->configuration.ignore_sysex) + return; + break; + } + } + + libremidi::ump msg; + std::copy(bytes.begin(), bytes.end(), msg.data); + msg.timestamp = timestamp; + cb(std::move(msg)); + } }; } } diff --git a/include/libremidi/detail/observer.hpp b/include/libremidi/detail/observer.hpp index 4fe86bc..3a7f138 100644 --- a/include/libremidi/detail/observer.hpp +++ b/include/libremidi/detail/observer.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/include/libremidi/detail/ump_stream.hpp b/include/libremidi/detail/ump_stream.hpp index 6a0b90a..3dd3f2a 100644 --- a/include/libremidi/detail/ump_stream.hpp +++ b/include/libremidi/detail/ump_stream.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -17,7 +18,7 @@ enum class segmentation_error * Utility function to segment an ump stream into individual messages. * Used to send a stream to APIs that work message-by-message. */ -inline void +inline stdx::error segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, auto realloc_func) { while (count > 0) @@ -35,24 +36,27 @@ segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, a const auto ump_bytes = cmidi2_ump_get_num_bytes(ump_stream[0]); // FIXME std::expected, propagate the error back to caller? - switch (write_func(ump_stream, ump_bytes)) + switch (int err = static_cast(write_func(ump_stream, ump_bytes))) { - case segmentation_error::no_error: + case 0: break; - case segmentation_error::need_space: + case -ENOMEM: + case ENOMEM: // Try again if we didn't have enough space in the OS queue realloc_func(); - if (write_func(ump_stream, ump_bytes) != segmentation_error::no_error) - return; + if (auto err = write_func(ump_stream, ump_bytes); err != std::errc{}) + return std::make_error_code(err); break; - case segmentation_error::other: - return; + default: + return from_errc(err); } const auto ump_uints = ump_bytes / 4; ump_stream += ump_uints; count -= ump_uints; } + + return stdx::error{}; } } diff --git a/include/libremidi/error.hpp b/include/libremidi/error.hpp index c84cd5b..c038d3f 100644 --- a/include/libremidi/error.hpp +++ b/include/libremidi/error.hpp @@ -1,82 +1,37 @@ #pragma once #include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include +#pragma GCC diagnostic pop + #include -#include #include +#include + +#if __has_include() && (__cpp_lib_source_location >= 201907L) + #include +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 { -//! Defines various error types. -enum midi_error +inline auto from_errc(int ret) noexcept { - WARNING, /*!< A non-critical error. */ - UNSPECIFIED, /*!< The default, unspecified error type. */ - NO_DEVICES_FOUND, /*!< No devices found on system. */ - INVALID_DEVICE, /*!< An invalid device ID was specified. */ - MEMORY_ERROR, /*!< An error occured during memory allocation. */ - INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ - INVALID_USE, /*!< The function was called incorrectly. */ - DRIVER_ERROR, /*!< A system driver error occured. */ - SYSTEM_ERROR, /*!< A system error occured. */ - THREAD_ERROR /*!< A thread error occured. */ -}; - -//! Base exception class for MIDI problems -struct LIBREMIDI_EXPORT midi_exception : public std::runtime_error -{ - using std::runtime_error::runtime_error; - ~midi_exception() override; -}; - -struct LIBREMIDI_EXPORT no_devices_found_error final : public midi_exception -{ - static constexpr auto code = midi_error::NO_DEVICES_FOUND; - using midi_exception::midi_exception; - ~no_devices_found_error() override; -}; -struct LIBREMIDI_EXPORT invalid_device_error final : public midi_exception -{ - static constexpr auto code = midi_error::INVALID_DEVICE; - using midi_exception::midi_exception; - ~invalid_device_error() override; -}; -struct LIBREMIDI_EXPORT memory_error final : public midi_exception -{ - static constexpr auto code = midi_error::MEMORY_ERROR; - using midi_exception::midi_exception; - ~memory_error() override; -}; -struct LIBREMIDI_EXPORT invalid_parameter_error final : public midi_exception -{ - static constexpr auto code = midi_error::INVALID_PARAMETER; - using midi_exception::midi_exception; - ~invalid_parameter_error() override; -}; -struct LIBREMIDI_EXPORT invalid_use_error final : public midi_exception -{ - static constexpr auto code = midi_error::INVALID_USE; - using midi_exception::midi_exception; - ~invalid_use_error() override; -}; -struct LIBREMIDI_EXPORT driver_error final : public midi_exception -{ - static constexpr auto code = midi_error::DRIVER_ERROR; - using midi_exception::midi_exception; - ~driver_error() override; -}; -struct LIBREMIDI_EXPORT system_error final : public midi_exception -{ - static constexpr auto code = midi_error::SYSTEM_ERROR; - using midi_exception::midi_exception; - ~system_error() override; -}; -struct LIBREMIDI_EXPORT thread_error final : public midi_exception -{ - static constexpr auto code = midi_error::THREAD_ERROR; - using midi_exception::midi_exception; - ~thread_error() override; -}; + return static_cast(-ret); +} /*! \brief Error callback function \param type Type of error. @@ -85,5 +40,25 @@ struct LIBREMIDI_EXPORT thread_error final : public midi_exception Note that class behaviour is undefined after a critical error (not a warning) is reported. */ -using midi_error_callback = std::function; +using midi_error_callback = std::function; +using midi_warning_callback = std::function; } + +#if !defined(LIBREMIDI_LOG) + #if !defined(__LIBREMIDI_DEBUG__) + #define LIBREMIDI_LOG(...) \ + do \ + { \ + } while (0) + #else + #include + #define LIBREMIDI_LOG(...) \ + do \ + { \ + [](auto&&... args) { \ + (std::cerr << ... << args); \ + std::cerr << std::endl; \ + }(__VA_ARGS__); \ + } while (0) + #endif +#endif diff --git a/include/libremidi/error_handler.hpp b/include/libremidi/error_handler.hpp new file mode 100644 index 0000000..9fb8c4e --- /dev/null +++ b/include/libremidi/error_handler.hpp @@ -0,0 +1,63 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include + +namespace libremidi +{ +struct error_handler +{ + //! Error reporting function for libremidi classes. + void error_impl( + const midi_error_callback& callback, std::string_view errorString, + const source_location& location) const + { + if (callback) + { + if (first_error) + return; + + first_error = true; + callback(errorString, location); + first_error = false; + } + else + { + LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")"); + } + } + + //! Warning reporting function for libremidi classes. + void warning_impl( + const midi_warning_callback& callback, std::string_view errorString, + const source_location& location) const + { + if (callback) + { + if (first_warning) + return; + + first_warning = true; + callback(errorString, location); + first_warning = false; + } + else + { + LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")"); + } + } + + // To prevent infinite error loops + mutable bool first_error{}; + mutable bool first_warning{}; +}; + +// Needed as apple still doesn't support source_location in xcode 15.3 +#define libremidi_handle_error(config, str) error_impl(config.on_error, str, libremidi::source_location::current()) +#define libremidi_handle_warning(config, str) warning_impl(config.on_warning, str, libremidi::source_location::current()) +} diff --git a/include/libremidi/input_configuration.hpp b/include/libremidi/input_configuration.hpp index 77d8444..574f02f 100644 --- a/include/libremidi/input_configuration.hpp +++ b/include/libremidi/input_configuration.hpp @@ -5,7 +5,6 @@ #include #include -#include namespace libremidi { @@ -41,19 +40,23 @@ enum timestamp_mode using timestamp = int64_t; using message_callback = std::function; -using raw_callback = std::function, timestamp)>; +using raw_callback = std::function, timestamp)>; using timestamp_callback = std::function; struct input_configuration { //! Set a callback function to be invoked for incoming MIDI messages. - //! Mandatory! - message_callback on_message; + //! 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; + timestamp_callback get_timestamp{}; //! Set an error callback function to be invoked when an error has occured. /*! @@ -61,7 +64,7 @@ struct input_configuration best to set the error callback function before opening a port. */ midi_error_callback on_error{}; - midi_error_callback on_warning{}; + midi_warning_callback on_warning{}; //! Specify whether certain MIDI message types should be queued or ignored //! during input. @@ -81,16 +84,22 @@ struct input_configuration }; using ump_callback = std::function; +using raw_ump_callback = std::function, timestamp)>; struct ump_input_configuration { //! Set a callback function to be invoked for incoming UMP messages. - ump_callback on_message; + //! 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; + timestamp_callback get_timestamp{}; //! Set an error callback function to be invoked when an error has occured. /*! @@ -98,7 +107,7 @@ struct ump_input_configuration best to set the error callback function before opening a port. */ midi_error_callback on_error{}; - midi_error_callback on_warning{}; + midi_warning_callback on_warning{}; //! Specify whether certain MIDI message types should be queued or ignored //! during input. diff --git a/include/libremidi/libremidi-c.cpp b/include/libremidi/libremidi-c.cpp new file mode 100644 index 0000000..1413f70 --- /dev/null +++ b/include/libremidi/libremidi-c.cpp @@ -0,0 +1,507 @@ +// clang-format off +#include +#include +#include +// clang-format on + +#include +#include +#include +#include + +struct libremidi_midi_observer_handle +{ + libremidi::observer self; +}; + +struct libremidi_midi_in_handle +{ + libremidi::midi_in self; +}; + +struct libremidi_midi_out_handle +{ + libremidi::midi_out self; +}; + +namespace libremidi +{ + +static void assign_error_callback(const auto& src, auto& dst) +{ + if (src.callback) + { + dst = [src](std::string_view errorText, const source_location& loc) { + src.callback(src.context, errorText.data(), errorText.size(), &loc); + }; + } +} +} + +extern "C" { +const char* libremidi_get_version(void) +{ + return LIBREMIDI_VERSION; +} + +void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +{ + if (!cb) + return; + libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); }); +} + +void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +{ + if (!cb) + return; + libremidi::midi2::for_all_backends([=](auto b) { cb(ctx, b.API); }); +} + +const char* libremidi_api_identifier(libremidi_api api) +{ + return libremidi::get_api_name(api).data(); +} + +const char* libremidi_api_display_name(libremidi_api api) +{ + return libremidi::get_api_display_name(api).data(); +} + +libremidi_api libremidi_get_compiled_api_by_identifier(const char* name) +{ + libremidi_api ret = libremidi_api::UNSPECIFIED; + libremidi::midi_any::for_all_backends([&](auto& b) { + if (name == b.name) + ret = b.API; + }); + return ret; +} + +int libremidi_midi_api_configuration_init(libremidi_api_configuration* conf) +{ + memset(conf, 0, sizeof(*conf)); + return 0; +} + +int libremidi_midi_observer_configuration_init(libremidi_observer_configuration* conf) +{ + memset(conf, 0, sizeof(*conf)); + return 0; +} + +int libremidi_midi_configuration_init(libremidi_midi_configuration* conf) +{ + memset(conf, 0, sizeof(*conf)); + return 0; +} + +int libremidi_midi_in_port_clone(const libremidi_midi_in_port* port, libremidi_midi_in_port** dst) +{ + if (!port || !dst) + return -EINVAL; + + auto copied = new libremidi::input_port{*reinterpret_cast(port)}; + *dst = reinterpret_cast(copied); + return 0; +} + +int libremidi_midi_in_port_free(libremidi_midi_in_port* port) +{ + delete reinterpret_cast(port); + return 0; +} + +int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** name, size_t* len) +{ + if (!port || !name || !len) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *name = p.port_name.data(); + *len = p.port_name.size(); + return 0; +} + +int libremidi_midi_out_port_clone( + const libremidi_midi_out_port* port, libremidi_midi_out_port** dst) +{ + if (!port || !dst) + return -EINVAL; + + auto copied = new libremidi::output_port{*reinterpret_cast(port)}; + *dst = reinterpret_cast(copied); + return 0; +} + +int libremidi_midi_out_port_free(libremidi_midi_out_port* port) +{ + delete reinterpret_cast(port); + return 0; +} + +int libremidi_midi_out_port_name( + const libremidi_midi_out_port* port, const char** name, size_t* len) +{ + if (!port || !name || !len) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *name = p.port_name.data(); + *len = p.port_name.size(); + return 0; +} + +int libremidi_midi_observer_new( + const libremidi_observer_configuration* c, libremidi_api_configuration* api, + libremidi_midi_observer_handle** out) +{ + if (!out || !c) + return -EINVAL; + + libremidi::observer_configuration conf; + libremidi::assign_error_callback(c->on_error, conf.on_error); + libremidi::assign_error_callback(c->on_warning, conf.on_warning); + + conf.track_hardware = c->track_hardware; + conf.track_virtual = c->track_virtual; + conf.track_any = c->track_any; + conf.notify_in_constructor = c->notify_in_constructor; + + auto api_conf = libremidi::observer_configuration_for(static_cast(api->api)); + + if (c->input_added.callback) + { + conf.input_added = [cb = c->input_added](const auto& port) { + cb.callback(cb.context, reinterpret_cast(&port)); + }; + } + if (c->input_removed.callback) + { + conf.input_removed = [cb = c->input_removed](const auto& port) { + cb.callback(cb.context, reinterpret_cast(&port)); + }; + } + if (c->output_added.callback) + { + conf.output_added = [cb = c->output_added](const auto& port) { + cb.callback(cb.context, reinterpret_cast(&port)); + }; + } + if (c->output_removed.callback) + { + conf.output_removed = [cb = c->output_removed](const auto& port) { + cb.callback(cb.context, reinterpret_cast(&port)); + }; + } + + try + { + auto ptr = new libremidi_midi_observer_handle{ + libremidi::observer{std::move(conf), std::move(api_conf)}}; + *out = ptr; + return 0; + } + catch (...) + { + return -EINVAL; + } +} + +int libremidi_midi_observer_enumerate_input_ports( + libremidi_midi_observer_handle* ptr, void* context, + void (*cb)(void* ctx, const libremidi_midi_in_port*)) +{ + if (!ptr || !cb) + return -EINVAL; + + for (const auto& port : ptr->self.get_input_ports()) + { + cb(context, reinterpret_cast(&port)); + } + + return 0; +} + +int libremidi_midi_observer_enumerate_output_ports( + libremidi_midi_observer_handle* ptr, void* context, + void (*cb)(void* ctx, const libremidi_midi_out_port*)) +{ + if (!ptr || !cb) + return -EINVAL; + + for (const auto& port : ptr->self.get_output_ports()) + { + cb(context, reinterpret_cast(&port)); + } + + return 0; +} + +int libremidi_midi_observer_free(libremidi_midi_observer_handle* ptr) +{ + delete ptr; + return 0; +} + +int libremidi_midi_in_new( + const libremidi_midi_configuration* c, const libremidi_api_configuration* api, + libremidi_midi_in_handle** out) +{ + if (!out || !c) + return -EINVAL; + if (!c->virtual_port && !c->in_port) + return -EINVAL; + + *out = nullptr; + + auto api_conf = libremidi::midi_in_configuration_for(static_cast(api->api)); + + // Create the MIDI object + switch (c->version) + { + case libremidi_midi_configuration::MIDI1: + case libremidi_midi_configuration::MIDI1_RAW: { + libremidi::input_configuration conf; + libremidi::assign_error_callback(c->on_error, conf.on_error); + libremidi::assign_error_callback(c->on_warning, conf.on_warning); + conf.ignore_sensing = c->ignore_sensing; + conf.ignore_sysex = c->ignore_sysex; + conf.ignore_timing = c->ignore_timing; + conf.timestamps = c->timestamps; + + if (c->get_timestamp.callback) + { + conf.get_timestamp + = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; + } + + if (c->on_midi1_message.callback) + conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) { + cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size()); + }; + else if (c->on_midi1_raw_data.callback) + { + conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span msg, int64_t ts) { + cb.callback(cb.context, ts, msg.data(), msg.size()); + }; + } + else + { + return -EINVAL; + } + + try + { + auto ptr = new libremidi_midi_in_handle{ + libremidi::midi_in{std::move(conf), std::move(api_conf)}}; + *out = ptr; + } + catch (...) + { + return -EINVAL; + } + break; + } + case libremidi_midi_configuration::MIDI2: + case libremidi_midi_configuration::MIDI2_RAW: { + libremidi::ump_input_configuration conf; + libremidi::assign_error_callback(c->on_error, conf.on_error); + libremidi::assign_error_callback(c->on_warning, conf.on_warning); + conf.ignore_sensing = c->ignore_sensing; + conf.ignore_sysex = c->ignore_sysex; + conf.ignore_timing = c->ignore_timing; + conf.timestamps = c->timestamps; + + if (c->get_timestamp.callback) + { + conf.get_timestamp + = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; + } + + if (c->on_midi2_message.callback) + conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) { + cb.callback(cb.context, msg.timestamp, msg.data, msg.size()); + }; + else if (c->on_midi2_raw_data.callback) + { + conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span msg, int64_t ts) { + cb.callback(cb.context, ts, msg.data(), msg.size()); + }; + } + else + { + return -EINVAL; + } + + try + { + auto ptr = new libremidi_midi_in_handle{ + libremidi::midi_in{std::move(conf), std::move(api_conf)}}; + *out = ptr; + } + catch (...) + { + return -EINVAL; + } + break; + } + default: + return -EINVAL; + } + + // Open the port + auto ptr = *out; + if (!ptr) + return -EINVAL; + + if (c->virtual_port) + { + if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{}) + { + *out = nullptr; + delete ptr; + return -EIO; + } + } + else + { + auto port = reinterpret_cast(c->in_port); + if (auto ret = ptr->self.open_port(*port); ret != stdx::error{}) + { + *out = nullptr; + delete ptr; + return -EIO; + } + } + + return 0; +} +int libremidi_midi_in_is_connected(const libremidi_midi_in_handle* in) +{ + if (!in) + return -EINVAL; + return in->self.is_port_connected() ? 1 : 0; +} + +libremidi_timestamp libremidi_midi_in_absolute_timestamp(libremidi_midi_in_handle* in) +{ + if (!in) + return -EINVAL; + return in->self.absolute_timestamp(); +} + +int libremidi_midi_in_free(libremidi_midi_in_handle* ptr) +{ + delete ptr; + return 0; +} + +int libremidi_midi_out_new( + const libremidi_midi_configuration* c, const libremidi_api_configuration* api, + libremidi_midi_out_handle** out) +{ + if (!out || !c) + return -EINVAL; + if (!c->virtual_port && !c->out_port) + return -EINVAL; + + *out = nullptr; + + auto api_conf = libremidi::midi_out_configuration_for(static_cast(api->api)); + + // Create the MIDI object + libremidi::output_configuration conf; + libremidi::assign_error_callback(c->on_error, conf.on_error); + libremidi::assign_error_callback(c->on_warning, conf.on_warning); + conf.timestamps = c->timestamps; + try + { + auto ptr + = new libremidi_midi_out_handle{libremidi::midi_out{std::move(conf), std::move(api_conf)}}; + *out = ptr; + } + catch (...) + { + return -EINVAL; + } + + // Open the port + auto ptr = *out; + if (!ptr) + return -EINVAL; + + if (c->virtual_port) + { + if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{}) + { + *out = nullptr; + delete ptr; + return -EIO; + } + } + else + { + auto port = reinterpret_cast(c->in_port); + if (auto ret = ptr->self.open_port(*port); ret != stdx::error{}) + { + *out = nullptr; + delete ptr; + return -EIO; + } + } + return 0; +} + +int libremidi_midi_out_is_connected(const libremidi_midi_out_handle* out) +{ + if (!out) + return -EINVAL; + return out->self.is_port_connected() ? 1 : 0; +} + +int libremidi_midi_out_send_message( + libremidi_midi_out_handle* out, const libremidi_midi1_symbol* msg, size_t sz) +{ + if (!out || !msg || sz > std::numeric_limits::max()) + return -EINVAL; + + auto res = out->self.send_message(msg, sz); + return res != stdx::error{} ? -EIO : 0; +} + +int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) +{ + if (!out || !msg || sz > std::numeric_limits::max()) + return -EINVAL; + + auto res = out->self.send_ump(msg, sz); + return res != stdx::error{} ? -EIO : 0; +} + +int libremidi_midi_out_schedule_message( + libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi1_symbol* msg, size_t sz) +{ + if (!out || !msg || sz > std::numeric_limits::max()) + return -EINVAL; + + auto res = out->self.schedule_message(ts, msg, sz); + return res != stdx::error{} ? -EIO : 0; +} + +int libremidi_midi_out_schedule_ump( + libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi2_symbol* msg, size_t sz) +{ + if (!out || !msg || sz > std::numeric_limits::max()) + return -EINVAL; + + auto res = out->self.schedule_ump(ts, msg, sz); + return res != stdx::error{} ? -EIO : 0; +} + +int libremidi_midi_out_free(libremidi_midi_out_handle* ptr) +{ + delete ptr; + return 0; +} +} diff --git a/include/libremidi/libremidi-c.h b/include/libremidi/libremidi-c.h new file mode 100644 index 0000000..c43d7e1 --- /dev/null +++ b/include/libremidi/libremidi-c.h @@ -0,0 +1,275 @@ +#pragma once +#include + +#include +#include +#include + +#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 + +#if __cplusplus +extern "C" { +#endif +typedef unsigned char libremidi_midi1_symbol; +typedef libremidi_midi1_symbol* libremidi_midi1_message; + +typedef uint32_t libremidi_midi2_symbol; +typedef libremidi_midi2_symbol* libremidi_midi2_message; + +typedef int64_t libremidi_timestamp; + +typedef struct libremidi_midi_in_port libremidi_midi_in_port; +typedef struct libremidi_midi_out_port libremidi_midi_out_port; +typedef struct libremidi_midi_in_handle libremidi_midi_in_handle; +typedef struct libremidi_midi_out_handle libremidi_midi_out_handle; +typedef struct libremidi_midi_observer_handle libremidi_midi_observer_handle; + +typedef struct libremidi_api_configuration libremidi_api_configuration; + +enum libremidi_timestamp_mode +{ + NoTimestamp, + + Relative, + Absolute, + SystemMonotonic, + AudioFrame, + Custom +}; + +typedef struct libremidi_api_configuration +{ + enum libremidi_api api; + enum + { + Observer, + Input, + Output + } configuration_type; + + void* data; +} libremidi_api_configuration; + +typedef struct libremidi_observer_configuration +{ + struct + { + void* context; + void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location); + } on_error; + struct + { + void* context; + void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location); + } on_warning; + + struct + { + void* context; + void (*callback)(void* ctx, const libremidi_midi_in_port*); + } input_added; + struct + { + void* context; + void (*callback)(void* ctx, const libremidi_midi_in_port*); + } input_removed; + struct + { + void* context; + void (*callback)(void* ctx, const libremidi_midi_out_port*); + } output_added; + struct + { + void* context; + void (*callback)(void* ctx, const libremidi_midi_out_port*); + } output_removed; + + bool track_hardware; + bool track_virtual; + bool track_any; + bool notify_in_constructor; +} libremidi_observer_configuration; + +typedef struct libremidi_midi1_callback +{ + void* context; + void (*callback)(void* ctx, libremidi_timestamp, const libremidi_midi1_symbol*, size_t len); +} libremidi_midi1_callback; + +typedef struct libremidi_midi2_callback +{ + void* context; + void (*callback)(void* ctx, libremidi_timestamp, const libremidi_midi2_symbol*, size_t len); +} libremidi_midi2_callback; + +typedef struct libremidi_midi_configuration +{ + // Indicates the kind of callback requested, e.g. set MIDI1_RAW if you use the on_midi1_raw_data + enum + { + MIDI1 = (1 << 1), + MIDI1_RAW = (1 << 2), + MIDI2 = (1 << 3), + MIDI2_RAW = (1 << 4) + } version; + + union + { + libremidi_midi_in_port* in_port; + libremidi_midi_out_port* out_port; + }; + + union + { + libremidi_midi1_callback on_midi1_message; + libremidi_midi1_callback on_midi1_raw_data; + libremidi_midi2_callback on_midi2_message; + libremidi_midi2_callback on_midi2_raw_data; + }; + + struct + { + void* context; + libremidi_timestamp (*callback)(void* ctx, libremidi_timestamp); + } get_timestamp; + + struct + { + void* context; + void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location); + } on_error; + struct + { + void* context; + void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location); + } on_warning; + + const char* port_name; + bool virtual_port; + + bool ignore_sysex; + bool ignore_timing; + bool ignore_sensing; + + enum libremidi_timestamp_mode timestamps; +} libremidi_midi_configuration; + +/// API utilities +LIBREMIDI_EXPORT +const char* libremidi_get_version(void); + +LIBREMIDI_EXPORT +void libremidi_midi1_available_apis(void* ctx, void (*)(void*, libremidi_api)); +LIBREMIDI_EXPORT +void libremidi_midi2_available_apis(void* ctx, void (*)(void*, libremidi_api)); +LIBREMIDI_EXPORT +const char* libremidi_api_identifier(libremidi_api); +LIBREMIDI_EXPORT +const char* libremidi_api_display_name(libremidi_api); +LIBREMIDI_EXPORT +libremidi_api libremidi_get_compiled_api_by_identifier(const char*); + +/// Create configurations +LIBREMIDI_EXPORT +int libremidi_midi_api_configuration_init(libremidi_api_configuration*); + +LIBREMIDI_EXPORT +int libremidi_midi_observer_configuration_init(libremidi_observer_configuration*); + +LIBREMIDI_EXPORT +int libremidi_midi_configuration_init(libremidi_midi_configuration*); + +/// Read information about port objects +LIBREMIDI_EXPORT +int libremidi_midi_in_port_clone(const libremidi_midi_in_port* port, libremidi_midi_in_port** dst); + +LIBREMIDI_EXPORT +int libremidi_midi_in_port_free(libremidi_midi_in_port* port); + +LIBREMIDI_EXPORT +int libremidi_midi_in_port_name( + const libremidi_midi_in_port* port, const char** name, size_t* len); + +LIBREMIDI_EXPORT +int libremidi_midi_out_port_clone( + const libremidi_midi_out_port* port, libremidi_midi_out_port** dst); + +LIBREMIDI_EXPORT +int libremidi_midi_out_port_free(libremidi_midi_out_port* port); + +LIBREMIDI_EXPORT +int libremidi_midi_out_port_name( + const libremidi_midi_out_port* port, const char** name, size_t* len); + +/// Observer API +LIBREMIDI_EXPORT +int libremidi_midi_observer_new( + const libremidi_observer_configuration*, libremidi_api_configuration*, + libremidi_midi_observer_handle**); + +LIBREMIDI_EXPORT +int libremidi_midi_observer_enumerate_input_ports( + libremidi_midi_observer_handle*, void* context, + void (*)(void* ctx, const libremidi_midi_in_port*)); + +LIBREMIDI_EXPORT +int libremidi_midi_observer_enumerate_output_ports( + libremidi_midi_observer_handle*, void* context, + void (*)(void* ctx, const libremidi_midi_out_port*)); + +LIBREMIDI_EXPORT +int libremidi_midi_observer_free(libremidi_midi_observer_handle*); + +/// MIDI input API (read MIDI messages) +LIBREMIDI_EXPORT +int libremidi_midi_in_new( + const libremidi_midi_configuration*, const libremidi_api_configuration*, + libremidi_midi_in_handle**); + +LIBREMIDI_EXPORT +int libremidi_midi_in_is_connected(const libremidi_midi_in_handle*); + +LIBREMIDI_EXPORT +libremidi_timestamp libremidi_midi_in_absolute_timestamp(libremidi_midi_in_handle*); + +LIBREMIDI_EXPORT +int libremidi_midi_in_free(libremidi_midi_in_handle*); + +/// MIDI output API (send MIDI messages) +LIBREMIDI_EXPORT +int libremidi_midi_out_new( + const libremidi_midi_configuration*, const libremidi_api_configuration*, + libremidi_midi_out_handle**); + +LIBREMIDI_EXPORT +int libremidi_midi_out_is_connected(const libremidi_midi_out_handle*); + +LIBREMIDI_EXPORT +int libremidi_midi_out_send_message( + libremidi_midi_out_handle*, const libremidi_midi1_symbol*, size_t); + +LIBREMIDI_EXPORT +int libremidi_midi_out_send_ump(libremidi_midi_out_handle*, const libremidi_midi2_symbol*, size_t); + +LIBREMIDI_EXPORT +int libremidi_midi_out_schedule_message( + libremidi_midi_out_handle*, int64_t ts, const libremidi_midi1_symbol*, size_t); + +LIBREMIDI_EXPORT +int libremidi_midi_out_schedule_ump( + libremidi_midi_out_handle*, int64_t ts, const libremidi_midi2_symbol*, size_t); + +LIBREMIDI_EXPORT +int libremidi_midi_out_free(libremidi_midi_out_handle*); + +#if __cplusplus +} +#endif diff --git a/include/libremidi/libremidi.cpp b/include/libremidi/libremidi.cpp index 19254b5..3cb79ae 100644 --- a/include/libremidi/libremidi.cpp +++ b/include/libremidi/libremidi.cpp @@ -7,9 +7,6 @@ #include -#include -#include - namespace libremidi { @@ -56,17 +53,20 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name) midi2::for_all_backends([&](auto b) { apis.push_back(b.API); }); return apis; } - -LIBREMIDI_INLINE midi_exception::~midi_exception() = default; -LIBREMIDI_INLINE no_devices_found_error::~no_devices_found_error() = default; -LIBREMIDI_INLINE invalid_device_error::~invalid_device_error() = default; -LIBREMIDI_INLINE memory_error::~memory_error() = default; -LIBREMIDI_INLINE invalid_parameter_error::~invalid_parameter_error() = default; -LIBREMIDI_INLINE invalid_use_error::~invalid_use_error() = default; -LIBREMIDI_INLINE driver_error::~driver_error() = default; -LIBREMIDI_INLINE system_error::~system_error() = default; -LIBREMIDI_INLINE thread_error::~thread_error() = default; - +LIBREMIDI_INLINE +libremidi::API midi_api(const std::any& conf) +{ + libremidi::API ret = libremidi::API::UNSPECIFIED; + midi_any::for_all_backends([&](T) { + if (std::any_cast(&conf) + || std::any_cast(&conf) + || std::any_cast(&conf)) + { + ret = T::API; + } + }); + return ret; +} LIBREMIDI_INLINE std::any midi_in_configuration_for(libremidi::API api) { diff --git a/include/libremidi/libremidi.hpp b/include/libremidi/libremidi.hpp index bf9f547..4085980 100644 --- a/include/libremidi/libremidi.hpp +++ b/include/libremidi/libremidi.hpp @@ -69,7 +69,6 @@ #include #endif #include -#include namespace libremidi { @@ -132,19 +131,20 @@ public: [[nodiscard]] libremidi::API get_current_api() const noexcept; //! Open a MIDI input connection - void open_port(const input_port& pt, std::string_view local_port_name = "libremidi input"); + 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. - void open_virtual_port(std::string_view portName = "libremidi virtual port"); + stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port"); - void set_port_name(std::string_view portName); + stdx::error set_port_name(std::string_view portName); //! Close an open MIDI connection (if one exists). - void close_port(); + 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; @@ -182,11 +182,11 @@ public: [[nodiscard]] libremidi::API get_current_api() const noexcept; //! Open a MIDI output connection. - void + 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). - void close_port() const; + 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; @@ -200,48 +200,56 @@ public: //! //! \param portName An optional name for the application port that is //! used to connect to portId can be specified. - void open_virtual_port(std::string_view portName = "libremidi virtual port") const; + stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port") const; - void set_port_name(std::string_view portName) 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. */ - void send_message(const libremidi::message& message) const; + stdx::error send_message(const libremidi::message& message) const; //! Immediately send a single message to an open MIDI output port. - void send_message(const unsigned char* message, size_t size) const; - void send_message(std::span) const; - void send_message(unsigned char b0) const; - void send_message(unsigned char b0, unsigned char b1) const; - void send_message(unsigned char b0, unsigned char b1, unsigned char b2) const; + stdx::error send_message(const unsigned char* message, size_t size) const; + stdx::error send_message(std::span) 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) - void schedule_message(int64_t timestamp, const unsigned char* message, size_t size); + 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. - void send_ump(const uint32_t* message, size_t size) const; - void send_ump(const libremidi::ump&) const; - void send_ump(std::span) const; - void send_ump(uint32_t b0) const; - void send_ump(uint32_t b0, uint32_t b1) const; - void send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const; - void send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const; + 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; + 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 - void send_ump(const midi::universal_packet& pkt) const { send_ump(pkt.data, pkt.size()); } - void send_ump(const midi::sysex7& msg, int group = 0) + 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); }); } - void send_ump(const midi::sysex8& msg, int stream, int group = 0) + 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); }); } @@ -249,7 +257,7 @@ public: //! Try to schedule an UMP packet later in time if the underlying API supports it //! (currently not implemented anywhere) - void schedule_ump(int64_t timestamp, const uint32_t* message, size_t size); + stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const; private: std::unique_ptr impl_; diff --git a/include/libremidi/message.hpp b/include/libremidi/message.hpp index 541e5d0..da8366e 100644 --- a/include/libremidi/message.hpp +++ b/include/libremidi/message.hpp @@ -1,7 +1,12 @@ #pragma once #include +#include #include +#include +#if defined(__cpp_exceptions) + #include +#endif namespace libremidi { @@ -120,7 +125,7 @@ struct message { #if defined(__cpp_exceptions) if (channel <= 0 || channel > 16) - throw std::range_error("message::uses_channel: out of range"); + throw std::range_error("out of range"); #endif return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0); } @@ -240,7 +245,9 @@ struct meta_events static_cast(mpqn)}; } - static message time_signature(int numerator, int denominator) + 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; @@ -251,8 +258,14 @@ struct meta_events ++powTwo; } - return {0xff, 0x58, 0x04, static_cast(numerator), static_cast(powTwo), - 1, 96}; + return { + 0xff, + 0x58, + 0x04, + static_cast(numerator), + static_cast(powTwo), + static_cast(clocks_per_click), + static_cast(notated_32nd_notes_per_beat)}; } // Where key index goes from -7 (7 flats, Câ™­ Major) to +7 (7 sharps, C♯ @@ -261,7 +274,7 @@ struct meta_events { #if defined(__cpp_exceptions) if (keyIndex < -7 || keyIndex > 7) - throw std::range_error("meta_events::key_signature: out of range"); + throw std::range_error("out of range"); #endif return { 0xff, 0x59, 0x02, static_cast(keyIndex), diff --git a/include/libremidi/midi_in.cpp b/include/libremidi/midi_in.cpp index d529ac6..29ba76a 100644 --- a/include/libremidi/midi_in.cpp +++ b/include/libremidi/midi_in.cpp @@ -9,11 +9,63 @@ namespace libremidi { -LIBREMIDI_INLINE auto make_midi_in(auto base_conf, std::any api_conf, auto backends) + +static libremidi::ump_input_configuration +convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) noexcept +{ + libremidi::ump_input_configuration c2; + c2.on_message = [cb = base_conf.on_message, + converter = midi2_to_midi1{}](libremidi::ump&& msg) mutable -> void { + converter.convert( + msg.data, 1, msg.timestamp, [cb](const unsigned char* midi, std::size_t n, int64_t ts) { + cb(libremidi::message{{midi, midi + n}, ts}); + return stdx::error{}; + }); + }; + c2.get_timestamp = base_conf.get_timestamp; + c2.on_error = base_conf.on_error; + c2.on_warning = base_conf.on_warning; + c2.ignore_sysex = base_conf.ignore_sysex; + c2.ignore_timing = base_conf.ignore_timing; + c2.ignore_sensing = base_conf.ignore_sensing; + c2.timestamps = base_conf.timestamps; + return c2; +} + +static libremidi::input_configuration +convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_conf) noexcept +{ + libremidi::input_configuration c2; + c2.on_message = [cb = base_conf.on_message, + converter = midi1_to_midi2{}](libremidi::message&& msg) mutable -> void { + converter.convert( + msg.bytes.data(), msg.bytes.size(), msg.timestamp, + [cb](const uint32_t* ump, std::size_t n, int64_t ts) { + if(n >= 4) + { + libremidi::ump u{ump[0],ump[1],ump[2],ump[3]}; + u.timestamp = ts; + cb(std::move(u)); + } + return stdx::error{}; + }); + }; + c2.get_timestamp = base_conf.get_timestamp; + c2.on_error = base_conf.on_error; + c2.on_warning = base_conf.on_warning; + c2.ignore_sysex = base_conf.ignore_sysex; + c2.ignore_timing = base_conf.ignore_timing; + c2.ignore_sensing = base_conf.ignore_sensing; + c2.timestamps = base_conf.timestamps; + return c2; +} + +static LIBREMIDI_INLINE std::unique_ptr +make_midi_in(auto base_conf, std::any api_conf, auto backends) { std::unique_ptr ptr; - assert(base_conf.on_message); + assert(base_conf.on_message || base_conf.on_raw_data); auto from_api = [&](T& /*backend*/) mutable { if (auto conf = std::any_cast(&api_conf)) @@ -27,58 +79,199 @@ LIBREMIDI_INLINE auto make_midi_in(auto base_conf, std::any api_conf, auto backe return ptr; } -LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept +/// MIDI 1 helpers +static LIBREMIDI_INLINE std::unique_ptr +make_midi1_in(const input_configuration& base_conf) { for (const auto& api : available_apis()) { try { - impl_ = make_midi_in(base_conf, midi_in_configuration_for(api), midi1::available_backends); + auto impl + = make_midi_in(base_conf, midi_in_configuration_for(api), midi1::available_backends); + if (impl) + return impl; } catch (const std::exception& e) { } - - if (impl_) - return; } - if (!impl_) - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + + // No MIDI 1 backend, try the MIDI 2 ones with a wrap: + { + auto c2 = convert_midi1_to_midi2_input_configuration(base_conf); + for (const auto& api : available_ump_apis()) + { + try + { + auto impl = make_midi_in(c2, midi_in_configuration_for(api), midi2::available_backends); + if (impl) + return impl; + } + catch (const std::exception& e) + { + } + } + } + + return std::make_unique(input_configuration{}, dummy_configuration{}); +} + +static LIBREMIDI_INLINE std::unique_ptr +make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api) +{ + if (libremidi::is_midi1(api)) + { + return make_midi_in(base_conf, api_conf, midi1::available_backends); + } + else if (libremidi::is_midi2(api)) + { + auto c2 = convert_midi1_to_midi2_input_configuration(base_conf); + return make_midi_in(c2, api_conf, midi2::available_backends); + } + return {}; +} + +static LIBREMIDI_INLINE std::unique_ptr +make_midi1_in(const input_configuration& base_conf, const std::any& api_conf) +{ + if (!api_conf.has_value()) + { + return make_midi1_in(base_conf); + } + else if (auto api_p = std::any_cast(&api_conf)) + { + if (*api_p == libremidi::API::UNSPECIFIED) + { + return make_midi1_in(base_conf); + } + else + { + return make_midi1_in(base_conf, midi_in_configuration_for(*api_p), *api_p); + } + } + else + { + if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED) + return {}; + else + return make_midi1_in(base_conf, api_conf, libremidi::midi_api(api_conf)); + } +} + +/// MIDI 1 constructors +LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept + : impl_{make_midi1_in(base_conf)} +{ } LIBREMIDI_INLINE midi_in::midi_in(input_configuration base_conf, std::any api_conf) - : impl_{make_midi_in(base_conf, api_conf, midi1::available_backends)} + : impl_{make_midi1_in(base_conf, api_conf)} { if (!impl_) - throw midi_exception("Could not open midi in for the given api"); + { + error_handler e; + e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); + impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + } } -LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept +/// MIDI 2 helpers +static LIBREMIDI_INLINE std::unique_ptr +make_midi2_in(const ump_input_configuration& base_conf) { - for (const auto& api : available_apis()) + for (const auto& api : available_ump_apis()) { try { - impl_ = make_midi_in(base_conf, midi_in_configuration_for(api), midi2::available_backends); + if (auto ret + = make_midi_in(base_conf, midi_in_configuration_for(api), midi2::available_backends)) + return ret; } catch (const std::exception& e) { } - - if (impl_) - return; } - if (!impl_) - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + + // No MIDI 2 backend, try the MIDI 1 ones with a wrap: + { + auto c2 = convert_midi2_to_midi1_input_configuration(base_conf); + for (const auto& api : available_apis()) + { + try + { + if (auto ret = make_midi_in(c2, midi_in_configuration_for(api), midi1::available_backends)) + return ret; + } + catch (const std::exception& e) + { + } + } + } + + return {}; +} + +static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( + const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api) +{ + if (is_midi2(api)) + { + return make_midi_in(base_conf, api_conf, midi2::available_backends); + } + else if (is_midi1(api)) + { + auto c2 = convert_midi2_to_midi1_input_configuration(base_conf); + return make_midi_in(c2, api_conf, midi1::available_backends); + } + + return {}; +} + +static LIBREMIDI_INLINE std::unique_ptr +make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf) +{ + if (!api_conf.has_value()) + { + return make_midi2_in(base_conf); + } + else if (auto api_p = std::any_cast(&api_conf)) + { + if (*api_p == libremidi::API::UNSPECIFIED) + { + return make_midi2_in(base_conf); + } + else + { + return make_midi2_in(base_conf, midi_in_configuration_for(*api_p), *api_p); + } + } + else + { + if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED) + return {}; + else + return make_midi2_in(base_conf, api_conf, libremidi::midi_api(api_conf)); + } +} + +/// MIDI 2 constructors +LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept + : impl_{make_midi2_in(base_conf)} +{ } LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf) - : impl_{make_midi_in(base_conf, api_conf, midi2::available_backends)} + : impl_{make_midi2_in(base_conf, api_conf)} { if (!impl_) - throw midi_exception("Could not open midi in for the given api"); + { + error_handler e; + e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); + impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + } } LIBREMIDI_INLINE midi_in::~midi_in() = default; @@ -91,9 +284,12 @@ LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept } LIBREMIDI_INLINE -void midi_in::set_port_name(std::string_view portName) +stdx::error midi_in::set_port_name(std::string_view portName) { - impl_->set_port_name(portName); + if(impl_->is_port_open()) + return impl_->set_port_name(portName); + + return std::errc::not_connected; } LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept @@ -111,37 +307,50 @@ libremidi::API midi_in::get_current_api() const noexcept } LIBREMIDI_INLINE -void midi_in::open_port(const input_port& port, std::string_view portName) +stdx::error midi_in::open_port(const input_port& port, std::string_view portName) { - if (impl_->is_port_open()) - return; + if (auto err = impl_->is_client_open(); err != stdx::error{}) + return std::errc::not_connected; - if (impl_->open_port(port, portName)) + if (impl_->is_port_open()) + return std::errc::operation_not_supported; + + auto ret = impl_->open_port(port, portName); + if (ret == stdx::error{}) { impl_->connected_ = true; impl_->port_open_ = true; } + return ret; } LIBREMIDI_INLINE -void midi_in::open_virtual_port(std::string_view portName) +stdx::error midi_in::open_virtual_port(std::string_view portName) { + if (auto err = impl_->is_client_open(); err != stdx::error{}) + return std::errc::not_connected; + if (impl_->is_port_open()) - return; + return std::errc::operation_not_supported; - if (impl_->open_virtual_port(portName)) - { + auto ret = impl_->open_virtual_port(portName); + if (ret == stdx::error{}) impl_->port_open_ = true; - } + return ret; } LIBREMIDI_INLINE -void midi_in::close_port() +stdx::error midi_in::close_port() { - impl_->close_port(); + if (auto err = impl_->is_client_open(); err != stdx::error{}) + return std::errc::not_connected; + + auto ret = impl_->close_port(); impl_->connected_ = false; impl_->port_open_ = false; + + return ret; } LIBREMIDI_INLINE diff --git a/include/libremidi/midi_out.cpp b/include/libremidi/midi_out.cpp index 66e6b7b..82b3b7a 100644 --- a/include/libremidi/midi_out.cpp +++ b/include/libremidi/midi_out.cpp @@ -10,7 +10,8 @@ namespace libremidi { -LIBREMIDI_INLINE auto make_midi_out(auto base_conf, std::any api_conf) +static LIBREMIDI_INLINE std::unique_ptr +make_midi_out_impl(auto base_conf, std::any api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { @@ -26,23 +27,66 @@ LIBREMIDI_INLINE auto make_midi_out(auto base_conf, std::any api_conf) return ptr; } -LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept +static LIBREMIDI_INLINE std::unique_ptr +make_midi_out(const output_configuration& base_conf) { for (const auto& api : available_apis()) { try { - impl_ = make_midi_out(base_conf, midi_out_configuration_for(api)); + if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api))) + return ret; } catch (const std::exception& e) { } - - if (impl_) - return; } - if (!impl_) - impl_ = std::make_unique(output_configuration{}, dummy_configuration{}); + + for (const auto& api : available_ump_apis()) + { + try + { + if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api))) + return ret; + } + catch (const std::exception& e) + { + } + } + + return std::make_unique(output_configuration{}, dummy_configuration{}); +} + +static LIBREMIDI_INLINE std::unique_ptr +make_midi_out(const output_configuration& base_conf, const std::any& api_conf) +{ + if (!api_conf.has_value()) + { + return make_midi_out(base_conf); + } + else if (auto api_p = std::any_cast(&api_conf)) + { + if (*api_p == libremidi::API::UNSPECIFIED) + { + return make_midi_out(base_conf); + } + else + { + return make_midi_out_impl(base_conf, midi_out_configuration_for(*api_p)); + } + } + else + { + if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED) + return make_midi_out(base_conf); + else + return make_midi_out_impl(base_conf, api_conf); + } +} + +LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept + : impl_{make_midi_out(base_conf)} +{ } LIBREMIDI_INLINE @@ -50,7 +94,11 @@ midi_out::midi_out(output_configuration base_conf, std::any api_conf) : impl_{make_midi_out(base_conf, api_conf)} { if (!impl_) - throw midi_exception("Could not open midi out for the given api"); + { + error_handler e; + e.libremidi_handle_error(base_conf, "Could not open midi out for the given api"); + impl_ = std::make_unique(output_configuration{}, dummy_configuration{}); + } } LIBREMIDI_INLINE midi_out::~midi_out() = default; @@ -61,6 +109,7 @@ LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept other.impl_ = std::make_unique(output_configuration{}, dummy_configuration{}); } + LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept { this->impl_ = std::move(other.impl_); @@ -70,9 +119,12 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept } LIBREMIDI_INLINE -void midi_out::set_port_name(std::string_view portName) const +stdx::error midi_out::set_port_name(std::string_view portName) const { - impl_->set_port_name(portName); + if(impl_->is_port_open()) + return impl_->set_port_name(portName); + + return std::errc::not_connected; } LIBREMIDI_INLINE @@ -82,36 +134,48 @@ libremidi::API midi_out::get_current_api() const noexcept } LIBREMIDI_INLINE -void midi_out::open_port(const output_port& port, std::string_view portName) const +stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const { - if (impl_->is_port_open()) - return; + if (auto err = impl_->is_client_open(); err != stdx::error{}) + return std::errc::not_connected; - if (impl_->open_port(port, portName)) + if (impl_->is_port_open()) + return std::errc::operation_not_supported; + + auto ret = impl_->open_port(port, portName); + if (ret == stdx::error{}) { impl_->connected_ = true; impl_->port_open_ = true; } + return ret; } LIBREMIDI_INLINE -void midi_out::open_virtual_port(std::string_view portName) const +stdx::error midi_out::open_virtual_port(std::string_view portName) const { + if (auto err = impl_->is_client_open(); err != stdx::error{}) + return std::errc::not_connected; + if (impl_->is_port_open()) - return; + return std::errc::operation_not_supported; - if (impl_->open_virtual_port(portName)) - { + auto ret = impl_->open_virtual_port(portName); + if (ret == stdx::error{}) impl_->port_open_ = true; - } + return ret; } LIBREMIDI_INLINE -void midi_out::close_port() const +stdx::error midi_out::close_port() const { - impl_->close_port(); + if (auto err = impl_->is_client_open(); err != stdx::error{}) + return std::errc::not_connected; + + auto ret = impl_->close_port(); impl_->connected_ = false; impl_->port_open_ = false; + return ret; } LIBREMIDI_INLINE @@ -127,89 +191,136 @@ bool midi_out::is_port_connected() const noexcept } LIBREMIDI_INLINE -void midi_out::send_message(const libremidi::message& message) const +stdx::error midi_out::send_message(const libremidi::message& message) const { - send_message(message.bytes.data(), message.bytes.size()); + return send_message(message.bytes.data(), message.bytes.size()); } LIBREMIDI_INLINE -void midi_out::send_message(std::span message) const +stdx::error midi_out::send_message(std::span message) const { - send_message(message.data(), message.size()); + return send_message(message.data(), message.size()); } LIBREMIDI_INLINE -void midi_out::send_message(unsigned char b0) const +stdx::error midi_out::send_message(unsigned char b0) const { - send_message(&b0, 1); + return send_message(&b0, 1); } LIBREMIDI_INLINE -void midi_out::send_message(unsigned char b0, unsigned char b1) const +stdx::error midi_out::send_message(unsigned char b0, unsigned char b1) const { - send_message(std::to_array({b0, b1})); + return send_message(std::to_array({b0, b1})); } LIBREMIDI_INLINE -void midi_out::send_message(unsigned char b0, unsigned char b1, unsigned char b2) const +stdx::error midi_out::send_message(unsigned char b0, unsigned char b1, unsigned char b2) const { - send_message(std::to_array({b0, b1, b2})); + return send_message(std::to_array({b0, b1, b2})); } LIBREMIDI_INLINE -void midi_out::send_message(const unsigned char* message, size_t size) const +stdx::error midi_out::send_message(const unsigned char* message, size_t size) const { #if defined(LIBREMIDI_ASSERTIONS) assert(size > 0); #endif - impl_->send_message(message, size); + return impl_->send_message(message, size); } LIBREMIDI_INLINE -void midi_out::send_ump(const uint32_t* message, size_t size) const +int64_t midi_out::current_time() +{ + return impl_->current_time(); +} + +LIBREMIDI_INLINE +stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message, size_t size) const +{ +#if defined(LIBREMIDI_ASSERTIONS) + assert(size > 0); +#endif + + return impl_->schedule_message(ts, message, size); +} + +LIBREMIDI_INLINE +stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const { #if defined(LIBREMIDI_ASSERTIONS) assert(size > 0); assert(size <= 4); #endif - impl_->send_ump(message, size); + return impl_->send_ump(message, size); } LIBREMIDI_INLINE -void midi_out::send_ump(const libremidi::ump& message) const +stdx::error midi_out::send_ump(const libremidi::ump& message) const { - send_ump(message.data, message.size()); + return send_ump(message.data, message.size()); } LIBREMIDI_INLINE -void midi_out::send_ump(std::span message) const +stdx::error midi_out::send_ump(std::span message) const { - send_ump(message.data(), message.size()); + return send_ump(message.data(), message.size()); } LIBREMIDI_INLINE -void midi_out::send_ump(uint32_t b0) const +stdx::error midi_out::send_ump(uint32_t b0) const { - send_ump(&b0, 1); + return send_ump(&b0, 1); } LIBREMIDI_INLINE -void midi_out::send_ump(uint32_t b0, uint32_t b1) const +stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1) const { - send_ump(std::to_array({b0, b1})); + return send_ump(std::to_array({b0, b1})); } LIBREMIDI_INLINE -void midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const +stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const { - send_ump(std::to_array({b0, b1, b2})); + return send_ump(std::to_array({b0, b1, b2})); } LIBREMIDI_INLINE -void midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const +stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const { - send_ump(std::to_array({b0, b1, b2, b3})); + return send_ump(std::to_array({b0, b1, b2, b3})); } +LIBREMIDI_INLINE +stdx::error midi_out::send_ump(int32_t b0) const +{ + return send_ump(reinterpret_cast(&b0), 1); +} + +LIBREMIDI_INLINE +stdx::error midi_out::send_ump(int64_t b01) const +{ + auto ptr = reinterpret_cast(&b01); + uint32_t msg[2]{ptr[1], ptr[0]}; + return send_ump(msg, 2); +} + +LIBREMIDI_INLINE +stdx::error midi_out::send_ump(uint64_t b01) const +{ + auto ptr = reinterpret_cast(&b01); + uint32_t msg[2]{ptr[1], ptr[0]}; + return send_ump(msg, 2); +} + +LIBREMIDI_INLINE +stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t size) const +{ +#if defined(LIBREMIDI_ASSERTIONS) + assert(size > 0); +#endif + + return impl_->schedule_ump(ts, message, size); +} } diff --git a/include/libremidi/observer.cpp b/include/libremidi/observer.cpp index 178361e..442cffb 100644 --- a/include/libremidi/observer.cpp +++ b/include/libremidi/observer.cpp @@ -7,7 +7,37 @@ namespace libremidi { -LIBREMIDI_INLINE auto make_observer(auto base_conf, std::any api_conf) + +static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf) +{ + for (const auto& api : available_apis()) + { + try + { + if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) + return impl_; + } + catch (const std::exception& e) + { + } + } + + for (const auto& api : available_ump_apis()) + { + try + { + if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) + return impl_; + } + catch (const std::exception& e) + { + } + } + + return std::make_unique(observer_configuration{}, dummy_configuration{}); +} + +LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { @@ -23,30 +53,46 @@ LIBREMIDI_INLINE auto make_observer(auto base_conf, std::any api_conf) return ptr; } -LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept +LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf, std::any api_conf) { - for (const auto& api : available_apis()) + if (!api_conf.has_value()) { - try - { - impl_ = make_observer(base_conf, observer_configuration_for(api)); - } - catch (const std::exception& e) - { - } - - if (impl_) - return; + return make_observer(base_conf); } - if (!impl_) - impl_ = std::make_unique(observer_configuration{}, dummy_configuration{}); + else if (auto api_p = std::any_cast(&api_conf)) + { + if (*api_p == libremidi::API::UNSPECIFIED) + { + return make_observer(base_conf); + } + else + { + return make_observer_impl(base_conf, observer_configuration_for(*api_p)); + } + } + else + { + if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED) + return {}; + else + return make_observer_impl(base_conf, api_conf); + } +} + +LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept + : impl_{make_observer(base_conf)} +{ } LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf) : impl_{make_observer(base_conf, api_conf)} { if (!impl_) - throw midi_exception("Could not open observer for the given API"); + { + error_handler e; + e.libremidi_handle_error(base_conf, "Could not open observer for the given api"); + impl_ = std::make_unique(observer_configuration{}, dummy_configuration{}); + } } LIBREMIDI_INLINE observer::observer(observer&& other) noexcept diff --git a/include/libremidi/observer_configuration.hpp b/include/libremidi/observer_configuration.hpp index 5f59c72..0e427a3 100644 --- a/include/libremidi/observer_configuration.hpp +++ b/include/libremidi/observer_configuration.hpp @@ -22,22 +22,22 @@ struct LIBREMIDI_EXPORT port_information // PipeWire: unused // WinMM: unused // WinUWP: unused - client_handle client; + client_handle client = static_cast(-1); // ALSA Raw: { uint16_t card, device, sub, padding; } // ALSA Seq: { uint32_t client, uint32_t port; } - // CoreMIDI: MidiObjectRef + // 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; + port_handle port = static_cast(-1); - std::string manufacturer; - std::string device_name; - std::string port_name; - std::string display_name; + 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; @@ -59,12 +59,12 @@ using output_port_callback = std::function; struct observer_configuration { midi_error_callback on_error{}; - midi_error_callback on_warning{}; + 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; + 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; diff --git a/include/libremidi/output_configuration.hpp b/include/libremidi/output_configuration.hpp index 3588289..44e3373 100644 --- a/include/libremidi/output_configuration.hpp +++ b/include/libremidi/output_configuration.hpp @@ -3,8 +3,6 @@ #include #include -#include - namespace libremidi { struct output_configuration @@ -15,7 +13,7 @@ struct output_configuration best to set the error callback function before opening a port. */ midi_error_callback on_error{}; - midi_error_callback on_warning{}; + midi_warning_callback on_warning{}; //! Timestamp mode for the timestamps passed to schedule_message uint32_t timestamps : 3 = timestamp_mode::Absolute; diff --git a/include/libremidi/reader.cpp b/include/libremidi/reader.cpp index e4272bc..5beb5b1 100644 --- a/include/libremidi/reader.cpp +++ b/include/libremidi/reader.cpp @@ -71,7 +71,7 @@ struct validator if (track.empty()) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: empty track" << std::endl; + std::cerr << "empty track" << std::endl; #endif return false; } @@ -85,7 +85,7 @@ struct validator if (it == track.end()) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: track has no END OF TRACK" << std::endl; + std::cerr << "track has no END OF TRACK" << std::endl; #endif return false; } @@ -94,7 +94,7 @@ struct validator { #if defined(__LIBREMIDI_DEBUG__) std::cerr << std::distance(it, track.end()); - std::cerr << "libremidi::reader: track does not end with END OF TRACK" << std::endl; + std::cerr << "track does not end with END OF TRACK" << std::endl; #endif return false; } @@ -544,7 +544,7 @@ try if (size == 0) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: empty buffer passed to parse." << std::endl; + std::cerr << "empty buffer passed to parse." << std::endl; #endif return parse_result::invalid; } @@ -557,7 +557,7 @@ try if (static_cast(headerId) != str_to_headerid("MThd") || headerLength != 6) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: couldn't parse header" << std::endl; + std::cerr << "couldn't parse header" << std::endl; #endif return parse_result::invalid; } @@ -567,7 +567,7 @@ try if (format > 2) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: unknown format" << std::endl; + std::cerr << "unknown format" << std::endl; #endif return parse_result::invalid; } @@ -579,7 +579,7 @@ try if (timeDivision & 0x8000) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: found SMPTE time frames (unsupported)" << std::endl; + std::cerr << "found SMPTE time frames (unsupported)" << std::endl; int fps = (timeDivision >> 16) & 0x7f; if (fps != -30 && fps != -29 && fps != -25 && fps != -24) return parse_result::invalid; @@ -604,7 +604,7 @@ try if (headerId != str_to_headerid("MTrk")) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: couldn't find track header" << std::endl; + std::cerr << "couldn't find track header" << std::endl; #endif return parse_result::incomplete; } @@ -613,7 +613,7 @@ try if (available < headerLength) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: not enough data available" << std::endl; + std::cerr << "not enough data available" << std::endl; #endif return parse_result::incomplete; } @@ -652,7 +652,7 @@ try else { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: could not read event" << std::endl; + std::cerr << "could not read event" << std::endl; #endif dataPtr = trackEnd; result = parse_result::incomplete; @@ -664,7 +664,7 @@ try catch (const std::exception& e) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: " << e.what() << std::endl; + std::cerr << "" << e.what() << std::endl; #endif dataPtr = trackEnd; result = parse_result::incomplete; @@ -697,7 +697,7 @@ try catch (const std::exception& e) { #if defined(__LIBREMIDI_DEBUG__) - std::cerr << "libremidi::reader: " << e.what() << std::endl; + std::cerr << "" << e.what() << std::endl; #endif return parse_result::invalid; } diff --git a/include/libremidi/system_error2.hpp b/include/libremidi/system_error2.hpp new file mode 100644 index 0000000..b5dc1cd --- /dev/null +++ b/include/libremidi/system_error2.hpp @@ -0,0 +1,3501 @@ +#ifndef STDX_COMPILER_HPP + #define STDX_COMPILER_HPP + + // Check compiler macros. Note that Clang defines __GNUC__ and other GNU macros as well, + // but GNU does not define Clang macros, so we must check for Clang first. + + #if defined(__llvm__) || defined(__clang__) + + // -------- LLVM/Clang + + #define STDX_CLANG_COMPILER 1 + + #if defined(__cpp_variable_templates) && (__cplusplus >= 201703L) + #define STDX_VARIABLE_TEMPLATES 1 + #endif + + #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) + + // -------- GNU G++ + + #include + + #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 + +namespace stdx { + +// Implementation of std::void_t for use with pre-C++17 compilers. +// +namespace detail { + +template +struct void_t_impl +{ + using type = void; +}; + +} // end namespace detail + +template +using void_t = typename detail::void_t_impl::type; + +template +struct dependent_type +{ + using type = U; +}; + +template +using dependent_type_t = typename dependent_type::type; + +template +struct disjunction : std::false_type +{ }; + +template +struct disjunction : B1 +{ }; + +template +struct disjunction + : + std::conditional_t< + bool(B1::value), + B1, + disjunction + > +{ }; + +struct sentinel_type +{ + static constexpr bool value = true; + using type = void; +}; + +template +struct bool_constant : std::integral_constant +{ }; + +// Implementation of std::remove_cvref for use with pre-C++20 compilers. +// +template +struct remove_cvref +{ + using type = std::remove_cv_t>; +}; + +template +using remove_cvref_t = typename remove_cvref::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 +using is_trivially_copyable = std::is_trivial; + +template +using is_trivially_copy_constructible = std::has_trivial_copy_constructor; + +template +using is_trivially_destructible = std::has_trivial_destructor; + #elif (__GNUC__ < 5) +template +using is_trivially_copyable = std::is_trivial; + +template +using is_trivially_copy_constructible = std::has_trivial_copy_constructor; + +template +using is_trivially_destructible = std::is_trivially_destructible; + #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 +using is_trivially_move_constructible = std::is_trivially_move_constructible; + #else +template +using is_trivially_move_constructible = is_trivially_copyable; + #endif + + #if defined(__cpp_lib_trivially_relocatable) +using std::is_trivially_relocatable; + #elif defined(__has_builtin) + #if __has_builtin(__is_trivially_relocatable) +template +struct is_trivially_relocatable : std::bool_constant<__is_trivially_relocatable(T)> { }; + #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE + #else +template +struct is_trivially_relocatable : is_trivially_copyable { }; + #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE + #endif + #else +template +struct is_trivially_relocatable : is_trivially_copyable { }; + #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 + #include + + +namespace stdx { + +namespace detail { + +template +using use_static_cast = bool_constant< + ((std::is_integral::value || std::is_enum::value) + && (std::is_integral::value || std::is_enum::value)) + || (std::is_same::value && std::is_copy_constructible::value) + >; + +template +using is_integral_ptr_t = bool_constant< + std::is_same::value + || std::is_same::value + >; + +template +using use_reinterpret_cast = bool_constant< + !std::is_same::value + && (( + std::is_pointer::value + && std::is_pointer::value + && std::is_convertible::value + ) + || (std::is_pointer::value && is_integral_ptr_t::value) + || (std::is_pointer::value && is_integral_ptr_t::value) + ) + >; + + #if defined(STDX_GCC_COMPILER) +template +using use_union_type_punning = bool_constant< + !use_static_cast::value + && !use_reinterpret_cast::value + && !std::is_array::value + && !std::is_array::value + >; + +template +union bit_cast_union +{ + From from; + To to; +}; + #else +template +using use_union_type_punning = std::false_type; + #endif + +template +using can_bit_cast = bool_constant< + (sizeof(To) == sizeof(From)) + && is_trivially_copyable::value + && is_trivially_copyable::value + >; + +} // end namespace detail + +template < + class To, + class From, + class = std::enable_if_t< + detail::can_bit_cast::value + && detail::use_static_cast::value + > + > +constexpr To bit_cast(const From& from) noexcept +{ + return static_cast(from); +} + +template < + class To, + class From, + class = std::enable_if_t< + detail::can_bit_cast::value + && detail::use_reinterpret_cast::value + >, + int = 0 + > +constexpr To bit_cast(const From& from) noexcept +{ + return reinterpret_cast(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::value + && detail::use_union_type_punning::value + >, + class = void + > +constexpr To bit_cast(const From& from) noexcept +{ + return detail::bit_cast_union{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::value + && !detail::use_static_cast::value + && !detail::use_reinterpret_cast::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::value + && !detail::use_static_cast::value + && !detail::use_reinterpret_cast::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 +struct is_bit_castable + : + bool_constant< + (sizeof(To) == sizeof(From)) + && is_trivially_copyable::value + && is_trivially_copyable::value + > +{ }; + +} // end namespace stdx + +#endif + + + +#ifndef STDX_INTRUSIVE_POINTER_HPP + #define STDX_INTRUSIVE_POINTER_HPP + + + #include + #include + #include + +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, + 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& shared_reference_count() noexcept + { + return m_reference_count; + } + +private: + + std::atomic m_reference_count; +}; + +struct default_intrusive_reference_count +{ + template + std::atomic& operator()(Pointer p) const noexcept + { + return p->shared_reference_count(); + } +}; + +namespace detail { + +template +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 + friend class reference_count_base; + + constexpr intrusive_ptr_base() = default; + + constexpr intrusive_ptr_base(pointer p) noexcept + : m_impl(p) + { } + + template + constexpr explicit intrusive_ptr_base( + RefCountAccessorForwardingReference&& f, + std::enable_if_t< + std::is_constructible< + RefCountAccessor, + RefCountAccessorForwardingReference&& + >::value + >* = nullptr + ) + : m_impl(std::forward(f)) + { } + + template + explicit intrusive_ptr_base( + pointer ptr, + RefCountAccessorForwardingReference&& f, + std::enable_if_t< + std::is_constructible< + RefCountAccessor, + RefCountAccessorForwardingReference&& + >::value + >* = nullptr + ) + : m_impl(ptr, std::forward(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(f), + std::forward(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(f), + std::forward(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(f), + std::forward(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::value + > + > + constexpr explicit impl(RefCountAccess&& f) + : RefCountAccessor(std::forward(f)) + { } + + template < + class RefCountAccess, + class = std::enable_if_t< + std::is_constructible::value + > + > + impl(pointer ptr, RefCountAccess&& f) + : + PointerImplementation(ptr), + RefCountAccessor(std::forward(f)) + { } + + template < + class RefCountAccess, + class D, + class = std::enable_if_t< + std::is_constructible::value + && std::is_constructible::value + > + > + constexpr impl(RefCountAccess&& f, D&& d) + : + RefCountAccessor(std::forward(f)), + Deleter(std::forward(d)) + { } + + template < + class RefCountAccess, + class D, + class = std::enable_if_t< + std::is_constructible::value + && std::is_constructible::value + > + > + impl(pointer ptr, RefCountAccess&& f, D&& d) + : + PointerImplementation(ptr), + RefCountAccessor(std::forward(f)), + Deleter(std::forward(d)) + { } + + impl(const impl&) = default; + impl& operator = (const impl&) = default; + impl(impl&&) = default; + impl& operator = (impl&&) = default; + + Deleter& get_deleter() noexcept + { + return static_cast(*this); + } + + const Deleter& get_deleter() const noexcept + { + return static_cast(*this); + } + + void assign(pointer ptr) noexcept + { + static_cast(*this).assign(ptr); + } + + void assign(std::nullptr_t) noexcept + { + static_cast(*this).assign(nullptr); + } + + void swap(impl& other) + { + std::swap( + static_cast(*this), + static_cast(other) + ); + + std::swap(static_cast(*this), static_cast(other)); + std::swap( + static_cast(this->get_deleter()), + static_cast(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(m_impl).shared_object; + } + + constexpr pointer ptr() const noexcept + { + return static_cast(m_impl).shared_object; + } + + RefCountAccessor& ref_count_func() noexcept + { + return static_cast(m_impl); + } + + const RefCountAccessor& ref_count_func() const noexcept + { + return static_cast(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{ + 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 + void invoke_deleter(pointer p, WeakReferenceCountDescriptor* d) + { + maybe_delete_shared_object(p, m_impl.get_deleter(), d); + } + + template + 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 + > +{ + using base_type = detail::intrusive_ptr_base< + T, + RefCountAccessor, + Deleter, + Pointer, + detail::pointer_wrapper + >; + +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 + constexpr intrusive_ptr(std::nullptr_t, RefCountAccess&& f) + : base_type(std::forward(f)) + { } + + template + constexpr intrusive_ptr(std::nullptr_t, RefCountAccess&& f, D&& d) + : base_type(std::forward(f), std::forward(d)) + { } + + constexpr explicit intrusive_ptr(Pointer ptr) noexcept + : base_type(ptr) + { + // reference count must initially be >= 1 + } + + template + intrusive_ptr(Pointer ptr, RefCountAccess&& f) noexcept + : base_type(ptr, std::forward(f)) + { + // reference count must initially be >= 1 + } + + template + intrusive_ptr(Pointer ptr, RefCountAccess&& f, D&& d) noexcept + : + base_type( + ptr, + std::forward(f), + std::forward(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::value> + > + intrusive_ptr(const intrusive_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(*this) = static_cast(rhs); + return *this; + } + + // Move assignment + // + intrusive_ptr& operator = (intrusive_ptr&& rhs) noexcept + { + if (this != std::addressof(rhs)) + { + this->decrement_shared_reference_count(); + static_cast(*this) = std::move(static_cast(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(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 + friend class intrusive_ptr; +}; + +// -------------- Global equality operators +// +template +bool operator == ( + const intrusive_ptr& lhs, + const intrusive_ptr& rhs + ) noexcept +{ + return lhs.get() == rhs.get(); +} + +template +bool operator != ( + const intrusive_ptr& lhs, + const intrusive_ptr& rhs + ) noexcept +{ + return !(lhs == rhs); +} + +template +bool operator < ( + const intrusive_ptr& lhs, + const intrusive_ptr& rhs + ) noexcept +{ + using pointer1 = typename intrusive_ptr::pointer; + using pointer2 = typename intrusive_ptr::pointer; + using common_type = typename std::common_type::type; + + return std::less{}(lhs.get(), rhs.get()); +} + +template +bool operator > ( + const intrusive_ptr& lhs, + const intrusive_ptr& rhs + ) noexcept +{ + return rhs < lhs; +} + +template +bool operator <= ( + const intrusive_ptr& lhs, + const intrusive_ptr& rhs + ) noexcept +{ + return !(rhs < lhs); +} + +template +bool operator >= ( + const intrusive_ptr& lhs, + const intrusive_ptr& rhs + ) noexcept +{ + return !(lhs < rhs); +} + +template +bool operator == (const intrusive_ptr& lhs, std::nullptr_t) noexcept +{ + return !lhs; +} + +template +bool operator == (std::nullptr_t, const intrusive_ptr& rhs) noexcept +{ + return !rhs; +} + +template +bool operator != (const intrusive_ptr& lhs, std::nullptr_t) noexcept +{ + return bool(lhs); +} + +template +bool operator != (std::nullptr_t, const intrusive_ptr& rhs) noexcept +{ + return bool(rhs); +} + +template +bool operator < (const intrusive_ptr& lhs, std::nullptr_t) noexcept +{ + using pointer = typename intrusive_ptr::pointer; + return std::less{}(lhs.get(), nullptr); +} + +template +bool operator < (std::nullptr_t, const intrusive_ptr& rhs) noexcept +{ + using pointer = typename intrusive_ptr::pointer; + return std::less{}(nullptr, rhs.get()); +} + +template +bool operator > (const intrusive_ptr& lhs, std::nullptr_t) noexcept +{ + return (nullptr < lhs); +} + +template +bool operator > (std::nullptr_t lhs, const intrusive_ptr& rhs) noexcept +{ + return (rhs < nullptr); +} + +template +bool operator <= (const intrusive_ptr& lhs, std::nullptr_t rhs) noexcept +{ + return !(nullptr < lhs); +} + +template +bool operator <= (std::nullptr_t lhs, const intrusive_ptr& rhs) noexcept +{ + return !(rhs < nullptr); +} + +template +bool operator >= (const intrusive_ptr& lhs, std::nullptr_t rhs) noexcept +{ + return !(lhs < nullptr); +} + +template +bool operator >= (std::nullptr_t lhs, const intrusive_ptr& rhs) noexcept +{ + return !(nullptr < rhs); +} + +template +void swap(intrusive_ptr& lhs, intrusive_ptr& rhs) noexcept +{ + lhs.swap(rhs); +} + + #ifdef STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE +template +struct is_trivially_relocatable> : std::true_type +{ }; + #endif + +} // end namespace stdx + +#endif // include guard + + + +#ifndef STDX_STRING_REF_HPP + #define STDX_STRING_REF_HPP + + #include + #include + #include + +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 + 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; + + 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 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(this) + header_size(); + } + + const char* data() const noexcept + { + return reinterpret_cast(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(::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(this->context); + } + + string_arena_base* get_arena() noexcept + { + return static_cast(this->context); + } + + static string_ref::state_type copy_construct(const string_ref& base) noexcept + { + const shared_string_ref& s = static_cast(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(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(base); + string_arena* a = static_cast(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 + 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(this) + header_size(); + } + + const char* data() const noexcept + { + return reinterpret_cast(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 + explicit shared_string_ref(allocated_string_arena* a) noexcept + : + string_ref{ + a->begin(), + a->end(), + string_ref::resource_management{ + ©_construct, + &move_construct, + &allocator_destroy + }, + a + } + { } + + template + 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; + using arena_type = allocated_string_arena; + + 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 + static void allocator_destroy(string_ref& base) noexcept + { + using arena_type = allocated_string_arena; + + shared_string_ref& s = static_cast(base); + arena_type* a = static_cast(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(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 + shared_string_ref(const Allocator& alloc, const char* beg) + : string_ref{allocate_string_ref(alloc, beg, detail::cstring_null_scan(beg) - beg)} + { } + + template + 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 + + +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 +constexpr T* launder(T* p) noexcept +{ + return __builtin_launder(p); +} + #endif + #endif + #endif + + #if !defined(STDX_HAVE_NATIVE_LAUNDER) +template +constexpr T* launder(T* p) noexcept +{ + return p; +} + #endif + +} // end namespace stdx + +#endif + + + +#ifndef STDX_ERROR_HPP + #define STDX_ERROR_HPP + + #include + #include + #include + #include + #include + + +namespace stdx { + +class error; + +namespace detail { + +template +struct error_ctor_args; + +} // end namespace detail + +} // end namespace stdx + +namespace stdx_adl { + +namespace detail { + +template +struct can_use_adl_to_call_make_error : std::false_type +{ }; + +inline void make_error() noexcept { } + +template +struct can_use_adl_to_call_make_error< + stdx::detail::error_ctor_args, + std::enable_if_t< + std::is_same< + decltype(make_error(std::declval()...)), + stdx::error + >::value + > + > + : std::true_type +{ }; + +template +constexpr auto construct_error_from_adl(Args&&... args) noexcept( + noexcept(make_error(std::forward(args)...)) + ) +{ + return make_error(std::forward(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 +struct error_traits +{ }; + +namespace detail { + +template +struct is_convertible_from_exception_using_traits : std::false_type +{ }; + +template +struct is_convertible_from_exception_using_traits< + E, + std::enable_if_t< + std::is_convertible< + decltype(error_traits::from_exception(std::declval())), + E + >::value + > + > + : std::true_type +{ }; + +template +struct is_convertible_to_exception_using_traits : std::false_type +{ }; + +template +struct is_convertible_to_exception_using_traits< + E, + std::enable_if_t< + std::is_convertible< + decltype(error_traits::to_exception(std::declval())), + std::exception_ptr + >::value + > + > + : std::true_type +{ }; + +template +E from_exception_impl(std::exception_ptr e, std::is_convertible) noexcept +{ + return e; +} + +template +E from_exception_impl( + std::exception_ptr e, + is_convertible_from_exception_using_traits + ) noexcept +{ + return error_traits::from_exception(std::move(e)); +} + +void from_exception_impl(std::exception_ptr, sentinel_type) = delete; + +template +std::exception_ptr to_exception_impl(E&& e, std::is_convertible) noexcept +{ + return std::forward(e); +} + +template +std::exception_ptr to_exception_impl( + E&& e, + is_convertible_to_exception_using_traits + ) noexcept +{ + return error_traits::to_exception(std::forward(e)); +} + +template +void to_exception_impl(const E&, sentinel_type) = delete; + +} // end namespace detail + +template +E from_exception(std::exception_ptr e) noexcept +{ + return detail::from_exception_impl( + std::move(e), + disjunction< + std::is_convertible, + detail::is_convertible_from_exception_using_traits, + sentinel_type + >{} + ); +} + +template +std::exception_ptr to_exception(E&& e) noexcept +{ + return detail::to_exception_impl( + std::forward(e), + disjunction< + std::is_convertible, std::exception_ptr>, + detail::is_convertible_to_exception_using_traits>, + 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 +struct error_value; + +namespace detail { + +template +struct is_error_value : std::false_type +{ }; + +template +struct is_error_value> : std::true_type +{ }; + +} // end namespace detail + +struct error_resource_management +{ + using copy_constructor = error_value(*)(const error&); + using move_constructor = error_value(*)(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::value + > + > + constexpr dependent_type_t> 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::value + && std::is_convertible::value + > + > + constexpr dependent_type_t, error_value<>> move(E&& e) const + { + return m_resource_management.move ? + m_resource_management.move(static_cast(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 +struct error_type_is_erasable + : + bool_constant< + is_trivially_relocatable::value + && (sizeof(T) <= sizeof(ErasedType)) + && (alignof(T) <= alignof(ErasedType)) + > +{ }; + +template +struct error_type_is_erasable : std::false_type +{ }; + +template +struct error_type_is_erasable : std::false_type +{ }; + +template +using can_use_static_cast = bool_constant< + std::is_integral::value + || std::is_enum::value + >; + +struct erased_error +{ + using integral_type = std::intptr_t; + using storage_type = std::aligned_storage_t; + + constexpr erased_error() noexcept : code{} + { } + + template < + class T, + class = std::enable_if_t< + error_type_is_erasable::value + && can_use_static_cast::value + > + > + constexpr erased_error(T value) noexcept + : code{static_cast(value)} + { } + + template < + class T, + class = std::enable_if_t< + error_type_is_erasable::value + && !can_use_static_cast::value + && is_bit_castable::value + >, + class = void + > + constexpr erased_error(T value) noexcept + : code{bit_cast(value)} + { } + + template < + class T, + class = std::enable_if_t< + error_type_is_erasable::value + && !can_use_static_cast::value + && !is_bit_castable::value + >, + int = 0 + > + erased_error(T value) noexcept(std::is_nothrow_move_constructible::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::value + && can_use_static_cast::value + > + > +constexpr T error_cast_impl(erased_error e) noexcept +{ + return static_cast(e.code); +} + +template < + class T, + class = std::enable_if_t< + error_type_is_erasable::value + && !can_use_static_cast::value + && is_bit_castable::value + >, + class = void + > +constexpr T error_cast_impl(erased_error e) noexcept +{ + return bit_cast(e.code); +} + +template < + class T, + class = std::enable_if_t< + error_type_is_erasable::value + && !can_use_static_cast::value + && !is_bit_castable::value + > + > +constexpr T error_cast_impl(erased_error&& e) noexcept( + std::is_nothrow_move_constructible::value + ) +{ + return std::move(*stdx::launder(reinterpret_cast(&e.storage))); +} + +template < + class T, + class = std::enable_if_t< + error_type_is_erasable::value + && !can_use_static_cast::value + && !is_bit_castable::value + > + > +constexpr T error_cast_impl(const erased_error& e) noexcept( + std::is_nothrow_copy_constructible::value + ) +{ + return *stdx::launder(reinterpret_cast(&e.storage)); +} + +} // end namespace detail + +template +struct error_value +{ + using value_type = T; + + constexpr error_value(const T& v) noexcept( + std::is_nothrow_copy_constructible::value + ) + : m_value(v) + { } + + constexpr error_value(T&& v) noexcept( + std::is_nothrow_move_constructible::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(m_value); + } + + STDX_LEGACY_CONSTEXPR T&& value() && noexcept + { + return static_cast(m_value); + } + + T m_value; +}; + +template <> +struct error_value +{ + template < + class T, + class = std::enable_if_t< + !std::is_same, error_value>::value + && !detail::is_error_value>::value + && std::is_constructible::value + > + > + constexpr error_value(T&& v) noexcept( + std::is_nothrow_constructible::value + ) + : m_value(std::forward(v)) + { } + + template < + class T, + class = std::enable_if_t< + std::is_constructible::value + > + > + constexpr error_value(const error_value& v) noexcept( + std::is_nothrow_constructible::value + ) + : error_value(v.value()) + { } + + template < + class T, + class = std::enable_if_t< + std::is_constructible::value + > + > + constexpr error_value(error_value&& v) noexcept( + std::is_nothrow_constructible::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 +struct error_ctor_args {}; + +template +struct can_construct_error_from_adl +{ }; + +template +struct can_construct_error_from_adl, error_ctor_args> + : stdx_adl::detail::can_use_adl_to_call_make_error> +{ }; + +template <> +struct can_construct_error_from_adl, error_ctor_args<>> : std::false_type +{ }; + +template +struct can_construct_error_from_adl, error_ctor_args> + : std::false_type +{ }; + +template +struct can_construct_error_from_adl< + error_ctor_args, + error_ctor_args, ErrorDomain> + > + : std::false_type +{ }; + +template +struct can_construct_error_from_adl< + error_ctor_args, + error_ctor_args, ErrorDomain> + > + : std::false_type +{ }; + +template +struct can_construct_error_from_adl< + error_ctor_args, + error_ctor_args, ErrorDomain> + > + : std::false_type +{ }; + +template +struct is_convertible_to_error_using_traits : std::false_type +{ }; + +template <> +struct is_convertible_to_error_using_traits> + : std::false_type +{ }; + +template +struct is_convertible_to_error_using_traits< + error_ctor_args, + std::enable_if_t< + std::is_same< + decltype(error_traits::to_error(std::declval())), + error + >::value + > + > + : std::true_type +{ }; + +template +constexpr auto construct_error_impl( + is_convertible_to_error_using_traits>, + Args&&... args + ) noexcept(noexcept(error_traits::to_error(std::declval()...))) +{ + return error_traits::to_error(std::forward(args)...); +} + +template +constexpr auto construct_error_impl( + can_construct_error_from_adl, + Args&&... args + ) noexcept(noexcept(stdx_adl::detail::construct_error_from_adl(std::declval()...))) +{ + return stdx_adl::detail::construct_error_from_adl(std::forward(args)...); +} + +struct cannot_construct_error +{ + static constexpr bool value = false; + using type = void; +}; + +template +void construct_error_impl(cannot_construct_error, Args&&...) = delete; + +template +using construct_error_disjunction_impl_t = disjunction< + is_convertible_to_error_using_traits, + can_construct_error_from_adl, + cannot_construct_error + >; + +template +using construct_error_disjunction_t = construct_error_disjunction_impl_t< + error_ctor_args, + error_ctor_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::value + > + > + constexpr error(const error_value& 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::value + > + > + constexpr error(error_value&& v, const error_domain& d) noexcept + : m_domain(&d), m_value(static_cast(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::value + > + > + constexpr error(A&& a, Args&&... args) noexcept( + noexcept( + detail::construct_error_impl( + std::declval>(), + std::forward(a), + std::forward(args)... + ) + ) + ) + : + error( + detail::construct_error_impl( + detail::construct_error_disjunction_t{}, + std::forward(a), + std::forward(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(std::declval())) + > + > +constexpr T error_cast(const error& e) noexcept( + noexcept(detail::error_cast_impl(std::declval())) + ) +{ + return detail::error_cast_impl(detail::error_cref_access{e}.ref()); +} + +template < + class T, + class = void_t(std::declval()))> + > +constexpr T error_cast(error&& e) noexcept( + noexcept(detail::error_cast_impl(std::declval())) + ) +{ + return detail::error_cast_impl(detail::error_move_access{std::move(e)}.rvalue_ref()); +} + +namespace detail { + +struct default_error_constructors +{ + template + static error_value<> copy_constructor(const error& e) noexcept( + std::is_nothrow_copy_constructible::value + && std::is_nothrow_move_constructible::value + ) + { + T value = error_cast(e); + return error_value<>{std::move(value)}; + } + + template + static error_value<> move_constructor(error&& e) noexcept( + std::is_nothrow_move_constructible::value + ) + { + return error_value<>{error_cast(std::move(e))}; + } + + template + static void destructor(error& e) noexcept + { + detail::erased_error& value = error_ref_access{e}.ref(); + stdx::launder(reinterpret_cast(&value.storage))->~T(); + } + + template + constexpr static error_resource_management::copy_constructor copy() noexcept + { + return is_trivially_copy_constructible::value ? + nullptr : ©_constructor; + } + + template + constexpr static error_resource_management::move_constructor move() noexcept + { + return is_trivially_move_constructible::value ? + nullptr : &move_constructor; + } + + template + constexpr static error_resource_management::destructor destroy() noexcept + { + return is_trivially_destructible::value ? + nullptr : &destructor; + } +}; + +} // end namespace detail + +template +struct default_error_resource_management_t : error_resource_management +{ + constexpr default_error_resource_management_t() noexcept + : + error_resource_management{ + detail::default_error_constructors::copy(), + detail::default_error_constructors::move(), + detail::default_error_constructors::destroy() + } + {} +}; + + #if defined(STDX_VARIABLE_TEMPLATES) +template +inline constexpr default_error_resource_management_t default_error_resource_management {}; + #endif + +template <> +struct error_traits +{ + 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{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; + + friend struct error_traits; + +public: + + constexpr error_code_error_domain() noexcept + : + error_domain{ + {0x84e99cdcecae4443ULL, 0x9050179b713fd2afULL}, + default_error_resource_management_t{} + } + { } + + 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 +{ + 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 +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 ptr; +}; + +template +struct exception_ptr_wrapper_impl +{ + 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; + +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 : 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{} + } + { } + + 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(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{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 +{ + 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{e}}, + dynamic_exception_domain + }; + } +}; + +} // end namespace stdx + +namespace std { + +template<> +struct is_error_code_enum : std::true_type +{ }; + +} // end namespace std + +#endif + + + +#if __cplusplus >= 201703L + #include + #include + #include +#endif + +#include + +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(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(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(lhs) == error_cast(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(e)); +} + +// ---------- ErrorCodeErrorDomain +// +inline string_ref error_code_error_domain::message(const error& e) const noexcept +{ + assert(e.domain() == *this); + + auto ptr = error_cast(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(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(lhs); + auto ptr2 = error_cast(rhs); + if (ptr1 && ptr2) return ptr1->code == ptr2->code.default_error_condition(); + return false; + } + + if (rhs.domain() == generic_domain) + { + auto ptr1 = error_cast(lhs); + if (ptr1) return ptr1->code == error_cast(rhs); + } + + return false; +} + +inline stdx::error error_traits::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{static_cast(ec.default_error_condition().value())}, + generic_domain + }; + } + + return error{ + error_value{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(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(lhs).get(); + + if (rhs.domain() == *this) + { + std::exception_ptr eptr2 = error_cast(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(lhs); + + if (rhs.domain() == *this) + { + return code == error_cast(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(error_cast(e))) + }; +} + +} // end namespace stdx + + + diff --git a/include/libremidi/writer.cpp b/include/libremidi/writer.cpp index 7401b5e..984865c 100644 --- a/include/libremidi/writer.cpp +++ b/include/libremidi/writer.cpp @@ -26,106 +26,51 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if !defined(LIBREMIDI_HEADER_ONLY) #include #endif -#include +#include +#include #include #include -#include +#if !defined(__cpp_lib_bit_cast) +namespace std +{ +template +[[nodiscard]] +constexpr R bit_cast(const T& v) noexcept +{ + union + { + R res; + T init; + } u{.init = v}; + return u.res; +} +} +#endif namespace libremidi { namespace util { -static LIBREMIDI_INLINE std::ostream& write_uint16_be(std::ostream& out, uint16_t value) +template +static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) { - union + static_assert( + std::endian::native == std::endian::big || std::endian::native == std::endian::little); + if constexpr (std::endian::native == std::endian::big) { - uint8_t bytes[2]; - uint16_t v; - } data; - data.v = value; - out << data.bytes[1]; - out << data.bytes[0]; - return out; -} - -[[maybe_unused]] static LIBREMIDI_INLINE std::ostream& -write_int16_be(std::ostream& out, int16_t value) -{ - union + out << value; + } + else { - uint8_t bytes[2]; - int16_t v; - } data; - data.v = value; - out << data.bytes[1]; - out << data.bytes[0]; - return out; -} - -static LIBREMIDI_INLINE std::ostream& write_uint32_be(std::ostream& out, uint32_t value) -{ - union - { - uint8_t bytes[4]; - uint32_t v; - } data; - data.v = value; - out << data.bytes[3]; - out << data.bytes[2]; - out << data.bytes[1]; - out << data.bytes[0]; - return out; -} - -[[maybe_unused]] static LIBREMIDI_INLINE std::ostream& -write_int32_be(std::ostream& out, int32_t value) -{ - union - { - uint8_t bytes[4]; - int32_t v; - } data; - data.v = value; - out << data.bytes[3]; - out << data.bytes[2]; - out << data.bytes[1]; - out << data.bytes[0]; - return out; -} - -[[maybe_unused]] static LIBREMIDI_INLINE std::ostream& -write_float_be(std::ostream& out, float value) -{ - union - { - uint8_t bytes[4]; - float v; - } data; - data.v = value; - out << data.bytes[3]; - out << data.bytes[2]; - out << data.bytes[1]; - out << data.bytes[0]; - return out; -} - -[[maybe_unused]] static LIBREMIDI_INLINE std::ostream& -write_double_be(std::ostream& out, double value) -{ - union - { - uint8_t bytes[8]; - double v; - } data; - data.v = value; - out << data.bytes[7]; - out << data.bytes[6]; - out << data.bytes[5]; - out << data.bytes[4]; - out << data.bytes[3]; - out << data.bytes[2]; - out << data.bytes[1]; - out << data.bytes[0]; + static constexpr auto N = sizeof(value); + struct storage + { + uint8_t bytes[N]; + }; + auto data = std::bit_cast(value); + std::reverse(data.bytes, data.bytes + N); + out.write(reinterpret_cast(data.bytes), static_cast(N)); + } return out; } @@ -140,7 +85,7 @@ static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector< bytes[1] = static_cast((aValue >> 21) & 0x7F); // next largest 7 bits bytes[2] = static_cast((aValue >> 14) & 0x7F); bytes[3] = static_cast((aValue >> 7) & 0x7F); - bytes[4] = static_cast((aValue)&0x7F); // least significant 7 bits + bytes[4] = static_cast((aValue) & 0x7F); // least significant 7 bits int start = 0; while (start < 5 && bytes[start] == 0) @@ -195,14 +140,11 @@ LIBREMIDI_INLINE void writer::write(std::ostream& out) const { // MIDI File Header - out << 'M'; - out << 'T'; - out << 'h'; - out << 'd'; - util::write_uint32_be(out, 6); - util::write_uint16_be(out, (tracks.size() == 1) ? 0 : 1); - util::write_uint16_be(out, static_cast(tracks.size())); - util::write_uint16_be(out, ticksPerQuarterNote); + out.write("MThd", 4); + util::write_be(out, 6); + util::write_be(out, (tracks.size() == 1) ? 0 : 1); + util::write_be(out, static_cast(tracks.size())); + util::write_be(out, ticksPerQuarterNote); std::vector trackRawData; for (const auto& event_list : tracks) @@ -261,11 +203,8 @@ void writer::write(std::ostream& out) const } // Write the track ID marker "MTrk": - out << 'M'; - out << 'T'; - out << 'r'; - out << 'k'; - util::write_uint32_be(out, static_cast(trackRawData.size())); + out.write("MTrk", 4); + util::write_be(out, static_cast(trackRawData.size())); out.write( reinterpret_cast(trackRawData.data()), static_cast(trackRawData.size())); diff --git a/include/libremidi/writer.hpp b/include/libremidi/writer.hpp index 7ea90bd..489df86 100644 --- a/include/libremidi/writer.hpp +++ b/include/libremidi/writer.hpp @@ -27,6 +27,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include namespace libremidi @@ -34,7 +35,7 @@ namespace libremidi struct LIBREMIDI_EXPORT writer { public: - int ticksPerQuarterNote{120}; + uint16_t ticksPerQuarterNote{120}; std::vector tracks; void add_event(int tick, int track, const message& m); diff --git a/include/raygui/dark/style_dark.h b/include/raygui/dark/style_dark.h index 674b125..7986ca8 100644 --- a/include/raygui/dark/style_dark.h +++ b/include/raygui/dark/style_dark.h @@ -15,29 +15,29 @@ // Custom style name: Dark static const GuiStyleProp darkStyleProps[DARK_STYLE_PROPS_COUNT] = { - { 0, 0, 0x878787ff }, // DEFAULT_BORDER_COLOR_NORMAL - { 0, 1, 0x2c2c2cff }, // DEFAULT_BASE_COLOR_NORMAL - { 0, 2, 0xc3c3c3ff }, // DEFAULT_TEXT_COLOR_NORMAL - { 0, 3, 0xe1e1e1ff }, // DEFAULT_BORDER_COLOR_FOCUSED - { 0, 4, 0x848484ff }, // DEFAULT_BASE_COLOR_FOCUSED - { 0, 5, 0x181818ff }, // DEFAULT_TEXT_COLOR_FOCUSED - { 0, 6, 0x000000ff }, // DEFAULT_BORDER_COLOR_PRESSED - { 0, 7, 0xefefefff }, // DEFAULT_BASE_COLOR_PRESSED - { 0, 8, 0x202020ff }, // DEFAULT_TEXT_COLOR_PRESSED - { 0, 9, 0x6a6a6aff }, // DEFAULT_BORDER_COLOR_DISABLED - { 0, 10, 0x818181ff }, // DEFAULT_BASE_COLOR_DISABLED - { 0, 11, 0x606060ff }, // DEFAULT_TEXT_COLOR_DISABLED - { 0, 16, 0x00000010 }, // DEFAULT_TEXT_SIZE - { 0, 17, 0x00000000 }, // DEFAULT_TEXT_SPACING - { 0, 18, 0x9d9d9dff }, // DEFAULT_LINE_COLOR - { 0, 19, 0x3c3c3cff }, // DEFAULT_BACKGROUND_COLOR - { 0, 20, 0x00000018 }, // DEFAULT_TEXT_LINE_SPACING - { 1, 5, 0xf7f7f7ff }, // LABEL_TEXT_COLOR_FOCUSED - { 1, 8, 0x898989ff }, // LABEL_TEXT_COLOR_PRESSED - { 4, 5, 0xb0b0b0ff }, // SLIDER_TEXT_COLOR_FOCUSED - { 5, 5, 0x848484ff }, // PROGRESSBAR_TEXT_COLOR_FOCUSED - { 9, 5, 0xf5f5f5ff }, // TEXTBOX_TEXT_COLOR_FOCUSED - { 10, 5, 0xf6f6f6ff }, // VALUEBOX_TEXT_COLOR_FOCUSED + { 0, 0, (long int)0x878787ff }, // DEFAULT_BORDER_COLOR_NORMAL + { 0, 1, (long int)0x2c2c2cff }, // DEFAULT_BASE_COLOR_NORMAL + { 0, 2, (long int)0xc3c3c3ff }, // DEFAULT_TEXT_COLOR_NORMAL + { 0, 3, (long int)0xe1e1e1ff }, // DEFAULT_BORDER_COLOR_FOCUSED + { 0, 4, (long int)0x848484ff }, // DEFAULT_BASE_COLOR_FOCUSED + { 0, 5, (long int)0x181818ff }, // DEFAULT_TEXT_COLOR_FOCUSED + { 0, 6, (long int)0x000000ff }, // DEFAULT_BORDER_COLOR_PRESSED + { 0, 7, (long int)0xefefefff }, // DEFAULT_BASE_COLOR_PRESSED + { 0, 8, (long int)0x202020ff }, // DEFAULT_TEXT_COLOR_PRESSED + { 0, 9, (long int)0x6a6a6aff }, // DEFAULT_BORDER_COLOR_DISABLED + { 0, 10, (long int)0x818181ff }, // DEFAULT_BASE_COLOR_DISABLED + { 0, 11, (long int)0x606060ff }, // DEFAULT_TEXT_COLOR_DISABLED + { 0, 16, (long int)0x00000010 }, // DEFAULT_TEXT_SIZE + { 0, 17, (long int)0x00000000 }, // DEFAULT_TEXT_SPACING + { 0, 18, (long int)0x9d9d9dff }, // DEFAULT_LINE_COLOR + { 0, 19, (long int)0x3c3c3cff }, // DEFAULT_BACKGROUND_COLOR + { 0, 20, (long int)0x00000018 }, // DEFAULT_TEXT_LINE_SPACING + { 1, 5, (long int)0xf7f7f7ff }, // LABEL_TEXT_COLOR_FOCUSED + { 1, 8, (long int)0x898989ff }, // LABEL_TEXT_COLOR_PRESSED + { 4, 5, (long int)0xb0b0b0ff }, // SLIDER_TEXT_COLOR_FOCUSED + { 5, 5, (long int)0x848484ff }, // PROGRESSBAR_TEXT_COLOR_FOCUSED + { 9, 5, (long int)0xf5f5f5ff }, // TEXTBOX_TEXT_COLOR_FOCUSED + { 10, 5, (long int)0xf6f6f6ff }, // VALUEBOX_TEXT_COLOR_FOCUSED }; // WARNING: This style uses a custom font: "PixelOperator.ttf" (size: 16, spacing: 0) diff --git a/lib/libremidi.lib b/lib/libremidi.lib new file mode 100644 index 0000000..aa21c4f Binary files /dev/null and b/lib/libremidi.lib differ diff --git a/lib/raylib.dll b/raylib.dll similarity index 100% rename from lib/raylib.dll rename to raylib.dll diff --git a/src/midi.h b/src/midi.h index a681742..65454bb 100644 --- a/src/midi.h +++ b/src/midi.h @@ -5,6 +5,7 @@ #include "libremidi/observer_configuration.hpp" #include "raylib.h" +#include #include #include #include "datatypes.h" @@ -23,11 +24,7 @@ class Midi { public: Midi() { - int i = 0; - for(const libremidi::output_port& port : obs.get_output_ports()){ - midi_out_ports.push_back({port, i, false}); - i++; - } + enumerate(); } std::vector midiNotes = { @@ -60,6 +57,15 @@ class Midi { float timer; + void enumerate(){ + std::cout << (int)obs.get_current_api() << std::endl; + int i = 0; + for(const libremidi::output_port& port : obs.get_output_ports()){ + midi_out_ports.push_back({port, i, false}); + i++; + } + } + void open_midi_out(MidiOutPort *out_port){ if(!midi_outs.contains(out_port->number)){ auto out = std::make_shared(); diff --git a/src/ui.h b/src/ui.h index bf1799a..b649507 100644 --- a/src/ui.h +++ b/src/ui.h @@ -44,9 +44,11 @@ class AppUI{ int s_midi = s_bpm + 55; GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); + if(GuiButton((Rectangle){20, (float)s_midi, 60, 30}, "REload")) sm->midi.enumerate(); + int i = 0; for(auto& mo : sm->midi.midi_out_ports){ - GuiCheckBox((Rectangle){20, (float)s_midi + 25 + i*20.0f, 20, 20}, mo.port.port_name.c_str(), &mo.enabled); + GuiCheckBox((Rectangle){20, (float)s_midi + 25 + i*20.0f, 20, 20}, mo.port.port_name.substr(0,32).c_str(), &mo.enabled); i++; } diff --git a/viseq.exe b/viseq.exe new file mode 100644 index 0000000..9cbe83e Binary files /dev/null and b/viseq.exe differ