diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..06baf60 --- /dev/null +++ b/.clangd @@ -0,0 +1,3 @@ +CompileFlags: + Add: [-I/home/sebastian/code/viseq/include, -std=c++23] + diff --git a/Makefile b/Makefile index 343962e..84f779b 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,21 @@ -CXX = g++ -CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 -CXXINC = -isysteminclude +.PHONY: clean -LDLIBS = -lraylib -lasound -llibremidi -pthread -LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib +CXX := g++ +CXXFLAGS := -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 +CXXINC := -Iinclude -isysteminclude -hdr_files = ${wildcard src/*.h} +LDLIBS := -lraylib -lasound -llibremidi -pthread +LDFLAGS := -Llib -L/usr/lib -L/usr/local/lib -src_files = ${wildcard src/*.cpp} -#src_files += ${wildcard include/libremidi/*.cpp} +SRCS := $(wildcard src/*.cpp) +OBJECTS := $(patsubst src/%.cpp,build/%.o, $(SRCS)) -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) +viseq: $(OBJECTS) + $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) $(LDLIBS) -$(hdr_files): %.h : %_hdr - $(NOECHO) $(NOOP) - -%_src: - $(NOECHO) $(NOOP) - -%_hdr: - $(NOECHO) $(NOOP) +$(OBJECTS): build/%.o : src/%.cpp + @mkdir -p build + $(CXX) -c $< -o $@ $(CXXFLAGS) $(CXXINC) clean: rm -f build/* diff --git a/include/libremidi/api-c.h b/include/libremidi/api-c.h index 8822253..510cd79 100644 --- a/include/libremidi/api-c.h +++ b/include/libremidi/api-c.h @@ -1,13 +1,15 @@ #pragma once +#ifndef LIBREMIDI_API_C_H +#define LIBREMIDI_API_C_H -#if __cplusplus +#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD) extern "C" { #endif //! MIDI API specifier arguments. //! To get information on which feature is supported by each back-end, check their backend file //! in e.g. backends/winmm.hpp, etc. -typedef enum libremidi_api +enum libremidi_api { UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */ @@ -22,6 +24,8 @@ typedef enum libremidi_api PIPEWIRE, /*!< PipeWire */ KEYBOARD, /*!< Computer keyboard input */ NETWORK, /*!< MIDI over IP */ + ANDROID_AMIDI, /*!< Android AMidi API */ + KDMAPI, /*!< OmniMIDI KDMAPI (Windows) */ // MIDI 2.0 APIs ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */ @@ -34,8 +38,11 @@ typedef enum libremidi_api PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */ DUMMY = 0xFFFF /*!< A compilable but non-functional API. */ -} libremidi_api; +}; -#if __cplusplus +typedef enum libremidi_api libremidi_api; + +#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD) } #endif +#endif diff --git a/include/libremidi/api.hpp b/include/libremidi/api.hpp index 44add71..2a957fc 100644 --- a/include/libremidi/api.hpp +++ b/include/libremidi/api.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { //! MIDI API specifier arguments. //! To get information on which feature is supported by each back-end, check their backend file @@ -64,6 +64,8 @@ inline constexpr libremidi::API default_api() noexcept return API::ALSA_SEQ; #elif defined(__emscripten__) return API::EMSCRIPTEN_WEBMIDI; +#elif defined(LIBREMIDI_ANDROID) + return API::ANDROID_AMIDI; #else return API::DUMMY; #endif diff --git a/include/libremidi/backends.hpp b/include/libremidi/backends.hpp index 18c42a1..5c445ed 100644 --- a/include/libremidi/backends.hpp +++ b/include/libremidi/backends.hpp @@ -29,7 +29,9 @@ #if defined(LIBREMIDI_JACK) #include - #include + #if defined(LIBREMIDI_HAS_JACK_GET_VERSION) + #include + #endif #endif #if defined(LIBREMIDI_PIPEWIRE) @@ -48,6 +50,10 @@ #include #endif +#if defined(LIBREMIDI_KDMAPI) + #include +#endif + #if defined(LIBREMIDI_WINUWP) #include #endif @@ -71,7 +77,11 @@ #include #endif -namespace libremidi +#if defined(LIBREMIDI_ANDROID) + #include +#endif + +NAMESPACE_LIBREMIDI { // The order here will control the order of the API search in // the constructor. @@ -83,7 +93,7 @@ constexpr auto make_tl(unused, Args...) namespace midi1 { -static constexpr auto available_backends = make_tl( +LIBREMIDI_STATIC constexpr auto available_backends = make_tl( 0 #if defined(LIBREMIDI_ALSA) , @@ -101,6 +111,10 @@ static constexpr auto available_backends = make_tl( , winmm_backend{} #endif +#if defined(LIBREMIDI_KDMAPI) + , + kdmapi_backend{} +#endif #if defined(LIBREMIDI_WINUWP) , winuwp_backend{} @@ -124,6 +138,10 @@ static constexpr auto available_backends = make_tl( #if defined(LIBREMIDI_NETWORK) , net::backend{} +#endif +#if defined(LIBREMIDI_ANDROID) + , + android::backend{} #endif , dummy_backend{}); @@ -149,7 +167,7 @@ auto for_backend(libremidi::API api, F&& f) namespace midi2 { -static constexpr auto available_backends = make_tl( +LIBREMIDI_STATIC constexpr auto available_backends = make_tl( 0 #if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP , @@ -167,7 +185,7 @@ static constexpr auto available_backends = make_tl( , net_ump::backend{} #endif -#if defined(LIBREMIDI_JACK) +#if defined(LIBREMIDI_JACK) && defined(LIBREMIDI_HAS_JACK_GET_VERSION) , jack_ump::backend{} #endif @@ -216,7 +234,7 @@ auto for_backend(libremidi::API api, F&& f) void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -231,7 +249,7 @@ void for_input_configuration(auto f, libremidi::input_api_configuration& api_con void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -246,7 +264,7 @@ void for_output_configuration(auto f, libremidi::output_api_configuration& api_c void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; diff --git a/include/libremidi/backends/alsa_raw.hpp b/include/libremidi/backends/alsa_raw.hpp index e80b3d2..275bb06 100644 --- a/include/libremidi/backends/alsa_raw.hpp +++ b/include/libremidi/backends/alsa_raw.hpp @@ -9,7 +9,7 @@ // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // Thanks Craig Stuart Sapp -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { struct backend { diff --git a/include/libremidi/backends/alsa_raw/config.hpp b/include/libremidi/backends/alsa_raw/config.hpp index ace8343..b94e1e2 100644 --- a/include/libremidi/backends/alsa_raw/config.hpp +++ b/include/libremidi/backends/alsa_raw/config.hpp @@ -10,12 +10,12 @@ #if __has_include() #include -namespace libremidi +NAMESPACE_LIBREMIDI { using poll_descriptors = pollfd; } #else -namespace libremidi +NAMESPACE_LIBREMIDI { struct poll_descriptors { @@ -26,7 +26,7 @@ struct poll_descriptors } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { /** diff --git a/include/libremidi/backends/alsa_raw/helpers.hpp b/include/libremidi/backends/alsa_raw/helpers.hpp index 3eb955f..c5483f5 100644 --- a/include/libremidi/backends/alsa_raw/helpers.hpp +++ b/include/libremidi/backends/alsa_raw/helpers.hpp @@ -20,9 +20,7 @@ // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // Thanks Craig Stuart Sapp -namespace libremidi -{ -namespace +NAMESPACE_LIBREMIDI { struct alsa_raw_port_id { @@ -47,12 +45,12 @@ inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3); -} namespace alsa_raw { struct alsa_raw_port_info { + // hw:1,2,0 std::string device; std::string card_name; std::string device_name; @@ -98,7 +96,6 @@ struct enumerator : handler{self} , configuration{self.configuration} { - } // 1: is an input / output @@ -121,8 +118,9 @@ struct enumerator else if (status < 0 && status != -ENXIO) { handler.libremidi_handle_error( - configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub) - + " : " + snd.strerror(status)); + configuration, + "Cannot get rawmidi information: " + device_identifier(card, device, sub) + " : " + + snd.strerror(status)); return status; } else @@ -208,7 +206,8 @@ inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name) if (status < 0) { self.handler.libremidi_handle_error( - self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status)); + self.configuration, + "cannot open control for card"s + name + " : " + snd.strerror(status)); } } diff --git a/include/libremidi/backends/alsa_raw/midi_in.hpp b/include/libremidi/backends/alsa_raw/midi_in.hpp index 22449b8..3ae0972 100644 --- a/include/libremidi/backends/alsa_raw/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw/midi_in.hpp @@ -10,7 +10,7 @@ #include #include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { class midi_in_impl : public midi1::in_api @@ -264,8 +264,7 @@ private: using namespace std::literals; libremidi_handle_error( - this->configuration, - "error starting MIDI input thread: "s + e.what()); + this->configuration, "error starting MIDI input thread: "s + e.what()); return e.code(); } return stdx::error{}; @@ -316,19 +315,21 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer, fds); + }}); } else { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); + }}); } } @@ -342,7 +343,7 @@ private: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/alsa_raw/midi_out.hpp b/include/libremidi/backends/alsa_raw/midi_out.hpp index 227b883..7746718 100644 --- a/include/libremidi/backends/alsa_raw/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw/midi_out.hpp @@ -8,7 +8,7 @@ #include #include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { class midi_out_impl final : public midi1::out_api @@ -44,8 +44,7 @@ public: int status = snd.rawmidi.open(NULL, &midiport_, portname, mode); if (status < 0) { - libremidi_handle_error( - this->configuration, "cannot open device."); + libremidi_handle_error(this->configuration, "cannot open device."); return from_errc(status); } return stdx::error{}; @@ -87,8 +86,7 @@ public: { if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0) { - libremidi_handle_error( - this->configuration, "cannot write message."); + libremidi_handle_error(this->configuration, "cannot write message."); return from_errc(err); } diff --git a/include/libremidi/backends/alsa_raw/observer.hpp b/include/libremidi/backends/alsa_raw/observer.hpp index f7559b5..d52fe8c 100644 --- a/include/libremidi/backends/alsa_raw/observer.hpp +++ b/include/libremidi/backends/alsa_raw/observer.hpp @@ -9,7 +9,7 @@ #endif #include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { template class observer_impl_base @@ -28,6 +28,21 @@ public: explicit observer_impl_base( observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} + { + } + + ~observer_impl_base() + { +#if LIBREMIDI_HAS_UDEV + m_termination_event.notify(); + + if (m_thread.joinable()) + m_thread.join(); +#endif + } + + // Must be called for child classes due to virtual function calls + void finish_init() { if (!configuration.has_callbacks()) return; @@ -48,16 +63,6 @@ public: #endif } - ~observer_impl_base() - { -#if LIBREMIDI_HAS_UDEV - m_termination_event.notify(); - - if (m_thread.joinable()) - m_thread.join(); -#endif - } - std::vector get_input_ports() const noexcept override { std::vector ret; @@ -84,7 +89,6 @@ public: return ret; } -private: #if LIBREMIDI_HAS_UDEV void run() { @@ -98,6 +102,12 @@ private: return; } + // Check eventfd to see if we need to exit + if (m_fds[1].revents & POLLIN) + { + break; + } + // Check udev if (m_fds[0].revents & POLLIN) { @@ -122,12 +132,6 @@ private: m_fds[0].revents = 0; } - // Check eventfd - if (m_fds[1].revents & POLLIN) - { - break; - } - // Check timer if (m_fds[2].revents & POLLIN) { @@ -146,26 +150,35 @@ private: -> std::conditional_t { #if LIBREMIDI_HAS_UDEV - auto [container, device, type] = get_udev_soundcard_info(m_udev, p.card); + auto [container, device, type, manufacturer, product, serial] + = get_udev_soundcard_info(m_udev, p.card); #else container_identifier container{}; device_identifier device{}; - libremidi::port_information::port_type type{}; + libremidi::transport_type type{}; + std::string manufacturer; + std::string product; + std::string serial; #endif std::string display_name = p.subdevice_name; if (display_name.empty()) display_name = p.device_name; if (display_name.empty()) display_name = p.card_name; + if (display_name.empty()) + display_name = product; if (display_name.empty()) display_name = "unknown"; return { - {.client = 0, + {.api = get_current_api(), + .client = 0, .container = container, .device = device, .port = raw_to_port_handle({p.card, p.dev, p.sub}), - .manufacturer = p.card_name, + .manufacturer = manufacturer, + .product = product, + .serial = serial, .device_name = p.device_name, .port_name = p.subdevice_name, .display_name = std::move(display_name), @@ -243,11 +256,16 @@ private: }; } -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { struct observer_impl : observer_impl_base { - using alsa_raw::observer_impl_base::observer_impl_base; + observer_impl(observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf) + : observer_impl_base{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; } }; } diff --git a/include/libremidi/backends/alsa_raw_ump.hpp b/include/libremidi/backends/alsa_raw_ump.hpp index a6316e7..fc1c4da 100644 --- a/include/libremidi/backends/alsa_raw_ump.hpp +++ b/include/libremidi/backends/alsa_raw_ump.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct backend { diff --git a/include/libremidi/backends/alsa_raw_ump/config.hpp b/include/libremidi/backends/alsa_raw_ump/config.hpp index 39c71b6..d9e82f2 100644 --- a/include/libremidi/backends/alsa_raw_ump/config.hpp +++ b/include/libremidi/backends/alsa_raw_ump/config.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct input_configuration { diff --git a/include/libremidi/backends/alsa_raw_ump/helpers.hpp b/include/libremidi/backends/alsa_raw_ump/helpers.hpp index 6c22ed5..a4c2e5b 100644 --- a/include/libremidi/backends/alsa_raw_ump/helpers.hpp +++ b/include/libremidi/backends/alsa_raw_ump/helpers.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct midi2_enumerator : alsa_raw::enumerator { diff --git a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp index 0403d29..6748286 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp @@ -10,7 +10,7 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { class midi_in_impl : public midi2::in_api @@ -44,7 +44,8 @@ public: SND_RAWMIDI_NONBLOCK; // fixme if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0) { - libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); + libremidi_handle_error( + this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); return from_errc(err); } @@ -143,7 +144,8 @@ public: while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0) { const auto to_ns = [this] { return absolute_timestamp(); }; - m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); + m_processing.on_bytes( + {words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -166,7 +168,8 @@ public: const auto to_ns = [ts] { return static_cast(ts.tv_sec) * 1'000'000'000 + static_cast(ts.tv_nsec); }; - m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); + m_processing.on_bytes( + {words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -257,8 +260,7 @@ private: using namespace std::literals; libremidi_handle_error( - this->configuration, - "error starting MIDI input thread: "s + e.what()); + this->configuration, "error starting MIDI input thread: "s + e.what()); return e.code(); } return stdx::error{}; @@ -309,19 +311,21 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer, fds); + }}); } else { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); + }}); } } @@ -335,7 +339,7 @@ private: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp index 26c7155..0ca5b87 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp @@ -13,7 +13,7 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { class midi_out_impl final : public midi2::out_api diff --git a/include/libremidi/backends/alsa_raw_ump/observer.hpp b/include/libremidi/backends/alsa_raw_ump/observer.hpp index 03e3286..0c44805 100644 --- a/include/libremidi/backends/alsa_raw_ump/observer.hpp +++ b/include/libremidi/backends/alsa_raw_ump/observer.hpp @@ -2,13 +2,18 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { - class observer_impl : public alsa_raw::observer_impl_base { - using alsa_raw::observer_impl_base::observer_impl_base; +public: + observer_impl( + libremidi::observer_configuration&& conf, alsa_raw_ump::observer_configuration&& apiconf) + : observer_impl_base{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; } }; - } diff --git a/include/libremidi/backends/alsa_seq.hpp b/include/libremidi/backends/alsa_seq.hpp index ccbc40b..bac33c3 100644 --- a/include/libremidi/backends/alsa_seq.hpp +++ b/include/libremidi/backends/alsa_seq.hpp @@ -20,7 +20,7 @@ #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct backend @@ -42,7 +42,7 @@ struct backend if (!snd.available || !snd.seq.available) return false; - return ::access("/dev/snd/seq", F_OK) == 0; + return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0; } }; diff --git a/include/libremidi/backends/alsa_seq/config.hpp b/include/libremidi/backends/alsa_seq/config.hpp index 8153fb3..26ff5d5 100644 --- a/include/libremidi/backends/alsa_seq/config.hpp +++ b/include/libremidi/backends/alsa_seq/config.hpp @@ -15,7 +15,7 @@ typedef struct snd_seq_addr } #endif -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { // Possible parameters: diff --git a/include/libremidi/backends/alsa_seq/helpers.hpp b/include/libremidi/backends/alsa_seq/helpers.hpp index 5ca667c..8f5a72c 100644 --- a/include/libremidi/backends/alsa_seq/helpers.hpp +++ b/include/libremidi/backends/alsa_seq/helpers.hpp @@ -9,7 +9,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct event_handle { @@ -42,8 +42,6 @@ struct event_handle ~event_handle() { snd.seq.free_event(ev); } }; -namespace -{ inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept { return (port << 32) + client; @@ -56,6 +54,24 @@ inline constexpr std::pair seq_from_port_handle(port_handle p) noexcep return {client, port}; } +inline void for_all_clients( + const libasound& snd, snd_seq_t* seq, const std::function& func) +{ + snd_seq_client_info_t* cinfo{}; + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_t* pinfo{}; + snd_seq_port_info_alloca(&pinfo); + + snd.seq.client_info_set_client(cinfo, -1); + while (snd.seq.query_next_client(seq, cinfo) >= 0) + { + int client = snd.seq.client_info_get_client(cinfo); + if (client == 0) + continue; + func(*cinfo); + } +} + inline void for_all_ports( const libasound& snd, snd_seq_t* seq, const std::function& func) @@ -82,6 +98,21 @@ inline void for_all_ports( } } +inline void for_all_ports( + const libasound& snd, snd_seq_t* seq, int client, + const std::function& func) +{ + snd_seq_port_info_t* pinfo{}; + snd_seq_port_info_alloca(&pinfo); + + snd.seq.port_info_set_client(pinfo, client); + snd.seq.port_info_set_port(pinfo, -1); + while (snd.seq.query_next_port(seq, pinfo) >= 0) + { + func(*pinfo); + } +} + // This function is used to count or get the pinfo structure for a given port // number. inline unsigned int iterate_port_info( @@ -125,7 +156,6 @@ inline unsigned int iterate_port_info( return count; return 0; } -} // A structure to hold variables related to the ALSA API // implementation. @@ -159,15 +189,15 @@ struct alsa_data snd.seq.set_client_name(seq, configuration.client_name.data()); #if __has_include() - if (snd.seq.ump.set_client_midi_version) + if (snd.seq.set_client_midi_version) { switch (configuration.midi_version) { case 1: - snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI); + snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI); break; case 2: - snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); + snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); break; } } @@ -272,8 +302,7 @@ struct alsa_data // Make subscription if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0) { - self.libremidi_handle_error( - self.configuration, "ALSA error allocation port subscription."); + self.libremidi_handle_error(self.configuration, "ALSA error allocation port subscription."); return err; } diff --git a/include/libremidi/backends/alsa_seq/midi_in.hpp b/include/libremidi/backends/alsa_seq/midi_in.hpp index c232c6c..8d0be5d 100644 --- a/include/libremidi/backends/alsa_seq/midi_in.hpp +++ b/include/libremidi/backends/alsa_seq/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct dummy_processing { @@ -195,11 +195,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -487,13 +482,13 @@ public: using poll_params = typename ConfigurationImpl::poll_parameters_type; this->configuration.manual_poll( poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) { - if constexpr (ConfigurationImpl::midi_version == 1) - return this->process_event(ev); + if constexpr (ConfigurationImpl::midi_version == 1) + return this->process_event(ev); #if __has_include() - else - return this->process_ump_event(ev); + else + return this->process_ump_event(ev); #endif - }}); + }}); return 0; } @@ -530,7 +525,7 @@ public: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr diff --git a/include/libremidi/backends/alsa_seq/midi_out.hpp b/include/libremidi/backends/alsa_seq/midi_out.hpp index aaa91cf..0865c5e 100644 --- a/include/libremidi/backends/alsa_seq/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { class midi_out_impl final @@ -110,11 +110,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); diff --git a/include/libremidi/backends/alsa_seq/observer.hpp b/include/libremidi/backends/alsa_seq/observer.hpp index 41261a6..77a331f 100644 --- a/include/libremidi/backends/alsa_seq/observer.hpp +++ b/include/libremidi/backends/alsa_seq/observer.hpp @@ -13,19 +13,26 @@ #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { +struct client_info +{ + std::string client_name; + int client_id{}; + std::optional card{}; +}; + struct port_info { std::string client_name; std::string port_name; int client{}; int port{}; - bool isInput{}; - bool isOutput{}; + bool is_input{}; + bool is_output{}; std::optional card{}; - libremidi::port_information::port_type type{}; + libremidi::transport_type type{}; }; template @@ -93,12 +100,69 @@ public: } } - std::optional get_info(int client, int port) const noexcept + std::optional + get_client_info(int client, snd_seq_client_info_t& cinfo) const noexcept { + client_info p; + p.client_id = client; + + if (auto name = snd.seq.client_info_get_name(&cinfo)) + p.client_name = name; + + if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0) + p.card = card; + + return p; + } + + std::optional get_info( + int client, int port, snd_seq_client_info_t& cinfo, + snd_seq_port_info_t& pinfo) const noexcept + { + const auto tp = snd.seq.port_info_get_type(&pinfo); + const auto cap = snd.seq.port_info_get_capability(&pinfo); + if ((cap & SND_SEQ_PORT_CAP_NO_EXPORT) != 0) + return std::nullopt; + port_info p; p.client = client; p.port = port; + bool ok = this->configuration.track_any; + + static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER + | SND_SEQ_PORT_TYPE_APPLICATION; + + if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) + { + p.type = libremidi::transport_type::hardware; + ok = true; + } + else if ((tp & virtual_port) && this->configuration.track_virtual) + { + p.type = libremidi::transport_type::software; + ok = true; + } + if (!ok) + return {}; + + if (auto name = snd.seq.client_info_get_name(&cinfo)) + p.client_name = name; + + if (auto name = snd.seq.port_info_get_name(&pinfo)) + p.port_name = name; + + if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0) + p.card = card; + + p.is_input = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ); + p.is_output = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE); + + return p; + } + + std::optional get_info(int client, int port) const noexcept + { snd_seq_client_info_t* cinfo; snd_seq_client_info_alloca(&cinfo); if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0) @@ -109,39 +173,7 @@ public: if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0) return std::nullopt; - const auto tp = snd.seq.port_info_get_type(pinfo); - bool ok = this->configuration.track_any; - - static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER - | SND_SEQ_PORT_TYPE_APPLICATION; - - if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) - { - p.type = libremidi::port_information::port_type::hardware; - ok = true; - } - else if ((tp & virtual_port) && this->configuration.track_virtual) - { - p.type = libremidi::port_information::port_type::software; - ok = true; - } - if (!ok) - return {}; - - if (auto name = snd.seq.client_info_get_name(cinfo)) - p.client_name = name; - - if (auto name = snd.seq.port_info_get_name(pinfo)) - p.port_name = name; - - if (int card = snd.seq.client_info_get_card(cinfo); card >= 0) - p.card = card; - - const auto cap = snd.seq.port_info_get_capability(pinfo); - p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ); - p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE); - - return p; + return get_info(client, port, *cinfo, *pinfo); } template @@ -153,7 +185,10 @@ public: container_identifier container{}; device_identifier device{}; - libremidi::port_information::port_type type = p.type; + libremidi::transport_type type = p.type; + std::string manufacturer; + std::string product; + std::string serial; #if LIBREMIDI_HAS_UDEV if (p.card) { @@ -161,14 +196,20 @@ public: container = res.container; device = res.path; type = res.type; + manufacturer = res.vendor; + product = res.product; + serial = res.serial; } #endif return { - {.client = std::uintptr_t(this->seq), + {.api = get_current_api(), + .client = std::uintptr_t(this->seq), .container = container, .device = device, .port = alsa_seq::seq_to_port_handle(p.client, p.port), - .manufacturer = "", + .manufacturer = manufacturer, + .product = product, + .serial = serial, .device_name = p.client_name, .port_name = p.port_name, .display_name = p.port_name, @@ -179,10 +220,10 @@ public: { alsa_seq::for_all_ports( snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - register_port(clt, pt); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + register_port(clt, pt); + }); } libremidi::API get_current_api() const noexcept override @@ -198,12 +239,12 @@ public: std::vector ret; alsa_seq::for_all_ports( snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - if (auto p = get_info(clt, pt)) - if (p->isInput) - ret.push_back(to_port_info(*p)); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + if (auto p = get_info(clt, pt, client, port)) + if (p->is_input) + ret.push_back(to_port_info(*p)); + }); return ret; } @@ -212,12 +253,12 @@ public: std::vector ret; alsa_seq::for_all_ports( snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - if (auto p = get_info(clt, pt)) - if (p->isOutput) - ret.push_back(to_port_info(*p)); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + if (auto p = get_info(clt, pt, client, port)) + if (p->is_output) + ret.push_back(to_port_info(*p)); + }); return ret; } @@ -231,12 +272,12 @@ public: return; m_knownClients[{p.client, p.port}] = p; - if (p.isInput && configuration.input_added) + if (p.is_input && configuration.input_added) { configuration.input_added(to_port_info(p)); } - if (p.isOutput && configuration.output_added) + if (p.is_output && configuration.output_added) { configuration.output_added(to_port_info(p)); } @@ -250,31 +291,55 @@ public: auto p = it->second; m_knownClients.erase(it); - if (p.isInput && configuration.input_removed) + if (p.is_input && configuration.input_removed) { configuration.input_removed(to_port_info(p)); } - if (p.isOutput && configuration.output_removed) + if (p.is_output && configuration.output_removed) { configuration.output_removed(to_port_info(p)); } } } - void handle_event(const snd_seq_event_t& ev) + void handle_event_direct(const snd_seq_event_t& ev) { switch (ev.type) { + case SND_SEQ_EVENT_CLIENT_START: { + // TODO + break; + } + case SND_SEQ_EVENT_CLIENT_EXIT: { + // TODO + break; + } + case SND_SEQ_EVENT_CLIENT_CHANGE: { + // TODO + break; + } +#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS + case SND_SEQ_EVENT_UMP_EP_CHANGE: { + // TODO + break; + } + case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: { + // TODO + break; + } +#endif case SND_SEQ_EVENT_PORT_START: { - register_port(ev.data.addr.client, ev.data.addr.port); + this->register_port(ev.data.addr.client, ev.data.addr.port); break; } case SND_SEQ_EVENT_PORT_EXIT: { - unregister_port(ev.data.addr.client, ev.data.addr.port); + this->unregister_port(ev.data.addr.client, ev.data.addr.port); break; } case SND_SEQ_EVENT_PORT_CHANGE: + // TODO + break; default: break; } @@ -309,38 +374,87 @@ public: { // Create relevant descriptors auto& snd = alsa_data::snd; + + // 1. Descriptor count const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN); - descriptors_.resize(n + 1); - snd.seq.poll_descriptors(this->seq, descriptors_.data(), n, POLLIN); - descriptors_.back() = this->termination_event; + int total_descriptors = n; + total_descriptors++; // eventfd for terminating the thread + + // 2. Create storage + descriptors.resize(total_descriptors); + + // 3. Store descriptors + snd.seq.poll_descriptors(this->seq, descriptors.data(), n, POLLIN); + descriptors[n] = this->termination_event; // Start the listening thread - thread = std::thread{[this] { + thread = std::thread{[this, n] { auto& snd = alsa_data::snd; const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); for (;;) { - int err = poll(descriptors_.data(), descriptors_.size(), static_cast(period)); + int err = poll(descriptors.data(), descriptors.size(), static_cast(period)); if (err >= 0) { // We got our stop-thread signal - if (descriptors_.back().revents & POLLIN) + if (descriptors[n].revents & POLLIN) break; + // Put ALSA event in our queue snd_seq_event_t* ev{}; event_handle handle{snd}; while (snd.seq.event_input(this->seq, &ev) >= 0) { handle.reset(ev); - this->handle_event(*ev); + this->handle_event_delayed(*ev); + } + + // Process the events in a deferred way. + // This is because udev takes some milliseconds to populate its field after a + // port was added + auto tm = std::chrono::steady_clock::now(); + for (auto it = queued_events.begin(); it != queued_events.end();) + { + if ((tm - it->second) >= this->configuration.poll_period) + { + this->handle_event_direct(it->first); + it = queued_events.erase(it); + } + else + { + break; + } } } } }}; } + void handle_event_delayed(const snd_seq_event_t& ev) + { + switch (ev.type) + { + case SND_SEQ_EVENT_CLIENT_START: + case SND_SEQ_EVENT_CLIENT_EXIT: + case SND_SEQ_EVENT_CLIENT_CHANGE: + +#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS + case SND_SEQ_EVENT_UMP_EP_CHANGE: + case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: +#endif + + case SND_SEQ_EVENT_PORT_START: + case SND_SEQ_EVENT_PORT_EXIT: + case SND_SEQ_EVENT_PORT_CHANGE: + queued_events.emplace_back(ev, std::chrono::steady_clock::now()); + break; + default: + break; + } + } + ~observer_threaded() { termination_event.notify(); @@ -351,7 +465,8 @@ public: eventfd_notifier termination_event{}; std::thread thread; - std::vector descriptors_; + std::vector descriptors; + std::vector> queued_events; }; template @@ -363,16 +478,16 @@ public: { this->configuration.manual_poll( poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) { - this->handle_event(v); - return 0; - }}); + this->handle_event_direct(v); + return 0; + }}); } ~observer_manual() { this->configuration.stop_poll(this->vaddr); } }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr diff --git a/include/libremidi/backends/alsa_seq/shared_handler.hpp b/include/libremidi/backends/alsa_seq/shared_handler.hpp index 580344c..7404a46 100644 --- a/include/libremidi/backends/alsa_seq/shared_handler.hpp +++ b/include/libremidi/backends/alsa_seq/shared_handler.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct shared_handler : public libremidi::shared_context @@ -127,13 +127,13 @@ struct shared_handler : public libremidi::shared_context { case callback_added: { auto [addr, cb] - = std::move(*std::get_if(&ev.payload)); + = std::move(*get_if(&ev.payload)); addresses.push_back(addr); callbacks.push_back(std::move(cb)); break; } case callback_removed: - auto addr = *std::get_if(&ev.payload); + auto addr = *get_if(&ev.payload); if (auto index = index_of_address(addr); index >= 0) { addresses.erase(addresses.begin() + index); @@ -179,7 +179,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; snd_seq_t* client{}; diff --git a/include/libremidi/backends/alsa_seq_ump.hpp b/include/libremidi/backends/alsa_seq_ump.hpp index b9f83ff..56b0f86 100644 --- a/include/libremidi/backends/alsa_seq_ump.hpp +++ b/include/libremidi/backends/alsa_seq_ump.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr @@ -42,7 +42,7 @@ inline std::unique_ptr make< } -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { struct backend { @@ -63,7 +63,7 @@ struct backend if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available) return false; - return ::access("/dev/snd/seq", F_OK) == 0; + return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0; } }; } diff --git a/include/libremidi/backends/alsa_seq_ump/config.hpp b/include/libremidi/backends/alsa_seq_ump/config.hpp index f57a306..661de4a 100644 --- a/include/libremidi/backends/alsa_seq_ump/config.hpp +++ b/include/libremidi/backends/alsa_seq_ump/config.hpp @@ -11,7 +11,7 @@ extern "C" { typedef struct snd_seq_ump_event snd_seq_ump_event_t; } -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { struct poll_parameters diff --git a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp index 3cfb063..bb9be5c 100644 --- a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { class midi_out_impl final @@ -106,11 +106,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -137,7 +132,7 @@ public: static_assert(std::errc{0} == std::errc{}); return std::errc{}; }; - segment_ump_stream(ump_stream, count, write_func, []() {}); + segment_ump_stream(ump_stream, count, write_func, []() { }); snd.seq.drain_output(this->seq); return stdx::error{}; diff --git a/include/libremidi/backends/android/android.hpp b/include/libremidi/backends/android/android.hpp new file mode 100644 index 0000000..9dc3a7c --- /dev/null +++ b/include/libremidi/backends/android/android.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct backend +{ + using midi_in = android::midi_in; + using midi_out = android::midi_out; + using midi_observer = android::observer; + using midi_in_configuration = android::input_configuration; + using midi_out_configuration = android::output_configuration; + using midi_observer_configuration = android::observer_configuration; + static const constexpr auto API = libremidi::API::ANDROID_AMIDI; + static const constexpr std::string_view name = "android"; + static const constexpr std::string_view display_name = "Android MIDI API"; + + static bool available() noexcept + { +#if defined(__ANDROID__) + return true; +#else + return false; +#endif + } +}; +} +} diff --git a/include/libremidi/backends/android/config.hpp b/include/libremidi/backends/android/config.hpp new file mode 100644 index 0000000..55d8ae0 --- /dev/null +++ b/include/libremidi/backends/android/config.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct input_configuration +{ + std::string_view client_name = "libremidi client"; +}; + +struct output_configuration +{ + std::string_view client_name = "libremidi client"; +}; + +struct observer_configuration +{ + std::string_view client_name = "libremidi client"; +}; +} +} \ No newline at end of file diff --git a/include/libremidi/backends/android/helpers.cpp b/include/libremidi/backends/android/helpers.cpp new file mode 100644 index 0000000..da90bf3 --- /dev/null +++ b/include/libremidi/backends/android/helpers.cpp @@ -0,0 +1,239 @@ +#include +#include +#include + +NAMESPACE_LIBREMIDI::android +{ +// Main source of knowledge is RtMidi implementation +// which was done by Yellow Labrador + +JNIEnv* context::get_thread_env() +{ + JNIEnv* env; + JavaVM* jvm; + jsize count; + if (JNI_GetCreatedJavaVMs(&jvm, 1, &count) != JNI_OK || count < 1) + { + LOGE("No JVM found"); + return nullptr; + } + + if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) + { + if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) + { + LOGE("Failed to attach thread"); + return nullptr; + } + } + + return env; +} + +jobject context::get_context(JNIEnv* env) +{ + auto activity_thread = env->FindClass("android/app/ActivityThread"); + auto current_activity_thread = env->GetStaticMethodID( + activity_thread, "currentActivityThread", "()Landroid/app/ActivityThread;"); + auto at = env->CallStaticObjectMethod(activity_thread, current_activity_thread); + + if (!at) + { + LOGE("Failed to get ActivityThread"); + return nullptr; + } + + auto get_application + = env->GetMethodID(activity_thread, "getApplication", "()Landroid/app/Application;"); + auto app = env->CallObjectMethod(at, get_application); + + if (!app) + { + LOGE("Failed to get Application"); + return nullptr; + } + + return app; +} + +jobject context::get_midi_manager(JNIEnv* env, jobject ctx) +{ + auto context_class = env->FindClass("android/content/Context"); + auto get_system_service = env->GetMethodID( + context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + auto midi_service_str = env->NewStringUTF("midi"); + auto service = env->CallObjectMethod(ctx, get_system_service, midi_service_str); + env->DeleteLocalRef(midi_service_str); + return service; +} + +void context::refresh_midi_devices(JNIEnv* env, jobject ctx, bool is_output) +{ + cleanup_devices(env); + + auto midi_service = get_midi_manager(env, ctx); + if (!midi_service) + return; + + auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager"); + auto get_devices_method + = env->GetMethodID(midi_mgr_class, "getDevices", "()[Landroid/media/midi/MidiDeviceInfo;"); + auto device_array = (jobjectArray)env->CallObjectMethod(midi_service, get_devices_method); + + auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto get_input_count = env->GetMethodID(device_info_class, "getInputPortCount", "()I"); + auto get_output_count = env->GetMethodID(device_info_class, "getOutputPortCount", "()I"); + + jsize count = env->GetArrayLength(device_array); + for (jsize i = 0; i < count; ++i) + { + auto device_info = env->GetObjectArrayElement(device_array, i); + int port_count = is_output ? env->CallIntMethod(device_info, get_input_count) + : env->CallIntMethod(device_info, get_output_count); + + if (port_count > 0) + { + midi_devices.push_back(env->NewGlobalRef(device_info)); + } + env->DeleteLocalRef(device_info); + } + + env->DeleteLocalRef(device_array); + env->DeleteLocalRef(midi_service); +} + +std::string context::port_name(JNIEnv* env, unsigned int port_number) +{ + if (port_number >= midi_devices.size()) + { + LOGE("Invalid port number"); + return ""; + } + + auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto get_props_method + = env->GetMethodID(device_info_class, "getProperties", "()Landroid/os/Bundle;"); + auto bundle = env->CallObjectMethod(midi_devices[port_number], get_props_method); + + auto bundle_class = env->FindClass("android/os/Bundle"); + auto get_string_method + = env->GetMethodID(bundle_class, "getString", "(Ljava/lang/String;)Ljava/lang/String;"); + auto key = env->NewStringUTF("name"); + auto name_str = (jstring)env->CallObjectMethod(bundle, get_string_method, key); + + std::string result; + if (name_str) + { + const char* name_chars = env->GetStringUTFChars(name_str, nullptr); + result = name_chars; + env->ReleaseStringUTFChars(name_str, name_chars); + env->DeleteLocalRef(name_str); + } + + env->DeleteLocalRef(key); + env->DeleteLocalRef(bundle); + + return result; +} + +void context::cleanup_devices(JNIEnv* env) +{ + for (jobject device : midi_devices) + { + env->DeleteGlobalRef(device); + } + midi_devices.clear(); +} + +void context::open_device(jobject device_info, void* target, bool is_output) +{ + auto env = get_thread_env(); + if (!env) + return; + + auto ctx = get_context(env); + if (!ctx) + return; + + auto midi_mgr = get_midi_manager(env, ctx); + if (!midi_mgr) + return; + + // Check if we're on the main thread + auto looper_class = env->FindClass("android/os/Looper"); + auto get_my_looper_method + = env->GetStaticMethodID(looper_class, "myLooper", "()Landroid/os/Looper;"); + auto my_looper = env->CallStaticObjectMethod(looper_class, get_my_looper_method); + + jobject handler = nullptr; + if (!my_looper) + { + LOGI("Not on a Looper thread, using main looper"); + auto get_main_looper_method + = env->GetStaticMethodID(looper_class, "getMainLooper", "()Landroid/os/Looper;"); + auto main_looper = env->CallStaticObjectMethod(looper_class, get_main_looper_method); + + auto handler_class = env->FindClass("android/os/Handler"); + auto handler_ctor = env->GetMethodID(handler_class, "", "(Landroid/os/Looper;)V"); + handler = env->NewObject(handler_class, handler_ctor, main_looper); + + env->DeleteLocalRef(main_looper); + } + + // Create the callback listener + auto callback_class = env->FindClass("dev/celtera/libremidi/MidiDeviceCallback"); + if (!callback_class) + { + LOGE("MidiDeviceCallback class not found - ensure the Java class is loaded"); + if (handler) + env->DeleteLocalRef(handler); + return; + } + + auto callback_ctor = env->GetMethodID(callback_class, "", "(JZ)V"); + auto callback + = env->NewObject(callback_class, callback_ctor, (jlong)target, (jboolean)is_output); + + // Open the device + auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager"); + auto open_device_method = env->GetMethodID( + midi_mgr_class, "openDevice", + "(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/" + "MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V"); + + env->CallVoidMethod(midi_mgr, open_device_method, device_info, callback, handler); + + env->DeleteLocalRef(callback); + if (handler) + env->DeleteLocalRef(handler); +} + +extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened( + JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output) +{ + if (!midi_device || target_ptr == 0) + { + LOGE("Invalid device or target pointer in callback"); + return; + } + + AMidiDevice* amidi_device = nullptr; + AMidiDevice_fromJava(env, midi_device, &amidi_device); + + if (!amidi_device) + { + LOGE("Failed to convert Java MIDI device to AMidiDevice"); + return; + } + + if (is_output) + { + midi_out::open_callback(reinterpret_cast(target_ptr), amidi_device); + } + else + { + midi_in::open_callback(reinterpret_cast(target_ptr), amidi_device); + } +} + +} diff --git a/include/libremidi/backends/android/helpers.hpp b/include/libremidi/backends/android/helpers.hpp new file mode 100644 index 0000000..7f4ce41 --- /dev/null +++ b/include/libremidi/backends/android/helpers.hpp @@ -0,0 +1,40 @@ +#pragma once +#include + +#include +#include + +#include +#include + +#include +#include + +#define LOG_TAG "libremidi" +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct context +{ + static inline std::string client_name; + static inline std::vector midi_devices; + + static JNIEnv* get_thread_env(); + static jobject get_context(JNIEnv* env); + static jobject get_midi_manager(JNIEnv* env, jobject context); + static void refresh_midi_devices(JNIEnv* env, jobject context, bool is_output); + static void open_device(jobject device_info, void* target, bool is_output); + static std::string port_name(JNIEnv* env, unsigned int port_number); + static void cleanup_devices(JNIEnv* env); +}; + +extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened( + JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output); +} +} diff --git a/include/libremidi/backends/android/midi_in.hpp b/include/libremidi/backends/android/midi_in.hpp new file mode 100644 index 0000000..78fca93 --- /dev/null +++ b/include/libremidi/backends/android/midi_in.hpp @@ -0,0 +1,165 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class midi_in + : public midi1::in_api + , public error_handler +{ +public: + explicit midi_in( + libremidi::input_configuration&& conf, libremidi::android::input_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + libremidi_handle_error( + configuration, "Android backend only supports one client name per process"); + return; + } + context::client_name = std::string(aconf.client_name); + client_open_ = stdx::error{}; + } + + ~midi_in() override { close_port(); } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + stdx::error open_port(const input_port& port, std::string_view /*name*/) override + { + if (is_port_open()) + { + libremidi_handle_error(configuration, "midi_in::open_port: a port is already open"); + return std::errc{}; + } + + const unsigned int port_number = port.port; + auto env = context::get_thread_env(); + if (!env) + { + libremidi_handle_error(configuration, "midi_in::open_port: failed to get JNI environment"); + return std::errc{}; + } + + auto ctx = context::get_context(env); + if (!ctx) + { + libremidi_handle_error(configuration, "midi_in::open_port: failed to get Android context"); + return std::errc{}; + } + + context::refresh_midi_devices(env, ctx, false); + + if (port_number >= context::midi_devices.size()) + { + libremidi_handle_error(configuration, "midi_in::open_port: invalid port number"); + return std::errc{}; + } + + port_open = true; + running = true; + context::open_device(context::midi_devices[port_number], this, false); + return stdx::error{}; + } + + stdx::error close_port() override + { + if (!is_port_open()) + return std::errc{}; + + running = false; + + if (poll_thread.joinable()) + { + poll_thread.join(); + } + + if (midi_output_port) + { + AMidiOutputPort_close(midi_output_port); + midi_output_port = nullptr; + } + + if (receive_device) + { + AMidiDevice_release(receive_device); + receive_device = nullptr; + } + + port_open = false; + return stdx::error{}; + } + + bool is_port_open() const noexcept { return port_open; } + + void start_midi_thread() { poll_thread = std::thread(&midi_in::poll_midi, this); } + + timestamp absolute_timestamp() const noexcept override { return system_ns(); } + void poll_midi() + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = false, + .absolute_is_monotonic = false, + .has_samples = false, + }; + + uint8_t buffer[1024]; + int32_t op_code; + int64_t timestamp; + size_t num_bytes; + + while (running) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + ssize_t num_messages = AMidiOutputPort_receive( + midi_output_port, &op_code, buffer, sizeof(buffer), &num_bytes, ×tamp); + + if (num_messages < 0) + { + LOGE("Error receiving MIDI data: %d", (int)num_messages); + continue; + } + + if (num_messages > 0 && num_bytes > 0) + { + const auto to_ns = [=] { return timestamp; }; + + m_processing.on_bytes_multi( + {buffer, buffer + num_bytes}, + m_processing.timestamp(to_ns, timestamp)); + } + } + } + + static void open_callback(midi_in* self, AMidiDevice* device) + { + self->receive_device = device; + AMidiOutputPort_open(device, 0, &self->midi_output_port); + self->start_midi_thread(); + } + +private: + libremidi::input_configuration configuration; + AMidiDevice* receive_device = nullptr; + AMidiOutputPort* midi_output_port = nullptr; + std::thread poll_thread; + std::atomic port_open{false}; + std::atomic running{false}; + midi1::input_state_machine m_processing{this->configuration}; +}; +} +} diff --git a/include/libremidi/backends/android/midi_out.hpp b/include/libremidi/backends/android/midi_out.hpp new file mode 100644 index 0000000..fb12986 --- /dev/null +++ b/include/libremidi/backends/android/midi_out.hpp @@ -0,0 +1,126 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class midi_out final + : public midi1::out_api + , public error_handler +{ +public: + explicit midi_out( + libremidi::output_configuration&& conf, libremidi::android::output_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + libremidi_handle_error( + configuration, "Android backend only supports one client name per process"); + return; + } + context::client_name = std::string(aconf.client_name); + client_open_ = stdx::error{}; + } + + ~midi_out() override { close_port(); } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + stdx::error open_port(const output_port& port, std::string_view /*name*/) override + { + if (is_port_open()) + { + libremidi_handle_error(configuration, "midi_out::open_port: a port is already open"); + return std::errc{}; + } + + const unsigned int port_number = port.port; + + auto env = context::get_thread_env(); + if (!env) + { + libremidi_handle_error(configuration, "midi_out::open_port: failed to get JNI environment"); + return std::errc{}; + } + + auto ctx = context::get_context(env); + if (!ctx) + { + libremidi_handle_error(configuration, "midi_out::open_port: failed to get Android context"); + return std::errc{}; + } + + context::refresh_midi_devices(env, ctx, true); + + if (port_number >= context::midi_devices.size()) + { + libremidi_handle_error(configuration, "midi_out::open_port: invalid port number"); + return std::errc{}; + } + + port_open = true; + context::open_device(context::midi_devices[port_number], this, true); + return stdx::error{}; + } + + stdx::error close_port() override + { + if (!is_port_open()) + return std::errc{}; + + if (midi_input_port) + { + AMidiInputPort_close(midi_input_port); + midi_input_port = nullptr; + } + + if (send_device) + { + AMidiDevice_release(send_device); + send_device = nullptr; + } + + port_open = false; + return stdx::error{}; + } + + bool is_port_open() const noexcept { return port_open; } + + stdx::error send_message(const unsigned char* message, size_t size) override + { + if (!is_port_open() || !midi_input_port) + { + libremidi_handle_error(configuration, "midi_out::send_message: port is not open"); + return std::errc{}; + } + + ssize_t result = AMidiInputPort_send(midi_input_port, message, size); + if (result < 0) + { + libremidi_handle_error(configuration, "midi_out::send_message: failed to send MIDI data"); + return std::errc{}; + } + return stdx::error{}; + } + + static void open_callback(midi_out* self, AMidiDevice* device) + { + self->send_device = device; + AMidiInputPort_open(device, 0, &self->midi_input_port); + } + +private: + libremidi::output_configuration configuration; + AMidiDevice* send_device = nullptr; + AMidiInputPort* midi_input_port = nullptr; + bool port_open = false; +}; +} +} diff --git a/include/libremidi/backends/android/observer.hpp b/include/libremidi/backends/android/observer.hpp new file mode 100644 index 0000000..f320002 --- /dev/null +++ b/include/libremidi/backends/android/observer.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class observer final + : public libremidi::observer_api + , public error_handler +{ +public: + explicit observer( + libremidi::observer_configuration&& conf, libremidi::android::observer_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + LOGW("Android backend only supports one client name per process"); + } + context::client_name = std::string(aconf.client_name); + } + + ~observer() + { + auto env = context::get_thread_env(); + if (env) + { + context::cleanup_devices(env); + } + } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + std::vector get_input_ports() const noexcept override + { + auto env = context::get_thread_env(); + if (!env) + return {}; + + auto ctx = context::get_context(env); + if (!ctx) + return {}; + + context::refresh_midi_devices(env, ctx, false); + + std::vector ports; + for (size_t i = 0; i < context::midi_devices.size(); ++i) + { + libremidi::input_port port; + port.api = libremidi::API::ANDROID_AMIDI; + port.port_name = context::port_name(env, i); + port.port = i; + ports.push_back(std::move(port)); + } + + return ports; + } + + std::vector get_output_ports() const noexcept override + { + auto env = context::get_thread_env(); + if (!env) + return {}; + + auto ctx = context::get_context(env); + if (!ctx) + return {}; + + context::refresh_midi_devices(env, ctx, true); + + std::vector ports; + for (size_t i = 0; i < context::midi_devices.size(); ++i) + { + libremidi::output_port port; + port.port_name = context::port_name(env, i); + port.port = i; + ports.push_back(std::move(port)); + } + + return ports; + } + +private: + libremidi::observer_configuration configuration; +}; +} +} diff --git a/include/libremidi/backends/coremidi.hpp b/include/libremidi/backends/coremidi.hpp index 86fd485..eab24e8 100644 --- a/include/libremidi/backends/coremidi.hpp +++ b/include/libremidi/backends/coremidi.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct core_backend { diff --git a/include/libremidi/backends/coremidi/config.hpp b/include/libremidi/backends/coremidi/config.hpp index 359dece..db31956 100644 --- a/include/libremidi/backends/coremidi/config.hpp +++ b/include/libremidi/backends/coremidi/config.hpp @@ -18,7 +18,7 @@ typedef MIDIObjectRef MIDIClientRef; using MIDIClientRef = uint32_t; using MIDIObjectRef = uint32_t; #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct coremidi_input_configuration diff --git a/include/libremidi/backends/coremidi/error_domain.hpp b/include/libremidi/backends/coremidi/error_domain.hpp index fb33d39..ff82ccc 100644 --- a/include/libremidi/backends/coremidi/error_domain.hpp +++ b/include/libremidi/backends/coremidi/error_domain.hpp @@ -3,7 +3,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct coremidi_error_domain : public stdx::error_domain { diff --git a/include/libremidi/backends/coremidi/helpers.hpp b/include/libremidi/backends/coremidi/helpers.hpp index 07e0913..2d85134 100644 --- a/include/libremidi/backends/coremidi/helpers.hpp +++ b/include/libremidi/backends/coremidi/helpers.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include #include #include -#include #include #include @@ -21,13 +21,12 @@ #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime #endif -namespace libremidi +NAMESPACE_LIBREMIDI { using CFString_handle = unique_handle; using CFStringMutable_handle = unique_handle<__CFString, CFRelease>; -namespace -{ -static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept + +LIBREMIDI_STATIC std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept { CFStringRef res; if (MIDIObjectGetStringProperty(object, property, &res) || !res) @@ -39,14 +38,14 @@ static inline std::string get_string_property(MIDIObjectRef object, CFStringRef return name; } -static inline int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept +LIBREMIDI_STATIC int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept { SInt32 res; MIDIObjectGetIntegerProperty(object, property, &res); return res; } -static inline CFString_handle toCFString(std::string_view str) noexcept +LIBREMIDI_STATIC CFString_handle toCFString(std::string_view str) noexcept { return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)}; } @@ -72,12 +71,12 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime) } #endif -static inline auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name) +LIBREMIDI_STATIC auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name) { CFStringRef str = nullptr; MIDIObjectGetStringProperty(prop, name, &str); return CFString_handle{str}; -}; +} // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. @@ -220,22 +219,19 @@ locate_object(auto& self, const port_information& info, MIDIObjectType requested auto ret = MIDIObjectFindByUniqueID(uid, &object, &type); if (ret != noErr) { - self.libremidi_handle_error( - self.configuration, "cannot find port: " + info.port_name); + self.libremidi_handle_error(self.configuration, "cannot find port: " + info.port_name); return 0; } if (type != requested_type || object == 0) { self.libremidi_handle_error( - self.configuration, "invalid object: " + info.port_name + " : " - + std::to_string(object)); + self.configuration, "invalid object: " + info.port_name + " : " + std::to_string(object)); return 0; } return object; } -} // A structure to hold variables related to the CoreMIDI API // implementation. diff --git a/include/libremidi/backends/coremidi/midi_in.hpp b/include/libremidi/backends/coremidi/midi_in.hpp index deaaf82..0f84246 100644 --- a/include/libremidi/backends/coremidi/midi_in.hpp +++ b/include/libremidi/backends/coremidi/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_core final : public midi1::in_api @@ -25,8 +25,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -64,8 +63,7 @@ public: { close_client(*this); libremidi_handle_error( - this->configuration, "error creating macOS MIDI input port: " - + std::to_string(result)); + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); return from_osstatus(result); } @@ -74,8 +72,7 @@ public: { MIDIPortDispose(port); close_client(*this); - libremidi_handle_error( - this->configuration, "error connecting macOS MIDI input port."); + libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port."); return from_osstatus(result); } @@ -105,10 +102,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override { diff --git a/include/libremidi/backends/coremidi/midi_out.hpp b/include/libremidi/backends/coremidi/midi_out.hpp index e260475..39c76ac 100644 --- a/include/libremidi/backends/coremidi/midi_out.hpp +++ b/include/libremidi/backends/coremidi/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_core final : public midi1::out_api @@ -24,8 +24,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -60,8 +59,7 @@ public: if (result != noErr) { close_client(*this); - libremidi_handle_error( - this->configuration, "error creating macOS MIDI output port."); + libremidi_handle_error(this->configuration, "error creating macOS MIDI output port."); return from_osstatus(result); } @@ -80,9 +78,7 @@ public: if (result != noErr) { - libremidi_handle_error( - this->configuration, - "error creating macOS virtual MIDI source."); + libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source."); return from_osstatus(result); } @@ -92,10 +88,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } stdx::error send_message(const unsigned char* message, size_t size) override { @@ -118,8 +111,8 @@ public: const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME(); const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes; - Byte buffer[bufsize + 16]; // pad for other struct members - ByteCount listSize = sizeof(buffer); + Byte* buffer = (Byte*)alloca(bufsize + 16); // pad for other struct members + ByteCount listSize = bufsize + 16; MIDIPacketList* packetList = (MIDIPacketList*)buffer; ByteCount remainingBytes = nBytes; @@ -137,8 +130,7 @@ public: if (!packet) { - libremidi_handle_error( - this->configuration, "could not allocate packet list"); + libremidi_handle_error(this->configuration, "could not allocate packet list"); return std::errc::message_size; } @@ -163,9 +155,7 @@ public: auto result = MIDISend(this->port, this->destinationId, packetList); if (result != noErr) { - libremidi_handle_warning( - this->configuration, - "error sending MIDI message to port."); + libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return std::errc::io_error; } } diff --git a/include/libremidi/backends/coremidi/observer.hpp b/include/libremidi/backends/coremidi/observer.hpp index 4f422d2..1a3a93e 100644 --- a/include/libremidi/backends/coremidi/observer.hpp +++ b/include/libremidi/backends/coremidi/observer.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_core : public observer_api @@ -22,6 +22,10 @@ public: explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} + { + } + + void finish_init() { if (configuration.client_name.empty()) configuration.client_name = "libremidi observer"; @@ -32,15 +36,13 @@ public: auto result = MIDIClientCreate( toCFString(configuration.client_name).get(), +[](const MIDINotification* message, void* ctx) { - ((observer_core*)ctx)->notify(message); - }, - this, &client); + ((observer_core*)ctx)->notify(message); + }, this, &client); if (result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); return; } @@ -79,7 +81,7 @@ public: return {}; // Get the MIDI device from the entity - libremidi::port_information::port_type type{}; + libremidi::transport_type type{}; libremidi::container_identifier usb_location_id{}; libremidi::device_identifier usb_vendor_product{}; { @@ -88,11 +90,11 @@ public: { if (device) { - using enum libremidi::port_information::port_type; + using enum libremidi::transport_type; SInt32 devid_res{}; if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr) { - type = (libremidi::port_information::port_type)(hardware | usb); + type = (libremidi::transport_type)(hardware | usb); usb_location_id = (uint64_t)devid_res; } @@ -100,25 +102,26 @@ public: if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res) == noErr) { - type = (libremidi::port_information::port_type)(hardware | usb); + type = (libremidi::transport_type)(hardware | usb); usb_vendor_product = (uint64_t)vendor_res; } const auto driver = get_string_property(device, kMIDIPropertyDriverOwner); if (driver == "com.apple.AppleMIDIUSBDriver") - type = (libremidi::port_information::port_type)(hardware | usb); + type = (libremidi::transport_type)(hardware | usb); if (driver == "com.apple.AppleMIDIBluetoothDriver") - type = (libremidi::port_information::port_type)(hardware | bluetooth); + type = (libremidi::transport_type)(hardware | bluetooth); if (driver == "com.apple.AppleMIDIIACDriver") - type = (libremidi::port_information::port_type)(software); + type = (libremidi::transport_type)(software); if (driver == "com.apple.AppleMIDIRTPDriver") - type = (libremidi::port_information::port_type)(network); + type = (libremidi::transport_type)(network); } } } return std::conditional_t{ - {.client = (std::uintptr_t)this->client, + {.api = get_current_api(), + .client = (std::uintptr_t)this->client, .container = usb_location_id, .device = usb_vendor_product, .port = std::bit_cast(get_int_property(obj, kMIDIPropertyUniqueID)), @@ -215,3 +218,17 @@ private: MIDIClientRef client{}; }; } + +NAMESPACE_LIBREMIDI::coremidi +{ +struct observer_impl : observer_core +{ + observer_impl(observer_configuration&& conf, coremidi_observer_configuration&& apiconf) + : observer_core{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; } +}; +} diff --git a/include/libremidi/backends/coremidi_ump.hpp b/include/libremidi/backends/coremidi_ump.hpp index 49b5da9..3aa8457 100644 --- a/include/libremidi/backends/coremidi_ump.hpp +++ b/include/libremidi/backends/coremidi_ump.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { struct backend { diff --git a/include/libremidi/backends/coremidi_ump/config.hpp b/include/libremidi/backends/coremidi_ump/config.hpp index dee80f9..7d5f927 100644 --- a/include/libremidi/backends/coremidi_ump/config.hpp +++ b/include/libremidi/backends/coremidi_ump/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { struct input_configuration { diff --git a/include/libremidi/backends/coremidi_ump/midi_in.hpp b/include/libremidi/backends/coremidi_ump/midi_in.hpp index fb94fb7..019612c 100644 --- a/include/libremidi/backends/coremidi_ump/midi_in.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class midi_in_impl final @@ -26,8 +26,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -69,8 +68,7 @@ public: { close_client(*this); libremidi_handle_error( - this->configuration, "error creating macOS MIDI input port: " - + std::to_string(result)); + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); return from_osstatus(result); } @@ -79,8 +77,7 @@ public: { MIDIPortDispose(port); close_client(*this); - libremidi_handle_error( - this->configuration, "error connecting macOS MIDI input port."); + libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port."); return from_osstatus(result); } @@ -113,10 +110,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override { diff --git a/include/libremidi/backends/coremidi_ump/midi_out.hpp b/include/libremidi/backends/coremidi_ump/midi_out.hpp index 4ef9a67..e71d956 100644 --- a/include/libremidi/backends/coremidi_ump/midi_out.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_out.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class midi_out_impl final : public midi2::out_api @@ -27,8 +27,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -63,8 +62,7 @@ public: if (result != noErr) { close_client(*this); - libremidi_handle_error( - this->configuration, "error creating macOS MIDI output port."); + libremidi_handle_error(this->configuration, "error creating macOS MIDI output port."); return from_osstatus(result); } @@ -84,9 +82,7 @@ public: if (result != noErr) { this->endpoint = 0; - libremidi_handle_error( - this->configuration, - "error creating macOS virtual MIDI source."); + libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source."); return from_osstatus(result); } @@ -94,10 +90,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override { @@ -142,9 +135,7 @@ public: auto result = MIDISendEventList(this->port, this->destinationId, eventList); if (result != noErr) { - libremidi_handle_warning( - this->configuration, - "error sending MIDI message to port."); + libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return std::errc::io_error; } } diff --git a/include/libremidi/backends/coremidi_ump/observer.hpp b/include/libremidi/backends/coremidi_ump/observer.hpp index 35a3f18..18dad5f 100644 --- a/include/libremidi/backends/coremidi_ump/observer.hpp +++ b/include/libremidi/backends/coremidi_ump/observer.hpp @@ -1,17 +1,20 @@ #pragma once -#include #include +#include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class observer_impl final : public libremidi::observer_core { public: - explicit observer_impl(libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) - : observer_core{std::move(conf), coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} + explicit observer_impl( + libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) + : observer_core{ + std::move(conf), + coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} { - + finish_init(); } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; } diff --git a/include/libremidi/backends/dummy.hpp b/include/libremidi/backends/dummy.hpp index 971e2ed..0244997 100644 --- a/include/libremidi/backends/dummy.hpp +++ b/include/libremidi/backends/dummy.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_dummy : public observer_api { @@ -40,19 +40,9 @@ public: { return stdx::error{}; } - stdx::error open_virtual_port(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_client_name(std::string_view /*clientName*/) override - { - return stdx::error{}; - } - stdx::error set_port_name(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; } timestamp absolute_timestamp() const noexcept override { return 0; } }; @@ -72,20 +62,10 @@ public: { return stdx::error{}; } - stdx::error open_virtual_port(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_client_name(std::string_view /*clientName*/) override - { - return stdx::error{}; - } - stdx::error set_port_name(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override { return stdx::error{}; diff --git a/include/libremidi/backends/emscripten.hpp b/include/libremidi/backends/emscripten.hpp index ccd7dd2..d948fef 100644 --- a/include/libremidi/backends/emscripten.hpp +++ b/include/libremidi/backends/emscripten.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct emscripten_backend diff --git a/include/libremidi/backends/emscripten/config.hpp b/include/libremidi/backends/emscripten/config.hpp index 6a1d735..869ea96 100644 --- a/include/libremidi/backends/emscripten/config.hpp +++ b/include/libremidi/backends/emscripten/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct emscripten_input_configuration diff --git a/include/libremidi/backends/emscripten/helpers.hpp b/include/libremidi/backends/emscripten/helpers.hpp index 3064ae1..9a30fa6 100644 --- a/include/libremidi/backends/emscripten/helpers.hpp +++ b/include/libremidi/backends/emscripten/helpers.hpp @@ -3,7 +3,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { namespace webmidi_helpers { diff --git a/include/libremidi/backends/emscripten/midi_access.hpp b/include/libremidi/backends/emscripten/midi_access.hpp index 3ca9f9e..fc5f171 100644 --- a/include/libremidi/backends/emscripten/midi_access.hpp +++ b/include/libremidi/backends/emscripten/midi_access.hpp @@ -16,7 +16,7 @@ EMSCRIPTEN_KEEPALIVE void libremidi_devices_input(int port, double timestamp, int len, char* bytes); } -namespace libremidi +NAMESPACE_LIBREMIDI { namespace webmidi_helpers { @@ -37,16 +37,25 @@ public: int input_count() const noexcept { + if (!available()) + return 0; + return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;); } int output_count() const noexcept { + if (!available()) + return 0; + return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;); } void load_current_infos() noexcept { + if (!available()) + return; + #define get_js_string(variable_to_read, ...) \ (char*)EM_ASM_INT( \ { \ @@ -206,6 +215,9 @@ public: stdx::error send_message(int port_index, const char* bytes, int len) { + if (!available()) + return std::errc::operation_not_supported; + const auto& id = m_current_outputs[port_index].id; EM_ASM( { @@ -216,6 +228,8 @@ public: output.send(Array.from(bytes)); }, bytes, len, id.c_str()); + + return stdx::error{}; } const std::vector& inputs() const noexcept { return m_current_inputs; } @@ -248,6 +262,9 @@ private: void start_stream(int port_index) { + if (!available()) + return; + // Isn't life great... // https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays const auto& id = m_current_inputs[port_index].id; @@ -282,6 +299,9 @@ private: void stop_stream(int port_index) { + if (!available()) + return; + const auto& id = m_current_inputs[port_index].id; EM_ASM(const id = UTF8ToString($1); diff --git a/include/libremidi/backends/emscripten/midi_in.cpp b/include/libremidi/backends/emscripten/midi_in.cpp index 7b37356..3ca7476 100644 --- a/include/libremidi/backends/emscripten/midi_in.cpp +++ b/include/libremidi/backends/emscripten/midi_in.cpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten( input_configuration&& conf, emscripten_input_configuration&& apiconf) @@ -32,8 +32,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std:: if (portNumber < 0 || portNumber >= midi.input_count()) { - libremidi_handle_error( - this->configuration, "no MIDI output sources found."); + libremidi_handle_error(this->configuration, "no MIDI output sources found."); return std::errc::invalid_argument; } diff --git a/include/libremidi/backends/emscripten/midi_in.hpp b/include/libremidi/backends/emscripten/midi_in.hpp index feacd37..294c61f 100644 --- a/include/libremidi/backends/emscripten/midi_in.hpp +++ b/include/libremidi/backends/emscripten/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_emscripten final : public midi1::in_api diff --git a/include/libremidi/backends/emscripten/midi_out.cpp b/include/libremidi/backends/emscripten/midi_out.cpp index 0fd89cf..e08d2ad 100644 --- a/include/libremidi/backends/emscripten/midi_out.cpp +++ b/include/libremidi/backends/emscripten/midi_out.cpp @@ -2,7 +2,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten( output_configuration&& conf, emscripten_output_configuration&& apiconf) @@ -29,8 +29,7 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std: if (portNumber >= midi.output_count()) { - libremidi_handle_error( - this->configuration, "no MIDI output sources found."); + libremidi_handle_error(this->configuration, "no MIDI output sources found."); return std::errc::invalid_argument; } @@ -38,16 +37,19 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std: return stdx::error{}; } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(const output_port& p, std::string_view nm) +LIBREMIDI_INLINE stdx::error +midi_out_emscripten::open_port(const output_port& p, std::string_view nm) { return open_port(p.port, nm); } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() { +LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() +{ return stdx::error{}; } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size) +LIBREMIDI_INLINE stdx::error +midi_out_emscripten::send_message(const unsigned char* message, size_t size) { if (m_portNumber < 0) libremidi_handle_error( diff --git a/include/libremidi/backends/emscripten/midi_out.hpp b/include/libremidi/backends/emscripten/midi_out.hpp index a62849d..62f3b55 100644 --- a/include/libremidi/backends/emscripten/midi_out.hpp +++ b/include/libremidi/backends/emscripten/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_emscripten final : public midi1::out_api diff --git a/include/libremidi/backends/emscripten/observer.cpp b/include/libremidi/backends/emscripten/observer.cpp index 9cdda2d..b67a38b 100644 --- a/include/libremidi/backends/emscripten/observer.cpp +++ b/include/libremidi/backends/emscripten/observer.cpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE observer_emscripten::observer_emscripten( observer_configuration&& conf, emscripten_observer_configuration&& apiconf) @@ -42,7 +42,8 @@ static auto to_port_info(int index, const webmidi_helpers::device_information& d -> std::conditional_t { return { - {.client = 0, + {.api = libremidi::API::WEBMIDI, + .client = 0, .port = (uint64_t)index, .manufacturer = "", .device_name = "", diff --git a/include/libremidi/backends/emscripten/observer.hpp b/include/libremidi/backends/emscripten/observer.hpp index 017785d..81fb730 100644 --- a/include/libremidi/backends/emscripten/observer.hpp +++ b/include/libremidi/backends/emscripten/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_emscripten final : public observer_api { diff --git a/include/libremidi/backends/jack.hpp b/include/libremidi/backends/jack.hpp index 7b52368..c703ecb 100644 --- a/include/libremidi/backends/jack.hpp +++ b/include/libremidi/backends/jack.hpp @@ -13,7 +13,7 @@ // // *********************************************************************// -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_backend { diff --git a/include/libremidi/backends/jack/config.hpp b/include/libremidi/backends/jack/config.hpp index d99ed92..6feabcb 100644 --- a/include/libremidi/backends/jack/config.hpp +++ b/include/libremidi/backends/jack/config.hpp @@ -11,7 +11,7 @@ typedef uint32_t jack_nframes_t; typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg); } -namespace libremidi +NAMESPACE_LIBREMIDI { using jack_callback_function = std::function; struct jack_callback diff --git a/include/libremidi/backends/jack/error_domain.hpp b/include/libremidi/backends/jack/error_domain.hpp index f63f27f..bc82ef8 100644 --- a/include/libremidi/backends/jack/error_domain.hpp +++ b/include/libremidi/backends/jack/error_domain.hpp @@ -11,7 +11,7 @@ #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_error_domain : public stdx::error_domain { @@ -34,36 +34,36 @@ public: stdx::string_ref message(const stdx::error& e) const noexcept override { const auto status = error_cast(e); - if(status == jack_status_t{}) + if (status == jack_status_t{}) return "Success"; - if(status & JackInvalidOption) + if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option"; - if(status & JackServerFailed) + if (status & JackServerFailed) return "Unable to connect to the JACK server"; - if(status & JackServerError) + if (status & JackServerError) return "Communication error with the JACK server"; - if(status & JackNoSuchClient) + if (status & JackNoSuchClient) return "Requested client does not exist"; - if(status & JackLoadFailure) + if (status & JackLoadFailure) return "Unable to load internal client"; - if(status & JackInitFailure) + if (status & JackInitFailure) return "Unable to initialize client"; - if(status & JackShmFailure) + if (status & JackShmFailure) return "Unable to access shared memory"; - if(status & JackVersionError) + if (status & JackVersionError) return "Client's protocol version does not match"; - if(status & JackBackendError) + if (status & JackBackendError) return "Backend error"; - if(status & JackClientZombie) + if (status & JackClientZombie) return "Client zombified failure"; - if(status & JackFailure) + if (status & JackFailure) return "Failure"; - if(status & JackNameNotUnique) + if (status & JackNameNotUnique) return "The desired client name was not unique"; // Can't happen in libremidi as we set JackNoStartServer - if(status & JackServerStarted) + if (status & JackServerStarted) return "Server was started"; return "Unknown JACK status code"; diff --git a/include/libremidi/backends/jack/helpers.hpp b/include/libremidi/backends/jack/helpers.hpp index 4811ca5..1bf5f27 100644 --- a/include/libremidi/backends/jack/helpers.hpp +++ b/include/libremidi/backends/jack/helpers.hpp @@ -9,7 +9,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_client { @@ -41,21 +41,22 @@ struct jack_client } } - template + template static auto to_port_info(jack_client_t* client, jack_port_t* port) -> std::conditional_t { return {{ + .api = Api, .client = reinterpret_cast(client), .port = 0, .manufacturer = "", - .device_name = "", + .device_name = jack_get_client_name(client), .port_name = jack_port_name(port), .display_name = get_port_display_name(port), }}; } - template + template static auto get_ports( jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags, bool midi2) noexcept -> std::vector> @@ -78,7 +79,7 @@ struct jack_client if (port) { if (bool(midi2) == bool(jack_port_flags(port) & 0x20)) - ret.push_back(to_port_info(client, port)); + ret.push_back(to_port_info(client, port)); } i++; } @@ -137,13 +138,13 @@ struct jack_helpers : jack_client configuration.set_process_func( {.token = this_instance, .callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int { - if (auto pt = p.lock()) - if (pt->load()) - self.process(nf); + if (auto pt = p.lock()) + if (pt->load()) + self.process(nf); - self.thread_lock.check_client_released(); - return 0; - }}); + self.thread_lock.check_client_released(); + return 0; + }}); this->client = configuration.context; return jack_status_t{}; @@ -155,26 +156,25 @@ struct jack_helpers : jack_client = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); if (this->client != nullptr) { - if(status & JackNameNotUnique) { - self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed."); + if (status & JackNameNotUnique) + { + self.libremidi_handle_warning( + self.configuration, "JACK client with the same name already exists, renamed."); } - jack_set_process_callback( - this->client, - +[](jack_nframes_t nf, void* ctx) -> int { - auto& self = *static_cast(ctx); - jack_port_t* port = self.port; + jack_set_process_callback(this->client, +[](jack_nframes_t nf, void* ctx) -> int { + auto& self = *static_cast(ctx); + jack_port_t* port = self.port; - // Is port created? - if (port == nullptr) - return 0; + // Is port created? + if (port == nullptr) + return 0; - self.process(nf); + self.process(nf); - self.thread_lock.check_client_released(); - return 0; - }, - &self); + self.thread_lock.check_client_released(); + return 0; + }, &self); jack_activate(this->client); } return status; @@ -208,8 +208,7 @@ struct jack_helpers : jack_client if (self.configuration.client_name.size() + portName.size() + 2u >= static_cast(jack_port_name_size())) { - self.libremidi_handle_error( - self.configuration, "port name length limit exceeded"); + self.libremidi_handle_error(self.configuration, "port name length limit exceeded"); return std::errc::invalid_argument; } diff --git a/include/libremidi/backends/jack/midi_in.hpp b/include/libremidi/backends/jack/midi_in.hpp index f49e582..2f7467b 100644 --- a/include/libremidi/backends/jack/midi_in.hpp +++ b/include/libremidi/backends/jack/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_jack final : public midi1::in_api @@ -53,8 +53,8 @@ public: err != 0 && err != EEXIST) { libremidi_handle_error( - configuration, "could not connect to port: " + port.port_name + " -> " - + jack_port_name(this->port)); + configuration, + "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port)); return from_errc(err); } return stdx::error{}; diff --git a/include/libremidi/backends/jack/midi_out.hpp b/include/libremidi/backends/jack/midi_out.hpp index 544662a..fcac4f9 100644 --- a/include/libremidi/backends/jack/midi_out.hpp +++ b/include/libremidi/backends/jack/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_jack : public midi1::out_api @@ -38,8 +38,7 @@ public: if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); err != 0 && err != EEXIST) { - libremidi_handle_error( - configuration, "could not connect to port" + port.port_name); + libremidi_handle_error(configuration, "could not connect to port" + port.port_name); return from_errc(err); } @@ -167,7 +166,7 @@ public: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/jack/observer.hpp b/include/libremidi/backends/jack/observer.hpp index f1467e2..87e6651 100644 --- a/include/libremidi/backends/jack/observer.hpp +++ b/include/libremidi/backends/jack/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_jack final : public observer_api @@ -70,7 +70,8 @@ public: { seen_input_ports.insert(ports[i]); if (this->configuration.input_added && configuration.notify_in_constructor) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } i++; } @@ -101,7 +102,8 @@ public: { seen_output_ports.insert(ports[i]); if (this->configuration.output_added && configuration.notify_in_constructor) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } i++; } @@ -138,13 +140,15 @@ public: { seen_input_ports.insert(name); if (this->configuration.input_added) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } else if (flags & JackPortIsInput) { seen_output_ports.insert(name); if (this->configuration.output_added) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } } else @@ -152,13 +156,15 @@ public: if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) { if (this->configuration.input_removed) - this->configuration.input_removed(to_port_info(client, port)); + this->configuration.input_removed( + to_port_info(client, port)); seen_input_ports.erase(it); } if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) { if (this->configuration.output_removed) - this->configuration.output_removed(to_port_info(client, port)); + this->configuration.output_removed( + to_port_info(client, port)); seen_output_ports.erase(it); } } @@ -171,39 +177,37 @@ public: if (!configuration.has_callbacks()) return; - jack_set_port_registration_callback( - this->client, - +[](jack_port_id_t p, int r, void* arg) { - auto& self = *(observer_jack*)arg; - if (auto port = jack_port_by_id(self.client, p)) - { - self.on_port_callback(port, r != 0); - } - }, - this); + jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) { + auto& self = *(observer_jack*)arg; + if (auto port = jack_port_by_id(self.client, p)) + { + self.on_port_callback(port, r != 0); + } + }, this); jack_set_port_rename_callback( this->client, +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) { - const auto& self = *static_cast(arg); + const auto& self = *static_cast(arg); - auto port = jack_port_by_id(self.client, p); - if (!port) - return; - }, - this); + auto port = jack_port_by_id(self.client, p); + if (!port) + return; + }, this); } libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; } std::vector get_input_ports() const noexcept override { - return get_ports(this->client, nullptr, port_type, JackPortIsOutput, false); + return get_ports( + this->client, nullptr, port_type, JackPortIsOutput, false); } std::vector get_output_ports() const noexcept override { - return get_ports(this->client, nullptr, port_type, JackPortIsInput, false); + return get_ports( + this->client, nullptr, port_type, JackPortIsInput, false); } ~observer_jack() diff --git a/include/libremidi/backends/jack/shared_handler.hpp b/include/libremidi/backends/jack/shared_handler.hpp index 0ab18d0..b44f37d 100644 --- a/include/libremidi/backends/jack/shared_handler.hpp +++ b/include/libremidi/backends/jack/shared_handler.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi::jack +NAMESPACE_LIBREMIDI::jack { // Create a JACK client which will be shared across objects @@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context client = jack_client_open(v.data(), JackNoStartServer, &status); assert(client); assert(status == 0); - jack_set_process_callback( - client, - +[](jack_nframes_t cnt, void* ctx) -> int { - ((shared_handler*)ctx)->jack_callback(cnt); - return 0; - }, - this); + jack_set_process_callback(client, +[](jack_nframes_t cnt, void* ctx) -> int { + ((shared_handler*)ctx)->jack_callback(cnt); + return 0; + }, this); } virtual void start_processing() override { jack_activate(client); } @@ -73,11 +70,10 @@ struct shared_handler : public libremidi::shared_context switch (ev.type) { case in_callback_added: - midiin_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + midiin_callbacks.push_back(std::move(*get_if(&ev.payload))); break; case in_callback_removed: { - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();) { if (it->token == idx) @@ -93,11 +89,10 @@ struct shared_handler : public libremidi::shared_context break; } case out_callback_added: - midiout_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + midiout_callbacks.push_back(std::move(*get_if(&ev.payload))); break; case out_callback_removed: - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();) { if (it->token == idx) @@ -141,7 +136,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; boost::lockfree::spsc_queue events{16}; diff --git a/include/libremidi/backends/jack_ump.hpp b/include/libremidi/backends/jack_ump.hpp index 2246c24..89b6a9e 100644 --- a/include/libremidi/backends/jack_ump.hpp +++ b/include/libremidi/backends/jack_ump.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi::jack_ump +NAMESPACE_LIBREMIDI::jack_ump { struct backend { diff --git a/include/libremidi/backends/jack_ump/config.hpp b/include/libremidi/backends/jack_ump/config.hpp index e828e25..ab5fe8b 100644 --- a/include/libremidi/backends/jack_ump/config.hpp +++ b/include/libremidi/backends/jack_ump/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi::jack_ump +NAMESPACE_LIBREMIDI::jack_ump { struct input_configuration { diff --git a/include/libremidi/backends/jack_ump/midi_in.hpp b/include/libremidi/backends/jack_ump/midi_in.hpp index 7aa6087..6b185b3 100644 --- a/include/libremidi/backends/jack_ump/midi_in.hpp +++ b/include/libremidi/backends/jack_ump/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi::jack_ump +NAMESPACE_LIBREMIDI::jack_ump { class midi_in_jack final : public midi2::in_api diff --git a/include/libremidi/backends/jack_ump/midi_out.hpp b/include/libremidi/backends/jack_ump/midi_out.hpp index 643070b..a6ebaa7 100644 --- a/include/libremidi/backends/jack_ump/midi_out.hpp +++ b/include/libremidi/backends/jack_ump/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi::jack_ump +NAMESPACE_LIBREMIDI::jack_ump { class midi_out_jack : public midi2::out_api @@ -170,7 +170,7 @@ public: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr diff --git a/include/libremidi/backends/jack_ump/observer.hpp b/include/libremidi/backends/jack_ump/observer.hpp index 699166a..0db4160 100644 --- a/include/libremidi/backends/jack_ump/observer.hpp +++ b/include/libremidi/backends/jack_ump/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi::jack_ump +NAMESPACE_LIBREMIDI::jack_ump { class observer_jack final : public observer_api @@ -77,7 +77,8 @@ public: { seen_input_ports.insert(ports[i]); if (this->configuration.input_added && configuration.notify_in_constructor) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } i++; } @@ -114,7 +115,8 @@ public: { seen_output_ports.insert(ports[i]); if (this->configuration.output_added && configuration.notify_in_constructor) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } i++; } @@ -154,13 +156,15 @@ public: { seen_input_ports.insert(name); if (this->configuration.input_added) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } else if (flags & JackPortIsInput) { seen_output_ports.insert(name); if (this->configuration.output_added) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } } else @@ -168,13 +172,15 @@ public: if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) { if (this->configuration.input_removed) - this->configuration.input_removed(to_port_info(client, port)); + this->configuration.input_removed( + to_port_info(client, port)); seen_input_ports.erase(it); } if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) { if (this->configuration.output_removed) - this->configuration.output_removed(to_port_info(client, port)); + this->configuration.output_removed( + to_port_info(client, port)); seen_output_ports.erase(it); } } @@ -210,12 +216,14 @@ public: std::vector get_input_ports() const noexcept override { - return get_ports(this->client, nullptr, port_type, JackPortIsOutput, true); + return get_ports( + this->client, nullptr, port_type, JackPortIsOutput, true); } std::vector get_output_ports() const noexcept override { - return get_ports(this->client, nullptr, port_type, JackPortIsInput, true); + return get_ports( + this->client, nullptr, port_type, JackPortIsInput, true); } ~observer_jack() diff --git a/include/libremidi/backends/kdmapi.hpp b/include/libremidi/backends/kdmapi.hpp new file mode 100644 index 0000000..46846f9 --- /dev/null +++ b/include/libremidi/backends/kdmapi.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include + +#include + +//*********************************************************************// +// API: OmniMIDI KDMAPI (Keppy's Direct MIDI API) +//*********************************************************************// + +// OmniMIDI is a low-latency MIDI synthesizer driver for Windows. +// KDMAPI provides a direct interface to OmniMIDI, bypassing the +// Windows Multimedia API for lower latency. +// +// Note: KDMAPI is output-only. The midi_in class is a placeholder +// that always fails - use a different backend for MIDI input. +// +// https://github.com/KeppySoftware/OmniMIDI + +namespace libremidi +{ + +struct kdmapi_backend +{ + using midi_in = kdmapi::midi_in; + using midi_out = kdmapi::midi_out; + using midi_observer = kdmapi::observer; + using midi_in_configuration = kdmapi::input_configuration; + using midi_out_configuration = kdmapi::output_configuration; + using midi_observer_configuration = kdmapi::observer_configuration; + static const constexpr auto API = libremidi::API::KDMAPI; + static const constexpr std::string_view name = "kdmapi"; + static const constexpr std::string_view display_name = "OmniMIDI (KDMAPI)"; + + static inline bool available() noexcept + { + return kdmapi::kdmapi_loader::instance().is_available(); + } +}; + +} + diff --git a/include/libremidi/backends/kdmapi/config.hpp b/include/libremidi/backends/kdmapi/config.hpp new file mode 100644 index 0000000..df55793 --- /dev/null +++ b/include/libremidi/backends/kdmapi/config.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +namespace libremidi::kdmapi +{ +// KDMAPI does not support MIDI input +struct input_configuration +{ +}; + +struct output_configuration +{ + // Use the no-buffer variant of SendDirectData for lowest latency + // This bypasses OmniMIDI's internal buffer + bool use_no_buffer = false; +}; + +struct observer_configuration +{ +}; + +} + diff --git a/include/libremidi/backends/kdmapi/helpers.hpp b/include/libremidi/backends/kdmapi/helpers.hpp new file mode 100644 index 0000000..affcf1b --- /dev/null +++ b/include/libremidi/backends/kdmapi/helpers.hpp @@ -0,0 +1,178 @@ +#pragma once +// clang-format off +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN 1 + +#include +#include +// clang-format on + +#include + +#include +#include + +namespace libremidi::kdmapi +{ + +using IsKDMAPIAvailable_t = BOOL(WINAPI*)(); +using InitializeKDMAPIStream_t = BOOL(WINAPI*)(); +using TerminateKDMAPIStream_t = BOOL(WINAPI*)(); +using ResetKDMAPIStream_t = VOID(WINAPI*)(); +using SendDirectData_t = VOID(WINAPI*)(DWORD); +using SendDirectDataNoBuf_t = VOID(WINAPI*)(DWORD); +using SendDirectLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using SendDirectLongDataNoBuf_t = UINT(WINAPI*)(LPSTR, DWORD); +using PrepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using UnprepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using ReturnKDMAPIVer_t = BOOL(WINAPI*)(LPDWORD, LPDWORD, LPDWORD, LPDWORD); + +class kdmapi_loader +{ +public: + static kdmapi_loader& instance() + { + static kdmapi_loader loader; + return loader; + } + + bool is_available() const noexcept { return m_available; } + + HMODULE handle() const noexcept { return m_handle; } + + // KDMAPI function pointers + IsKDMAPIAvailable_t IsKDMAPIAvailable{}; + InitializeKDMAPIStream_t InitializeKDMAPIStream{}; + TerminateKDMAPIStream_t TerminateKDMAPIStream{}; + ResetKDMAPIStream_t ResetKDMAPIStream{}; + SendDirectData_t SendDirectData{}; + SendDirectDataNoBuf_t SendDirectDataNoBuf{}; + SendDirectLongData_t SendDirectLongData{}; + SendDirectLongDataNoBuf_t SendDirectLongDataNoBuf{}; + PrepareLongData_t PrepareLongData{}; + UnprepareLongData_t UnprepareLongData{}; + ReturnKDMAPIVer_t ReturnKDMAPIVer{}; + +private: + kdmapi_loader() + { + // Try to get OmniMIDI if it's already loaded + m_handle = GetModuleHandleW(L"OmniMIDI"); + if (!m_handle) + { + // Try to load it explicitly + m_handle = LoadLibraryW(L"OmniMIDI.dll"); + } + + if (!m_handle) + { + m_available = false; + return; + } + +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + + // Load all function pointers + IsKDMAPIAvailable + = reinterpret_cast(GetProcAddress(m_handle, "IsKDMAPIAvailable")); + InitializeKDMAPIStream = reinterpret_cast( + GetProcAddress(m_handle, "InitializeKDMAPIStream")); + TerminateKDMAPIStream = reinterpret_cast( + GetProcAddress(m_handle, "TerminateKDMAPIStream")); + ResetKDMAPIStream + = reinterpret_cast(GetProcAddress(m_handle, "ResetKDMAPIStream")); + SendDirectData + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectData")); + SendDirectDataNoBuf + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectDataNoBuf")); + SendDirectLongData + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectLongData")); + SendDirectLongDataNoBuf = reinterpret_cast( + GetProcAddress(m_handle, "SendDirectLongDataNoBuf")); + PrepareLongData + = reinterpret_cast(GetProcAddress(m_handle, "PrepareLongData")); + UnprepareLongData + = reinterpret_cast(GetProcAddress(m_handle, "UnprepareLongData")); + ReturnKDMAPIVer + = reinterpret_cast(GetProcAddress(m_handle, "ReturnKDMAPIVer")); + +#if !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif + + // Check if the minimum required functions are available + if (!IsKDMAPIAvailable || !InitializeKDMAPIStream || !TerminateKDMAPIStream || !SendDirectData) + { + m_available = false; + return; + } + + // Call IsKDMAPIAvailable to activate KDMAPI mode in OmniMIDI + m_available = IsKDMAPIAvailable() != FALSE; + } + + ~kdmapi_loader() + { + } + + kdmapi_loader(const kdmapi_loader&) = delete; + kdmapi_loader& operator=(const kdmapi_loader&) = delete; + + HMODULE m_handle{}; + bool m_available{false}; +}; + +class kdmapi_stream_manager +{ +public: + static kdmapi_stream_manager& instance() + { + static kdmapi_stream_manager mgr; + return mgr; + } + + bool acquire() + { + std::lock_guard lock{m_mutex}; + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return false; + + if (m_rc == 0) + { + if (!loader.InitializeKDMAPIStream()) + return false; + } + ++m_rc; + return true; + } + + void release() + { + std::lock_guard lock{m_mutex}; + if (m_rc == 0) + return; + + --m_rc; + if (m_rc == 0) + { + auto& loader = kdmapi_loader::instance(); + if (loader.TerminateKDMAPIStream) + loader.TerminateKDMAPIStream(); + } + } + +private: + kdmapi_stream_manager() = default; + ~kdmapi_stream_manager() = default; + kdmapi_stream_manager(const kdmapi_stream_manager&) = delete; + kdmapi_stream_manager& operator=(const kdmapi_stream_manager&) = delete; + + std::mutex m_mutex; + std::size_t m_rc{0}; +}; + +} diff --git a/include/libremidi/backends/kdmapi/midi_in.hpp b/include/libremidi/backends/kdmapi/midi_in.hpp new file mode 100644 index 0000000..fa8eeaf --- /dev/null +++ b/include/libremidi/backends/kdmapi/midi_in.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include + +namespace libremidi::kdmapi +{ + +using midi_in = libremidi::midi_in_dummy; + +} diff --git a/include/libremidi/backends/kdmapi/midi_out.hpp b/include/libremidi/backends/kdmapi/midi_out.hpp new file mode 100644 index 0000000..5ade417 --- /dev/null +++ b/include/libremidi/backends/kdmapi/midi_out.hpp @@ -0,0 +1,173 @@ +#pragma once +#include +#include +#include +#include + +namespace libremidi::kdmapi +{ + +class midi_out final + : public midi1::out_api + , public error_handler +{ +public: + struct + : libremidi::output_configuration + , kdmapi::output_configuration + { + } configuration; + + midi_out(libremidi::output_configuration&& conf, kdmapi::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + { + libremidi_handle_error( + configuration, "KDMAPI is not available. Is OmniMIDI installed?"); + this->client_open_ = std::errc::no_such_device; + return; + } + + this->client_open_ = stdx::error{}; + } + + ~midi_out() override + { + midi_out::close_port(); + this->client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; } + + stdx::error open_port(const output_port& port, std::string_view) override + { + if (port.api != libremidi::API::KDMAPI) + { + libremidi_handle_error(configuration, "port is not a KDMAPI port"); + return std::errc::invalid_argument; + } + + return do_open(); + } + + stdx::error do_open() + { + auto& mgr = kdmapi_stream_manager::instance(); + if (!mgr.acquire()) + { + libremidi_handle_error(configuration, "failed to initialize KDMAPI stream"); + return std::errc::io_error; + } + + connected_ = true; + return stdx::error{}; + } + + stdx::error close_port() override + { + if (connected_) + { + auto& mgr = kdmapi_stream_manager::instance(); + mgr.release(); + connected_ = false; + } + return stdx::error{}; + } + + stdx::error send_message(const unsigned char* message, size_t size) override + { + if (!connected_) + return std::errc::not_connected; + + if (size == 0) + { + libremidi_handle_warning(configuration, "message argument is empty!"); + return std::errc::invalid_argument; + } + + auto& loader = kdmapi_loader::instance(); + + if (message[0] == 0xF0) + { + // SysEx message + return send_sysex(message, size); + } + else + { + // Channel or system message + if (size > 3) + { + libremidi_handle_warning( + configuration, "message size is greater than 3 bytes (and not sysex)!"); + return std::errc::message_size; + } + + // Pack MIDI bytes into DWORD + DWORD packet = 0; + std::copy_n(message, size, reinterpret_cast(&packet)); + + // Send the message + if (configuration.use_no_buffer && loader.SendDirectDataNoBuf) + loader.SendDirectDataNoBuf(packet); + else + loader.SendDirectData(packet); + } + + return stdx::error{}; + } + +private: + stdx::error send_sysex(const unsigned char* message, size_t size) + { + auto& loader = kdmapi_loader::instance(); + + if (configuration.use_no_buffer && loader.SendDirectLongDataNoBuf) + { + // Use the no-buffer variant - it takes raw data directly + loader.SendDirectLongDataNoBuf( + const_cast(reinterpret_cast(message)), static_cast(size)); + return stdx::error{}; + } + + // Standard path with MIDIHDR + buffer_.assign(message, message + size); + + MIDIHDR sysex{}; + sysex.lpData = reinterpret_cast(buffer_.data()); + sysex.dwBufferLength = static_cast(size); + sysex.dwFlags = 0; + + if (loader.PrepareLongData) + { + UINT result = loader.PrepareLongData(&sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) + { + libremidi_handle_error(configuration, "error preparing sysex header"); + return std::errc::io_error; + } + } + + if (loader.SendDirectLongData) + { + UINT result = loader.SendDirectLongData(&sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) + { + libremidi_handle_error(configuration, "error sending sysex message"); + // Still try to unprepare + } + } + + if (loader.UnprepareLongData) + { + loader.UnprepareLongData(&sysex, sizeof(MIDIHDR)); + } + + return stdx::error{}; + } + + std::vector buffer_; +}; + +} diff --git a/include/libremidi/backends/kdmapi/observer.hpp b/include/libremidi/backends/kdmapi/observer.hpp new file mode 100644 index 0000000..19b5df2 --- /dev/null +++ b/include/libremidi/backends/kdmapi/observer.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include + +namespace libremidi::kdmapi +{ + +class observer final : public observer_api +{ +public: + struct + : libremidi::observer_configuration + , kdmapi::observer_configuration + { + } configuration; + + explicit observer( + libremidi::observer_configuration&& conf, kdmapi::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (!configuration.has_callbacks()) + return; + + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return; + + // KDMAPI provides a single virtual output port + if (configuration.notify_in_constructor) + { + if (configuration.output_added) + { + auto ports = get_output_ports(); + for (const auto& port : ports) + configuration.output_added(port); + } + } + } + + ~observer() = default; + + libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; } + + std::vector get_input_ports() const noexcept override + { + // KDMAPI does not provide input ports + return {}; + } + + std::vector get_output_ports() const noexcept override + { + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return {}; + + // Get KDMAPI version for display name + std::string display_name = "OmniMIDI"; + if (loader.ReturnKDMAPIVer) + { + DWORD major{}, minor{}, build{}, rev{}; + if (loader.ReturnKDMAPIVer(&major, &minor, &build, &rev)) + { + display_name += " (KDMAPI "; + display_name += std::to_string(major); + display_name += "."; + display_name += std::to_string(minor); + display_name += "."; + display_name += std::to_string(build); + display_name += ")"; + } + } + + return {libremidi::output_port{ + {.api = libremidi::API::KDMAPI, + .client = 0, + .port = 0, + .manufacturer = "KeppySoftware", + .device_name = "OmniMIDI", + .port_name = "OmniMIDI", + .display_name = std::move(display_name)}, + }}; + } +}; + +} diff --git a/include/libremidi/backends/keyboard.hpp b/include/libremidi/backends/keyboard.hpp index 6d18a31..650b510 100644 --- a/include/libremidi/backends/keyboard.hpp +++ b/include/libremidi/backends/keyboard.hpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct kbd_backend { diff --git a/include/libremidi/backends/keyboard/config.hpp b/include/libremidi/backends/keyboard/config.hpp index 401728e..c469abf 100644 --- a/include/libremidi/backends/keyboard/config.hpp +++ b/include/libremidi/backends/keyboard/config.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { enum kbd_event { @@ -36,7 +36,7 @@ enum kbd_event // actually based on macOS virtual codes as it seems // impossible to get raw scan codes. // https://eastmanreference.com/complete-list-of-applescript-key-codes -static inline const std::map& scancode_map_macos(){ +LIBREMIDI_STATIC const std::map& scancode_map_macos(){ static const std::map ret{ { 0, NOTE_0}, // C0 { 13, NOTE_0 + 1}, @@ -83,7 +83,7 @@ static inline const std::map& scancode_map_macos(){ // Use: https://kbdlayout.info/KBDUSX/scancodes // Also valid for windows -static inline const std::map& scancode_map_linux() { +LIBREMIDI_STATIC const std::map& scancode_map_linux() { static const std::map ret{ { 0x1E, NOTE_0}, // C0 { 0x11, NOTE_0 + 1}, diff --git a/include/libremidi/backends/keyboard/midi_in.hpp b/include/libremidi/backends/keyboard/midi_in.hpp index 185b2f9..674ff38 100644 --- a/include/libremidi/backends/keyboard/midi_in.hpp +++ b/include/libremidi/backends/keyboard/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_kbd final : public midi1::in_api diff --git a/include/libremidi/backends/linux/alsa.hpp b/include/libremidi/backends/linux/alsa.hpp index a98a18e..2846ea8 100644 --- a/include/libremidi/backends/linux/alsa.hpp +++ b/include/libremidi/backends/linux/alsa.hpp @@ -13,9 +13,13 @@ #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10) #define LIBREMIDI_ALSA_HAS_UMP 1 #endif + + #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 14) + #define LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS 1 + #endif #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct libasound @@ -278,6 +282,7 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc) + LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real) @@ -292,6 +297,12 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo) LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port) + +#if LIBREMIDI_ALSA_HAS_UMP + LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version) + LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_endpoint_info) + LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_block_info) +#endif } bool available{true}; @@ -336,6 +347,7 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc) + LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real) @@ -352,6 +364,12 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port) +#if LIBREMIDI_ALSA_HAS_UMP + LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version) + LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_endpoint_info) + LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_block_info) +#endif + #if LIBREMIDI_ALSA_HAS_UMP struct ump_t { @@ -362,7 +380,6 @@ struct libasound available = false; return; } - LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct) @@ -370,10 +387,10 @@ struct libasound bool available{true}; - LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct) + } ump; #endif } seq{library}; @@ -389,39 +406,98 @@ struct libasound return; } - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name) - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof) - LIBREMIDI_SYMBOL_INIT(snd_ump, close) - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name) - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof) + // Core UMP functions LIBREMIDI_SYMBOL_INIT(snd_ump, open) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents) + LIBREMIDI_SYMBOL_INIT(snd_ump, close) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_INIT(snd_ump, read) LIBREMIDI_SYMBOL_INIT(snd_ump, tread) LIBREMIDI_SYMBOL_INIT(snd_ump, write) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents) + + // Endpoint info functions + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_card) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_device) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_flags) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol_caps) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_num_blocks) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_version) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_manufacturer_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_family_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_model_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_sw_revision) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_product_id) + + // Block info functions + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_set_block_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_block_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_active) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_flags) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_direction) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_first_group) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_num_groups) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_midi_ci_version) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_sysex8_streams) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_ui_hint) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name) } bool available{true}; - LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name) - LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof) - LIBREMIDI_SYMBOL_DEF(snd_ump, close) - LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name) - LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof) + + // Core UMP functions LIBREMIDI_SYMBOL_DEF(snd_ump, open) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents) + LIBREMIDI_SYMBOL_DEF(snd_ump, close) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_DEF(snd_ump, read) LIBREMIDI_SYMBOL_DEF(snd_ump, tread) LIBREMIDI_SYMBOL_DEF(snd_ump, write) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents) + + // Endpoint info functions + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_card) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_device) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_flags) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol_caps) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_num_blocks) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_version) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_manufacturer_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_family_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_model_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_sw_revision) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_product_id) + + // Block info functions + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_set_block_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_block_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_active) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_flags) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_direction) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_first_group) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_num_groups) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_midi_ci_version) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_sysex8_streams) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_ui_hint) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name) } ump{library}; #endif }; @@ -449,6 +525,8 @@ struct libasound #define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info) #undef snd_seq_port_info_alloca #define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info) +#undef snd_seq_port_subscribe_alloca +#define snd_seq_port_subscribe_alloca(ptr) snd_dylib_alloca(ptr, seq, port_subscribe) #undef snd_seq_queue_tempo_alloca #define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo) diff --git a/include/libremidi/backends/linux/dylib_loader.hpp b/include/libremidi/backends/linux/dylib_loader.hpp index 808e197..80c2490 100644 --- a/include/libremidi/backends/linux/dylib_loader.hpp +++ b/include/libremidi/backends/linux/dylib_loader.hpp @@ -1,10 +1,11 @@ #pragma once +#include #if __has_include() #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class dylib_loader { diff --git a/include/libremidi/backends/linux/helpers.hpp b/include/libremidi/backends/linux/helpers.hpp index b86b461..ccce4c9 100644 --- a/include/libremidi/backends/linux/helpers.hpp +++ b/include/libremidi/backends/linux/helpers.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct eventfd_notifier { diff --git a/include/libremidi/backends/linux/pipewire.hpp b/include/libremidi/backends/linux/pipewire.hpp index 4553cec..171e20c 100644 --- a/include/libremidi/backends/linux/pipewire.hpp +++ b/include/libremidi/backends/linux/pipewire.hpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { #pragma GCC diagnostic push diff --git a/include/libremidi/backends/linux/udev.hpp b/include/libremidi/backends/linux/udev.hpp index bffa857..ff33e8d 100644 --- a/include/libremidi/backends/linux/udev.hpp +++ b/include/libremidi/backends/linux/udev.hpp @@ -8,7 +8,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct libudev @@ -149,7 +149,10 @@ struct udev_soundcard_info { container_identifier container; device_identifier path; - port_information::port_type type{}; + transport_type type{}; + std::string vendor; + std::string product; + std::string serial; }; inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card) @@ -174,18 +177,50 @@ inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, in const auto usb_device = udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"); + const auto pci_device = udev.device_get_parent_with_subsystem_devtype(dev, "pci", nullptr); - port_information::port_type type = port_information::port_type::hardware; + transport_type type = transport_type::hardware; if (usb_device) - type = (port_information::port_type)(type | port_information::port_type::usb); - else - type = (port_information::port_type)(type | port_information::port_type::pci); + type = (transport_type)(type | transport_type::usb); + else if (pci_device) + type = (transport_type)(type | transport_type::pci); auto id_path = udev.device_get_property_value(dev, "ID_PATH"); if (!id_path) id_path = ""; - auto ret = udev_soundcard_info{.container = id_path, .path = path, .type = type}; + auto read_property = [&](const char* property) { + auto res = udev.device_get_property_value(dev, property); + if (!res) + res = ""; + if (strlen(res) == 0) + { + if (usb_device) + { + res = udev.device_get_property_value(usb_device, property); + if (!res) + res = ""; + } + else if (pci_device) + { + res = udev.device_get_property_value(pci_device, property); + if (!res) + res = ""; + } + } + return res; + }; + + auto id_vendor = read_property("ID_VENDOR_FROM_DATABASE"); + auto id_product = read_property("ID_MODEL_FROM_DATABASE"); + auto id_serial = read_property("ID_SERIAL"); + auto ret = udev_soundcard_info{ + .container = id_path, + .path = path, + .type = type, + .vendor = id_vendor, + .product = id_product, + .serial = id_serial}; udev.device_unref(dev); return ret; } diff --git a/include/libremidi/backends/net/config.hpp b/include/libremidi/backends/net/config.hpp index 8365b50..f441557 100644 --- a/include/libremidi/backends/net/config.hpp +++ b/include/libremidi/backends/net/config.hpp @@ -2,12 +2,15 @@ #include #include + +#if !defined(BOOST_ASIO_IO_CONTEXT_HPP) namespace boost::asio { struct io_context; } +#endif -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { enum class protocol { @@ -46,7 +49,7 @@ struct dgram_observer_configuration } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { enum class protocol { diff --git a/include/libremidi/backends/net/helpers.hpp b/include/libremidi/backends/net/helpers.hpp index 11847fc..ca1251b 100644 --- a/include/libremidi/backends/net/helpers.hpp +++ b/include/libremidi/backends/net/helpers.hpp @@ -23,7 +23,7 @@ public: string_ref message(const error& e) const noexcept override { auto err = e.m_value; - + } [[noreturn]] void throw_exception(const error& e) const override @@ -35,7 +35,7 @@ public: */ } -namespace libremidi +NAMESPACE_LIBREMIDI { template struct optionally_owned diff --git a/include/libremidi/backends/net/midi_in.hpp b/include/libremidi/backends/net/midi_in.hpp index 605d73d..fb1b03a 100644 --- a/include/libremidi/backends/net/midi_in.hpp +++ b/include/libremidi/backends/net/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { template struct osc_parser @@ -115,7 +115,7 @@ struct osc_parser } -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { struct osc_parser_midi1 @@ -264,7 +264,7 @@ public: }; } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { struct osc_parser_midi2 { diff --git a/include/libremidi/backends/net/midi_out.hpp b/include/libremidi/backends/net/midi_out.hpp index c77f2f1..ccb4e1e 100644 --- a/include/libremidi/backends/net/midi_out.hpp +++ b/include/libremidi/backends/net/midi_out.hpp @@ -8,7 +8,7 @@ #include #include -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { struct osc_midi1_packet @@ -146,7 +146,7 @@ public: } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { struct osc_midi2_packet { diff --git a/include/libremidi/backends/net/observer.hpp b/include/libremidi/backends/net/observer.hpp index d2b5516..30320b5 100644 --- a/include/libremidi/backends/net/observer.hpp +++ b/include/libremidi/backends/net/observer.hpp @@ -1,13 +1,13 @@ #pragma once #include -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { using net_observer_configuration = libremidi::dummy_configuration; using observer = libremidi::observer_dummy; } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { using net_observer_configuration = libremidi::dummy_configuration; using observer = libremidi::observer_dummy; diff --git a/include/libremidi/backends/network.hpp b/include/libremidi/backends/network.hpp index 9ebc122..f393a99 100644 --- a/include/libremidi/backends/network.hpp +++ b/include/libremidi/backends/network.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { struct backend { diff --git a/include/libremidi/backends/network_ump.hpp b/include/libremidi/backends/network_ump.hpp index 00c4807..90842ba 100644 --- a/include/libremidi/backends/network_ump.hpp +++ b/include/libremidi/backends/network_ump.hpp @@ -6,7 +6,7 @@ #include -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { struct backend { diff --git a/include/libremidi/backends/pipewire.hpp b/include/libremidi/backends/pipewire.hpp index e66760a..914a44d 100644 --- a/include/libremidi/backends/pipewire.hpp +++ b/include/libremidi/backends/pipewire.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi::pipewire +NAMESPACE_LIBREMIDI::pipewire { struct backend { diff --git a/include/libremidi/backends/pipewire/config.hpp b/include/libremidi/backends/pipewire/config.hpp index efb5c25..69efb05 100644 --- a/include/libremidi/backends/pipewire/config.hpp +++ b/include/libremidi/backends/pipewire/config.hpp @@ -12,7 +12,7 @@ struct pw_filter; struct spa_io_position; } -namespace libremidi +NAMESPACE_LIBREMIDI { using pipewire_callback_function = std::function; struct pipewire_callback diff --git a/include/libremidi/backends/pipewire/context.hpp b/include/libremidi/backends/pipewire/context.hpp index 5256904..d13b7af 100644 --- a/include/libremidi/backends/pipewire/context.hpp +++ b/include/libremidi/backends/pipewire/context.hpp @@ -25,7 +25,7 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" -namespace libremidi +NAMESPACE_LIBREMIDI { template using hash_map = std::unordered_map; @@ -188,13 +188,13 @@ struct pipewire_context pipewire_context& self = *(pipewire_context*)object; if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) self.register_port(id, type); - }, + }, .global_remove = [](void* object, uint32_t id) { pipewire_context& self = *(pipewire_context*)object; self.unregister_port(id); - }, - }; + }, + }; // Start listening pw_registry_add_listener(this->registry, &this->registry_listener, ®istry_events, this); @@ -245,8 +245,8 @@ struct pipewire_context // Remove from the listeners auto it = std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) { - return l.id == id; - }); + return l.id == id; + }); if (it != port_listener.end()) { pw.proxy_destroy((pw_proxy*)it->port); @@ -267,14 +267,14 @@ struct pipewire_context static constexpr struct pw_core_events core_events = { .version = PW_VERSION_CORE_EVENTS, .done = - [](void* object, uint32_t id, int seq) { + [](void* object, uint32_t id, int seq) { auto& self = *(pipewire_context*)object; - if(id == PW_ID_CORE && seq == self.pending) + if (id == PW_ID_CORE && seq == self.pending) { self.done = 1; libpipewire::instance().main_loop_quit(self.main_loop); } - }, + }, }; spa_zero(core_listener); diff --git a/include/libremidi/backends/pipewire/helpers.hpp b/include/libremidi/backends/pipewire/helpers.hpp index 3de21c6..a8dc2be 100644 --- a/include/libremidi/backends/pipewire/helpers.hpp +++ b/include/libremidi/backends/pipewire/helpers.hpp @@ -11,7 +11,12 @@ #include #include -namespace libremidi +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wsign-compare" +#endif + +NAMESPACE_LIBREMIDI { struct pipewire_helpers { @@ -82,10 +87,10 @@ struct pipewire_helpers static constexpr struct pw_filter_events filter_events = {.version = PW_VERSION_FILTER_EVENTS, .process = +[](void* _data, struct spa_io_position* position) -> void { - // FIXME likely we need the thread_lock check here too - Self& self = *static_cast(_data); - self.process(position); - }}; + // FIXME likely we need the thread_lock check here too + Self& self = *static_cast(_data); + self.process(position); + }}; #pragma GCC diagnostic pop this->filter->create_filter(self.configuration.client_name, filter_events, &self); @@ -217,6 +222,7 @@ struct pipewire_helpers return stdx::error{}; } + template void add_callbacks(std::string format, const observer_configuration& conf) { assert(global_context); @@ -232,12 +238,12 @@ struct pipewire_helpers if (port.direction == SPA_DIRECTION_INPUT) { if (conf.output_added) - conf.output_added(to_port_info(port)); + conf.output_added(to_port_info(port)); } else { if (conf.input_added) - conf.input_added(to_port_info(port)); + conf.input_added(to_port_info(port)); } } }; @@ -254,12 +260,12 @@ struct pipewire_helpers if (port.direction == SPA_DIRECTION_INPUT) { if (conf.output_removed) - conf.output_removed(to_port_info(port)); + conf.output_removed(to_port_info(port)); } else { if (conf.input_removed) - conf.input_removed(to_port_info(port)); + conf.input_removed(to_port_info(port)); } } }; @@ -407,7 +413,7 @@ struct pipewire_helpers return stdx::error{}; } - template + template static auto to_port_info(const pipewire_context::port_info& port) -> std::conditional_t { @@ -424,6 +430,7 @@ struct pipewire_helpers } return {{ + .api = Api, .client = 0, .port = port.id, .manufacturer = "", @@ -435,7 +442,7 @@ 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 + template static auto get_ports( std::string_view format, const observer_configuration& conf, const pipewire_context& ctx) noexcept @@ -454,7 +461,7 @@ struct pipewire_helpers (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { if (port.format.find(format) != std::string::npos) - ret.push_back(to_port_info(port)); + ret.push_back(to_port_info(port)); } } @@ -465,7 +472,7 @@ struct pipewire_helpers (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { if (port.format.find(format) != std::string::npos) - ret.push_back(to_port_info(port)); + ret.push_back(to_port_info(port)); } } } @@ -474,3 +481,7 @@ struct pipewire_helpers } }; } + +#if defined(__clang__) + #pragma clang diagnostic pop +#endif diff --git a/include/libremidi/backends/pipewire/midi_in.hpp b/include/libremidi/backends/pipewire/midi_in.hpp index 163ef0a..6d0ae97 100644 --- a/include/libremidi/backends/pipewire/midi_in.hpp +++ b/include/libremidi/backends/pipewire/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_pipewire final : public midi1::in_api @@ -74,10 +74,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } timestamp absolute_timestamp() const noexcept override { return system_ns(); } diff --git a/include/libremidi/backends/pipewire/midi_out.hpp b/include/libremidi/backends/pipewire/midi_out.hpp index fcb5d90..38bf1c2 100644 --- a/include/libremidi/backends/pipewire/midi_out.hpp +++ b/include/libremidi/backends/pipewire/midi_out.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_pipewire : public midi1::out_api @@ -79,10 +79,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } int process(spa_io_position* pos) { diff --git a/include/libremidi/backends/pipewire/observer.hpp b/include/libremidi/backends/pipewire/observer.hpp index 57d8127..acc87a3 100644 --- a/include/libremidi/backends/pipewire/observer.hpp +++ b/include/libremidi/backends/pipewire/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_pipewire final : public observer_api @@ -19,14 +19,15 @@ public: { } configuration; - explicit observer_pipewire(observer_configuration&& conf, pipewire_observer_configuration&& apiconf) + explicit observer_pipewire( + observer_configuration&& conf, pipewire_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { create_context(*this); // FIXME port rename callback { - this->add_callbacks("midi", configuration); + this->add_callbacks("midi", configuration); this->start_thread(); } @@ -46,12 +47,14 @@ public: std::vector get_input_ports() const noexcept override { - return get_ports("midi", this->configuration, *this->global_context); + return get_ports( + "midi", this->configuration, *this->global_context); } std::vector get_output_ports() const noexcept override { - return get_ports("midi", this->configuration, *this->global_context); + return get_ports( + "midi", this->configuration, *this->global_context); } ~observer_pipewire() diff --git a/include/libremidi/backends/pipewire/shared_handler.hpp b/include/libremidi/backends/pipewire/shared_handler.hpp index 5dab246..a6e531a 100644 --- a/include/libremidi/backends/pipewire/shared_handler.hpp +++ b/include/libremidi/backends/pipewire/shared_handler.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi::pipewire +NAMESPACE_LIBREMIDI::pipewire { // Create a PipeWire client which will be shared across objects @@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context client = pipewire_client_open(v.data(), PipewireNoStartServer, &status); assert(client); assert(status == 0); - pipewire_set_process_callback( - client, - +[](pipewire_nframes_t cnt, void* ctx) -> int { - ((shared_handler*)ctx)->pipewire_callback(cnt); - return 0; - }, - this); + pipewire_set_process_callback(client, +[](pipewire_nframes_t cnt, void* ctx) -> int { + ((shared_handler*)ctx)->pipewire_callback(cnt); + return 0; + }, this); } virtual void start_processing() override { pipewire_activate(client); } @@ -74,10 +71,10 @@ struct shared_handler : public libremidi::shared_context { case in_callback_added: midiin_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + std::move(*get_if(&ev.payload))); break; case in_callback_removed: { - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();) { if (it->token == idx) @@ -94,10 +91,10 @@ struct shared_handler : public libremidi::shared_context } case out_callback_added: midiout_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + std::move(*get_if(&ev.payload))); break; case out_callback_removed: - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();) { if (it->token == idx) @@ -141,7 +138,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; boost::lockfree::spsc_queue events{16}; diff --git a/include/libremidi/backends/pipewire_ump.hpp b/include/libremidi/backends/pipewire_ump.hpp index 3b1cb0f..eed0a7e 100644 --- a/include/libremidi/backends/pipewire_ump.hpp +++ b/include/libremidi/backends/pipewire_ump.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi::pipewire_ump +NAMESPACE_LIBREMIDI::pipewire_ump { struct backend { diff --git a/include/libremidi/backends/pipewire_ump/config.hpp b/include/libremidi/backends/pipewire_ump/config.hpp index a9dcb58..ab59684 100644 --- a/include/libremidi/backends/pipewire_ump/config.hpp +++ b/include/libremidi/backends/pipewire_ump/config.hpp @@ -2,7 +2,7 @@ #include #include -namespace libremidi::pipewire_ump +NAMESPACE_LIBREMIDI::pipewire_ump { struct input_configuration { diff --git a/include/libremidi/backends/pipewire_ump/midi_in.hpp b/include/libremidi/backends/pipewire_ump/midi_in.hpp index 129f147..05b112b 100644 --- a/include/libremidi/backends/pipewire_ump/midi_in.hpp +++ b/include/libremidi/backends/pipewire_ump/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi::pipewire_ump +NAMESPACE_LIBREMIDI::pipewire_ump { class midi_in_pipewire final : public midi2::in_api @@ -75,10 +75,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } timestamp absolute_timestamp() const noexcept override { return system_ns(); } diff --git a/include/libremidi/backends/pipewire_ump/midi_out.hpp b/include/libremidi/backends/pipewire_ump/midi_out.hpp index f584d77..1c2bbff 100644 --- a/include/libremidi/backends/pipewire_ump/midi_out.hpp +++ b/include/libremidi/backends/pipewire_ump/midi_out.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi::pipewire_ump +NAMESPACE_LIBREMIDI::pipewire_ump { class midi_out_pipewire : public midi2::out_api @@ -81,10 +81,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } int process(spa_io_position* pos) { diff --git a/include/libremidi/backends/pipewire_ump/observer.hpp b/include/libremidi/backends/pipewire_ump/observer.hpp index 7d52db1..e3b1bd8 100644 --- a/include/libremidi/backends/pipewire_ump/observer.hpp +++ b/include/libremidi/backends/pipewire_ump/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi::pipewire_ump +NAMESPACE_LIBREMIDI::pipewire_ump { class observer_pipewire final : public observer_api @@ -28,7 +28,7 @@ public: // FIXME port rename callback { - this->add_callbacks("UMP", configuration); + this->add_callbacks("UMP", configuration); this->start_thread(); } @@ -48,12 +48,14 @@ public: std::vector get_input_ports() const noexcept override { - return get_ports("UMP", this->configuration, *this->global_context); + return get_ports( + "UMP", this->configuration, *this->global_context); } std::vector get_output_ports() const noexcept override { - return get_ports("UMP", this->configuration, *this->global_context); + return get_ports( + "UMP", this->configuration, *this->global_context); } ~observer_pipewire() diff --git a/include/libremidi/backends/winmidi.hpp b/include/libremidi/backends/winmidi.hpp index 9839d4c..a641ab2 100644 --- a/include/libremidi/backends/winmidi.hpp +++ b/include/libremidi/backends/winmidi.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { struct backend { @@ -19,6 +19,8 @@ struct backend static const constexpr std::string_view name = "winmidi"; static const constexpr std::string_view display_name = "Windows MIDI Services"; - static constexpr inline bool available() noexcept { return true; } + static inline bool available() noexcept { + return libremidi::instance()->ready; + } }; -} // namespace libremidi +} // NAMESPACE_LIBREMIDI diff --git a/include/libremidi/backends/winmidi/config.hpp b/include/libremidi/backends/winmidi/config.hpp index a8d1b98..c1ef02f 100644 --- a/include/libremidi/backends/winmidi/config.hpp +++ b/include/libremidi/backends/winmidi/config.hpp @@ -3,15 +3,17 @@ #include +#if !defined(LIBREMIDI_MODULE_BUILD) || !defined(_WIN32) namespace winrt::Microsoft::Windows::Devices::Midi2 { -class MidiSession; -class MidiEndpointDeviceInformation; -class MidiEndpointConnection; +struct MidiSession; +struct MidiEndpointDeviceInformation; +struct MidiEndpointConnection; } +#endif // TODO allow to share midi session and endpoints -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { struct input_configuration diff --git a/include/libremidi/backends/winmidi/helpers.hpp b/include/libremidi/backends/winmidi/helpers.hpp index 50d07c4..8d95a7d 100644 --- a/include/libremidi/backends/winmidi/helpers.hpp +++ b/include/libremidi/backends/winmidi/helpers.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -15,18 +16,20 @@ #include #include -// MinGW support -#if !defined(_MSC_VER) -namespace Microsoft::Windows::Devices::Midi2::Initialization -{ -struct IMidiClientInitializer; -struct MidiClientInitializerUuid; -} +#if __has_include() +#include + #define LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS 1 -__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::IMidiClientInitializer, - 0x8087b303, 0xd551, 0xbce2, 0x1e, 0xad, 0xa2, 0x50, 0x0d, 0x50, 0xc5, 0x80); -__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::MidiClientInitializerUuid, - 0xc3263827, 0xc3b0, 0xbdbd, 0x25, 0x00, 0xce, 0x63, 0xa3, 0xf3, 0xf2, 0xc3); +#define LIBREMIDI_DEFINE_GUID_CONSTEXPR(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + LIBREMIDI_STATIC constexpr const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + +NAMESPACE_LIBREMIDI { + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiEndpointConnectionMessagesReceivedCallback, 0x8087b303, 0x0519, 0x31d1, 0x31, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiEndpointConnectionRaw, 0x8087b303, 0x0519, 0x31d1, 0x31, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiClientInitializer, 0x8087b303, 0xd551, 0xbce2, 0x1e, 0xad, 0xa2, 0x50, 0x0d, 0x50, 0xc5, 0x80); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_MidiClientInitializerUuid, 0xc3263827, 0xc3b0, 0xbdbd, 0x25, 0x00, 0xce, 0x63, 0xa3, 0xf3, 0xf2, 0xc3); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_MidiSrvTransportUuid, 0x2ba15e4e, 0x5417, 0x4a66, 0x85, 0xb8, 0x2b, 0x22, 0x60, 0xef, 0xbc, 0x84); +} #endif #include @@ -35,20 +38,18 @@ __CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::MidiClientIn namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2; namespace foundation = winrt::Windows::Foundation; -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Storage::Streams; using namespace winrt::Microsoft::Windows::Devices::Midi2; -using namespace Windows::Devices::Enumeration; -using namespace ::Microsoft::Windows::Devices::Midi2::Initialization; inline bool ichar_equals(char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); + return std::tolower(static_cast(a)) + == std::tolower(static_cast(b)); } inline bool iequals(std::string_view lhs, std::string_view rhs) @@ -81,16 +82,138 @@ get_port(const std::string& device_name, int group_terminal_block) } return {nullptr, nullptr}; } + +// From Microsoft.Windows.Devices.Midi2.Initialization.hpp +typedef enum +{ + Platform_x64 = 1, + // Platform_Arm64 = 2, + // Platform_Arm64EC = 3, + Platform_Arm64X = 4, +} MidiAppSDKPlatform; + +struct IMidiClientInitializer : ::IUnknown +{ + // returns the SDK version info. Supply nullptr for arguments you don't care about + STDMETHOD(GetInstalledWindowsMidiServicesSdkVersion)( + MidiAppSDKPlatform* buildPlatform, + USHORT* versionMajor, + USHORT* versionMinor, + USHORT* versionPatch, + + LPWSTR* buildSource, + LPWSTR* versionName, + LPWSTR* versionFullString + ) = 0; + + // demand-starts the service if present + STDMETHOD(EnsureServiceAvailable)() = 0; +}; +struct winmidi_shared_data_instance +{ + IMidiClientInitializer* initializer{nullptr}; + bool ready{false}; + winmidi_shared_data_instance() + { + // 1. Check if MIDI Services are available + { + ::IUnknown* servicePointer{ nullptr }; + auto hr = CoCreateInstance( + libremidi::IID_MidiSrvTransportUuid, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&servicePointer) + ); + + if (SUCCEEDED(hr)) + { + if (servicePointer == nullptr) + { + ready = false; + return; + } + + // Here is the good case: + servicePointer->Release(); + servicePointer = nullptr; + ready = true; + } + else if (hr == REGDB_E_CLASSNOTREG) + { + servicePointer = nullptr; + ready = false; + return; + } + else + { + servicePointer = nullptr; + ready = false; + return; + } + } + + // 2. Check if MIDI services can be created + { + if (SUCCEEDED(CoCreateInstance( + libremidi::IID_MidiClientInitializerUuid, + NULL, + CLSCTX::CLSCTX_INPROC_SERVER | CLSCTX::CLSCTX_FROM_DEFAULT_CONTEXT, + libremidi::IID_IMidiClientInitializer, + reinterpret_cast(&initializer) + ))) + { + if (initializer != nullptr) + { + ready = true; + } + else + { + ready = false; + return; + } + } + else + { + ready = false; + return; + } + } + + // 3. Check if MIDI services can be used + if (SUCCEEDED(initializer->EnsureServiceAvailable())) + { + ready = true; + } + else + { + ready = false; + initializer->Release(); + initializer = nullptr; + return; + } + } + + ~winmidi_shared_data_instance() + { + if (initializer != nullptr) + { + initializer->Release(); + initializer = nullptr; + } + } +}; + struct winmidi_shared_data { - std::shared_ptr initializer; - bool m_ready{false}; + std::shared_ptr self = libremidi::instance(); + winmidi_shared_data() - : initializer{libremidi::instance()} - , m_ready{ - initializer && initializer->InitializeSdkRuntime() - && initializer->EnsureServiceAvailable()} + { + } + + ~winmidi_shared_data() { } }; + } diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index f37c861..b8d0fc4 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { class midi_in_impl final : public midi2::in_api @@ -19,6 +19,56 @@ public: { } configuration; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + struct raw_callback_type final : IMidiEndpointConnectionMessagesReceivedCallback + { + explicit raw_callback_type(midi_in_impl& self) + : self{self} + { + + } + + midi_in_impl& self; + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + { + if (!ppvObject) + return E_POINTER; + + if (riid == __uuidof(IUnknown) || + riid == libremidi::IID_IMidiEndpointConnectionMessagesReceivedCallback) + { + *ppvObject = static_cast(this); + AddRef(); + return S_OK; + } + + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() override + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() override + { + return 1; + } + + HRESULT STDMETHODCALLTYPE MessagesReceived( + GUID sessionId, + GUID connectionId, + UINT64 timestamp, + UINT32 wordCount, + UINT32 *messages) override { + HRESULT res{}; + self.process_message(sessionId, connectionId, timestamp, wordCount, messages); + return res; + } + } raw_callback{*this}; +#endif + explicit midi_in_impl( libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} @@ -42,7 +92,7 @@ public: stdx::error open_port(const input_port& port, std::string_view) override { - auto device_id = std::get_if(&port.device); + auto device_id = get_if(&port.device); if (!device_id) return std::errc::invalid_argument; @@ -55,19 +105,28 @@ public: // TODO use a MidiGroupEndpointListener for the filtering m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS m_revoke_token = m_endpoint.MessageReceived( [this]( const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&, const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { process_message(args); }); +#else + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + + m_raw_endpoint->SetMessagesReceivedCallback( + &raw_callback + ); +#endif m_endpoint.Open(); return stdx::error{}; } - void process_message(const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) + void process_message( + const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) { static constexpr timestamp_backend_info timestamp_info{ .has_absolute_timestamps = true, @@ -95,9 +154,49 @@ public: {ump_space, ump_space + b.Size()}, m_processing.timestamp(to_ns, 0)); } +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + void process_message( + const GUID& /* sessionId */, + const GUID& /* connectionId */, + UINT64 timestamp, + UINT32 wordCount, + UINT32 *ump) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = false, + .has_samples = false, + }; + + if(wordCount == 0) + return; + + if (m_group_filter >= 0) + { + int group = cmidi2_ump_get_group(ump); + if (group != m_group_filter) + return; + } + + auto to_ns = [t = timestamp] { return t; }; + m_processing.on_bytes( + {ump, ump + wordCount}, m_processing.timestamp(to_ns, 0)); + } +#endif + stdx::error close_port() override { + if(!m_endpoint) + return std::errc::not_connected; + +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS m_endpoint.MessageReceived(m_revoke_token); +#else + if(m_raw_endpoint) { + m_raw_endpoint->RemoveMessagesReceivedCallback(); + m_raw_endpoint = nullptr; + } +#endif m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); return stdx::error{}; } @@ -108,6 +207,9 @@ private: MidiSession m_session; winrt::event_token m_revoke_token{}; winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + winrt::impl::com_ref m_raw_endpoint{}; +#endif midi2::input_state_machine m_processing{this->configuration}; int m_group_filter = -1; }; diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index 00817ea..29b334c 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -1,11 +1,11 @@ #pragma once -#include #include #include #include #include +#include -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { class midi_out_impl final @@ -38,7 +38,7 @@ public: stdx::error open_port(const output_port& port, std::string_view) override { - auto device_id = std::get_if(&port.device); + auto device_id = get_if(&port.device); if (!device_id) return std::errc::invalid_argument; @@ -47,6 +47,9 @@ public: return std::errc::address_not_available; m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + #endif m_endpoint.Open(); return stdx::error{}; @@ -54,6 +57,9 @@ public: stdx::error close_port() override { + if(!m_endpoint) + return std::errc::not_connected; + m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); return stdx::error{}; } @@ -61,8 +67,10 @@ public: stdx::error send_ump(const uint32_t* message, size_t size) override { auto write_func = [this](const uint32_t* ump, int64_t bytes) -> std::errc { + +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS MidiSendMessageResults ret{}; - switch(bytes / 4) + switch (bytes / 4) { case 1: ret = m_endpoint.SendSingleMessagePacket(MidiMessage32(0, ump[0])); @@ -74,23 +82,52 @@ public: ret = m_endpoint.SendSingleMessagePacket(MidiMessage96(0, ump[0], ump[1], ump[2])); break; case 4: - ret = m_endpoint.SendSingleMessagePacket(MidiMessage128(0, ump[0], ump[1], ump[2], ump[3])); + ret = m_endpoint.SendSingleMessagePacket( + MidiMessage128(0, ump[0], ump[1], ump[2], ump[3])); break; default: return std::errc::bad_message; } - - if(ret != MidiSendMessageResults::Succeeded) + if (ret != MidiSendMessageResults::Succeeded) return std::errc::bad_message; + #else + HRESULT ret{}; + switch (bytes / 4) + { + case 1: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(1, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 1, (UINT32*)ump); + break; + case 2: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(2, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 2, (UINT32*)ump); + break; + case 3: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(3, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 3, (UINT32*)ump); + break; + case 4: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(4, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 4, (UINT32*)ump); + break; + default: + return std::errc::bad_message; + } + #endif + if(ret < 0) + return std::errc::io_error; return std::errc{0}; }; - return segment_ump_stream(message, size, write_func, []() {}); + return segment_ump_stream(message, size, write_func, []() { }); } private: MidiSession m_session; winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + winrt::impl::com_ref m_raw_endpoint{}; +#endif }; } diff --git a/include/libremidi/backends/winmidi/observer.hpp b/include/libremidi/backends/winmidi/observer.hpp index 304179e..b26f180 100644 --- a/include/libremidi/backends/winmidi/observer.hpp +++ b/include/libremidi/backends/winmidi/observer.hpp @@ -3,12 +3,12 @@ #include #include -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { struct port_info { - hstring id; - hstring name; + winrt::hstring id; + winrt::hstring name; }; class observer_impl final @@ -26,28 +26,22 @@ public: MidiEndpointDeviceWatcher::Added_revoker m_addHandler; MidiEndpointDeviceWatcher::Updated_revoker m_updHandler; MidiEndpointDeviceWatcher::Removed_revoker m_delHandler; - std::map> m_known_input_devices; - std::map> m_known_output_devices; + std::map> m_known_input_devices; + std::map> m_known_output_devices; std::mutex m_devices_mtx; + std::atomic_bool m_in_constructor{true}; explicit observer_impl( libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { + struct on_delete { observer_impl& self; ~on_delete() { self.m_in_constructor = false; } } on_delete{*this}; + if (!configuration.has_callbacks()) return; - if (configuration.notify_in_constructor) - { - 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); - } - + // Note: winmidi also notifies for existing devices, so the "notify in constructor" + // is handled in the callbacks if ((watcher = MidiEndpointDeviceWatcher::Create())) { namespace enumeration = winrt::Windows::Devices::Enumeration; @@ -84,18 +78,18 @@ public: return libremidi::API::WINDOWS_MIDI_SERVICES; } - static port_information::port_type code_to_type(std::string_view str) noexcept + static transport_type code_to_type(std::string_view str) noexcept { - using enum port_information::port_type; + using enum transport_type; if (str.starts_with("KS")) return hardware; if (str == "BLE") - return port_information::port_type(hardware | bluetooth); + return transport_type(hardware | bluetooth); if (str == "VPB" || str == "APP") return software; if (str == "LOOP") - return port_information::port_type(software | loopback); + return transport_type(software | loopback); if (str.starts_with("NET")) return network; return unknown; @@ -108,11 +102,14 @@ public: const auto& tinfo = p.GetTransportSuppliedInfo(); return { - {.client = 0, + {.api = libremidi::API::WINDOWS_MIDI_SERVICES, + .client = 0, .container = std::bit_cast(p.ContainerId()), .device = to_string(p.EndpointDeviceId()), .port = gp.Number(), .manufacturer = to_string(tinfo.ManufacturerName), + .product = to_string(tinfo.Name), + .serial = to_string(tinfo.SerialNumber), .device_name = to_string(p.Name()), .port_name = to_string(gp.Name()), .display_name = to_string(gp.Name()) + " " + std::to_string(gp.Number()), @@ -125,7 +122,8 @@ public: for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { - if(ep.Name().starts_with(L"Diagnostics")) { + if (ep.Name().starts_with(L"Diagnostics")) + { continue; } @@ -145,7 +143,8 @@ public: for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { - if(ep.Name().starts_with(L"Diagnostics")) { + if (ep.Name().starts_with(L"Diagnostics")) + { continue; } @@ -167,7 +166,7 @@ public: } void on_device_updated( - const MidiEndpointDeviceWatcher& e, + const MidiEndpointDeviceWatcher&, const MidiEndpointDeviceInformationUpdatedEventArgs& result) { // OPTIMIZEME @@ -199,7 +198,9 @@ public: std::lock_guard _{m_devices_mtx}; m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); } - configuration.input_added(std::move(ip)); + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.input_added(std::move(ip)); } if (configuration.output_added) { @@ -208,7 +209,9 @@ public: std::lock_guard _{m_devices_mtx}; m_known_output_devices[ep.EndpointDeviceId()].push_back(op); } - configuration.output_added(std::move(op)); + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.output_added(std::move(op)); } break; } @@ -220,7 +223,9 @@ public: std::lock_guard _{m_devices_mtx}; m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); } - configuration.input_added(std::move(ip)); + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.input_added(std::move(ip)); } break; case MidiGroupTerminalBlockDirection::BlockOutput: @@ -231,13 +236,16 @@ public: std::lock_guard _{m_devices_mtx}; m_known_output_devices[ep.EndpointDeviceId()].push_back(op); } - configuration.output_added(std::move(op)); + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.output_added(std::move(op)); } break; } } } - void remove_device(hstring eid) + + void remove_device(winrt::hstring eid) { std::vector to_remove_in; std::vector to_remove_out; diff --git a/include/libremidi/backends/winmm.hpp b/include/libremidi/backends/winmm.hpp index 6a64d13..04b5baf 100644 --- a/include/libremidi/backends/winmm.hpp +++ b/include/libremidi/backends/winmm.hpp @@ -18,7 +18,7 @@ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp // Thanks to Jean-Baptiste Berruchon for the sysex code. -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_backend diff --git a/include/libremidi/backends/winmm/config.hpp b/include/libremidi/backends/winmm/config.hpp index 61863e0..f681e45 100644 --- a/include/libremidi/backends/winmm/config.hpp +++ b/include/libremidi/backends/winmm/config.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_input_configuration diff --git a/include/libremidi/backends/winmm/error_domain.hpp b/include/libremidi/backends/winmm/error_domain.hpp index f1bf49c..fb4d04c 100644 --- a/include/libremidi/backends/winmm/error_domain.hpp +++ b/include/libremidi/backends/winmm/error_domain.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_error_domain : public stdx::error_domain @@ -34,28 +34,50 @@ public: { switch (error_cast(e)) { - case MMSYSERR_NOERROR: return "No error"; - case MMSYSERR_ERROR: return "Error"; - case MMSYSERR_BADDEVICEID: return "Bad device ID"; - case MMSYSERR_NOTENABLED: return "Not enabled"; - case MMSYSERR_ALLOCATED: return "Allocated"; - case MMSYSERR_INVALHANDLE: return "Invalid handle"; - case MMSYSERR_NODRIVER: return "No driver"; - case MMSYSERR_NOMEM: return "No memory"; - case MMSYSERR_NOTSUPPORTED: return "Not supported"; - case MMSYSERR_BADERRNUM: return "Bad errnum"; - case MMSYSERR_INVALFLAG: return "Invalid flag"; - case MMSYSERR_INVALPARAM: return "Invalid parameter"; - case MMSYSERR_HANDLEBUSY: return "Handle busy"; - case MMSYSERR_INVALIDALIAS: return "Invalid alias"; - case MMSYSERR_BADDB: return "Bad database"; - case MMSYSERR_KEYNOTFOUND: return "Key not found"; - case MMSYSERR_READERROR: return "Read error"; - case MMSYSERR_WRITEERROR: return "Write error"; - case MMSYSERR_DELETEERROR: return "Delete error"; - case MMSYSERR_VALNOTFOUND: return "Value not found"; - case MMSYSERR_NODRIVERCB: return "No driver callback"; - case MMSYSERR_MOREDATA: return "More data"; + case MMSYSERR_NOERROR: + return "No error"; + case MMSYSERR_ERROR: + return "Error"; + case MMSYSERR_BADDEVICEID: + return "Bad device ID"; + case MMSYSERR_NOTENABLED: + return "Not enabled"; + case MMSYSERR_ALLOCATED: + return "Allocated"; + case MMSYSERR_INVALHANDLE: + return "Invalid handle"; + case MMSYSERR_NODRIVER: + return "No driver"; + case MMSYSERR_NOMEM: + return "No memory"; + case MMSYSERR_NOTSUPPORTED: + return "Not supported"; + case MMSYSERR_BADERRNUM: + return "Bad errnum"; + case MMSYSERR_INVALFLAG: + return "Invalid flag"; + case MMSYSERR_INVALPARAM: + return "Invalid parameter"; + case MMSYSERR_HANDLEBUSY: + return "Handle busy"; + case MMSYSERR_INVALIDALIAS: + return "Invalid alias"; + case MMSYSERR_BADDB: + return "Bad database"; + case MMSYSERR_KEYNOTFOUND: + return "Key not found"; + case MMSYSERR_READERROR: + return "Read error"; + case MMSYSERR_WRITEERROR: + return "Write error"; + case MMSYSERR_DELETEERROR: + return "Delete error"; + case MMSYSERR_VALNOTFOUND: + return "Value not found"; + case MMSYSERR_NODRIVERCB: + return "No driver callback"; + case MMSYSERR_MOREDATA: + return "More data"; } return "Unknown error code"; } diff --git a/include/libremidi/backends/winmm/helpers.hpp b/include/libremidi/backends/winmm/helpers.hpp index ef5182a..c116c75 100644 --- a/include/libremidi/backends/winmm/helpers.hpp +++ b/include/libremidi/backends/winmm/helpers.hpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { // Convert a nullptr-terminated wide string or ANSI-encoded string to UTF-8. diff --git a/include/libremidi/backends/winmm/midi_in.hpp b/include/libremidi/backends/winmm/midi_in.hpp index f04d57e..2ada660 100644 --- a/include/libremidi/backends/winmm/midi_in.hpp +++ b/include/libremidi/backends/winmm/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_winmm final @@ -24,9 +24,7 @@ public: { if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400)) { - libremidi_handle_error( - configuration, - "InitializeCriticalSectionAndSpinCount failed."); + libremidi_handle_error(configuration, "InitializeCriticalSectionAndSpinCount failed."); this->client_open_ = std::errc::too_many_files_open; return; @@ -40,7 +38,7 @@ public: // Close a connection if it exists. midi_in_winmm::close_port(); - if(this->client_open_ == stdx::error{}) + if (this->client_open_ == stdx::error{}) DeleteCriticalSection(&(this->_mutex)); } @@ -53,8 +51,7 @@ public: std::bit_cast(this), CALLBACK_FUNCTION); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error creating Windows MM MIDI input port."); + libremidi_handle_error(configuration, "error creating Windows MM MIDI input port."); return from_mmerr(result); } @@ -101,8 +98,7 @@ public: { midiInClose(this->inHandle); this->inHandle = nullptr; - libremidi_handle_error( - configuration, "error starting Windows MM MIDI input port."); + libremidi_handle_error(configuration, "error starting Windows MM MIDI input port."); return from_mmerr(result); } @@ -126,8 +122,7 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - libremidi_handle_error( - configuration, "port not found: " + p.port_name); + libremidi_handle_error(configuration, "port not found: " + p.port_name); return std::errc::invalid_argument; } @@ -238,13 +233,13 @@ private: { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) const auto* sysex = reinterpret_cast(midiMessage); - if(inputStatus == MIM_LONGERROR) + if (inputStatus == MIM_LONGERROR) { self.m_processing.reset(); } else if (!self.configuration.ignore_sysex) { - if(sysex->dwBytesRecorded > 0) + if (sysex->dwBytesRecorded > 0) { const auto sysex_bytes = reinterpret_cast(sysex->lpData); diff --git a/include/libremidi/backends/winmm/midi_out.hpp b/include/libremidi/backends/winmm/midi_out.hpp index 3f8b596..3a854f7 100644 --- a/include/libremidi/backends/winmm/midi_out.hpp +++ b/include/libremidi/backends/winmm/midi_out.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_winmm final @@ -65,8 +65,7 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - libremidi_handle_error( - configuration, "port not found: " + p.port_name); + libremidi_handle_error(configuration, "port not found: " + p.port_name); return std::errc::invalid_argument; } @@ -107,8 +106,7 @@ public: auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error preparing sysex header."); + libremidi_handle_error(configuration, "error preparing sysex header."); return from_mmerr(result); } @@ -116,8 +114,7 @@ public: result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error sending sysex message."); + libremidi_handle_error(configuration, "error sending sysex message."); return from_mmerr(result); } @@ -148,8 +145,7 @@ public: auto result = midiOutShortMsg(this->outHandle, packet); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error sending MIDI message."); + libremidi_handle_error(configuration, "error sending MIDI message."); return from_mmerr(result); } } diff --git a/include/libremidi/backends/winmm/observer.hpp b/include/libremidi/backends/winmm/observer.hpp index 075f4ee..57948ed 100644 --- a/include/libremidi/backends/winmm/observer.hpp +++ b/include/libremidi/backends/winmm/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_winmm : public observer_api @@ -103,8 +103,12 @@ protected: auto rawName = ConvertToUTF8(deviceCaps.szPname); auto portName = rawName; MakeUniqueInPortName(portName, index); + return { - {.client = 0, + {.api = libremidi::API::WINDOWS_MM, + .client = 0, + .device + = usb_device_identifier{.vendor_id = deviceCaps.wMid, .product_id = deviceCaps.wPid}, .port = index, .manufacturer = "", .device_name = "", @@ -120,8 +124,12 @@ protected: auto rawName = ConvertToUTF8(deviceCaps.szPname); auto portName = rawName; MakeUniqueOutPortName(portName, index); + return { - {.client = 0, + {.api = libremidi::API::WINDOWS_MM, + .client = 0, + .device + = usb_device_identifier{.vendor_id = deviceCaps.wMid, .product_id = deviceCaps.wPid}, .port = index, .manufacturer = "", .device_name = "", @@ -164,7 +172,7 @@ protected: #if __has_include() && __cpp_lib_jthread >= 201911L #include -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_threaded final : public observer_winmm { @@ -194,7 +202,7 @@ private: #else #include #include -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_threaded final : public observer_winmm { @@ -234,7 +242,7 @@ private: } #endif -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_manual final : public observer_winmm { @@ -254,7 +262,7 @@ public: ~observer_manual() { } }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/winuwp.hpp b/include/libremidi/backends/winuwp.hpp index 18a3ecf..898344d 100644 --- a/include/libremidi/backends/winuwp.hpp +++ b/include/libremidi/backends/winuwp.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winuwp_backend { @@ -21,4 +21,4 @@ struct winuwp_backend static constexpr inline bool available() noexcept { return true; } }; -} // namespace libremidi +} // NAMESPACE_LIBREMIDI diff --git a/include/libremidi/backends/winuwp/config.hpp b/include/libremidi/backends/winuwp/config.hpp index 27bb1a1..a123b57 100644 --- a/include/libremidi/backends/winuwp/config.hpp +++ b/include/libremidi/backends/winuwp/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winuwp_input_configuration diff --git a/include/libremidi/backends/winuwp/helpers.hpp b/include/libremidi/backends/winuwp/helpers.hpp index 380e458..7b876f4 100644 --- a/include/libremidi/backends/winuwp/helpers.hpp +++ b/include/libremidi/backends/winuwp/helpers.hpp @@ -7,13 +7,14 @@ #include #include #include +#include #include #include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { inline void winrt_init() { @@ -23,8 +24,6 @@ inline void winrt_init() // winrt::init_apartment(); } -namespace -{ using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Midi; @@ -36,7 +35,7 @@ using namespace winrt::Windows::Storage::Streams; // get() function from winrt is that we avoid an assertion if waiting // from the STA thread. template -auto get(T const& async) +LIBREMIDI_STATIC auto get(T const& async) { if (async.Status() != AsyncStatus::Completed) { @@ -60,4 +59,3 @@ auto get(T const& async) return async.GetResults(); } } -} diff --git a/include/libremidi/backends/winuwp/midi_in.hpp b/include/libremidi/backends/winuwp/midi_in.hpp index 0ccfbc5..3d70613 100644 --- a/include/libremidi/backends/winuwp/midi_in.hpp +++ b/include/libremidi/backends/winuwp/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_winuwp final @@ -47,7 +47,8 @@ public: midi_start_timestamp = std::chrono::steady_clock::now(); port_.MessageReceived( - [=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort, + [this]( + const winrt::Windows::Devices::Midi::IMidiInPort&, const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) { this->process_message(args.Message()); }); diff --git a/include/libremidi/backends/winuwp/midi_out.hpp b/include/libremidi/backends/winuwp/midi_out.hpp index af7d2e7..48893f0 100644 --- a/include/libremidi/backends/winuwp/midi_out.hpp +++ b/include/libremidi/backends/winuwp/midi_out.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_winuwp final diff --git a/include/libremidi/backends/winuwp/observer.hpp b/include/libremidi/backends/winuwp/observer.hpp index 7b5d243..1921720 100644 --- a/include/libremidi/backends/winuwp/observer.hpp +++ b/include/libremidi/backends/winuwp/observer.hpp @@ -1,16 +1,17 @@ #pragma once #include +#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_winuwp_internal { public: struct port_info { - hstring id; - hstring name; + winrt::hstring id; + winrt::hstring name; }; struct callback { @@ -33,16 +34,16 @@ public: cbs.emplace_back(tk, f); return tk; } + void remove(int tk) { auto it = std::remove_if( cbs.begin(), cbs.end(), [tk](const callback& c) { return c.token == tk; }); - auto r = std::distance(it, cbs.end()); cbs.erase(it, cbs.end()); } }; - explicit observer_winuwp_internal(hstring deviceSelector) { initialize(deviceSelector); } + explicit observer_winuwp_internal(winrt::hstring deviceSelector) { initialize(deviceSelector); } ~observer_winuwp_internal() { terminate(); } std::vector get_ports() const @@ -66,10 +67,10 @@ public: return true; } - hstring get_port_id(unsigned int portNumber) const + winrt::hstring get_port_id(unsigned int portNumber) const { std::lock_guard lock(portListMutex_); - return portNumber < portList_.size() ? portList_[portNumber].id : hstring{}; + return portNumber < portList_.size() ? portList_[portNumber].id : winrt::hstring{}; } std::string get_port_name(unsigned int portNumber) const @@ -97,7 +98,7 @@ private: observer_winuwp_internal& operator=(const observer_winuwp_internal&) = delete; private: - void initialize(hstring deviceSelector) + void initialize(winrt::hstring deviceSelector) { deviceWatcher_ = DeviceInformation::CreateWatcher(deviceSelector); @@ -122,7 +123,7 @@ private: deviceWatcher_.Added(evTokenOnDeviceAdded_); } - void on_device_added(DeviceWatcher sender, DeviceInformation deviceInfo) + void on_device_added(const DeviceWatcher&, const DeviceInformation& deviceInfo) { port_info p; { @@ -133,12 +134,12 @@ private: portAddedEvent_(p); } - void on_device_removed(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) + void on_device_removed(const DeviceWatcher&, const DeviceInformationUpdate& deviceUpdate) { const auto id = deviceUpdate.Id(); auto pred = [&id](const port_info& portInfo) { return portInfo.id == id; }; std::optional p; - hstring name; + winrt::hstring name; { std::lock_guard lock(portListMutex_); auto iter = std::find_if(portList_.begin(), portList_.end(), pred); @@ -152,9 +153,9 @@ private: portRemovedEvent_(*p); } - void on_device_updated(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) { } + void on_device_updated(const DeviceWatcher&, const DeviceInformationUpdate&) { } - void on_device_enumeration_completed(DeviceWatcher sender, IInspectable const&) { } + void on_device_enumeration_completed(const DeviceWatcher&, const IInspectable&) { } private: std::vector portList_; @@ -224,7 +225,8 @@ public: -> std::conditional_t { return { - {.client = 0, + {.api = libremidi::API::WINDOWS_UWP, + .client = 0, .port = 0, .manufacturer = "", .device_name = "", diff --git a/include/libremidi/client.cpp b/include/libremidi/client.cpp index 4d2c2d2..1e22509 100644 --- a/include/libremidi/client.cpp +++ b/include/libremidi/client.cpp @@ -10,7 +10,7 @@ #ifdef LIBREMIDI_JACK #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE shared_configurations diff --git a/include/libremidi/client.hpp b/include/libremidi/client.hpp index 5faa69f..1a913c2 100644 --- a/include/libremidi/client.hpp +++ b/include/libremidi/client.hpp @@ -6,7 +6,7 @@ #include #include -namespace libremidi::midi1 +NAMESPACE_LIBREMIDI::midi1 { struct client_configuration { @@ -18,7 +18,7 @@ struct client_configuration //! Set a callback function to be invoked for incoming MIDI messages. //! Mandatory! std::function on_message - = [](const libremidi::input_port& /*port*/, libremidi::message&&) {}; + = [](const libremidi::input_port& /*port*/, libremidi::message&&) { }; //! Observation callbacks for when ports are added or removed input_port_callback input_added; diff --git a/include/libremidi/cmidi2.hpp b/include/libremidi/cmidi2.hpp index 580765d..57c80c7 100644 --- a/include/libremidi/cmidi2.hpp +++ b/include/libremidi/cmidi2.hpp @@ -1,15 +1,25 @@ + #ifndef CMIDI2_H_INCLUDED #define CMIDI2_H_INCLUDED +#include + #include #include #include -#define MIDI_2_0_RESERVED 0 -#define JR_TIMESTAMP_TICKS_PER_SECOND 31250 +#include +#if !defined(_MSC_VER) #pragma GCC system_header #pragma clang system_header +#endif + +#define CMIDI2_MIDI_2_0_RESERVED 0 +#define CMIDI2_JR_TIMESTAMP_TICKS_PER_SECOND 31250 +// FIXME: remove those global defs that are quite harmful (conflicts with other libraries) +#define MIDI_2_0_RESERVED 0 +#define JR_TIMESTAMP_TICKS_PER_SECOND 31250 #ifdef __cplusplus extern "C" { @@ -235,13 +245,6 @@ enum cmidi2_system_message_status CMIDI2_SYSTEM_STATUS_RESET = 0xFF, }; -// TODO: remove this. Use cmidi2_utility_message_status instead -enum cmidi2_jr_timestamp_status -{ - CMIDI2_JR_CLOCK = 0x10, - CMIDI2_JR_TIMESTAMP = 0x20, -}; - enum cmidi2_utility_message_status { CMIDI2_UTILITY_STATUS_NOOP = 0, @@ -277,7 +280,7 @@ enum cmidi2_flex_data_metadata_text_status CMIDI2_FLEX_DATA_STATUS_ARRANGER_NAME = 7, CMIDI2_FLEX_DATA_STATUS_PUBLISHER_NAME = 8, CMIDI2_FLEX_DATA_STATUS_PRIMARY_PERFORMER_NAME = 9, - CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMAER_NAME = 10, + CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMER_NAME = 10, CMIDI2_FLEX_DATA_STATUS_RECORDING_DATE = 11, CMIDI2_FLEX_DATA_STATUS_RECORDING_LOCATION = 12 }; @@ -344,7 +347,7 @@ enum cmidi2_ump_chord_name_chord_type CMIDI2_UMP_CHORD_TYPE_7_SUSPENDED_4TH = 27, }; -static inline uint8_t cmidi2_ump_get_num_bytes(uint32_t data) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_num_bytes(uint32_t data) { switch (((data & 0xF0000000) >> 28) & 0xF) { @@ -411,7 +414,7 @@ enum cmidi2_ump_function_block_discovery_flags // 7.1 UMP Stream Messages -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap) { cmidi2_ump128_t ret @@ -422,7 +425,7 @@ cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap return ret; } -static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( cmidi2_ump_version_t version, bool isStaticFunctionBlock, uint8_t numFunctionBlocks, bool midi2Capable, bool midi1Capable, bool rxJR, bool txJR) { @@ -436,7 +439,7 @@ static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( return ret; } -static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_device_identity_notification( uint32_t manufacturerIdIn7bitArray, uint8_t deviceFamilyLSB, uint8_t deviceFamilyMSB, uint8_t deviceFamilyModelLSB, uint8_t deviceFamilyModelMSB, uint32_t softwareRevisionIn7bitArray) @@ -452,7 +455,7 @@ static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification( return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14]) { cmidi2_ump128_t ret @@ -464,17 +467,17 @@ cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14]) return ret; } -static inline cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME, name); } -static inline cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID, id); } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR) { cmidi2_ump128_t ret @@ -485,7 +488,7 @@ cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR) return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool txJR) { cmidi2_ump128_t ret @@ -496,7 +499,7 @@ cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool t return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter) { cmidi2_ump128_t ret @@ -507,7 +510,7 @@ cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter) return ret; } -static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_info_notification( bool active, uint8_t numFunctionBlocks, uint8_t uiHint, uint8_t midi1, uint8_t direction, uint8_t firstGroup, uint8_t numSpannedGroup, uint8_t midiCIVersionFormat, uint8_t maxNumSysEx8Streams) @@ -522,12 +525,12 @@ static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification( return ret; } -static inline cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME, name); } -static inline cmidi2_ump128_t cmidi2_ump_start_of_clip() +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_start_of_clip() { cmidi2_ump128_t ret = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) @@ -536,7 +539,7 @@ static inline cmidi2_ump128_t cmidi2_ump_start_of_clip() return ret; } -static inline cmidi2_ump128_t cmidi2_ump_end_of_clip() +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_end_of_clip() { cmidi2_ump128_t ret = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) @@ -546,48 +549,46 @@ static inline cmidi2_ump128_t cmidi2_ump_end_of_clip() } // 7.2 Utility Messages -static inline uint32_t cmidi2_ump_noop(uint8_t group) +LIBREMIDI_STATIC uint32_t cmidi2_ump_noop() { - return (group & 0xF) << 24; + return 0; } -static inline uint32_t cmidi2_ump_jr_clock_direct(uint8_t group, uint32_t senderClockTime) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_clock_direct(uint16_t senderClockTime) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime; + return (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime; } -static inline uint32_t cmidi2_ump_jr_clock(uint8_t group, double senderClockTime) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_clock(double senderClockTime) { uint16_t value = (uint16_t)(senderClockTime * JR_TIMESTAMP_TICKS_PER_SECOND); - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value; + return (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value; } -static inline uint32_t cmidi2_ump_jr_timestamp_direct(uint8_t group, uint16_t senderClockTimestamp) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_timestamp_direct(uint16_t senderClockTimestamp) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) - + senderClockTimestamp; + return (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + senderClockTimestamp; } -static inline uint32_t cmidi2_ump_jr_timestamp(uint8_t group, double senderClockTimestamp) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_timestamp(double senderClockTimestamp) { uint16_t value = (uint16_t)(senderClockTimestamp * JR_TIMESTAMP_TICKS_PER_SECOND); - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value; + return (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value; } -static inline uint32_t cmidi2_ump_dctpq(uint8_t group, uint32_t dctpq) +LIBREMIDI_STATIC uint32_t cmidi2_ump_dctpq(uint32_t dctpq) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq; + return (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq; } -static inline uint32_t cmidi2_ump_dcs(uint8_t group, uint32_t ticks) +LIBREMIDI_STATIC uint32_t cmidi2_ump_dcs(uint32_t ticks) { // Note that unlike JR timestamp delta clockstamps accepts ticks up to 20 bits. - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16) - + (ticks & 0xFFFFF); + return (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16) + (ticks & 0xFFFFF); } // 7.6 System Common and System Real Time Messages -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uint8_t midi1Byte3) { return (CMIDI2_MESSAGE_TYPE_SYSTEM << 28) + ((group & 0xF) << 24) + (status << 16) @@ -595,66 +596,66 @@ cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uin } // 7.3 MIDI 1.0 Channel Voice Messages -static inline int32_t cmidi2_ump_midi1_message( +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_message( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4) { return (CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL << 28) + ((group & 0xF) << 24) + (((code & 0xF0) + (channel & 0xF)) << 16) + ((byte3 & 0x7F) << 8) + (byte4 & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_note_off(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, velocity & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_note_on(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, velocity & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_paf(uint8_t group, uint8_t channel, uint8_t note, uint8_t data) { return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_PAF, channel, note & 0x7F, data & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_cc(uint8_t group, uint8_t channel, uint8_t index, uint8_t data) { return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_CC, channel, index & 0x7F, data & 0x7F); } -static inline int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PROGRAM, channel, program & 0x7F, MIDI_2_0_RESERVED); } -static inline int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_CAF, channel, data & 0x7F, MIDI_2_0_RESERVED); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend_direct(uint8_t group, uint8_t channel, uint16_t data) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend_split(uint8_t group, uint8_t channel, uint8_t dataLSB, uint8_t dataMSB) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PITCH_BEND, channel, dataLSB & 0x7F, dataMSB & 0x7F); } -static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data) { data += 8192; return cmidi2_ump_midi1_message( @@ -662,7 +663,7 @@ static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel } // 7.4 MIDI 2.0 Channel Voice Messages -static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint16_t short1, uint16_t short2) { @@ -672,7 +673,7 @@ static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( + ((uint64_t)short1 << 16) + short2; } -static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_channel_message_8_8_32( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint32_t rest) { return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24) @@ -681,7 +682,7 @@ static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32( + rest; } -static inline uint16_t cmidi2_ump_pitch_7_9(double semitone) +LIBREMIDI_STATIC uint16_t cmidi2_ump_pitch_7_9(double semitone) { double actual = semitone < 0.0 ? 0.0 : semitone >= 128.0 ? 128.0 : semitone; uint16_t dec = (uint16_t)actual; @@ -689,7 +690,7 @@ static inline uint16_t cmidi2_ump_pitch_7_9(double semitone) return (dec << 9) + (int)(microtone * 512.0); } -static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone) +LIBREMIDI_STATIC uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone) { uint16_t ret = (uint16_t)(semitone & 0x7F) << 9; double actual = microtone < 0.0 ? 0.0 : microtone > 1.0 ? 1.0 : microtone; @@ -697,7 +698,7 @@ static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double micro return ret; } -static inline int64_t cmidi2_ump_midi2_note_off( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_note_off( uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity, uint16_t attributeData) { @@ -705,7 +706,7 @@ static inline int64_t cmidi2_ump_midi2_note_off( group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, attributeType, velocity, attributeData); } -static inline int64_t cmidi2_ump_midi2_note_on( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_note_on( uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity, uint16_t attributeData) { @@ -713,56 +714,56 @@ static inline int64_t cmidi2_ump_midi2_note_on( group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, attributeType, velocity, attributeData); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_paf(uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PAF, channel, note & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t cmidi2_ump_midi2_per_note_rcc( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_rcc( uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_RCC, channel, note & 0x7F, index, data); } -static inline int64_t cmidi2_ump_midi2_per_note_acc( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_acc( uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_ACC, channel, note & 0x7F, index, data); } -static inline int64_t cmidi2_ump_midi2_per_note_management( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_management( uint8_t group, uint8_t channel, uint8_t note, uint8_t optionFlags) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_MANAGEMENT, channel, note & 0x7F, optionFlags & 3, 0); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_cc(uint8_t group, uint8_t channel, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_CC, channel, index & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t cmidi2_ump_midi2_rpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_rpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_nrpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_nrpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_relative_rpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_relative_rpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -770,7 +771,7 @@ static inline int64_t cmidi2_ump_midi2_relative_rpn( dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_relative_nrpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_relative_nrpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -778,7 +779,7 @@ static inline int64_t cmidi2_ump_midi2_relative_nrpn( dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_program( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_program( uint8_t group, uint8_t channel, uint8_t optionFlags, uint8_t program, uint8_t bankMSB, uint8_t bankLSB) { @@ -787,13 +788,13 @@ static inline int64_t cmidi2_ump_midi2_program( ((program & 0x7F) << 24) + (bankMSB << 8) + bankLSB); } -static inline int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data) +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_CAF, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED, data); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsignedData) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -801,19 +802,19 @@ cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsi unsignedData); } -static inline int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data) +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data) { return cmidi2_ump_midi2_pitch_bend_direct(group, channel, 0x80000000 + data); } -static inline int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct( uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_PITCH_BEND, channel, note & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_per_note_pitch_bend_direct(group, channel, note, 0x80000000 + data); @@ -821,22 +822,22 @@ cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t not // Common utility functions for sysex support -static inline uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index) { return (uint8_t)(src >> ((7 - index) * 8) & 0xFF); } -static inline uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index) { return (uint8_t)(src >> ((7 - index) * 8) & 0xFF); } -static inline uint8_t cmidi2_ump_sysex_get_num_packets(uint8_t numBytes, uint8_t radix) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex_get_num_packets(size_t numBytes, uint8_t radix) { - return numBytes <= radix ? 1 : numBytes / radix + (numBytes % radix ? 1 : 0); + return numBytes <= radix ? 1 : (numBytes / radix + (numBytes % radix ? 1 : 0)); } -static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) { const uint8_t* bytes = (const uint8_t*)sequence; uint32_t ret = 0; @@ -845,7 +846,7 @@ static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) return ret; } -static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) { const uint8_t* bytes = (const uint8_t*)sequence; uint32_t ret = 0; @@ -854,38 +855,38 @@ static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) return ret; } -static inline bool cmidi2_util_is_platform_little_endian() +LIBREMIDI_STATIC bool cmidi2_util_is_platform_little_endian() { int i = 1; return *(char*)&i; } -static inline uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence) { return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint32_bytes_le(sequence) : cmidi2_ump_read_uint32_bytes_be(sequence); } -static inline uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence) { return ((uint64_t)cmidi2_ump_read_uint32_bytes_le(sequence) << 32) + cmidi2_ump_read_uint32_bytes_le((const uint8_t*)sequence + 4); } -static inline uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence) { return ((uint64_t)cmidi2_ump_read_uint32_bytes_be(sequence) << 32) + cmidi2_ump_read_uint32_bytes_be((const uint8_t*)sequence + 4); } -static inline uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence) { return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint64_bytes_le(sequence) : cmidi2_ump_read_uint64_bytes_be(sequence); } -static inline void cmidi2_ump_sysex_get_packet_of( - uint64_t* result1, uint64_t* result2, uint8_t group, uint8_t numBytes, const void* srcData, +LIBREMIDI_STATIC void cmidi2_ump_sysex_get_packet_of( + uint64_t* result1, uint64_t* result2, uint8_t group, size_t numBytes, const void* srcData, int32_t index, enum cmidi2_message_type messageType, int radix, bool hasStreamId, uint8_t streamId) { @@ -897,7 +898,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( enum cmidi2_sysex_status status; uint8_t size; - if (numBytes <= radix) + if (numBytes <= (size_t)radix) { status = CMIDI2_SYSEX_IN_ONE_UMP; size = numBytes; // single packet message @@ -909,7 +910,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( } else { - uint8_t isEnd = index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; + uint8_t isEnd = (size_t)index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; if (isEnd) { size = numBytes % radix ? numBytes % radix : radix; @@ -927,7 +928,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( dst8[2] = streamId; uint8_t dstOffset = hasStreamId ? 3 : 2; - for (uint8_t i = 0, j = index * radix; i < size; i++, j++) + for (size_t i = 0, j = index * radix; i < size; i++, j++) dst8[i + dstOffset] = src8[j]; *result1 = cmidi2_ump_read_uint64_bytes_be(dst8); @@ -936,7 +937,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( } // 7.7 System Exclusive 7-Bit Messages -static inline uint64_t cmidi2_ump_sysex7_direct( +LIBREMIDI_STATIC uint64_t cmidi2_ump_sysex7_direct( uint8_t group, uint8_t status, uint8_t numBytes, uint8_t data1, uint8_t data2, uint8_t data3, uint8_t data4, uint8_t data5, uint8_t data6) { @@ -947,7 +948,7 @@ static inline uint64_t cmidi2_ump_sysex7_direct( + (data5 << 8) + data6; } -static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) +LIBREMIDI_STATIC uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) { int i = 0; const uint8_t* csrc = (const uint8_t*)srcData; @@ -957,13 +958,13 @@ static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) return i - (csrc[0] == 0xF0 ? 1 : 0); } -static inline uint8_t cmidi2_ump_sysex7_get_num_packets(uint8_t numSysex7Bytes) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex7_get_num_packets(size_t numSysex7Bytes) { return cmidi2_ump_sysex_get_num_packets(numSysex7Bytes, 6); } -static inline uint64_t cmidi2_ump_sysex7_get_packet_of( - uint8_t group, uint8_t numBytes, const void* srcData, int32_t index) +LIBREMIDI_STATIC uint64_t +cmidi2_ump_sysex7_get_packet_of(uint8_t group, size_t numBytes, const void* srcData, int32_t index) { uint64_t result; int srcOffset = numBytes > 0 && ((const uint8_t*)srcData)[0] == 0xF0 ? 1 : 0; @@ -978,11 +979,11 @@ static inline uint64_t cmidi2_ump_sysex7_get_packet_of( // This returns NULL for success, or anything else for failure. typedef void* (*cmidi2_ump_handler_u64)(uint64_t data, void* context); +// Processes sysex7 inputs where we do not always end at F7 and thus takes length as the argument. // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_sysex7_process( - uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context) +LIBREMIDI_STATIC void* cmidi2_ump_sysex7_process_n( + uint8_t group, void* sysex, uint32_t length, cmidi2_ump_handler_u64 sendUMP, void* context) { - int32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex); int32_t numPackets = cmidi2_ump_sysex7_get_num_packets(length); for (int p = 0; p < numPackets; p++) { @@ -994,15 +995,23 @@ static inline void* cmidi2_ump_sysex7_process( return NULL; } +// This returns NULL for success, or anything else that `sendUMP` returns for failure. +LIBREMIDI_STATIC void* cmidi2_ump_sysex7_process( + uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context) +{ + uint32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex); + return cmidi2_ump_sysex7_process_n(group, sysex, length, sendUMP, context); +} + // 7.8 System Exclusive 8-Bit Messages -static inline int8_t cmidi2_ump_sysex8_get_num_packets(uint8_t numBytes) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex8_get_num_packets(size_t numBytes) { return cmidi2_ump_sysex_get_num_packets(numBytes, 13); } -static inline void cmidi2_ump_sysex8_get_packet_of( - uint8_t group, uint8_t streamId, uint8_t numBytes, const void* srcData, int32_t index, +LIBREMIDI_STATIC void cmidi2_ump_sysex8_get_packet_of( + uint8_t group, uint8_t streamId, size_t numBytes, const void* srcData, size_t index, uint64_t* result1, uint64_t* result2) { cmidi2_ump_sysex_get_packet_of( @@ -1017,7 +1026,7 @@ typedef void* (*cmidi2_ump_handler_u128)( uint64_t data1, uint64_t data2, size_t index, void* context); // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_sysex8_process( +LIBREMIDI_STATIC void* cmidi2_ump_sysex8_process( uint8_t group, void* sysex, uint32_t length, uint8_t streamId, cmidi2_ump_handler_u128 sendUMP, void* context) { @@ -1035,21 +1044,21 @@ static inline void* cmidi2_ump_sysex8_process( // 7.9 Mixed Data Set Message -static inline uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS) +LIBREMIDI_STATIC uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS) { uint32_t radix = 14 * 0x10000; return numTotalBytesInMDS / radix + (numTotalBytesInMDS % radix ? 1 : 0); } // Returns -1 if input is out of range -static inline int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk) +LIBREMIDI_STATIC int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk) { if (numTotalBytesinChunk > 14 * 65535) return -1; return numTotalBytesinChunk / 14 + (numTotalBytesinChunk % 14 ? 1 : 0); } -static inline void cmidi2_ump_mds_get_header( +LIBREMIDI_STATIC void cmidi2_ump_mds_get_header( uint8_t group, uint8_t mdsId, uint16_t numBytesInChunk, uint16_t numChunks, uint16_t chunkIndex, uint16_t manufacturerId, uint16_t deviceId, uint16_t subId, uint16_t subId2, uint64_t* result1, uint64_t* result2) @@ -1073,7 +1082,7 @@ static inline void cmidi2_ump_mds_get_header( } // srcData points to exact start of the source data. -static inline void cmidi2_ump_mds_get_payload_of( +LIBREMIDI_STATIC void cmidi2_ump_mds_get_payload_of( uint8_t group, uint8_t mdsId, uint16_t numBytes, const void* srcData, uint64_t* result1, uint64_t* result2) { @@ -1101,7 +1110,7 @@ typedef void* (*cmidi2_mds_handler)( uint64_t data1, uint64_t data2, size_t chunkId, size_t payloadId, void* context); // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_mds_process( +LIBREMIDI_STATIC void* cmidi2_ump_mds_process( uint8_t group, uint8_t mdsId, void* data, uint32_t length, cmidi2_mds_handler sendUMP, void* context) { @@ -1127,12 +1136,12 @@ static inline void* cmidi2_ump_mds_process( // 7.5 Flex Data Message -static inline uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData) +LIBREMIDI_STATIC uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData) { return numTotalBytesInFlexData / 12 + (numTotalBytesInFlexData % 12 ? 1 : 0); } -static inline void cmidi2_ump_flex_data_complete_packet( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_complete_packet( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, uint32_t data1, uint32_t data2, uint32_t data3, uint64_t* result1, uint64_t* result2) { @@ -1147,7 +1156,7 @@ static inline void cmidi2_ump_flex_data_complete_packet( *result2 = ((uint64_t)data2 << 32) + data3; } -static inline void cmidi2_ump_flex_data_get_packet_of( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_get_packet_of( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, uint16_t numBytes, const void* srcData, int32_t currentPacket, uint64_t* result1, uint64_t* result2) @@ -1173,7 +1182,7 @@ static inline void cmidi2_ump_flex_data_get_packet_of( } else { - uint8_t isEnd = currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; + uint8_t isEnd = (size_t)currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; if (isEnd) { size = numBytes % radix ? numBytes % radix : radix; @@ -1202,7 +1211,7 @@ typedef void* (*cmidi2_flex_data_handler)(uint64_t data1, uint64_t data2, void* // This returns NULL for success, or anything else that `sendUMP` returns for failure. // `text` is usually a null-terminated text string, but it may contain `\0` as Melisma in lyricText. Hence we still need `length`. -static inline void* cmidi2_ump_flex_data_process( +LIBREMIDI_STATIC void* cmidi2_ump_flex_data_process( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, const char* text, uint32_t length, cmidi2_flex_data_handler sendUMP, void* context) { @@ -1221,7 +1230,7 @@ static inline void* cmidi2_ump_flex_data_process( // individual flex data message generators -static inline void cmidi2_ump_flex_data_set_tempo_direct( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_tempo_direct( uint8_t group, uint8_t channel, uint32_t tempoIn10NanosecondsPerQN, uint64_t* result1, uint64_t* result2) { @@ -1230,7 +1239,7 @@ static inline void cmidi2_ump_flex_data_set_tempo_direct( CMIDI2_FLEX_DATA_STATUS_SET_TEMPO, tempoIn10NanosecondsPerQN, 0, 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_time_signature( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_time_signature( uint8_t group, uint8_t channel, uint8_t numerator, uint8_t denominator, uint8_t numberOf32thNotes, uint64_t* result1, uint64_t* result2) { @@ -1240,7 +1249,7 @@ static inline void cmidi2_ump_flex_data_set_time_signature( (numerator << 24) + (denominator << 16) + (numberOf32thNotes << 8), 0, 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_metronome( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_metronome( uint8_t group, uint8_t channel, uint8_t clocksPerPrimaryClick, uint8_t barAccent1, uint8_t barAccent2, uint8_t barAccent3, uint8_t subDivisionClicks1, uint8_t subDivisionClicks2, uint64_t* result1, uint64_t* result2) @@ -1252,7 +1261,7 @@ static inline void cmidi2_ump_flex_data_set_metronome( (subDivisionClicks1 << 24) + (subDivisionClicks2 << 16), 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_key_signature( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_key_signature( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t tonicNote, uint64_t* result1, uint64_t* result2) { @@ -1262,7 +1271,7 @@ static inline void cmidi2_ump_flex_data_set_key_signature( result1, result2); } -static inline void cmidi2_ump_flex_data_set_chord_name( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_chord_name( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t chordTonic, uint8_t chordType, uint8_t alter1Type, uint8_t alter1Degree, uint8_t alter2Type, uint8_t alter2Degree, uint8_t alter3Type, uint8_t alter3Degree, uint8_t alter4Type, @@ -1288,18 +1297,18 @@ static inline void cmidi2_ump_flex_data_set_chord_name( typedef uint32_t cmidi2_ump; -static inline void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value) +LIBREMIDI_STATIC void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value) { dst[0] = value; } -static inline void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value) +LIBREMIDI_STATIC void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value) { dst[0] = value >> 32; dst[1] = value & 0xFFFFFFFF; } -static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2) +LIBREMIDI_STATIC void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2) { dst[0] = value1 >> 32; dst[1] = value1 & 0xFFFFFFFF; @@ -1307,7 +1316,7 @@ static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_ dst[3] = value2 & 0xFFFFFFFF; } -static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) { ump += at / 4; switch (at % 4) @@ -1324,12 +1333,12 @@ static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) return 0; // This is unexpected. } -static inline uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump) { return *ump >> 28; } -static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) { switch (cmidi2_ump_get_message_type(ump)) { @@ -1346,267 +1355,272 @@ static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) return 0; // invalid } -static inline uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump) { return (*ump >> 24) & 0xF; } -static inline uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_status_byte(const cmidi2_ump* ump) +{ + return (*ump >> 16) & 0xFF; +} + +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump) { return (*ump >> 16) & 0xF0; } -static inline uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump) { return (*ump >> 16) & 0xF; } -static inline uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump) { return *(ump + 1); } -static inline uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump) { return ((cmidi2_ump_get_byte_at(ump, 1) & 0xF) << 16) + (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2) + cmidi2_ump_get_byte_at(ump, 3) * 0x80; } -static inline uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 4) << 8) + (cmidi2_ump_get_byte_at(ump, 5)); } -static inline uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6) << 8) + (cmidi2_ump_get_byte_at(ump, 7)); } -static inline uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // absolute RPN or relative RPN -static inline uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } // absolute RPN or relative RPN -static inline uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } // absolute RPN or relative RPN -static inline uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // absolute NRPN or relative NRPN -static inline uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } // absolute NRPN or relative NRPN -static inline uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } // absolute NRPN or relative NRPN -static inline uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 4); } -static inline uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6)); } -static inline uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 7)); } -static inline uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // either per-note or channel -static inline uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 4) << 8) + cmidi2_ump_get_byte_at(ump, 5); } -static inline uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6) << 8) + cmidi2_ump_get_byte_at(ump, 7); } -static inline uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 8) << 8) + cmidi2_ump_get_byte_at(ump, 9); } -static inline uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 10) << 8) + cmidi2_ump_get_byte_at(ump, 11); } -static inline uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 12) << 8) + cmidi2_ump_get_byte_at(ump, 13); } -static inline uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 14) << 8) + cmidi2_ump_get_byte_at(ump, 15); } @@ -1662,13 +1676,13 @@ typedef struct cmidi2_ump_binary_read_state enum cmidi2_ump_binary_reader_result_code resultCode; } cmidi2_ump_binary_read_state; -static inline void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state) +LIBREMIDI_STATIC void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state) { state->dataSize = 0; state->resultCode = CMIDI2_BINARY_READER_RESULT_INCOMPLETE; } -static inline void cmidi2_ump_binary_read_state_init( +LIBREMIDI_STATIC void cmidi2_ump_binary_read_state_init( cmidi2_ump_binary_read_state* state, void* context, uint8_t* dataBuffer, size_t dataCapacity, bool continueOnCompletion) { @@ -1699,7 +1713,7 @@ typedef bool (*cmidi2_ump_binary_read_continuity_checker_func)( cmidi2_ump_binary_read_state* stream, cmidi2_ump* ump); // it is a special copy function that only works with sysex8 memory state at `src` that can access beyond `sizeInBytes`. -static inline void cmidi2_internal_sysex8_copy_data_byte_swapping( +LIBREMIDI_STATIC void cmidi2_internal_sysex8_copy_data_byte_swapping( uint8_t* dst, uint8_t srcHead, uint32_t* srcTail, size_t sizeInBytes) { if (sizeInBytes == 0) @@ -1728,7 +1742,7 @@ static inline void cmidi2_internal_sysex8_copy_data_byte_swapping( /// Parse and store sysex8 binary, using some fine-tuned behavioral functions. /// Return the number of 32-bit ints (number of `cmidi2_ump`s) -static inline size_t cmidi2_ump_get_sysex8_data( +LIBREMIDI_STATIC size_t cmidi2_ump_get_sysex8_data( cmidi2_ump_stream_selector_func streamSelector, void* streamSelectorContext, cmidi2_ump_binary_read_continuity_checker_func continuityChecker, const cmidi2_ump* ump, const size_t umpCapacityInInt) @@ -1798,32 +1812,32 @@ static inline size_t cmidi2_ump_get_sysex8_data( /* byte stream splitter */ -static inline void* cmidi2_ump_sequence_next(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes(ptr)); } // similar to LV2_ATOM Utilities API... -#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next(iter)) -static inline void* cmidi2_ump_sequence_next_le(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next_le(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_le(ptr)); } -#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next_le(iter)) -static inline void* cmidi2_ump_sequence_next_be(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next_be(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_be(ptr)); } -#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next_be(iter)) // -------- @@ -1890,14 +1904,14 @@ typedef struct } cmidi2_profile_id; // Assumes the input value is already 7-bit encoded if required. -static inline void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v) +LIBREMIDI_STATIC void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v) { buf[0] = v & 0xFF; buf[1] = (v >> 8) & 0xFF; } // Assumes the input value is already 7-bit encoded if required. -static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0xFF; buf[1] = (v >> 8) & 0xFF; @@ -1905,20 +1919,20 @@ static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) buf[3] = (v >> 24) & 0xFF; } -static inline void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; } -static inline void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; buf[2] = (v >> 14) & 0x7F; } -static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; @@ -1926,7 +1940,7 @@ static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) buf[3] = (v >> 21) & 0x7F; } -static inline void cmidi2_ci_message_common( +LIBREMIDI_STATIC void cmidi2_ci_message_common( uint8_t* buf, uint8_t destination, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -1941,7 +1955,7 @@ static inline void cmidi2_ci_message_common( // Discovery -static inline void cmidi2_ci_discovery_common( +LIBREMIDI_STATIC void cmidi2_ci_discovery_common( uint8_t* buf, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint32_t deviceManufacturer3Bytes, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, @@ -1962,7 +1976,7 @@ static inline void cmidi2_ci_discovery_common( buf[29] = initiatorOutputPathId; } -static inline void cmidi2_ci_discovery( +LIBREMIDI_STATIC void cmidi2_ci_discovery( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId) @@ -1973,7 +1987,7 @@ static inline void cmidi2_ci_discovery( ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId); } -static inline void cmidi2_ci_discovery_reply( +LIBREMIDI_STATIC void cmidi2_ci_discovery_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, @@ -1986,7 +2000,7 @@ static inline void cmidi2_ci_discovery_reply( buf[30] = functionBlockOr7Fh; } -static inline void cmidi2_ci_discovery_invalidate_muid( +LIBREMIDI_STATIC void cmidi2_ci_discovery_invalidate_muid( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t targetMUID) { cmidi2_ci_message_common( @@ -1994,7 +2008,7 @@ static inline void cmidi2_ci_discovery_invalidate_muid( cmidi2_ci_direct_uint32_at(buf + 13, targetMUID); } -static inline void cmidi2_ci_ack_nak_common( +LIBREMIDI_STATIC void cmidi2_ci_ack_nak_common( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t statusCode, uint8_t statusData, uint8_t* details5Bytes, uint16_t messageLength, const char* messageText) @@ -2009,7 +2023,7 @@ static inline void cmidi2_ci_ack_nak_common( memcpy(buf + 23, messageText, messageLength); } -static inline void cmidi2_ci_ack( +LIBREMIDI_STATIC void cmidi2_ci_ack( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t ackStatusCode, uint8_t ackStatusData, uint8_t* ackDetails5Bytes, uint16_t messageLength, @@ -2020,7 +2034,7 @@ static inline void cmidi2_ci_ack( ackStatusCode, ackStatusData, ackDetails5Bytes, messageLength, messageText); } -static inline void cmidi2_ci_nak( +LIBREMIDI_STATIC void cmidi2_ci_nak( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t nakStatusCode, uint8_t nakStatusData, uint8_t* nakDetails5Bytes, uint16_t messageLength, @@ -2034,7 +2048,7 @@ static inline void cmidi2_ci_nak( // Protocol Negotiation // Note that it was removed in MIDI 2.0 specification June 2023 Updates (MIDI-CI 1.2). -static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info) +LIBREMIDI_STATIC void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info) { buf[0] = info.type; buf[1] = info.version; @@ -2043,7 +2057,7 @@ static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type buf[4] = info.reserved2; } -static inline void cmidi2_ci_protocols( +LIBREMIDI_STATIC void cmidi2_ci_protocols( uint8_t* buf, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes) { buf[0] = numSupportedProtocols; @@ -2051,7 +2065,7 @@ static inline void cmidi2_ci_protocols( cmidi2_ci_protocol_info(buf + 1 + i * 5, protocolTypes[i]); } -static inline void cmidi2_ci_protocol_negotiation( +LIBREMIDI_STATIC void cmidi2_ci_protocol_negotiation( uint8_t* buf, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes) @@ -2065,7 +2079,7 @@ static inline void cmidi2_ci_protocol_negotiation( cmidi2_ci_protocols(buf + 14, numSupportedProtocols, protocolTypes); } -static inline void cmidi2_ci_protocol_set( +LIBREMIDI_STATIC void cmidi2_ci_protocol_set( uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, cmidi2_ci_protocol_type_info newProtocolType) { @@ -2075,7 +2089,7 @@ static inline void cmidi2_ci_protocol_set( cmidi2_ci_protocol_info(buf + 14, newProtocolType); } -static inline void cmidi2_ci_protocol_test( +LIBREMIDI_STATIC void cmidi2_ci_protocol_test( uint8_t* buf, bool isInitiatorToResponder, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, uint8_t* testData48Bytes) { @@ -2088,7 +2102,7 @@ static inline void cmidi2_ci_protocol_test( memcpy(buf + 14, testData48Bytes, 48); } -static inline void cmidi2_ci_protocol_confirm_established( +LIBREMIDI_STATIC void cmidi2_ci_protocol_confirm_established( uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel) { cmidi2_ci_message_common( @@ -2097,7 +2111,7 @@ static inline void cmidi2_ci_protocol_confirm_established( buf[13] = authorityLevel; } -static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length) +LIBREMIDI_STATIC int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length) { return (length != 19 || buf[0] != 0x7E || buf[1] != 0x7F || buf[2] != CMIDI2_CI_SUB_ID || buf[3] != CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL || buf[4] != 1) @@ -2107,7 +2121,7 @@ static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t leng // Profile Configuration -static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) +LIBREMIDI_STATIC void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) { buf[0] = info.fixed_7e; buf[1] = info.bank; @@ -2116,14 +2130,14 @@ static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) buf[4] = info.level; } -static inline void cmidi2_ci_profile_inquiry( +LIBREMIDI_STATIC void cmidi2_ci_profile_inquiry( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID) { cmidi2_ci_message_common( buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY, 1, sourceMUID, destinationMUID); } -static inline void cmidi2_ci_profile_inquiry_reply( +LIBREMIDI_STATIC void cmidi2_ci_profile_inquiry_reply( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t numEnabledProfiles, cmidi2_profile_id* enabledProfiles, uint8_t numDisabledProfiles, cmidi2_profile_id* disabledProfiles) @@ -2139,7 +2153,7 @@ static inline void cmidi2_ci_profile_inquiry_reply( cmidi2_ci_profile(buf + pos + i * 5, disabledProfiles[i]); } -static inline void cmidi2_ci_profile_set( +LIBREMIDI_STATIC void cmidi2_ci_profile_set( uint8_t* buf, uint8_t destination, bool turnOn, uint32_t sourceMUID, uint32_t destinationMUID, cmidi2_profile_id profile) { @@ -2150,7 +2164,7 @@ static inline void cmidi2_ci_profile_set( cmidi2_ci_profile(buf + 13, profile); } -static inline void cmidi2_ci_profile_report( +LIBREMIDI_STATIC void cmidi2_ci_profile_report( uint8_t* buf, uint8_t source, bool isEnabledReport, uint32_t sourceMUID, cmidi2_profile_id profile) { @@ -2162,7 +2176,7 @@ static inline void cmidi2_ci_profile_report( cmidi2_ci_profile(buf + 13, profile); } -static inline void cmidi2_ci_profile_specific_data( +LIBREMIDI_STATIC void cmidi2_ci_profile_specific_data( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID, cmidi2_profile_id profile, uint32_t dataSize, void* data) { @@ -2175,7 +2189,7 @@ static inline void cmidi2_ci_profile_specific_data( // Property Exchange -static inline void cmidi2_ci_property_get_capabilities( +LIBREMIDI_STATIC void cmidi2_ci_property_get_capabilities( uint8_t* buf, uint8_t destination, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t maxSupportedRequests) { @@ -2188,7 +2202,7 @@ static inline void cmidi2_ci_property_get_capabilities( } // common to all of: has data & reply, get data & reply, set data & reply, subscribe & reply, notify -static inline void cmidi2_ci_property_common( +LIBREMIDI_STATIC void cmidi2_ci_property_common( uint8_t* buf, uint8_t destination, uint8_t messageTypeSubId2, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, uint16_t headerSize, void* header, uint16_t numChunks, uint16_t chunkIndex, uint16_t dataSize, void* data) @@ -2203,7 +2217,7 @@ static inline void cmidi2_ci_property_common( memcpy(buf + 22 + headerSize, data, dataSize); } -static inline void cmidi2_ci_property_get_capabilities_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_get_capabilities_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t maxSupportedRequests, uint8_t peMajorVersion, uint8_t peMinorVersion) { @@ -2216,7 +2230,7 @@ static inline void cmidi2_ci_property_get_capabilities_reply( buf[15] = peMinorVersion; } -static inline void cmidi2_ci_property_data_common( +LIBREMIDI_STATIC void cmidi2_ci_property_data_common( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, @@ -2236,7 +2250,7 @@ static inline void cmidi2_ci_property_data_common( memcpy(buf + headerSize + 22, propertyData, propertyDataLength); } -static inline void cmidi2_ci_property_get_data( +LIBREMIDI_STATIC void cmidi2_ci_property_get_data( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData) { @@ -2245,7 +2259,7 @@ static inline void cmidi2_ci_property_get_data( requestId, headerSize, headerData, 1, 1, 0, NULL); } -static inline void cmidi2_ci_property_get_data_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_get_data_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2256,7 +2270,7 @@ static inline void cmidi2_ci_property_get_data_reply( propertyDataLength, propertyData); } -static inline void cmidi2_ci_property_set_data( +LIBREMIDI_STATIC void cmidi2_ci_property_set_data( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2267,7 +2281,7 @@ static inline void cmidi2_ci_property_set_data( propertyData); } -static inline void cmidi2_ci_property_set_data_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_set_data_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData) { @@ -2276,7 +2290,7 @@ static inline void cmidi2_ci_property_set_data_reply( destinationMUID, requestId, headerSize, headerData, 1, 1, 0, NULL); } -static inline void cmidi2_ci_property_subscribe( +LIBREMIDI_STATIC void cmidi2_ci_property_subscribe( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2287,7 +2301,7 @@ static inline void cmidi2_ci_property_subscribe( propertyData); } -static inline void cmidi2_ci_property_subscribe_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_subscribe_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2298,7 +2312,7 @@ static inline void cmidi2_ci_property_subscribe_reply( propertyDataLength, propertyData); } -static inline void cmidi2_ci_property_notify( +LIBREMIDI_STATIC void cmidi2_ci_property_notify( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2311,7 +2325,7 @@ static inline void cmidi2_ci_property_notify( // Process Inquiry -static inline void cmidi2_ci_process_get_capabilities( +LIBREMIDI_STATIC void cmidi2_ci_process_get_capabilities( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -2321,7 +2335,7 @@ static inline void cmidi2_ci_process_get_capabilities( versionAndFormat, sourceMUID, destinationMUID); } -static inline void cmidi2_ci_process_get_capabilities_reply( +LIBREMIDI_STATIC void cmidi2_ci_process_get_capabilities_reply( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t processInquirySupportedFeatures) { @@ -2333,7 +2347,7 @@ static inline void cmidi2_ci_process_get_capabilities_reply( buf[13] = processInquirySupportedFeatures; } -static inline void cmidi2_ci_process_midi_report_common( +LIBREMIDI_STATIC void cmidi2_ci_process_midi_report_common( uint8_t* buf, uint8_t deviceId, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2349,7 +2363,7 @@ static inline void cmidi2_ci_process_midi_report_common( buf[17] = requestedNoteDataMessages; } -static inline void cmidi2_ci_process_get_midi_report( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2360,7 +2374,7 @@ static inline void cmidi2_ci_process_get_midi_report( requestedChannelControllerMessages, requestedNoteDataMessages); } -static inline void cmidi2_ci_process_get_midi_report_reply( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report_reply( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2371,7 +2385,7 @@ static inline void cmidi2_ci_process_get_midi_report_reply( requestedChannelControllerMessages, requestedNoteDataMessages); } -static inline void cmidi2_ci_process_get_midi_report_end( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report_end( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -2383,7 +2397,7 @@ static inline void cmidi2_ci_process_get_midi_report_end( // Miscellaneous MIDI Utilities /** Encodes `value` into `bytes` stream. Returns the length of the encoded value in bytes. */ -static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value) +LIBREMIDI_STATIC uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value) { uint8_t pos = 0; for (;; pos++) @@ -2400,7 +2414,7 @@ static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32 } /** Returns the length of `value` when it would be encoded into a byte stream. */ -static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) +LIBREMIDI_STATIC uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) { for (uint8_t ret = 1;; ret++) { @@ -2413,7 +2427,7 @@ static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) } /** Returns the 7-bit encoded value in `bytes` stream of `length` bytes. */ -static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length) +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length) { uint8_t* start = bytes; uint32_t value = 0; @@ -2430,38 +2444,71 @@ static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_ return value; } -static inline uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length) +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length) { - uint32_t metaLength; - uint8_t* start = bytes; - uint8_t* end = bytes + length; switch (bytes[0]) { - case 0xF0: + case 0xF0: { + uint8_t* start = bytes; + uint8_t* end = bytes + length; for (bytes++; bytes < end; bytes++) if (*bytes == 0xF7) break; bytes++; - break; - case 0xFF: - bytes++; - metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes); - bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength); - break; + return bytes - start; + } + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: + return 2; + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + return 3; + case CMIDI2_SYSTEM_STATUS_TUNE_REQUEST: + case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK: + case CMIDI2_SYSTEM_STATUS_START: + case CMIDI2_SYSTEM_STATUS_CONTINUE: + case CMIDI2_SYSTEM_STATUS_STOP: + case CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING: + case CMIDI2_SYSTEM_STATUS_RESET: // Use cmidi2_midi1_get_message_size_smf to handle meta-events + return 1; default: switch (bytes[0] & 0xF0) { case 0xC0: case 0xD0: - bytes += 2; - break; + return 2; default: - bytes += 3; - break; + return 3; } break; } - return bytes - start; + return 0; +} + +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size_live(uint8_t* bytes, uint32_t length) +{ + // 0xFF in live MIDI stream indicates reset. We handle it there. + return cmidi2_midi1_get_message_size(bytes, length); +} + +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size_smf(uint8_t* bytes, uint32_t length) +{ + switch (bytes[0]) + { + case 0xFF: // 0xFF in standard MIDI file indicates Meta-events, not RESET + { + uint32_t metaLength; + uint8_t* start = bytes; + uint8_t* end = bytes + length; + + bytes++; + metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes); + bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength); + + return bytes - start; + } + default: + return cmidi2_midi1_get_message_size(bytes, length); + } } // MIDI1 to UMP Translator @@ -2531,7 +2578,7 @@ typedef struct cmidi2_midi_conversion_context int32_t context_rpn; int32_t context_nrpn; int32_t context_dte; - // MIDI 2.0 Defalult Translation (UMP specification Appendix D.3) accepts only DTE LSB + // MIDI 2.0 Default Translation (UMP specification Appendix D.3) accepts only DTE LSB // as the conversion terminator, but cmidi2 allows DTE LSB to come first, // if this flag is enabled. bool allow_reordered_dte; @@ -2562,7 +2609,7 @@ enum cmidi2_midi_conversion_result CMIDI2_CONVERSION_RESULT_INVALID_INPUT = 0x40, }; -static inline void +LIBREMIDI_STATIC void cmidi2_midi_conversion_context_initialize(cmidi2_midi_conversion_context* context) { context->is_midi1_smf = false; @@ -2591,17 +2638,18 @@ typedef struct cmidi2_convert_sysex_context size_t dst_offset; } cmidi2_convert_sysex_context; -static inline void* +LIBREMIDI_STATIC void* cmidi2_internal_convert_add_midi1_sysex7_ump_to_list(uint64_t data, void* context) { cmidi2_convert_sysex_context* s7ctx = (cmidi2_convert_sysex_context*)context; s7ctx->conversion_context->ump[s7ctx->dst_offset] = data >> 32; s7ctx->conversion_context->ump[s7ctx->dst_offset + 1] = data & 0xFFFFFFFF; - s7ctx->conversion_context->ump_proceeded_bytes += 2; + s7ctx->conversion_context->ump_proceeded_bytes += 2 * sizeof(cmidi2_ump); + s7ctx->dst_offset += 2; return NULL; } -static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( +LIBREMIDI_STATIC void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( uint64_t data1, uint64_t data2, size_t index, void* context) { (void)index; @@ -2610,11 +2658,12 @@ static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( s8ctx->conversion_context->ump[s8ctx->dst_offset + 1] = data1 & 0xFFFFFFFF; s8ctx->conversion_context->ump[s8ctx->dst_offset + 2] = data2 >> 32; s8ctx->conversion_context->ump[s8ctx->dst_offset + 3] = data2 & 0xFFFFFFFF; - s8ctx->conversion_context->ump_proceeded_bytes += 4; + s8ctx->conversion_context->ump_proceeded_bytes += 4 * sizeof(cmidi2_ump); + s8ctx->dst_offset += 4; return NULL; } -static inline uint64_t +LIBREMIDI_STATIC uint64_t cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context, uint8_t channel) { bool isRpn = (context->context_rpn & 0x8080) == 0; @@ -2629,7 +2678,7 @@ cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context : cmidi2_ump_midi2_nrpn(context->group, channel, msb, lsb, data); } -static inline uint32_t cmidi2_internal_swap_endian(uint32_t v) +LIBREMIDI_STATIC uint32_t cmidi2_internal_swap_endian(uint32_t v) { return ((v & 0xFF) << 24) + (((v >> 8) & 0xFF) << 16) + (((v >> 16) & 0xFF) << 8) + ((v >> 24) & 0xFF); @@ -2651,7 +2700,8 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) { // FIXME: implement deltaTime to JR Timestamp conversion. - if (context->midi1[*sIdx] == 0xF0) + uint8_t status = context->midi1[*sIdx]; + if (status == 0xF0) { // sysex uint8_t* f7 @@ -2682,26 +2732,38 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) context->group, context->midi1 + *sIdx, cmidi2_internal_convert_add_midi1_sysex7_ump_to_list, &sysExCtx); } - *sIdx += sysexSize + 1; // +1 for 0xF7 + *sIdx += sysexSize + 2; // +1 for 0xF0 and 0xF7 } else { // fixed sized message size_t remaining = sLen - *sIdx; - size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, remaining); - if (len > remaining) + size_t inputMidi1Len + = context->is_midi1_smf + ? cmidi2_midi1_get_message_size_smf(context->midi1 + *sIdx, remaining) + : cmidi2_midi1_get_message_size_live(context->midi1 + *sIdx, remaining); + if (inputMidi1Len > remaining) return CMIDI2_CONVERSION_RESULT_INVALID_INPUT; - uint8_t byte2 = context->midi1[*sIdx + 1]; - uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0; - uint8_t channel = context->midi1[*sIdx] & 0xF; + uint8_t byte2 = inputMidi1Len > 1 ? context->midi1[*sIdx + 1] : 0; + uint8_t byte3 = inputMidi1Len > 2 ? context->midi1[*sIdx + 2] : 0; + uint8_t channel = status & 0xF; if (context->midi_protocol == CMIDI2_PROTOCOL_TYPE_MIDI1) { // generate MIDI1 UMPs - dst[*dIdx] = cmidi2_ump_midi1_message( - context->group, context->midi1[*sIdx] & 0xF0, channel, byte2, byte3); - *sIdx += len; - *dIdx += 4; + if (status > 0xF0) + { + dst[*dIdx] = cmidi2_ump_system_message(context->group, status, byte2, byte3); + *sIdx += inputMidi1Len; + *dIdx += 4; + } + else + { + dst[*dIdx] + = cmidi2_ump_midi1_message(context->group, status & 0xF0, channel, byte2, byte3); + *sIdx += inputMidi1Len; + *dIdx += 4; + } } else { @@ -2711,18 +2773,22 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) const int16_t NO_ATTRIBUTE_DATA = 0; bool bankValid, bankMsbValid, bankLsbValid; bool skipEmitUmp = false; - switch (context->midi1[*sIdx] & 0xF0) + int outputUmpLen = 0; + switch (status & 0xF0) { case CMIDI2_STATUS_NOTE_OFF: m2 = cmidi2_ump_midi2_note_off( context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA); + outputUmpLen = 2; break; case CMIDI2_STATUS_NOTE_ON: m2 = cmidi2_ump_midi2_note_on( context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA); + outputUmpLen = 2; break; case CMIDI2_STATUS_PAF: m2 = cmidi2_ump_midi2_paf(context->group, channel, byte2, byte3 << 25); + outputUmpLen = 2; break; case CMIDI2_STATUS_CC: switch (byte2) @@ -2747,7 +2813,10 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) context->context_dte = (context->context_dte & 0xFF) | (byte3 << 8); if (context->allow_reordered_dte && (context->context_dte & 0x8080) == 0) + { m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel); + outputUmpLen = 2; + } else skipEmitUmp = true; @@ -2760,6 +2829,7 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) if ((context->context_rpn & 0x8080) && (context->context_nrpn & 0x8080)) return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE; m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel); + outputUmpLen = 2; break; case CMIDI2_CC_BANK_SELECT: @@ -2772,6 +2842,7 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) break; default: m2 = cmidi2_ump_midi2_cc(context->group, channel, byte2, byte3 << 25); + outputUmpLen = 2; break; } break; @@ -2785,37 +2856,77 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) : CMIDI2_PROGRAM_CHANGE_OPTION_NONE, byte2, bankMsbValid ? context->context_bank >> 8 : 0, bankLsbValid ? context->context_bank & 0x7F : 0); + outputUmpLen = 2; context->context_bank = 0x8080; break; case CMIDI2_STATUS_CAF: m2 = cmidi2_ump_midi2_caf(context->group, channel, byte2 << 25); + outputUmpLen = 2; break; case CMIDI2_STATUS_PITCH_BEND: // Note: Pitch Bend values in the MIDI 1.0 Protocol are presented as Little Endian. m2 = cmidi2_ump_midi2_pitch_bend_direct( context->group, channel, ((byte3 << 7) + byte2) << 18); + outputUmpLen = 2; break; default: - return CMIDI2_CONVERSION_RESULT_INVALID_STATUS; + switch (status) + { + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: + inputMidi1Len = 2; + m2 = cmidi2_ump_system_message(context->group, status, byte2, byte3); + outputUmpLen = 1; + break; + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + inputMidi1Len = 3; + m2 = cmidi2_ump_system_message(context->group, status, byte2, byte3); + outputUmpLen = 1; + break; + case CMIDI2_SYSTEM_STATUS_TUNE_REQUEST: + case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK: + case CMIDI2_SYSTEM_STATUS_START: + case CMIDI2_SYSTEM_STATUS_CONTINUE: + case CMIDI2_SYSTEM_STATUS_STOP: + case CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING: + case CMIDI2_SYSTEM_STATUS_RESET: + inputMidi1Len = 1; + m2 = cmidi2_ump_system_message(context->group, status, 0, 0); + outputUmpLen = 1; + break; + default: + return CMIDI2_CONVERSION_RESULT_INVALID_STATUS; + } + break; } if (!skipEmitUmp) { - int platEndian = cmidi2_util_is_platform_little_endian() - ? CMIDI2_TRANSLATOR_LITTLE_ENDIAN - : CMIDI2_TRANSLATOR_BIG_ENDIAN; - int actualEndian - = context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN - ? context->ump_serialization_endianness - : platEndian; - if (platEndian != actualEndian) - m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32) - | cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF); - *(uint32_t*)(dst + *dIdx) = m2 >> 32; - *dIdx += 4; - *(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF; - *dIdx += 4; + switch (outputUmpLen) + { + case 1: + *(uint32_t*)(dst + *dIdx) = m2; + *dIdx += 4; + break; + case 2: { + int platEndian = cmidi2_util_is_platform_little_endian() + ? CMIDI2_TRANSLATOR_LITTLE_ENDIAN + : CMIDI2_TRANSLATOR_BIG_ENDIAN; + int actualEndian + = context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN + ? context->ump_serialization_endianness + : platEndian; + if (platEndian != actualEndian) + m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32) + | cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF); + *(uint32_t*)(dst + *dIdx) = m2 >> 32; + *dIdx += 4; + *(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF; + *dIdx += 4; + break; + } + } } - *sIdx += len; + *sIdx += inputMidi1Len; } } } @@ -2852,11 +2963,13 @@ static size_t cmidi2_internal_add_midi1_delta_time( /// Convert one single UMP (without JR Timestamp) to MIDI 1.0 Message (without delta time) /// It is a lengthy function, so it is recommended to wrap it in another non-inline function. -static inline size_t cmidi2_convert_single_ump_to_timed_midi1( +LIBREMIDI_STATIC size_t cmidi2_convert_single_ump_to_timed_midi1( uint8_t* dst, size_t maxBytes, cmidi2_ump* ump, int32_t deltaTime, cmidi2_midi_conversion_context* context, uint8_t* sysex7Buffer, size_t* sysex7BufferIndex) { - (void)maxBytes; + if (maxBytes < 1) + return 0; + size_t midiEventSize = 0; uint64_t sysex7U64; uint8_t sysex7NumBytesInUmp; @@ -2871,29 +2984,47 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( switch (messageType) { case CMIDI2_MESSAGE_TYPE_SYSTEM: + statusCode = cmidi2_ump_get_status_byte(ump); CMIDI2_INTERNAL_ADD_DELTA_TIME - midiEventSize = 1; + switch (statusCode) { - case 0xF1: - case 0xF3: - case 0xF9: - dst[1] = cmidi2_ump_get_system_message_byte2(ump); + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + midiEventSize = 3; + break; + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: midiEventSize = 2; break; + default: + midiEventSize = 1; + break; } + if (maxBytes < midiEventSize) + return 0; + dst[0] = statusCode; // no channel filtering + if (midiEventSize >= 2) + dst[1] = cmidi2_ump_get_midi1_byte2(ump); + if (midiEventSize >= 3) + dst[2] = cmidi2_ump_get_midi1_byte3(ump); break; case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: CMIDI2_INTERNAL_ADD_DELTA_TIME - midiEventSize = 3; - dst[1] = cmidi2_ump_get_midi1_byte2(ump); + switch (statusCode) { case 0xC0: case 0xD0: midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; + dst[1] = cmidi2_ump_get_midi1_byte2(ump); break; default: + midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; + dst[1] = cmidi2_ump_get_midi1_byte2(ump); dst[2] = cmidi2_ump_get_midi1_byte3(ump); break; } @@ -2905,6 +3036,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_RPN: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 12; + if (maxBytes < midiEventSize) + return 0; dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC; dst[1] = CMIDI2_CC_RPN_MSB; dst[2] = cmidi2_ump_get_midi2_rpn_msb(ump); @@ -2921,6 +3054,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_NRPN: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 12; + if (maxBytes < midiEventSize) + return 0; dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC; dst[1] = CMIDI2_CC_NRPN_MSB; dst[2] = cmidi2_ump_get_midi2_nrpn_msb(ump); @@ -2938,18 +3073,24 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_NOTE_ON: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_note_note(ump); dst[2] = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; break; case CMIDI2_STATUS_PAF: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_paf_note(ump); dst[2] = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000; break; case CMIDI2_STATUS_CC: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[0] = statusCode | cmidi2_ump_get_channel(ump); dst[1] = cmidi2_ump_get_midi2_cc_index(ump); dst[2] = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000; @@ -2960,6 +3101,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( if (cmidi2_ump_get_midi2_program_options(ump) & CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID) { midiEventSize = 8; + if (maxBytes < midiEventSize) + return 0; dst[6] = dst[0]; // copy dst[7] = cmidi2_ump_get_midi2_program_program(ump); dst[0] = (dst[6] & 0xF) + CMIDI2_STATUS_CC; @@ -2972,17 +3115,23 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( else { midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_program_program(ump); } break; case CMIDI2_STATUS_CAF: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000; break; case CMIDI2_STATUS_PITCH_BEND: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; uint32_t pitchBendV1 = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000; dst[1] = pitchBendV1 % 0x80; dst[2] = pitchBendV1 / 0x80; @@ -3006,6 +3155,9 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( // minimal implementation for single-byte sysex7 CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 1 + cmidi2_ump_get_sysex7_num_bytes(ump); + if (maxBytes < midiEventSize) + return 0; + sysex7U64 = cmidi2_ump_read_uint64_bytes(ump); for (size_t i = 0; i < midiEventSize - 1; i++) dst[i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i); @@ -3020,7 +3172,7 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( } // Left for API backward compatibility. -static inline size_t +LIBREMIDI_STATIC size_t cmidi2_convert_single_ump_to_midi1(uint8_t* dst, size_t maxBytes, cmidi2_ump* ump) { return cmidi2_convert_single_ump_to_timed_midi1(dst, maxBytes, ump, 0, NULL, NULL, 0); @@ -3103,7 +3255,7 @@ typedef struct cmidi2_ump_forge size_t offset; } cmidi2_ump_forge; -static inline void +LIBREMIDI_STATIC void cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capacityInBytes) { forge->ump = buffer; @@ -3111,7 +3263,7 @@ cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capaci forge->offset = 0; } -static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump) { int size = 4; if (forge->offset + size > forge->capacity) @@ -3122,7 +3274,7 @@ static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint3 return true; } -static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump) { int size = 8; if (forge->offset + size > forge->capacity) @@ -3133,7 +3285,7 @@ static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint6 return true; } -static inline bool +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t ump2) { int size = 16; @@ -3145,7 +3297,7 @@ cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t return true; } -static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump) { int size = cmidi2_ump_get_message_size_bytes(ump); if (forge->offset + size > forge->capacity) @@ -3155,7 +3307,7 @@ static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, c return true; } -static inline bool +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t size) { if (forge->offset + size > forge->capacity) @@ -3167,7 +3319,7 @@ cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t s // UMP sequence merger -static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time( +LIBREMIDI_STATIC bool cmidi2_internal_ump_merge_sequence_write_delta_time( int32_t timestamp1, int32_t timestamp2, int32_t* lastTimestamp, void* dst, int32_t* dIdx, size_t dstSize) { @@ -3179,14 +3331,14 @@ static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time( { cmidi2_ump_write32( (cmidi2_ump*)((uint8_t*)dst + *dIdx), - cmidi2_ump_jr_timestamp_direct(0, deltaTime > 0xFFFF ? 0xFFFF : deltaTime)); + cmidi2_ump_jr_timestamp_direct(deltaTime > 0xFFFF ? 0xFFFF : deltaTime)); *dIdx += 4; } *lastTimestamp += deltaTime; return true; } -static inline size_t cmidi2_ump_merge_sequences( +LIBREMIDI_STATIC size_t cmidi2_ump_merge_sequences( cmidi2_ump* dst, size_t dstCapacity, cmidi2_ump* seq1, size_t seq1Size, cmidi2_ump* seq2, size_t seq2Size) { diff --git a/include/libremidi/config.hpp b/include/libremidi/config.hpp index 64c8ea7..94367d8 100644 --- a/include/libremidi/config.hpp +++ b/include/libremidi/config.hpp @@ -1,11 +1,51 @@ #pragma once +#define LIBREMIDI_VERSION "5.4.3" + +// clang-format off +#if !defined(LIBREMIDI_BASE_NAMESPACE) + #define LIBREMIDI_BASE_NAMESPACE libremidi +#endif +#if defined(LIBREMIDI_MODULE_BUILD) + #define LIBREMIDI_MODULE_EXPORT export + #define LIBREMIDI_MODULE_BEGIN_EXPORT export { + #define LIBREMIDI_MODULE_END_EXPORT } + #define NAMESPACE_LIBREMIDI export namespace LIBREMIDI_BASE_NAMESPACE + #define NAMESPACE_STDX export namespace stdx + #define LIBREMIDI_STATIC inline + #define LIBREMIDI_STATIC_IMPLEMENTATION inline + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION inline + #define LIBREMIDI_INLINE_IMPLEMENTATION + #define LIBREMIDI_ANONYMOUS_NAMESPACE detail +#else + #define LIBREMIDI_MODULE_EXPORT + #define LIBREMIDI_MODULE_BEGIN_EXPORT + #define LIBREMIDI_MODULE_END_EXPORT + #define NAMESPACE_LIBREMIDI namespace LIBREMIDI_BASE_NAMESPACE + #define NAMESPACE_STDX namespace stdx + #define LIBREMIDI_STATIC static inline + +#if defined(LIBREMIDI_HEADER_ONLY) + #define LIBREMIDI_STATIC_IMPLEMENTATION static + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION static inline + #define LIBREMIDI_INLINE_IMPLEMENTATION inline +#else + #define LIBREMIDI_STATIC_IMPLEMENTATION static + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION static + #define LIBREMIDI_INLINE_IMPLEMENTATION +#endif + + #define LIBREMIDI_ANONYMOUS_NAMESPACE +#endif +// clang-format on + #if defined(_MSC_VER) #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN #endif -#if defined(LIBREMIDI_EXPORTS) +// Dynamic exports +#if defined(LIBREMIDI_EXPORTS) || defined(LIBREMIDI_MODULE_BUILD) #if defined(_MSC_VER) #define LIBREMIDI_EXPORT __declspec(dllexport) #elif defined(__GNUC__) || defined(__clang__) @@ -15,8 +55,7 @@ #define LIBREMIDI_EXPORT #endif -#define LIBREMIDI_VERSION "4.5.0" - +// Boost check to prevent ABI issues #if defined(LIBREMIDI_USE_BOOST) #if !__has_include() #error \ @@ -28,31 +67,77 @@ #endif #endif +// Use boost::small_vector if available #if __has_include() && !defined(LIBREMIDI_NO_BOOST) - #if LIBREMIDI_SLIM_MESSAGE > 0 #include -namespace libremidi +NAMESPACE_LIBREMIDI { using midi_bytes = boost::container::static_vector; } #else #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static constexpr int small_vector_minimum_size +LIBREMIDI_STATIC constexpr int small_vector_minimum_size = sizeof(boost::container::small_vector); using midi_bytes = boost::container::small_vector; } #endif #else #include -namespace libremidi +NAMESPACE_LIBREMIDI { using midi_bytes = std::vector; } #endif +// Use boost::variant2 if available +#define LIBREMIDI_NO_BOOST_VARIANT2 1 +#if __has_include() && !defined(LIBREMIDI_NO_BOOST_VARIANT2) + #include + #define LIBREMIDI_VARIANT_IS_BOOST_VARIANT2 +namespace libremidi_variant_alias = boost::variant2; +#endif + +#if !defined(LIBREMIDI_VARIANT_IS_BOOST_VARIANT2) + #include +namespace libremidi_variant_alias = std; +#endif + +namespace libremidi +{ +using monostate = libremidi_variant_alias::monostate; +template +concept nothrow_move_constructible = std::is_nothrow_move_constructible_v; + +template +using variant = libremidi_variant_alias::variant; + +template +using slow_variant = libremidi_variant_alias::variant; + +template +using variant_element = libremidi_variant_alias::variant_alternative; +template +using variant_element_t = libremidi_variant_alias::variant_alternative_t; + +using libremidi_variant_alias::operator==; +using libremidi_variant_alias::operator!=; +using libremidi_variant_alias::operator<; +using libremidi_variant_alias::operator>; +using libremidi_variant_alias::operator<=; +using libremidi_variant_alias::operator>=; + +// using boost::variant2::in_place; +// using libremidi_variant_alias::in_place; +using libremidi_variant_alias::get; +using libremidi_variant_alias::get_if; +using libremidi_variant_alias::in_place_index; +using libremidi_variant_alias::in_place_type; +using libremidi_variant_alias::visit; +} + #if __has_include() && defined(LIBREMIDI_USE_NI_MIDI2) #define LIBREMIDI_NI_MIDI2_COMPAT 1 #endif diff --git a/include/libremidi/configurations.hpp b/include/libremidi/configurations.hpp index 6f04b7b..9af9fba 100644 --- a/include/libremidi/configurations.hpp +++ b/include/libremidi/configurations.hpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -20,7 +22,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct unspecified_configuration { @@ -29,35 +31,38 @@ struct dummy_configuration { }; -using input_api_configuration = std::variant< +using input_api_configuration = libremidi_variant_alias::variant< unspecified_configuration, dummy_configuration, alsa_raw_input_configuration, alsa_raw_ump::input_configuration, alsa_seq::input_configuration, alsa_seq_ump::input_configuration, coremidi_input_configuration, coremidi_ump::input_configuration, emscripten_input_configuration, jack_input_configuration, - kbd_input_configuration, libremidi::net::dgram_input_configuration, - libremidi::net_ump::dgram_input_configuration, pipewire_input_configuration, - winmidi::input_configuration, winmm_input_configuration, winuwp_input_configuration, - jack_ump::input_configuration, pipewire_ump::input_configuration, libremidi::API>; + kbd_input_configuration, kdmapi::input_configuration, + libremidi::net::dgram_input_configuration, libremidi::net_ump::dgram_input_configuration, + pipewire_input_configuration, winmidi::input_configuration, winmm_input_configuration, + winuwp_input_configuration, jack_ump::input_configuration, pipewire_ump::input_configuration, + android::input_configuration, libremidi::API>; -using output_api_configuration = std::variant< +using output_api_configuration = libremidi_variant_alias::variant< unspecified_configuration, dummy_configuration, alsa_raw_output_configuration, alsa_raw_ump::output_configuration, alsa_seq::output_configuration, alsa_seq_ump::output_configuration, coremidi_output_configuration, coremidi_ump::output_configuration, emscripten_output_configuration, jack_output_configuration, - libremidi::net::dgram_output_configuration, libremidi::net_ump::dgram_output_configuration, - pipewire_output_configuration, winmidi::output_configuration, winmm_output_configuration, - winuwp_output_configuration, jack_ump::output_configuration, - pipewire_ump::output_configuration, libremidi::API>; + kdmapi::output_configuration, libremidi::net::dgram_output_configuration, + libremidi::net_ump::dgram_output_configuration, pipewire_output_configuration, + winmidi::output_configuration, winmm_output_configuration, winuwp_output_configuration, + jack_ump::output_configuration, pipewire_ump::output_configuration, + android::output_configuration, libremidi::API>; -using observer_api_configuration = std::variant< +using observer_api_configuration = libremidi_variant_alias::variant< unspecified_configuration, dummy_configuration, alsa_raw_observer_configuration, alsa_raw_ump::observer_configuration, alsa_seq::observer_configuration, alsa_seq_ump::observer_configuration, coremidi_observer_configuration, coremidi_ump::observer_configuration, emscripten_observer_configuration, - jack_observer_configuration, libremidi::net::dgram_observer_configuration, - libremidi::net_ump::dgram_observer_configuration, pipewire_observer_configuration, - winmidi::observer_configuration, winmm_observer_configuration, winuwp_observer_configuration, - jack_ump::observer_configuration, pipewire_ump::observer_configuration, libremidi::API>; + jack_observer_configuration, kdmapi::observer_configuration, + libremidi::net::dgram_observer_configuration, libremidi::net_ump::dgram_observer_configuration, + pipewire_observer_configuration, winmidi::observer_configuration, winmm_observer_configuration, + winuwp_observer_configuration, jack_ump::observer_configuration, + pipewire_ump::observer_configuration, android::observer_configuration, libremidi::API>; LIBREMIDI_EXPORT libremidi::API midi_api(const input_api_configuration& conf); @@ -65,4 +70,21 @@ LIBREMIDI_EXPORT libremidi::API midi_api(const output_api_configuration& conf); LIBREMIDI_EXPORT libremidi::API midi_api(const observer_api_configuration& conf); + +void set_client_name(auto&& conf, std::string name) +{ + std::visit([&](auto& impl) { + if constexpr (requires { impl.client_name; }) + impl.client_name = std::move(name); + }, conf); +} + +std::optional client_name(const auto& conf) noexcept +{ + return std::visit([&](auto& impl) -> std::optional { + if constexpr (requires { impl.client_name; }) + return impl.client_name; + return std::nullopt; + }, conf); +} } diff --git a/include/libremidi/defaults.hpp b/include/libremidi/defaults.hpp index edbc888..f50e267 100644 --- a/include/libremidi/defaults.hpp +++ b/include/libremidi/defaults.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in; class midi_out; diff --git a/include/libremidi/detail/conversion.hpp b/include/libremidi/detail/conversion.hpp index f963feb..eab7f13 100644 --- a/include/libremidi/detail/conversion.hpp +++ b/include/libremidi/detail/conversion.hpp @@ -10,7 +10,65 @@ #include -static inline void cmidi2_reverse(int64_t v, cmidi2_ump* output) +NAMESPACE_LIBREMIDI +{ +struct cmidi2_error_domain : public stdx::error_domain +{ +public: + constexpr cmidi2_error_domain() noexcept + : error_domain{{0x636d696469325f5fULL, 0x636f6e7665727400ULL}} + { + } + + stdx::string_ref name() const noexcept override { return "cmidi2_conversion"; } + + bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override + { + if (lhs.domain() == rhs.domain()) + return error_cast(lhs) + == error_cast(rhs); + + return false; + } + + stdx::string_ref message(const stdx::error& e) const noexcept override + { + const auto status = error_cast(e); + switch (status) + { + case CMIDI2_CONVERSION_RESULT_OK: + return "Success"; + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + return "Destination buffer is too small"; + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + return "Invalid System Exclusive message format"; + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + return "Invalid Data Entry, RPN, or NRPN sequence"; + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + return "Invalid or unsupported MIDI status byte"; + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + return "Incomplete 7-bit System Exclusive message"; + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return "Invalid input data length or content"; + default: + return "Unknown cmidi2 conversion error"; + } + } +}; + +inline stdx::error from_cmidi2_result(cmidi2_midi_conversion_result ret) noexcept +{ + static constexpr cmidi2_error_domain domain{}; + // We explicitly map OK to success (0 value error) just in case, + // though usually error{} is default constructed for success. + if (ret == CMIDI2_CONVERSION_RESULT_OK) + return stdx::error{}; + + return {ret, domain}; +} +} + +LIBREMIDI_STATIC void cmidi2_reverse(int64_t v, cmidi2_ump* output) { union { @@ -23,26 +81,26 @@ static inline void cmidi2_reverse(int64_t v, cmidi2_ump* output) memcpy(output, &e1, 8); } -static inline auto u7_to_u16(uint8_t in) -> uint16_t +LIBREMIDI_STATIC auto u7_to_u16(uint8_t in) -> uint16_t { static constexpr auto ratio = float(std::numeric_limits::max()) / 127.f; return std::clamp(std::round(in * ratio), 0.f, float(std::numeric_limits::max())); }; -static inline auto u7_to_u32(uint8_t in) -> uint32_t +LIBREMIDI_STATIC auto u7_to_u32(uint8_t in) -> uint32_t { static constexpr auto ratio = double(std::numeric_limits::max()) / 127.; return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); }; -static inline auto u16_to_u32(uint16_t in) -> uint32_t +LIBREMIDI_STATIC auto u16_to_u32(uint16_t in) -> uint32_t { static constexpr auto ratio = double(std::numeric_limits::max()) / double(std::numeric_limits::max()); return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); }; -static inline bool +LIBREMIDI_STATIC bool cmidi2_midi1_channel_voice_to_midi2(const uint8_t* bytes, std::size_t sz, cmidi2_ump* output) { if (sz < 2) @@ -99,7 +157,7 @@ cmidi2_midi1_channel_voice_to_midi2(const uint8_t* bytes, std::size_t sz, cmidi2 return true; } -static inline bool +LIBREMIDI_STATIC bool cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(const cmidi2_ump* input, cmidi2_ump* output) { if (cmidi2_ump_get_message_type(input) != CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL) @@ -165,9 +223,9 @@ cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(const cmidi2_ump* input, cmidi2_ return true; } -namespace libremidi +NAMESPACE_LIBREMIDI { -static inline libremidi::message midi1_from_ump(libremidi::ump u) +LIBREMIDI_STATIC libremidi::message midi1_from_ump(libremidi::ump u) { libremidi::message ret; ret.bytes.resize(4); @@ -183,7 +241,7 @@ static inline libremidi::message midi1_from_ump(libremidi::ump u) return ret; } -static inline libremidi::ump ump_from_midi1(libremidi::message u) +LIBREMIDI_STATIC libremidi::ump ump_from_midi1(libremidi::message u) { libremidi::ump ret; if (!u.bytes.empty()) @@ -207,26 +265,22 @@ struct midi1_to_midi2 context.ump_proceeded_bytes = 0; context.skip_delta_time = true; - switch (cmidi2_convert_midi1_to_ump(&context)) + switch (auto err = cmidi2_convert_midi1_to_ump(&context)) { case CMIDI2_CONVERSION_RESULT_OK: { + // FIXME handle sysex here if (auto n = context.ump_proceeded_bytes; n > 0) return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp); else - return std::errc::operation_not_supported; + return std::errc::no_message; } case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: - return std::errc::no_buffer_space; case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: - return std::errc::invalid_argument; + return from_cmidi2_result(err); default: return std::errc::operation_not_supported; } @@ -253,26 +307,21 @@ struct midi2_to_midi1 context.ump_proceeded_bytes = 0; context.skip_delta_time = true; - switch (cmidi2_convert_ump_to_midi1(&context)) + switch (auto err = cmidi2_convert_ump_to_midi1(&context)) { case CMIDI2_CONVERSION_RESULT_OK: { if (auto n = context.midi1_proceeded_bytes; n > 0) return on_midi(midi, n, timestamp); else - return std::errc::operation_not_supported; + return std::errc::no_message; } case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: - return std::errc::no_buffer_space; case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: - return std::errc::invalid_argument; case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: - return std::errc::invalid_argument; + return from_cmidi2_result(err); default: return std::errc::operation_not_supported; } diff --git a/include/libremidi/detail/memory.hpp b/include/libremidi/detail/memory.hpp index d398c42..02ddc27 100644 --- a/include/libremidi/detail/memory.hpp +++ b/include/libremidi/detail/memory.hpp @@ -1,9 +1,11 @@ #pragma once +#include + #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { template struct deleter diff --git a/include/libremidi/detail/midi_api.hpp b/include/libremidi/detail/midi_api.hpp index 2e5ccab..3225da4 100644 --- a/include/libremidi/detail/midi_api.hpp +++ b/include/libremidi/detail/midi_api.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_api { @@ -23,14 +23,7 @@ public: { return std::errc::function_not_supported; } - virtual stdx::error set_client_name(std::string_view) - { - return std::errc::function_not_supported; - } - virtual stdx::error set_port_name(std::string_view) - { - return std::errc::function_not_supported; - } + virtual stdx::error set_port_name(std::string_view) { return std::errc::function_not_supported; } virtual stdx::error close_port() = 0; diff --git a/include/libremidi/detail/midi_in.hpp b/include/libremidi/detail/midi_in.hpp index 29f7307..7f88b87 100644 --- a/include/libremidi/detail/midi_in.hpp +++ b/include/libremidi/detail/midi_in.hpp @@ -1,10 +1,10 @@ #pragma once #include -#include -#include #include +#include +#include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_api : public midi_api { diff --git a/include/libremidi/detail/midi_out.hpp b/include/libremidi/detail/midi_out.hpp index 7966a14..74a5b3d 100644 --- a/include/libremidi/detail/midi_out.hpp +++ b/include/libremidi/detail/midi_out.hpp @@ -6,7 +6,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_api : public midi_api diff --git a/include/libremidi/detail/midi_stream_decoder.hpp b/include/libremidi/detail/midi_stream_decoder.hpp index ec569e2..cea4644 100644 --- a/include/libremidi/detail/midi_stream_decoder.hpp +++ b/include/libremidi/detail/midi_stream_decoder.hpp @@ -10,9 +10,9 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static inline int64_t system_ns() noexcept +LIBREMIDI_STATIC int64_t system_ns() noexcept { namespace clk = std::chrono; return clk::duration_cast(clk::steady_clock::now().time_since_epoch()).count(); @@ -31,7 +31,7 @@ struct timestamp_backend_info bool has_samples{}; }; -template +template struct input_state_machine_base { const Configuration& configuration; @@ -415,22 +415,20 @@ private: on_bytes_segmented(const ump_callback& cb, std::span bytes, int64_t timestamp) { // Filter according to message type - switch(cmidi2_ump_get_message_type(bytes.data())) + switch (cmidi2_ump_get_message_type(bytes.data())) { - case CMIDI2_MESSAGE_TYPE_UTILITY: - { + case CMIDI2_MESSAGE_TYPE_UTILITY: { // All the utility messages are about timing if (this->configuration.ignore_timing) return; break; } - case CMIDI2_MESSAGE_TYPE_SYSTEM: - { + case CMIDI2_MESSAGE_TYPE_SYSTEM: { if (this->configuration.ignore_timing) { auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); - switch(status) + switch (status) { case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: case CMIDI2_SYSTEM_STATUS_SONG_POSITION: @@ -439,18 +437,17 @@ private: } } - if (this->configuration.ignore_sensing) - { - auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); - if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING) - return; - } - break; + if (this->configuration.ignore_sensing) + { + auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); + if (status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING) + return; + } + break; } case CMIDI2_MESSAGE_TYPE_SYSEX7: - case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS: - { + case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS: { if (this->configuration.ignore_sysex) return; break; diff --git a/include/libremidi/detail/observer.hpp b/include/libremidi/detail/observer.hpp index 3a7f138..f891e0d 100644 --- a/include/libremidi/detail/observer.hpp +++ b/include/libremidi/detail/observer.hpp @@ -1,13 +1,13 @@ #pragma once #include #include -#include #include +#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_api { diff --git a/include/libremidi/detail/ump_stream.hpp b/include/libremidi/detail/ump_stream.hpp index 70aa5b6..fe58af2 100644 --- a/include/libremidi/detail/ump_stream.hpp +++ b/include/libremidi/detail/ump_stream.hpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { enum class segmentation_error { diff --git a/include/libremidi/error.hpp b/include/libremidi/error.hpp index 59e4607..fbc1061 100644 --- a/include/libremidi/error.hpp +++ b/include/libremidi/error.hpp @@ -1,11 +1,15 @@ #pragma once #include +#if !defined(_MSC_VER) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include +#if !defined(_MSC_VER) #pragma GCC diagnostic pop +#endif #include #include @@ -13,10 +17,15 @@ #if __has_include() && (__cpp_lib_source_location >= 201907L) #include -namespace libremidi { using source_location = std::source_location; } +NAMESPACE_LIBREMIDI +{ +using source_location = std::source_location; +} #else -namespace libremidi { -struct source_location { +NAMESPACE_LIBREMIDI +{ +struct source_location +{ static source_location current() noexcept { return {}; } int line() const noexcept { return -1; } int offset() const noexcept { return -1; } @@ -26,7 +35,7 @@ struct source_location { } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { inline auto from_errc(int64_t ret) noexcept { @@ -40,8 +49,10 @@ inline auto from_errc(int64_t ret) noexcept Note that class behaviour is undefined after a critical error (not a warning) is reported. */ -using midi_error_callback = std::function; -using midi_warning_callback = std::function; +using midi_error_callback + = std::function; +using midi_warning_callback + = std::function; } #if !defined(LIBREMIDI_LOG) diff --git a/include/libremidi/error_handler.hpp b/include/libremidi/error_handler.hpp index 9fb8c4e..d3fc2a4 100644 --- a/include/libremidi/error_handler.hpp +++ b/include/libremidi/error_handler.hpp @@ -2,13 +2,13 @@ #include #include #include -#include #include +#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct error_handler { @@ -58,6 +58,8 @@ struct error_handler }; // Needed as apple still doesn't support source_location in xcode 15.3 -#define libremidi_handle_error(config, str) error_impl(config.on_error, str, libremidi::source_location::current()) -#define libremidi_handle_warning(config, str) warning_impl(config.on_warning, str, libremidi::source_location::current()) +#define libremidi_handle_error(config, str) \ + error_impl(config.on_error, str, libremidi::source_location::current()) +#define libremidi_handle_warning(config, str) \ + warning_impl(config.on_warning, str, libremidi::source_location::current()) } diff --git a/include/libremidi/input_configuration.hpp b/include/libremidi/input_configuration.hpp index 5b64974..64666e2 100644 --- a/include/libremidi/input_configuration.hpp +++ b/include/libremidi/input_configuration.hpp @@ -6,7 +6,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { //! Specify how timestamps are handled in the system enum timestamp_mode diff --git a/include/libremidi/libremidi-c.cpp b/include/libremidi/libremidi-c.cpp index 5b76f34..3062b4e 100644 --- a/include/libremidi/libremidi-c.cpp +++ b/include/libremidi/libremidi-c.cpp @@ -24,10 +24,10 @@ struct libremidi_midi_out_handle libremidi::midi_out self; }; -namespace libremidi +NAMESPACE_LIBREMIDI { -static void assign_error_callback(const auto& src, auto& dst) +LIBREMIDI_STATIC void assign_error_callback(const auto& src, auto& dst) { if (src.callback) { @@ -123,6 +123,16 @@ int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** return 0; } +int libremidi_midi_in_port_handle(const libremidi_midi_in_port* port, uint64_t* handle) +{ + if (!port || !handle) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *handle = static_cast(p.port); + return 0; +} + int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst) { @@ -152,6 +162,16 @@ int libremidi_midi_out_port_name( return 0; } +int libremidi_midi_out_port_handle(const libremidi_midi_out_port* port, uint64_t* handle) +{ + if (!port || !handle) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *handle = static_cast(p.port); + return 0; +} + int libremidi_midi_observer_new( const libremidi_observer_configuration* c, libremidi_api_configuration* api, libremidi_midi_observer_handle** out) @@ -280,7 +300,8 @@ int libremidi_midi_in_new( 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->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -323,7 +344,8 @@ int libremidi_midi_in_new( 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->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -470,7 +492,8 @@ int libremidi_midi_out_send_message( return res != stdx::error{} ? -EIO : 0; } -int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) +int libremidi_midi_out_send_ump( + libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) { if (!out || !msg || sz > std::numeric_limits::max()) return -EINVAL; diff --git a/include/libremidi/libremidi-c.h b/include/libremidi/libremidi-c.h index c43d7e1..bb9e60b 100644 --- a/include/libremidi/libremidi-c.h +++ b/include/libremidi/libremidi-c.h @@ -198,6 +198,10 @@ LIBREMIDI_EXPORT int libremidi_midi_in_port_name( const libremidi_midi_in_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_in_port_handle( + const libremidi_midi_in_port* port, uint64_t* handle); + LIBREMIDI_EXPORT int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst); @@ -209,6 +213,10 @@ LIBREMIDI_EXPORT int libremidi_midi_out_port_name( const libremidi_midi_out_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_out_port_handle( + const libremidi_midi_out_port* port, uint64_t* handle); + /// Observer API LIBREMIDI_EXPORT int libremidi_midi_observer_new( diff --git a/include/libremidi/libremidi.cpp b/include/libremidi/libremidi.cpp index b1c9dc5..fd1427e 100644 --- a/include/libremidi/libremidi.cpp +++ b/include/libremidi/libremidi.cpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE @@ -59,7 +59,7 @@ libremidi::API midi_api(const input_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::get_if(&conf)) + if (get_if(&conf)) { ret = T::API; } @@ -71,7 +71,7 @@ libremidi::API midi_api(const output_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::get_if(&conf)) + if (get_if(&conf)) { ret = T::API; } @@ -83,7 +83,7 @@ libremidi::API midi_api(const observer_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::get_if(&conf)) + if (get_if(&conf)) { ret = T::API; } diff --git a/include/libremidi/libremidi.hpp b/include/libremidi/libremidi.hpp index da95ba7..c354efa 100644 --- a/include/libremidi/libremidi.hpp +++ b/include/libremidi/libremidi.hpp @@ -70,7 +70,7 @@ #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { //! Main class for observing hotplug of MIDI 1.0 and 2.0 devices. //! The callbacks will be called whenever a device is added or removed diff --git a/include/libremidi/message.hpp b/include/libremidi/message.hpp index cc35516..182935d 100644 --- a/include/libremidi/message.hpp +++ b/include/libremidi/message.hpp @@ -9,7 +9,7 @@ #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { enum class message_type : uint8_t { diff --git a/include/libremidi/midi_in.cpp b/include/libremidi/midi_in.cpp index 322ba31..1d4d9a3 100644 --- a/include/libremidi/midi_in.cpp +++ b/include/libremidi/midi_in.cpp @@ -7,10 +7,10 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static libremidi::ump_input_configuration +LIBREMIDI_STATIC_IMPLEMENTATION libremidi::ump_input_configuration convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) noexcept { libremidi::ump_input_configuration c2; @@ -32,7 +32,7 @@ convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) return c2; } -static libremidi::input_configuration +LIBREMIDI_STATIC_IMPLEMENTATION libremidi::input_configuration convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_conf) noexcept { libremidi::input_configuration c2; @@ -41,12 +41,11 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c converter.convert( msg.bytes.data(), msg.bytes.size(), msg.timestamp, [cb](const uint32_t* ump, std::size_t n, int64_t ts) { - if(n >= 4) - { - libremidi::ump u{ump[0],ump[1],ump[2],ump[3]}; - u.timestamp = ts; - cb(std::move(u)); - } + libremidi::ump u{ump[0]}; + for (std::size_t i = 1; i < n && i < 4; i++) + u.data[i] = ump[i]; + u.timestamp = ts; + cb(std::move(u)); return stdx::error{}; }); }; @@ -60,7 +59,7 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c return c2; } -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends) { std::unique_ptr ptr; @@ -68,7 +67,7 @@ make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends) assert(base_conf.on_message || base_conf.on_raw_data); auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -80,7 +79,7 @@ make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends) } /// MIDI 1 helpers -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in(const input_configuration& base_conf) { for (const auto& api : available_apis()) @@ -117,7 +116,7 @@ make_midi1_in(const input_configuration& base_conf) return std::make_unique(input_configuration{}, dummy_configuration{}); } -static LIBREMIDI_INLINE std::unique_ptr make_midi1_in( +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in( const input_configuration& base_conf, const input_api_configuration& api_conf, libremidi::API api) { @@ -133,14 +132,14 @@ static LIBREMIDI_INLINE std::unique_ptr make_midi1_in( return {}; } -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in(const input_configuration& base_conf, const input_api_configuration& api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_midi1_in(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -179,7 +178,7 @@ midi_in::midi_in(const input_configuration& base_conf, const input_api_configura } /// MIDI 2 helpers -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in(const ump_input_configuration& base_conf) { for (const auto& api : available_ump_apis()) @@ -214,7 +213,7 @@ make_midi2_in(const ump_input_configuration& base_conf) return {}; } -static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in( const ump_input_configuration& base_conf, const input_api_configuration& api_conf, libremidi::API api) { @@ -231,14 +230,14 @@ static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( return {}; } -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_midi2_in(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -311,6 +310,9 @@ libremidi::API midi_in::get_current_api() const noexcept LIBREMIDI_INLINE stdx::error midi_in::open_port(const input_port& port, std::string_view portName) { + if (port.api != get_current_api()) + return std::errc::invalid_argument; + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; diff --git a/include/libremidi/midi_out.cpp b/include/libremidi/midi_out.cpp index d26bfb8..205f369 100644 --- a/include/libremidi/midi_out.cpp +++ b/include/libremidi/midi_out.cpp @@ -8,14 +8,14 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi_out_impl(auto base_conf, output_api_configuration api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -28,7 +28,7 @@ make_midi_out_impl(auto base_conf, output_api_configuration api_conf) return ptr; } -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi_out(const output_configuration& base_conf) { for (const auto& api : available_apis()) @@ -58,14 +58,14 @@ make_midi_out(const output_configuration& base_conf) return std::make_unique(output_configuration{}, dummy_configuration{}); } -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_midi_out(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -137,6 +137,9 @@ libremidi::API midi_out::get_current_api() const noexcept LIBREMIDI_INLINE stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const { + if (port.api != get_current_api()) + return std::errc::invalid_argument; + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; diff --git a/include/libremidi/observer.cpp b/include/libremidi/observer.cpp index 81abe3a..156cba9 100644 --- a/include/libremidi/observer.cpp +++ b/include/libremidi/observer.cpp @@ -7,10 +7,10 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static LIBREMIDI_INLINE std::unique_ptr make_observer(const auto& base_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_observer(const auto& base_conf) { for (const auto& api : available_apis()) { @@ -43,7 +43,7 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configurat { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -59,11 +59,11 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configurat LIBREMIDI_INLINE std::unique_ptr make_observer(const auto& base_conf, observer_api_configuration api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_observer(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { diff --git a/include/libremidi/observer_configuration.hpp b/include/libremidi/observer_configuration.hpp index 891cb28..1572272 100644 --- a/include/libremidi/observer_configuration.hpp +++ b/include/libremidi/observer_configuration.hpp @@ -1,122 +1,8 @@ #pragma once -#include -#include +#include -#include -#include -#include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { -using client_handle = std::uint64_t; -using port_handle = std::uint64_t; - -struct uuid -{ - std::array bytes; - - bool operator==(const uuid& other) const noexcept = default; - std::strong_ordering operator<=>(const uuid& other) const noexcept = default; -}; -using container_identifier = std::variant; -using device_identifier = std::variant; - -struct LIBREMIDI_EXPORT port_information -{ - enum port_type : uint8_t - { - unknown = 0, - - software = (1 << 1), - loopback = (1 << 2), - - hardware = (1 << 3), - usb = (1 << 4), - bluetooth = (1 << 5), - pci = (1 << 6), - - network = (1 << 7), - }; - - /// Handle to the API client object if the API provides one - // ALSA Raw: unused - // ALSA Seq: snd_seq_t* - // CoreMIDI: MidiClientRef - // WebMIDI: unused - // JACK: jack_client_t* - // PipeWire: unused // FIXME: pw_context? pw_main_loop? - // WinMM: unused - // WinUWP: unused - client_handle client = static_cast(-1); - - /// Container identifier if the API provides one - // WinMIDI: ContainerID GUID (bit_cast to a winapi or winrt::GUID ; - // this is not the string but the binary representation). - // ALSA: device id (std::string), e.g. ID_PATH as returned by udev: "pci-0000:00:14.0-usb-0:12:1.0" - // CoreMIDI: USBLocationID (int32_t) - container_identifier container = std::monostate{}; - - /// Device identifier if the API provides one - // WinMM: { uint16_t manufacturer_id, uint16_t product_id; } - // WinMIDI: EndpointDeviceId (std::string), e.g. "\\?\swd#midisrv#midiu_ksa..." - // ALSA: sysfs path (std::string), e.g. "/sys/devices/pci0000:00/0000:00:02.2/0000:02:00.0/sound/card0/controlC0" - // CoreMIDI: USBVendorProduct (int32_t) - device_identifier device = std::monostate{}; - - /// Handle to the port identifier if the API provides one - // ALSA Raw: bit_cast to struct { uint16_t card, device, sub, padding; }. - // ALSA Seq: bit_cast to struct { uint32_t client, port; } - // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t) - // WebMIDI: unused - // JACK: jack_port_id_t - // PipeWire: port.id - // WinMIDI: uint64_t terminal_block_number; (MidiGroupTerminalBlock::Number(), index is 1-based) - // WinMM: port index between 0 and midi{In,Out}GetNumDevs() - // WinUWP: unused - port_handle port = static_cast(-1); - - /// User-readable information - // CoreMIDI: kMIDIPropertyManufacturer - // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName - // WinMM: unavailable - std::string manufacturer{}; - - // CoreMIDI: kMIDIPropertyModel - // WinMIDI: MidiEndpointDeviceInformation::Name - // WinMM: unavailable - std::string device_name{}; - - // CoreMIDI: kMIDIPropertyName - // WinMIDI: MidiGroupTerminalBlock::Name - // WinMM: szPname - std::string port_name{}; - - // CoreMIDI: kMIDIPropertyDisplayName - // Otherwise: the closest to a unique name we can get - std::string display_name{}; - - /// Port type - // CoreMIDI: available - // WinMM: unavailable - // WinMIDI: available - port_type type = port_type::unknown; - - bool operator==(const port_information& other) const noexcept = default; - std::strong_ordering operator<=>(const port_information& other) const noexcept = default; -}; - -struct input_port : port_information -{ - bool operator==(const input_port& other) const noexcept = default; - std::strong_ordering operator<=>(const input_port& other) const noexcept = default; -}; -struct output_port : port_information -{ - bool operator==(const output_port& other) const noexcept = default; - std::strong_ordering operator<=>(const output_port& other) const noexcept = default; -}; - using input_port_callback = std::function; using output_port_callback = std::function; struct observer_configuration @@ -135,6 +21,9 @@ struct observer_configuration // Observe software (virtual) ports if the API provides it uint32_t track_virtual : 1 = false; + // Observe network ports if the API provides it + uint32_t track_network : 1 = false; + // Observe any port - some systems have other weird port types than hw / sw, this covers them uint32_t track_any : 1 = false; diff --git a/include/libremidi/output_configuration.hpp b/include/libremidi/output_configuration.hpp index 44e3373..10e776c 100644 --- a/include/libremidi/output_configuration.hpp +++ b/include/libremidi/output_configuration.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct output_configuration { diff --git a/include/libremidi/port_comparison.hpp b/include/libremidi/port_comparison.hpp new file mode 100644 index 0000000..4d7ef25 --- /dev/null +++ b/include/libremidi/port_comparison.hpp @@ -0,0 +1,565 @@ +#pragma once +#include +#if __has_include() + #include +namespace libremidi +{ +template +using temp_map_type = boost::container::flat_map; +} +#else + #include +namespace libremidi +{ +template +using temp_map_type = std::map; +} +#endif +#include +#include +#include +#include + +namespace libremidi +{ +// Simple compare-the-name equality. Useful for saving / reloading +struct port_name_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.port_name == rhs.port_name; + } +}; +struct port_name_less +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return std::tie(lhs.api, lhs.port_name) < std::tie(rhs.api, rhs.port_name); + } +}; + +// Compare an existing port with others. +// Note that on multiple APIs, this comparison method is only valid as long +// as no device gets connected / disconnected, as the port identifier / handle +// is sadly just the index in the list of devices returned by the OS, which will +// change as soon as a device changes. +// Thus, it should only ever be used to compare devices between a group obtained +// from a single call to get_input_ports / get_output_ports or in situations where we can +// be sure that there is no hot-plugging. +struct port_identity_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.port == rhs.port; + } +}; +struct port_identity_less +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return std::tie(lhs.api, lhs.port) < std::tie(rhs.api, rhs.port); + } +}; + +struct port_exactly_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device + && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer + && lhs.product == rhs.product && lhs.serial == rhs.serial + && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name + && lhs.display_name == rhs.display_name; + } +}; + +struct port_mostly_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device + && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer + && lhs.product == rhs.product && lhs.serial == rhs.serial + && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name; + } +}; + +struct port_heuristic_matcher +{ + // Configuration for weights + static constexpr int W_HARDWARE_ID = 1000; // Unique HW IDs (Container, Device) + static constexpr int W_SERIAL = 800; // Serial Number + static constexpr int W_NAME_EXACT = 400; // Display/Port/Device Names + static constexpr int W_METADATA = 100; // Manufacturer/Product + static constexpr int W_HANDLE = 50; // Port Index/Handle + + // Penalties for mismatches when data is present in both but differs + static constexpr int P_HARDWARE_MISMATCH = -2000; + static constexpr int P_SERIAL_MISMATCH = -1000; + static constexpr int P_NAME_MISMATCH = -100; + static constexpr int P_PRODUCT_MISMATCH = -10; + + struct match_score + { + int score = 0; + bool api_mismatch = false; + + bool is_match() const { return !api_mismatch && score > 0; } + constexpr auto operator<=>(const match_score& other) const noexcept = default; + }; + + static inline constexpr bool chars_equal_ignore_case(char lhs, char rhs) { + if(lhs >= 'A' && lhs <= 'Z') + lhs -= 'A' - 'a'; + if(rhs >= 'A' && rhs <= 'Z') + rhs -= 'A' - 'a'; + + return lhs == rhs; + } + + static inline double fuzzy_match_name(std::string_view s1, std::string_view s2) + { + const size_t len1 = s1.size(); + const size_t len2 = s2.size(); + if (len1 == 0 && len2 == 0) + return 1.0; + if (len1 == 0 || len2 == 0) + return 0; + if (len1 > 1024 || len2 > 1024) + return 0; + if (len1 > len2) + return fuzzy_match_name(s2, s1); + + auto col = (size_t*)alloca(sizeof(size_t) * (len1 + 1)); + std::fill_n(col, len1 + 1, 0); + + // Initialize first column (0, 1, 2... len1) + for (size_t i = 0; i <= len1; ++i) col[i] = i; + + // Compute Levenshtein distance + for (size_t j = 1; j <= len2; ++j) { + size_t prev_diag = col[0]; + col[0] = j; + + for (size_t i = 1; i <= len1; ++i) { + size_t prev_col = col[i]; + size_t cost = chars_equal_ignore_case(s1[i - 1], s2[j - 1]) ? 0 : 1; + + col[i] = std::min({ + col[i] + 1, // Deletion + col[i - 1] + 1, // Insertion + prev_diag + cost // Substitution + }); + + prev_diag = prev_col; + } + } + + const size_t distance = col[len1]; + const size_t max_len = std::max(len1, len2); + + if (max_len == 0) + return 1.0; + + return 1.0 - (static_cast(distance) / static_cast(max_len)); + } + + match_score calculate(const port_information& target, const port_information& candidate) const + { + match_score result; + + // 1. API Mismatch + // It is impossible for a port to be the same if the API is different. + if (target.api != libremidi::API::UNSPECIFIED) + { + if (target.api != candidate.api) + { + result.api_mismatch = true; + result.score = std::numeric_limits::min(); + return result; + } + } + + // 2. Hardware Identifiers & Serial Number + // High value, but unreliable presence. + switch(target.api) + { + case libremidi::API::COREMIDI: + case libremidi::API::COREMIDI_UMP: + case libremidi::API::WINDOWS_MM: + { + score_variant(result.score, target.device, candidate.device, W_HARDWARE_ID, P_HARDWARE_MISMATCH); + break; + } + default: + break; + } + + score_string(result.score, target.manufacturer, candidate.manufacturer, W_METADATA, P_HARDWARE_MISMATCH); + score_string(result.score, target.product, candidate.product, W_METADATA, P_HARDWARE_MISMATCH); + score_string(result.score, target.serial, candidate.serial, W_SERIAL, P_SERIAL_MISMATCH); + + // 3. Names + // We accumulate score for every name that matches. + score_string(result.score, target.display_name, candidate.display_name, W_NAME_EXACT, P_NAME_MISMATCH); + score_string(result.score, target.port_name, candidate.port_name, W_NAME_EXACT, P_NAME_MISMATCH); + score_string(result.score, target.device_name, candidate.device_name, W_NAME_EXACT, P_NAME_MISMATCH); + + // 4. Port Handle (Index) + // Only check if it's not the default -1. + // We rely on this primarily as a tie-breaker if names/hardware IDs are identical + // (e.g. two identical controllers plugged in). + if (target.port != static_cast(-1)) + { + if (target.port == candidate.port) + { + result.score += W_HANDLE; + } + } + + return result; + } + +private: + void score_string(int& score, std::string_view target_s, std::string_view cand_s, int reward, int penalty) const + { + // If the target doesn't know this info, we can't judge. Skip. + if (target_s.empty()) + return; + + const double res = fuzzy_match_name(cand_s, target_s); + if (res >= 0.5) + { + score += res * reward; + } + else if (!cand_s.empty()) + { + // If candidate value is empty, it's just missing info, not necessarily a mismatch. + score += penalty; + } + } + + // Helper for std::variant fields (device / container identifiers) + template + void score_variant(int& score, const T& target_v, const T& cand_v, int reward, int penalty) const + { + if (holds_alternative(target_v)) + return; + + // For those we want an exact search + if (target_v == cand_v) + { + score += reward; + } + else if (!holds_alternative(cand_v)) + { + // Candidate has a specific ID, and it differs from Target's specific ID. + score += penalty; + } + } +}; + +template +struct port_search_result +{ + const T* port = nullptr; + int score = 0; + bool found = false; +}; + +template +inline port_search_result find_closest_port(const T& target, std::span candidates) +{ + port_heuristic_matcher matcher{}; + + const T* best_match = nullptr; + port_heuristic_matcher::match_score best_score; + best_score.score = -1; + + for (const auto& candidate : candidates) + { + port_heuristic_matcher::match_score current = matcher.calculate(target, candidate); + + if (current.is_match() && current > best_score) + { + best_score = current; + best_match = &candidate; + } + } + + if (best_match) + return { best_match, best_score.score, true }; + + return { nullptr, 0, false }; +} + +template +inline std::vector +optimistic_serialized_port_lookup(const T& target, std::span ports) +{ + if (ports.empty()) + return {}; + + // 1. Look for an exact match on all fields + std::vector candidates; + for (auto& candidate : ports) + { + if (port_mostly_equal{}(target, candidate)) + candidates.push_back(&candidate); + } + switch (candidates.size()) + { + case 0: { + break; + } + case 1: { + return candidates; + } + default: { + // If we have an exact match return it + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + + // Else return the entire bunch as we have no way to differentiate + return candidates; + } + } + + // 2. Heuristics + + // Look for candidates in the same API + candidates.clear(); + for (auto& candidate : ports) + { + if (target.api != libremidi::API::UNSPECIFIED) + { + if (target.api == candidate.api) + { + // Port was set, let's give a high trust to this + if (target.port != static_cast(-1)) + { + if (target.port == candidate.port) + { + if (target.port_name == candidate.port_name + && target.device_name == candidate.device_name) + { + // We can be 99% confident it's the right one + candidates.push_back(&candidate); + } + else if ( + port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.port_name) + >= 0.8 + && port_heuristic_matcher::fuzzy_match_name( + target.device_name, candidate.device_name) + >= 0.8) + { + candidates.push_back(&candidate); + } + else + { + // Same API & same port, different port_name & device_name: + // very likely it's the wrong one + continue; + } + } + } + else + { +#define do_compare(MEMBER) \ + { \ + ok &= target.MEMBER == candidate.MEMBER || target.MEMBER.empty(); \ + if (!ok) \ + continue; \ + } + bool ok = true; + + // These three are compared later + // do_compare(display_name); + // do_compare(container); + // do_compare(device); + do_compare(manufacturer); + do_compare(product); + do_compare(serial); + do_compare(device_name); + do_compare(port_name); + +#undef do_compare + // If we got there it's a very good candidate + candidates.push_back(&candidate); + } + } + } + } + + switch (candidates.size()) + { + case 0: { + break; + } + case 1: { + // One candidate in the same API + return {candidates[0]}; + } + default: { + // Let's look if we have one that has the same container + if (!get_if(&target.container) + && !get_if(&target.device) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.container == candidate->container && target.device == candidate->device + && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container && target.device == candidate->device) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container) + return {candidate}; + for (auto* candidate : candidates) + if (target.device == candidate->device) + return {candidate}; + } + else if ( + !get_if(&target.container) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.container == candidate->container + && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container) + return {candidate}; + } + else if ( + !get_if(&target.device) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.device == candidate->device && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.device == candidate->device) + return {candidate}; + } + // Else return them all as we have no way to differentiate + return candidates; + } + } + + // Look for candidates in different APIs. + // Here most informations are different, so we only do a fuzzy match + candidates.clear(); + libremidi::temp_map_type ranked_candidates; + for (auto& candidate : ports) + { + int score = 0; + if (!target.port_name.empty() && !candidate.port_name.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.port_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.device_name.empty() && !candidate.device_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.device_name, candidate.device_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.port_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.port_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.port_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.device_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.device_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.device_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.device_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.manufacturer.empty() && !candidate.manufacturer.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.manufacturer, candidate.manufacturer); + if (res > 0.7) + { + score += 3 * res; + } + } + if (!target.product.empty() && !candidate.product.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.product, candidate.product); + if (res > 0.7) + { + score += 5 * res; + } + } + if (!target.serial.empty() && !candidate.serial.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.serial, candidate.serial); + if (res > 0.7) + { + score += 10 * res; + } + } + if (score > 0) + ranked_candidates[score] = &candidate; + } + + candidates.clear(); + candidates.reserve(ranked_candidates.size()); + for (auto [score, candidate] : ranked_candidates) + candidates.insert(candidates.begin(), candidate); + return candidates; +} +} diff --git a/include/libremidi/port_information.hpp b/include/libremidi/port_information.hpp new file mode 100644 index 0000000..9edeffb --- /dev/null +++ b/include/libremidi/port_information.hpp @@ -0,0 +1,164 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +namespace libremidi +{ + +struct LIBREMIDI_EXPORT port_information +{ + // Compat + using port_type = libremidi::transport_type; + + /// Which API is this port for. port_information objects are in general + /// not useable for different APIs than the API of the observer that created them. + libremidi::API api{}; + + /// Handle to the API client object if the API provides one + // Android: unused + // ALSA Raw: unused + // ALSA Seq: snd_seq_t* + // CoreMIDI: MidiClientRef + // JACK: jack_client_t* + // PipeWire: unused // FIXME: pw_context? pw_main_loop? + // WebMIDI: unused + // WinMIDI: TODO + // WinMM: unused + // WinUWP: unused + client_handle client = static_cast(-1); + + /// Container identifier if the API provides one + // Android: unavailable + // ALSA: device id (std::string), e.g. ID_PATH as returned by udev: "pci-0000:00:14.0-usb-0:12:1.0" + // CoreMIDI: USBLocationID (int32_t) + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: ContainerID GUID (bit_cast to a winapi or winrt::GUID ; + // this is not the string but the binary representation). + // WinMM: unavailable + // WinUWP: unavailable + container_identifier container = libremidi_variant_alias::monostate{}; + + /// Device identifier if the API provides one + // Android: unavailable + // ALSA: sysfs path (std::string), e.g. "/sys/devices/pci0000:00/0000:00:02.2/0000:02:00.0/sound/card0/controlC0" + // CoreMIDI: USBVendorProduct (int32_t) + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: EndpointDeviceId (std::string), e.g. "\\?\swd#midisrv#midiu_ksa..." + // WinMM: MIDI{IN,OUT}CAPS mId / pId { uint16_t manufacturer_id, uint16_t product_id; } + // WinUWP: unavailable + device_identifier device = libremidi_variant_alias::monostate{}; + + /// Handle to the port identifier if the API provides one + // Android: index of the MIDI device in the list provided by the OS. + // ALSA Raw: bit_cast to struct { uint16_t card, device, sub, padding; }. + // ALSA Seq: bit_cast to struct { uint32_t client, port; } + // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t) + // JACK: jack_port_id_t + // PipeWire: "port.id" (uint32_t) + // WebMIDI: index of the MIDI device in the list provided by the browser. + // WinMIDI: uint64_t terminal_block_number; (MidiGroupTerminalBlock::Number(), index is 1-based) + // WinMM: port index between 0 and midi{In,Out}GetNumDevs() + // WinUWP: index of the MIDI device in the list provided by the OS. + port_handle port = static_cast(-1); + + /// User-readable information + // Android: unavailable + // ALSA Raw: ID_VENDOR_FROM_DATABASE if provided by udev + // ALSA Seq: ID_VENDOR_FROM_DATABASE if provided by udev + // CoreMIDI: kMIDIPropertyManufacturer + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName + // WinMM: unavailable + // WinUWP: unavailable + std::string manufacturer{}; + + // Android: unavailable + // ALSA Raw: ID_MODEL_FROM_DATABASE if provided by udev + // ALSA Seq: ID_MODEL_FROM_DATABASE if provided by udev + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().Name + // WinUWP: unavailable + std::string product{}; + + /// "Unique" serial number. Note that this is super unreliable - pretty + /// much no MIDI device manufacturer bothers with unique per-device serial number + /// unlike most USB devices. + // Android: unavailable + // ALSA Raw: ID_USB_SERIAL if provided by udev. + // ALSA Seq: ID_USB_SERIAL if provided by udev. + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().SerialNumber + // WinUWP: unavailable + std::string serial{}; + + // Android: unavailable + // ALSA Raw: Name returned by snd_rawmidi_info_get_name + // ALSA Seq: Name returned by snd_seq_client_info_get_name + // CoreMIDI: kMIDIPropertyModel + // JACK: jack_get_client_name + // PipeWire: first part of port_alias, e.g. "A" in A:B + // WinMIDI: MidiEndpointDeviceInformation::Name + // WinMM: unavailable + // WinUWP: unavailable + std::string device_name{}; + + // Android: MidiDeviceInfo.getProperties().getString("name") + // ALSA Raw: Name returned by snd_rawmidi_info_get_subdevice_name + // ALSA Seq: Name returned by snd_seq_port_info_get_name + // CoreMIDI: kMIDIPropertyName + // JACK: jack_port_name + // PipeWire: "port.name" + // WinMIDI: MidiGroupTerminalBlock::Name + // WinMM: szPname + // WinUWP: DeviceInformation.Id() + std::string port_name{}; + + // CoreMIDI: kMIDIPropertyDisplayName + // WinUWP: DeviceInformation.Name() + // Otherwise: the closest to a unique name we can get + std::string display_name{}; + + /// Port type + // Android: unavailable + // ALSA Raw / Seq: available if udev is avaialble + // CoreMIDI: available + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: available + // WinMM: unavailable + // WinUWP: unavailable + port_type type = port_type::unknown; + + // Equality and comparison operators are deleted as there is not one + // single correct way to compare two port_information: + // in some cases it may be useful to only compare the names, while in other cases + // it is necessary to check whether this is the exact same low-level identifier. + // Thus, the end-user must define their own custom equality operators + // if using std:: containers or algorithms + bool operator==(const port_information& other) const noexcept = delete; + std::strong_ordering operator<=>(const port_information& other) const noexcept = delete; +}; + +struct input_port : port_information +{ + bool operator==(const input_port& other) const noexcept = delete; + std::strong_ordering operator<=>(const input_port& other) const noexcept = delete; +}; +struct output_port : port_information +{ + bool operator==(const output_port& other) const noexcept = delete; + std::strong_ordering operator<=>(const output_port& other) const noexcept = delete; +}; + +} diff --git a/include/libremidi/protocols/remote_control.hpp b/include/libremidi/protocols/remote_control.hpp index 9ce2a1d..8281509 100644 --- a/include/libremidi/protocols/remote_control.hpp +++ b/include/libremidi/protocols/remote_control.hpp @@ -12,7 +12,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { static constexpr auto to_underlying(auto e) { diff --git a/include/libremidi/reader.cpp b/include/libremidi/reader.cpp index 2d2a543..cf59923 100644 --- a/include/libremidi/reader.cpp +++ b/include/libremidi/reader.cpp @@ -55,18 +55,18 @@ std::ostream& operator<<(std::ostream& s, const libremidi::message& m) } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { namespace util { struct no_validator { - static inline bool validate_track([[maybe_unused]] const midi_track& track) { return true; } + static bool validate_track([[maybe_unused]] const midi_track& track) { return true; } }; struct validator { - static inline bool validate_track(const midi_track& track) + static bool validate_track(const midi_track& track) { if (track.empty()) { @@ -108,13 +108,13 @@ struct read_unchecked { // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits, // with top bit set to signify that another byte follows). - static inline void ensure_size( + static void ensure_size( [[maybe_unused]] const uint8_t* begin, [[maybe_unused]] const uint8_t* end, [[maybe_unused]] int64_t needed) { } - static inline uint32_t + static uint32_t read_variable_length(uint8_t const*& data, [[maybe_unused]] uint8_t const* end) { uint32_t result = 0; @@ -133,7 +133,7 @@ struct read_unchecked } } - static inline void read_bytes( + static void read_bytes( midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end, const std::size_t num) { @@ -142,20 +142,20 @@ struct read_unchecked buffer.push_back(*data++); } - static inline void read_bytes( + static void read_bytes( midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end, const int num) { read_bytes(buffer, data, end, static_cast(num)); } - static inline uint16_t read_uint16_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint16_t read_uint16_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint16_t result = *data++ << 8u; result += *data++; return result; } - static inline uint32_t read_uint24_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint32_t read_uint24_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint32_t result = *data++ << 16u; result += static_cast(*data++ << 8u); @@ -163,7 +163,7 @@ struct read_unchecked return result; } - static inline uint32_t read_uint32_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint32_t read_uint32_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint32_t result = *data++ << 24u; result += static_cast(*data++ << 16u); @@ -178,14 +178,14 @@ struct read_checked { // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits, // with top bit set to signify that another byte follows). - static inline void + static void ensure_size(const uint8_t* begin, const uint8_t* end, const std::size_t needed) { if (const auto available = static_cast(end - begin); available < needed) throw std::runtime_error("MIDI reader: not enough data to process"); } - static inline std::size_t read_variable_length(uint8_t const*& data, uint8_t const* end) + static std::size_t read_variable_length(uint8_t const*& data, uint8_t const* end) { std::size_t result = 0; while (true) @@ -204,26 +204,26 @@ struct read_checked } } - static inline void + static void read_bytes(midi_bytes& buffer, uint8_t const*& data, uint8_t const* end, const std::size_t num) { ensure_size(data, end, num); read_unchecked::read_bytes(buffer, data, end, num); } - static inline uint16_t read_uint16_be(uint8_t const*& data, uint8_t const* end) + static uint16_t read_uint16_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 2); return read_unchecked::read_uint16_be(data, end); } - static inline uint32_t read_uint24_be(uint8_t const*& data, uint8_t const* end) + static uint32_t read_uint24_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 3); return read_unchecked::read_uint24_be(data, end); } - static inline uint32_t read_uint32_be(uint8_t const*& data, uint8_t const* end) + static uint32_t read_uint32_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 4); return read_unchecked::read_uint32_be(data, end); @@ -537,7 +537,7 @@ LIBREMIDI_INLINE auto reader::parse(const uint8_t* dataPtr, std::size_t size) noexcept -> parse_result try { - using namespace libremidi::util; + using namespace util; tracks.clear(); @@ -589,7 +589,7 @@ try return parse_result::invalid; } - startingTempo = 120.0f; // midi default + startingTempo = 120.0f; // midi default ticksPerBeat = float(time_division); // ticks per beat (a beat is defined as a quarter note) parse_result result = parse_result::validated; diff --git a/include/libremidi/reader.hpp b/include/libremidi/reader.hpp index 4fda384..2cb8d34 100644 --- a/include/libremidi/reader.hpp +++ b/include/libremidi/reader.hpp @@ -27,7 +27,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -namespace libremidi +NAMESPACE_LIBREMIDI { /** * @brief reads Standard MIDI files (SMF). diff --git a/include/libremidi/shared_context.hpp b/include/libremidi/shared_context.hpp index ad9d9a4..5d9e68e 100644 --- a/include/libremidi/shared_context.hpp +++ b/include/libremidi/shared_context.hpp @@ -2,7 +2,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class shared_context diff --git a/include/libremidi/system_error2.hpp b/include/libremidi/system_error2.hpp index b5dc1cd..cee4fe2 100644 --- a/include/libremidi/system_error2.hpp +++ b/include/libremidi/system_error2.hpp @@ -1,3 +1,4 @@ +#pragma once #ifndef STDX_COMPILER_HPP #define STDX_COMPILER_HPP @@ -63,6 +64,12 @@ #define STDX_TRIVIALLY_RELOCATABLE #endif +#if defined(__clang__) && defined(__has_warning) + #pragma clang diagnostic push + #if __has_warning("-Wdeprecated-declarations") + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif +#endif #endif // STDX_COMPILER_HPP @@ -71,7 +78,7 @@ #include -namespace stdx { +NAMESPACE_STDX { // Implementation of std::void_t for use with pre-C++17 compilers. // @@ -180,7 +187,11 @@ using is_trivially_move_constructible = is_trivially_copyable; #if defined(__cpp_lib_trivially_relocatable) using std::is_trivially_relocatable; #elif defined(__has_builtin) - #if __has_builtin(__is_trivially_relocatable) + #if __has_builtin(__builtin_is_cpp_trivially_relocatable) +template +struct is_trivially_relocatable : std::bool_constant<__builtin_is_cpp_trivially_relocatable(T)> { }; + #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE + #elif __has_builtin(__is_trivially_relocatable) template struct is_trivially_relocatable : std::bool_constant<__is_trivially_relocatable(T)> { }; #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE @@ -214,7 +225,7 @@ struct is_trivially_relocatable : is_trivially_copyable { }; #include -namespace stdx { +NAMESPACE_STDX { namespace detail { @@ -374,7 +385,7 @@ struct is_bit_castable #include #include -namespace stdx { +NAMESPACE_STDX { struct default_intrusive_reference_count; class default_intrusive_reference_control; @@ -1151,7 +1162,7 @@ struct is_trivially_relocatable> : std::true_type #include #include -namespace stdx { +NAMESPACE_STDX { class string_ref; @@ -1596,7 +1607,7 @@ public: #include -namespace stdx { +NAMESPACE_STDX { #if __cplusplus >= 201703L #if defined(__cpp_lib_launder) @@ -1638,7 +1649,7 @@ constexpr T* launder(T* p) noexcept #include -namespace stdx { +NAMESPACE_STDX { class error; @@ -1685,7 +1696,7 @@ constexpr auto construct_error_from_adl(Args&&... args) noexcept( } // end namespace stdx_adl -namespace stdx { +NAMESPACE_STDX { enum class dynamic_exception_errc { @@ -2844,9 +2855,9 @@ struct is_error_code_enum : std::true_type #include -namespace stdx { +NAMESPACE_STDX { -namespace { +// namespace { inline const char* dynamic_exception_errc_str(unsigned ev) noexcept { @@ -2924,7 +2935,7 @@ inline const std::error_category& dynamic_exception_category() noexcept return dynamic_exception_error_category_instance; } -} // end anonymous namespace +//} // end anonymous namespace inline std::error_code make_error_code(dynamic_exception_errc code) noexcept { @@ -3143,9 +3154,9 @@ inline bool generic_error_domain::equivalent(const error& lhs, const error& rhs) return false; } -namespace { +// namespace { -string_ref generic_error_code_message(std::errc code) noexcept +inline string_ref generic_error_code_message(std::errc code) noexcept { switch (code) { @@ -3312,7 +3323,7 @@ string_ref generic_error_code_message(std::errc code) noexcept } } -} // end anonymous namespace +// } // end anonymous namespace inline string_ref generic_error_domain::message(const error& e) const noexcept { @@ -3406,9 +3417,9 @@ inline string_ref dynamic_exception_error_domain::message(const error& e) const return string_ref{"Unknown dynamic exception"}; } -namespace { +// namespace { -std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept +inline std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept { switch (code) { @@ -3432,7 +3443,7 @@ std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) no return std::errc{}; } -} // end anonymous namespace +// } // end anonymous namespace inline bool dynamic_exception_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept { @@ -3497,5 +3508,6 @@ inline string_ref dynamic_exception_code_error_domain::message(const error& e) c } // end namespace stdx - - +#if defined(__clang__) && defined(__has_warning) +#pragma clang diagnostic pop +#endif diff --git a/include/libremidi/types.hpp b/include/libremidi/types.hpp new file mode 100644 index 0000000..dfd0e2e --- /dev/null +++ b/include/libremidi/types.hpp @@ -0,0 +1,70 @@ +#pragma once +#include + +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +/// MIDI protocol supported by an endpoint or function block +enum class midi_protocol : uint8_t +{ + midi1 = (1 << 1), + midi2 = (1 << 2), + both = midi1 | midi2 +}; + +enum transport_type : uint8_t +{ + unknown = 0, + + software = (1 << 1), + loopback = (1 << 2), + + hardware = (1 << 3), + usb = (1 << 4), + bluetooth = (1 << 5), + pci = (1 << 6), + + network = (1 << 7), +}; + +/// UMP version information +struct ump_version +{ + uint8_t major{1}; + uint8_t minor{1}; + + bool operator==(const ump_version&) const noexcept = default; + std::strong_ordering operator<=>(const ump_version&) const noexcept = default; +}; + +using client_handle = std::uint64_t; +using port_handle = std::uint64_t; + +struct uuid +{ + std::array bytes; + + bool operator==(const uuid& other) const noexcept = default; + std::strong_ordering operator<=>(const uuid& other) const noexcept = default; +}; + +struct usb_device_identifier +{ + uint16_t vendor_id; + uint16_t product_id; + + bool operator==(const usb_device_identifier& other) const noexcept = default; + std::strong_ordering operator<=>(const usb_device_identifier& other) const noexcept = default; +}; + +using container_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, uuid, std::string, std::uint64_t>; +using device_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, std::string, std::uint64_t, usb_device_identifier>; +using endpoint_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, std::string, std::uint64_t>; +} diff --git a/include/libremidi/ump.hpp b/include/libremidi/ump.hpp index 073a272..2efd89f 100644 --- a/include/libremidi/ump.hpp +++ b/include/libremidi/ump.hpp @@ -5,7 +5,7 @@ #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { namespace midi2 { diff --git a/include/libremidi/ump_events.hpp b/include/libremidi/ump_events.hpp index 4d8a52b..24e201e 100644 --- a/include/libremidi/ump_events.hpp +++ b/include/libremidi/ump_events.hpp @@ -4,7 +4,7 @@ #include // FIXME! we need to review them all to check the channels -namespace libremidi::ump_events +NAMESPACE_LIBREMIDI::ump_events { inline libremidi::ump note_on(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) { @@ -63,7 +63,7 @@ inline libremidi::ump pitch_bend(uint8_t group, uint8_t channel, uint32_t value) } } -namespace libremidi::from_midi1 +NAMESPACE_LIBREMIDI::from_midi1 { inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, uint8_t vel) { @@ -122,7 +122,7 @@ inline libremidi::ump pitch_bend(uint8_t channel, uint16_t value) } } -namespace libremidi::from_01 +NAMESPACE_LIBREMIDI::from_01 { inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, double vel) { @@ -179,7 +179,7 @@ inline libremidi::ump pitch_bend(uint8_t channel, double value) } } -namespace libremidi::as_midi1 +NAMESPACE_LIBREMIDI::as_midi1 { inline auto note_off(const libremidi::ump& mess) { @@ -281,7 +281,7 @@ inline auto pitch_bend(const libremidi::ump& mess) } } -namespace libremidi::as_01 +NAMESPACE_LIBREMIDI::as_01 { inline auto note_off(const libremidi::ump& mess) { diff --git a/include/libremidi/writer.cpp b/include/libremidi/writer.cpp index 7a078fc..1d65b78 100644 --- a/include/libremidi/writer.cpp +++ b/include/libremidi/writer.cpp @@ -47,12 +47,12 @@ constexpr R bit_cast(const T& v) noexcept } } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { namespace util { template -static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::ostream& write_be(std::ostream& out, T value) { static_assert( std::endian::native == std::endian::big || std::endian::native == std::endian::little); @@ -77,7 +77,7 @@ static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) // Write a number to the midifile // as a variable length value which segments a file into 7-bit // values. Maximum size of aValue is 0x7fffffff -static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector& outdata) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION void write_variable_length(uint32_t aValue, std::vector& outdata) { uint8_t bytes[5] = {0}; @@ -99,7 +99,7 @@ static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector< outdata.push_back(bytes[4]); } -static LIBREMIDI_INLINE void +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION void add_event_track_count_check(std::vector& tracks, int track) { if (track < 0) diff --git a/include/libremidi/writer.hpp b/include/libremidi/writer.hpp index 489df86..348ce86 100644 --- a/include/libremidi/writer.hpp +++ b/include/libremidi/writer.hpp @@ -30,7 +30,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct LIBREMIDI_EXPORT writer { diff --git a/lib/libraylib.a b/lib/libraylib.a_ similarity index 100% rename from lib/libraylib.a rename to lib/libraylib.a_ diff --git a/src/app-manager.cpp b/src/app-manager.cpp new file mode 100644 index 0000000..89abaa8 --- /dev/null +++ b/src/app-manager.cpp @@ -0,0 +1,158 @@ +#include "app-manager.h" + +#include "interaction-manager.h" +#include "nlohmann/json.hpp" +#include "graph-manager.h" +#include "midi-node.h" +#include "state.h" +#include "ui.h" + +#include +#include + +namespace VISEQ::APP{ + void AppManager::init(){ + canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.rotation = 0.0f; + canvas->camera.zoom = 1.0f; + } + + void AppManager::close(){ + std::ofstream file("state.json"); + file << json(*this); + std::cout << std::setw(4) << json(*this) << std::endl; + } + + void AppManager::startSignals(){ + sm->startSignalThread(); + } + + void AppManager::loadState(){ + if(std::filesystem::exists("state.json")){ + std::ifstream f("state.json"); + json graph = json::parse(f); + + for(auto n : graph.at("gm").at("nodes")){ + std::cout << "object? " << n << std::endl; + if(n.is_object()){ + std::cout << "nodeType = " << n.at("nodeType").get() << std::endl; + + if(n.at("nodeType").get() == NodeType::MIDINODE) gm->addNode(n.template get>()); + if(n.at("nodeType").get() == NodeType::CONFIGNODE) gm->addNode(n.template get>()); + } + } + + int highest_id = 0; + for(auto n : graph.at("gm").at("nodes")){ + std::cout << n << std::endl; + if(n.is_object()){ + if(n.at("id").get() > highest_id) { + highest_id = n.at("id").get(); + } + } + } + + for(auto e : graph.at("gm").at("edges")){ + if(e.at("id") > highest_id){ + highest_id = e.at("id"); + } + } + + for(auto e : graph.at("gm").at("edges")){ + int start_id = e.at("start").get(); + int end_id = e.at("end").get(); + + VEC_WPTR_Node start = gm->getNodesByID(start_id); + VEC_WPTR_Node end = gm->getNodesByID(end_id); + + if(!start.empty() && !end.empty()) { + std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; + gm->addConnection(start.front(), end.front()); + } + } + gm->lastEdge().lock()->newId(highest_id); + + for(auto s : graph.at("sm").at("signals")){ + int start_id = s.at("start").get(); + int next_id = s.at("next").get(); + int last_id = s.at("last").get(); + std::shared_ptr ms = s; + VEC_WPTR_Node start = gm->getNodesByID(start_id); + VEC_WPTR_Node last = gm->getNodesByID(last_id); + VEC_WPTR_Node next = gm->getNodesByID(next_id); + + ms->start = start.front(); + ms->next = next.front(); + ms->last = last.front(); + sm->signals.push_back(ms); + + std::cout << std::format("adding signal with start node {}", start_id) << std::endl; + } + + } else { + for(int i = 0; i < 4; ++i){ + gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); + } + for(int i = 3; i >= 0; --i){ + gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); + } + + for(int i = 0; i < 8; ++i){ + gm->addConnection(gm->getNode(i), gm->getNode((i+1) % 8)); + } + sm->addSignalAtNode(gm->getFirstNode()); + } + + } + + void AppManager::updateMidiPorts(){ + for(auto& n : gm->getMidiNodes()){ + auto nc = std::dynamic_pointer_cast(n.lock()); + for(auto& mo : sm->midi.midi_out_ports){ + if(!nc->midi_outs.contains(mo.number) && mo.enabled){ + nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false}; + } + if(nc->midi_outs.contains(mo.number) && !mo.enabled){ + nc->midi_outs.erase(mo.number); + } + } + } + } + + void AppManager::update(){ + state->update(canvas->camera); + canvas->update(); + gm->update(); + + mutex.lock(); + updateMidiPorts(); + mutex.unlock(); + + im.update(); + + } + + void AppManager::draw(){ + BeginDrawing(); + ClearBackground((Color){46,46,46}); + canvas->drawStart(); + canvas->draw(); + + gm->drawNodes(SCREEN_MOUSE, canvas->camera); + gm->drawEdges(SCREEN_MOUSE, canvas->camera); + sm->drawSignals(SCREEN_MOUSE, canvas->camera); + + im.draw(); + + canvas->drawEnd(); + drawUI(); + EndDrawing(); + } + + void AppManager::drawUI(){ + mutex.lock(); + ui.draw(SCREEN_MOUSE, canvas->camera); + mutex.unlock(); + } +} diff --git a/src/app-manager.h b/src/app-manager.h index 61b9d8c..c0f80f3 100644 --- a/src/app-manager.h +++ b/src/app-manager.h @@ -1,15 +1,10 @@ #pragma once -#include -#include #include "interaction-manager.h" #include "nlohmann/json.hpp" -#include "datatypes.h" #include "canvas.h" #include "graph-manager.h" #include "midi-node.h" -#include "node.h" -#include "raylib.h" #include "signal-manager.h" #include "state.h" #include "ui.h" @@ -21,167 +16,29 @@ using json = nlohmann::json; namespace VISEQ::APP { class AppManager{ public: - AppManager() : im(gm->getPtr(), sm->getPtr(), canvas), ui(sm->getPtr(), gm->getPtr()) {} + AppManager() : im(gm->getPtr(), sm->getPtr(), canvas), ui(sm->getPtr(), gm->getPtr()) {} + std::mutex mutex; - std::mutex mutex; + std::shared_ptr state = APP::State::get(); - std::shared_ptr state = APP::State::get(); + std::shared_ptr canvas = std::make_shared(); + std::shared_ptr gm = std::make_shared(); + std::shared_ptr sm = std::make_shared(gm->getPtr(), &mutex); + VISEQ::INTERACTION::InteractionManager im; - std::shared_ptr canvas = std::make_shared(); - std::shared_ptr gm = std::make_shared(); - std::shared_ptr sm = std::make_shared(gm->getPtr(), &mutex); - VISEQ::INTERACTION::InteractionManager im; + UI::AppUI ui; - UI::AppUI ui; - - NLOHMANN_DEFINE_TYPE_INTRUSIVE(AppManager, gm, sm); - - void init(){ - canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.rotation = 0.0f; - canvas->camera.zoom = 1.0f; - } - - void close(){ - std::ofstream file("state.json"); - file << json(*this); - std::cout << std::setw(4) << json(*this) << std::endl; - } - - void startSignals(){ - sm->startSignalThread(); - } - - void loadState(){ - if(std::filesystem::exists("state.json")){ - std::ifstream f("state.json"); - json graph = json::parse(f); - - for(auto n : graph.at("gm").at("nodes")){ - std::cout << "object? " << n << std::endl; - if(n.is_object()){ - std::cout << "nodeType = " << n.at("nodeType").get() << std::endl; - - if(n.at("nodeType").get() == NodeType::MIDINODE) gm->addNode(n.template get>()); - if(n.at("nodeType").get() == NodeType::CONFIGNODE) gm->addNode(n.template get>()); - } - } - - int highest_id = 0; - for(auto n : graph.at("gm").at("nodes")){ - std::cout << n << std::endl; - if(n.is_object()){ - if(n.at("id").get() > highest_id) { - highest_id = n.at("id").get(); - } - } - } - - for(auto e : graph.at("gm").at("edges")){ - if(e.at("id") > highest_id){ - highest_id = e.at("id"); - } - } - - for(auto e : graph.at("gm").at("edges")){ - int start_id = e.at("start").get(); - int end_id = e.at("end").get(); - - VEC_WPTR_Node start = gm->getNodesByID(start_id); - VEC_WPTR_Node end = gm->getNodesByID(end_id); - - if(!start.empty() && !end.empty()) { - std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; - gm->addConnection(start.front(), end.front()); - } - } - gm->lastEdge().lock()->newId(highest_id); - - for(auto s : graph.at("sm").at("signals")){ - int start_id = s.at("start").get(); - int next_id = s.at("next").get(); - int last_id = s.at("last").get(); - std::shared_ptr ms = s; - VEC_WPTR_Node start = gm->getNodesByID(start_id); - VEC_WPTR_Node last = gm->getNodesByID(last_id); - VEC_WPTR_Node next = gm->getNodesByID(next_id); - - ms->start = start.front(); - ms->next = next.front(); - ms->last = last.front(); - sm->signals.push_back(ms); - - std::cout << std::format("adding signal with start node {}", start_id) << std::endl; - } - - } else { - for(int i = 0; i < 4; ++i){ - gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); - } - for(int i = 3; i >= 0; --i){ - gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); - } - - for(int i = 0; i < 8; ++i){ - gm->addConnection(gm->getNode(i), gm->getNode((i+1) % 8)); - } - sm->addSignalAtNode(gm->getFirstNode()); - } - - } - - void updateMidiPorts(){ - for(auto& n : gm->getMidiNodes()){ - auto nc = std::dynamic_pointer_cast(n.lock()); - for(auto& mo : sm->midi.midi_out_ports){ - if(!nc->midi_outs.contains(mo.number) && mo.enabled){ - nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false}; - } - if(nc->midi_outs.contains(mo.number) && !mo.enabled){ - nc->midi_outs.erase(mo.number); - } - } - } - } - - void update(){ - state->update(canvas->camera); - canvas->update(); - gm->update(); - - mutex.lock(); - updateMidiPorts(); - mutex.unlock(); - - im.update(); - - } - - void draw(){ - BeginDrawing(); - ClearBackground((Color){46,46,46}); - canvas->drawStart(); - canvas->draw(); - - gm->drawNodes(SCREEN_MOUSE, canvas->camera); - gm->drawEdges(SCREEN_MOUSE, canvas->camera); - sm->drawSignals(SCREEN_MOUSE, canvas->camera); - - im.draw(); - - canvas->drawEnd(); - drawUI(); - EndDrawing(); - } - - void drawUI(){ - mutex.lock(); - ui.draw(SCREEN_MOUSE, canvas->camera); - mutex.unlock(); - } - + void init(); + void close(); + void startSignals(); + void loadState(); + void updateMidiPorts(); + void update(); + void draw(); + void drawUI(); }; + + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppManager, gm, sm) } diff --git a/src/canvas.cpp b/src/canvas.cpp new file mode 100644 index 0000000..c0f08b5 --- /dev/null +++ b/src/canvas.cpp @@ -0,0 +1,88 @@ +#include "canvas.h" + +#include "datatypes.h" +//#include "interaction-event.h" +#include "state.h" +#include "draggable.h" +#include "raylib.h" +#include "raymath.h" +#include "vmath.h" +#include + +namespace VISEQ::APP::CANVAS { + bool Canvas::hovering() { + return state->mouse_z == z_index; + } + + void Canvas::drag(Vec2Df position) { + Vec2Df select1 = {SCREEN_MOUSE.x > select_start.x ? select_start.x : SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? select_start.y : SCREEN_MOUSE.y}; + Vec2Df select2 = {SCREEN_MOUSE.x > select_start.x ? SCREEN_MOUSE.x - select_start.x : select_start.x - SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? SCREEN_MOUSE.y - select_start.y : select_start.y - SCREEN_MOUSE.y}; + state->selection = (Rectangle){select1.x, select1.y, select2.x, select2.y}; + } + + void Canvas::pickUp() { + std::cout << "canvas pickUp" << std::endl; + BASE::Draggable::pickUp(); + select_start = SCREEN_MOUSE; + state->pushEvent({getPtr(), Interaction::DESELECT}); + } + + void Canvas::putDown() { + std::cout << "canvas putDown" << std::endl; + BASE::Draggable::putDown(); + state->pushEvent({getPtr(), Interaction::SELECT}); + } + + void Canvas::clearSelectionRect(){ + state->selection = (Rectangle){0,0,0,0}; + } + + void Canvas::drawStart(){ + BeginMode2D(camera); + } + + void Canvas::drawEnd(){ + EndMode2D(); + } + + GRAPH::GraphItemType Canvas::getType() { + return GRAPH::GraphItemType::CANVAS; + } + + std::shared_ptr Canvas::getPtr(){ + return shared_from_this(); + } + + void Canvas::update() { + camera_delta = state->mouse_delta * (-1.0f/camera.zoom); + + if(state->mouse_right.down) { + camera.target = Vector2Add(camera.target, camera_delta); + } + + if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); + if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); + if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); + + float wheel = GetMouseWheelMove(); + if(wheel != 0){ + if(!KEYS_DOWN.shift && !KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + camera.offset = state->raw_mouse; + camera.target = SCREEN_MOUSE; + float scaleFactor = 1.0f + (0.25f*fabsf(wheel)); + if (wheel < 0) scaleFactor = 1.0f/scaleFactor; + camera.zoom = Clamp(camera.zoom*scaleFactor, 0.125f, 64.0f); + } + } + } + + void Canvas::draw() { + for(int x = -SETTINGS.grid*100; x < SETTINGS.grid*100; x+=SETTINGS.grid){ + for(int y = -SETTINGS.grid*100; y < SETTINGS.grid*100; y+=SETTINGS.grid){ + DrawPixel(x, y, (Color){130,130,130,200}); + } + } + if(state->selection.width > 0 || state->selection.height > 0) DrawRectangleLines(state->selection.x, state->selection.y, state->selection.width, state->selection.height, (Color){150,150,150,150}); + + } +} diff --git a/src/canvas.h b/src/canvas.h index a871080..7bb2f75 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -1,99 +1,31 @@ #pragma once - #include "datatypes.h" -#include "interaction-event.h" -#include "state.h" #include "draggable.h" #include "raylib.h" -#include "raymath.h" #include "vmath.h" #include namespace VISEQ::APP::CANVAS { class Canvas : public BASE::Draggable, public std::enable_shared_from_this { public: - Canvas() {} + Canvas() {} - Camera2D camera; - Vec2Df camera_delta; - Vec2Df select_start; + Camera2D camera; + Vec2Df camera_delta; + Vec2Df select_start; - virtual bool hovering() override { - return state->mouse_z == z_index; - } - - virtual void drag(Vec2Df position) override { - Vec2Df select1 = {SCREEN_MOUSE.x > select_start.x ? select_start.x : SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? select_start.y : SCREEN_MOUSE.y}; - Vec2Df select2 = {SCREEN_MOUSE.x > select_start.x ? SCREEN_MOUSE.x - select_start.x : select_start.x - SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? SCREEN_MOUSE.y - select_start.y : select_start.y - SCREEN_MOUSE.y}; - state->selection = (Rectangle){select1.x, select1.y, select2.x, select2.y}; - } - - virtual void pickUp() override { - std::cout << "canvas pickUp" << std::endl; - BASE::Draggable::pickUp(); - select_start = SCREEN_MOUSE; - state->pushEvent({getPtr(), Interaction::DESELECT}); - } - - virtual void putDown() override { - std::cout << "canvas putDown" << std::endl; - BASE::Draggable::putDown(); - state->pushEvent({getPtr(), Interaction::SELECT}); - } - - void clearSelectionRect(){ - state->selection = (Rectangle){0,0,0,0}; - } - - virtual void drawStart(){ - BeginMode2D(camera); - } - - virtual void drawEnd(){ - EndMode2D(); - } - - GRAPH::GraphItemType getType() override { - return GRAPH::GraphItemType::CANVAS; - } - - std::shared_ptr getPtr(){ - return shared_from_this(); - } - - virtual void update() override { - camera_delta = state->mouse_delta * (-1.0f/camera.zoom); - - if(state->mouse_right.down) { - camera.target = Vector2Add(camera.target, camera_delta); - } - - if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); - if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); - if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); - - float wheel = GetMouseWheelMove(); - if(wheel != 0){ - if(!KEYS_DOWN.shift && !KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - camera.offset = state->raw_mouse; - camera.target = SCREEN_MOUSE; - float scaleFactor = 1.0f + (0.25f*fabsf(wheel)); - if (wheel < 0) scaleFactor = 1.0f/scaleFactor; - camera.zoom = Clamp(camera.zoom*scaleFactor, 0.125f, 64.0f); - } - } - } - - virtual void draw() { - for(int x = -SETTINGS.grid*100; x < SETTINGS.grid*100; x+=SETTINGS.grid){ - for(int y = -SETTINGS.grid*100; y < SETTINGS.grid*100; y+=SETTINGS.grid){ - DrawPixel(x, y, (Color){130,130,130,200}); - } - } - if(state->selection.width > 0 || state->selection.height > 0) DrawRectangleLines(state->selection.x, state->selection.y, state->selection.width, state->selection.height, (Color){150,150,150,150}); - - } + virtual bool hovering() override; + virtual void drag(Vec2Df position) override; + virtual void pickUp() override; + virtual void putDown() override; + void clearSelectionRect(); + virtual void drawStart(); + virtual void drawEnd(); + GRAPH::GraphItemType getType() override; + std::shared_ptr getPtr(); + virtual void update() override; + virtual void draw(); }; } diff --git a/src/clipboard.cpp b/src/clipboard.cpp new file mode 100644 index 0000000..ab16089 --- /dev/null +++ b/src/clipboard.cpp @@ -0,0 +1,16 @@ +#include "clipboard.h" + +namespace VISEQ::APP { + GRAPH::NODES::VEC_SPTR_Node Clipboard::pasteNodes(){ + return clipboard.back(); + } + + void Clipboard::copyNodes(GRAPH::NODES::VEC_SPTR_Node selected_nodes){ + if(clipboard.size() == 20) clipboard.pop_back(); + clipboard.emplace(clipboard.begin()); + for(auto n : selected_nodes){ + clipboard.front().push_back(n); + } + } + +} diff --git a/src/clipboard.h b/src/clipboard.h index e345567..98a61e6 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -1,6 +1,7 @@ #pragma once #include "node.h" +#include namespace VISEQ::APP { class Clipboard { @@ -9,17 +10,9 @@ namespace VISEQ::APP { std::vector clipboard; - GRAPH::NODES::VEC_SPTR_Node pasteNodes(){ - return clipboard.back(); - } + GRAPH::NODES::VEC_SPTR_Node pasteNodes(); - void copyNodes(GRAPH::NODES::VEC_SPTR_Node selected_nodes){ - if(clipboard.size() == 20) clipboard.pop_back(); - clipboard.emplace(clipboard.begin()); - for(auto n : selected_nodes){ - clipboard.front().push_back(n); - } - } + void copyNodes(GRAPH::NODES::VEC_SPTR_Node selected_nodes); }; } diff --git a/src/config-node.cpp b/src/config-node.cpp new file mode 100644 index 0000000..5557bc5 --- /dev/null +++ b/src/config-node.cpp @@ -0,0 +1,293 @@ +#include "config-node.h" + +#include "datatypes.h" +#include "edge.h" +#include "node.h" +#include "raymath.h" +#include "state.h" +#include + +namespace VISEQ::GRAPH::NODES { + + std::shared_ptr ConfigNode::getPtr() { + return std::static_pointer_cast(shared_from_this()); + } + + NodeType ConfigNode::getNodeType() const {return nodeType;} + + void ConfigNode::trigger() {} + + void ConfigNode::setMode(int _mode) { + mode = _mode; + } + + MIDI::MidiNote ConfigNode::getNotes() const { + MIDI::MidiNote cNote; + for(auto cn : getConnectedConfigNodes()){ + cNote += cn.lock()->getNotes(); + } + return midiNote + cNote; + } + + MIDI::MidiCC ConfigNode::getCC() const { + MIDI::MidiCC cCC; + for(auto cn : getConnectedConfigNodes()){ + cCC += cn.lock()->getCC(); + } + return midiCC + cCC; + } + + MIDI::MidiPC ConfigNode::getPC() const { + MIDI::MidiPC cPC; + for(auto cn : getConnectedConfigNodes()){ + cPC += cn.lock()->getPC(); + } + return midiPC + cPC; + } + + MIDI::MidiPitchBend ConfigNode::getPitchBend() const { + MIDI::MidiPitchBend cPitchBend; + for(auto cn : getConnectedConfigNodes()){ + cPitchBend += cn.lock()->getPitchBend(); + } + return midiPitchBend + cPitchBend; + } + + MIDI::MidiAftertouch ConfigNode::getAftertouch() const { + MIDI::MidiAftertouch cAftertouch; + for(auto cn : getConnectedConfigNodes()){ + cAftertouch += cn.lock()->getAftertouch(); + } + return midiAftertouch + cAftertouch; + } + + MIDI::MidiPolyPressure ConfigNode::getPolyPressure() const { + MIDI::MidiPolyPressure cPolyPressure; + for(auto cn : getConnectedConfigNodes()){ + cPolyPressure += cn.lock()->getPolyPressure(); + } + return midiPolyPressure + cPolyPressure; + } + + std::vector> ConfigNode::getConnectedConfigNodes() const { // TODO: check for cyclic connections of config nodes (crashes) + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + void ConfigNode::update() { + Node::update(); + mode = state->node_mode; + } + + void ConfigNode::interact(){ + float wheel = GetMouseWheelMove(); + + if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + switch(state->node_mode){ + case 0:{ + midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 1:{ + midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 2:{ + midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + }; + + if(midiNote.note < -127 ) midiNote.note = -127; + if(midiNote.note > 127 ) midiNote.note = 127; + + if(midiCC.value < -127 ) midiCC.value = -127; + if(midiCC.value > 127 ) midiCC.value = 127; + + if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + + } else if(KEYS_DOWN.alt) { + switch(state->node_mode){ + case 0:{ + midiNote.channel += wheel; + if(midiNote.channel < 0 ) midiNote.channel = 0; + if(midiNote.channel > 15) midiNote.channel = 15; + break;} + case 1:{ + midiCC.channel += wheel; + if(midiCC.channel < 0 ) midiCC.channel = 0; + if(midiCC.channel > 15 ) midiCC.channel = 15; + break;} + case 2:{ + midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; + if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; + if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; + break;} + }; + } + } + + void ConfigNode::draw(Vec2Df mouse, Camera2D cam) { + Node::draw(); + if(hovering() || selected) interact(); + + DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); + DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); + + Color gate_color = VS_COLOR_GREEN; + + switch(mode){ + case 0:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 1:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 2:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + } + if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); + if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); + if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); + + if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); + if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); + + if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); + + if(KEYS_DOWN.alt){ + float value = 0; + int offset_gate = 0; + switch(mode){ + case 0: + value = (float)getNotes().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 1: + value = (float)getCC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 2: + value = (float)getPC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 3: + value = (float)getPitchBend().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 4: + value = (float)getAftertouch().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 5: + value = (float)getPolyPressure().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + } + } else { + switch(mode){ + case 0:{ + int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); + DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 1:{ + int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); + DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 2:{ + int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); + DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 3:{ + int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); + DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 4:{ + int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); + DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 5:{ + int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); + DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + } + } + + if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); + //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); + } + + void to_json(json& j, const ConfigNode& N){ + std::vector conns; + std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); + + j = json{ + {"id", N.id}, + {"position", N.position}, + {"distributionMode", N.distributionMode}, + {"nodeType", N.nodeType}, + {"midi_note_channel",N.midiNote.channel}, + {"midi_note_note",N.midiNote.note}, + {"midi_note_velocity",N.midiNote.velocity}, + {"midi_note_gate",N.midiNote.gate}, + {"midi_cc_channel",N.midiCC.channel}, + {"midi_cc_control",N.midiCC.control}, + {"midi_cc_value",N.midiCC.value}, + {"midi_pc_channel",N.midiPC.channel}, + {"midi_pc_program",N.midiPC.program}, + {"midi_pc_value",N.midiPC.value}, + {"midi_pitch_bend_channel",N.midiPitchBend.channel}, + {"midi_pitch_bend_value",N.midiPitchBend.value}, + {"midi_aftertouch_channel",N.midiAftertouch.channel}, + {"midi_aftertouch_value",N.midiAftertouch.value}, + {"midi_polypressure_channel",N.midiPolyPressure.channel}, + {"midi_polypressure_value",N.midiPolyPressure.value}, + {"connections",conns}}; + } + + void from_json(const json& j, ConfigNode& N){ + std::cout << "configNode from_json" << std::endl; + + j.at("id").get_to(N.id); + j.at("position").get_to(N.position); + j.at("distributionMode").get_to(N.distributionMode); + j.at("nodeType").get_to(N.nodeType); + j.at("midi_note_channel").get_to(N.midiNote.channel); + j.at("midi_note_note").get_to(N.midiNote.note); + j.at("midi_note_velocity").get_to(N.midiNote.velocity); + j.at("midi_note_gate").get_to(N.midiNote.gate); + j.at("midi_cc_channel").get_to(N.midiCC.channel); + j.at("midi_cc_control").get_to(N.midiCC.control); + j.at("midi_cc_value").get_to(N.midiCC.value); + j.at("midi_pc_channel").get_to(N.midiPC.channel); + j.at("midi_pc_program").get_to(N.midiPC.program); + j.at("midi_pc_value").get_to(N.midiPC.value); + j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); + j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); + j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); + j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); + j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); + j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); + + //j.at("connections").get_to(N.connections); + } + + void to_json(json& j, const std::shared_ptr& N){ + j = *N; + } + + void from_json(const json& j, std::shared_ptr& N){ + N.reset(new ConfigNode(j.get())); + } +}; diff --git a/src/config-node.h b/src/config-node.h index ab70471..952f3a4 100644 --- a/src/config-node.h +++ b/src/config-node.h @@ -1,10 +1,7 @@ #pragma once #include "datatypes.h" -#include "edge.h" #include "node.h" -#include "raymath.h" -#include "state.h" #include namespace VISEQ::GRAPH::NODES { @@ -15,9 +12,7 @@ namespace VISEQ::GRAPH::NODES { ConfigNode() {std::cout << "ConfigNode Constructor std" << std::endl;} ConfigNode(Vec2Df position) : Node(position) {} - std::shared_ptr getPtr() { - return std::static_pointer_cast(shared_from_this()); - } + std::shared_ptr getPtr(); NodeType nodeType = NodeType::CONFIGNODE; @@ -31,274 +26,26 @@ namespace VISEQ::GRAPH::NODES { int mode = 0; float radius = 20.0f; - virtual NodeType getNodeType() const override {return nodeType;} + virtual NodeType getNodeType() const override; - virtual void trigger() override {} + virtual void trigger() override; - virtual void setMode(int _mode) override { - mode = _mode; - } + virtual void setMode(int _mode) override; - MIDI::MidiNote getNotes() const { - MIDI::MidiNote cNote; - for(auto cn : getConnectedConfigNodes()){ - cNote += cn.lock()->getNotes(); - } - return midiNote + cNote; - } + MIDI::MidiNote getNotes() const; + MIDI::MidiCC getCC() const; + MIDI::MidiPC getPC() const; + MIDI::MidiPitchBend getPitchBend() const; + MIDI::MidiAftertouch getAftertouch() const; + MIDI::MidiPolyPressure getPolyPressure() const; + std::vector> getConnectedConfigNodes() const; + void update() override; + void interact(); + void draw(Vec2Df mouse, Camera2D cam) override; - MIDI::MidiCC getCC() const { - MIDI::MidiCC cCC; - for(auto cn : getConnectedConfigNodes()){ - cCC += cn.lock()->getCC(); - } - return midiCC + cCC; - } - - MIDI::MidiPC getPC() const { - MIDI::MidiPC cPC; - for(auto cn : getConnectedConfigNodes()){ - cPC += cn.lock()->getPC(); - } - return midiPC + cPC; - } - - MIDI::MidiPitchBend getPitchBend() const { - MIDI::MidiPitchBend cPitchBend; - for(auto cn : getConnectedConfigNodes()){ - cPitchBend += cn.lock()->getPitchBend(); - } - return midiPitchBend + cPitchBend; - } - - MIDI::MidiAftertouch getAftertouch() const { - MIDI::MidiAftertouch cAftertouch; - for(auto cn : getConnectedConfigNodes()){ - cAftertouch += cn.lock()->getAftertouch(); - } - return midiAftertouch + cAftertouch; - } - - MIDI::MidiPolyPressure getPolyPressure() const { - MIDI::MidiPolyPressure cPolyPressure; - for(auto cn : getConnectedConfigNodes()){ - cPolyPressure += cn.lock()->getPolyPressure(); - } - return midiPolyPressure + cPolyPressure; - } - - std::vector> getConnectedConfigNodes() const { // TODO: check for cyclic connections of config nodes (crashes) - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - void update() override { - Node::update(); - mode = state->node_mode; - } - - void interact(){ - float wheel = GetMouseWheelMove(); - - if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - switch(state->node_mode){ - case 0:{ - midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 1:{ - midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 2:{ - midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - }; - - if(midiNote.note < -127 ) midiNote.note = -127; - if(midiNote.note > 127 ) midiNote.note = 127; - - if(midiCC.value < -127 ) midiCC.value = -127; - if(midiCC.value > 127 ) midiCC.value = 127; - - if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - - } else if(KEYS_DOWN.alt) { - switch(state->node_mode){ - case 0:{ - midiNote.channel += wheel; - if(midiNote.channel < 0 ) midiNote.channel = 0; - if(midiNote.channel > 15) midiNote.channel = 15; - break;} - case 1:{ - midiCC.channel += wheel; - if(midiCC.channel < 0 ) midiCC.channel = 0; - if(midiCC.channel > 15 ) midiCC.channel = 15; - break;} - case 2:{ - midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; - if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; - if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; - break;} - }; - } - } - - void draw(Vec2Df mouse, Camera2D cam) override { - Node::draw(); - if(hovering() || selected) interact(); - - DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); - DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); - - Color gate_color = VS_COLOR_GREEN; - - switch(mode){ - case 0:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 1:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 2:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - } - if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); - if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); - if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); - - if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); - if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); - - if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); - - if(KEYS_DOWN.alt){ - float value = 0; - int offset_gate = 0; - switch(mode){ - case 0: - value = (float)getNotes().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 1: - value = (float)getCC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 2: - value = (float)getPC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 3: - value = (float)getPitchBend().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 4: - value = (float)getAftertouch().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 5: - value = (float)getPolyPressure().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - } - } else { - switch(mode){ - case 0:{ - int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); - DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 1:{ - int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); - DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 2:{ - int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); - DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 3:{ - int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); - DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 4:{ - int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); - DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 5:{ - int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); - DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - } - } - - if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); - //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); - } - - friend void to_json(json& j, const ConfigNode& N){ - std::vector conns; - std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); - - j = json{ - {"id", N.id}, - {"position", N.position}, - {"distributionMode", N.distributionMode}, - {"nodeType", N.nodeType}, - {"midi_note_channel",N.midiNote.channel}, - {"midi_note_note",N.midiNote.note}, - {"midi_note_velocity",N.midiNote.velocity}, - {"midi_note_gate",N.midiNote.gate}, - {"midi_cc_channel",N.midiCC.channel}, - {"midi_cc_control",N.midiCC.control}, - {"midi_cc_value",N.midiCC.value}, - {"midi_pc_channel",N.midiPC.channel}, - {"midi_pc_program",N.midiPC.program}, - {"midi_pc_value",N.midiPC.value}, - {"midi_pitch_bend_channel",N.midiPitchBend.channel}, - {"midi_pitch_bend_value",N.midiPitchBend.value}, - {"midi_aftertouch_channel",N.midiAftertouch.channel}, - {"midi_aftertouch_value",N.midiAftertouch.value}, - {"midi_polypressure_channel",N.midiPolyPressure.channel}, - {"midi_polypressure_value",N.midiPolyPressure.value}, - {"connections",conns}}; - } - - friend void from_json(const json& j, ConfigNode& N){ - std::cout << "configNode from_json" << std::endl; - - j.at("id").get_to(N.id); - j.at("position").get_to(N.position); - j.at("distributionMode").get_to(N.distributionMode); - j.at("nodeType").get_to(N.nodeType); - j.at("midi_note_channel").get_to(N.midiNote.channel); - j.at("midi_note_note").get_to(N.midiNote.note); - j.at("midi_note_velocity").get_to(N.midiNote.velocity); - j.at("midi_note_gate").get_to(N.midiNote.gate); - j.at("midi_cc_channel").get_to(N.midiCC.channel); - j.at("midi_cc_control").get_to(N.midiCC.control); - j.at("midi_cc_value").get_to(N.midiCC.value); - j.at("midi_pc_channel").get_to(N.midiPC.channel); - j.at("midi_pc_program").get_to(N.midiPC.program); - j.at("midi_pc_value").get_to(N.midiPC.value); - j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); - j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); - j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); - j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); - j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); - j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); - - //j.at("connections").get_to(N.connections); - } + friend void to_json(json& j, const ConfigNode& N); + friend void from_json(const json& j, ConfigNode& N); + friend void to_json(json& j, const std::shared_ptr& N); + friend void from_json(const json& j, std::shared_ptr& N); }; } diff --git a/src/connector.h b/src/connector.h index da77833..8323a58 100644 --- a/src/connector.h +++ b/src/connector.h @@ -3,7 +3,7 @@ #include "connectable.h" #include "datatypes.h" #include "graph-item.h" -#include "interaction-event.h" +//#include "interaction-event.h" #include "raymath.h" #include "state.h" #include "vmath.h" diff --git a/src/draggable.cpp b/src/draggable.cpp new file mode 100644 index 0000000..2e12ef3 --- /dev/null +++ b/src/draggable.cpp @@ -0,0 +1,41 @@ +#include "draggable.h" + +namespace VISEQ::BASE { + bool Draggable::entered() { + return !is_hovering && hovering(); + } + + bool Draggable::exited() { + return is_hovering && !hovering(); + } + + bool Draggable::pickedUp() { + return held() && picked_up; + } + + void Draggable::pickUp() { + picked_up = true; + drag_offset = position - SCREEN_MOUSE; + } + + void Draggable::pickUp(Vec2Df selection_origin) { + picked_up = true; + drag_offset = position - selection_origin; + } + + void Draggable::drag(Vec2Df new_position) { + position = new_position; + } + + void Draggable::putDown() { + picked_up = false; + } + + bool Draggable::select() { + return CheckCollisionPointRec(position, state->selection); + } + + void Draggable::deselect() { + selected = false; + } +} diff --git a/src/draggable.h b/src/draggable.h index 896c41c..f8d7e66 100644 --- a/src/draggable.h +++ b/src/draggable.h @@ -20,43 +20,14 @@ namespace VISEQ::BASE { bool picked_up = false; - virtual bool entered() override { - return !is_hovering && hovering(); - } - - virtual bool exited() override { - return is_hovering && !hovering(); - } - - virtual bool pickedUp() { - return held() && picked_up; - } - - virtual void pickUp() override { - picked_up = true; - drag_offset = position - SCREEN_MOUSE; - } - - virtual void pickUp(Vec2Df selection_origin) override { - picked_up = true; - drag_offset = position - selection_origin; - } - - virtual void drag(Vec2Df new_position) override { - position = new_position; - } - - virtual void putDown() override { - picked_up = false; - } - - virtual bool select() override { - return CheckCollisionPointRec(position, state->selection); - } - - virtual void deselect() override { - selected = false; - } - + virtual bool entered() override; + virtual bool exited() override; + virtual bool pickedUp(); + virtual void pickUp() override; + virtual void pickUp(Vec2Df selection_origin) override; + virtual void drag(Vec2Df new_position) override; + virtual void putDown() override; + virtual bool select() override; + virtual void deselect() override; }; } diff --git a/src/edge.cpp b/src/edge.cpp new file mode 100644 index 0000000..9e4dd70 --- /dev/null +++ b/src/edge.cpp @@ -0,0 +1,36 @@ +#include "edge.h" + +#include +#include "node.h" + +namespace VISEQ::GRAPH::EDGES { + bool Edge::GActive() {return active;} + + EdgeType Edge::getEdgeType() const {return edgeType;} + + std::weak_ptr Edge::getNextNode(std::weak_ptr origin){ + return start.lock() == origin.lock() ? end : start; + } + + void Edge::draw(Vec2Df mouse_pos, Camera2D cam) {} + + void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){ + j = json{{"id",E.id}, {"start", E.start.lock()->id}, {"end",E.end.lock()->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}}; + } + + void from_json(const json& j, Edge& E){ + j.at("id").get_to(E.id); + j.at("pass_forward").get_to(E.pass_forward); + j.at("pass_backward").get_to(E.pass_backward); + } + + void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){ + j = *E; + } + + void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){ + E.reset(new Edge(j.get())); + } + + +} diff --git a/src/edge.h b/src/edge.h index 4515109..24517f2 100644 --- a/src/edge.h +++ b/src/edge.h @@ -35,15 +35,14 @@ namespace VISEQ::GRAPH::EDGES { float connector_offset = 8.0f; float switches_offset = 25.0f; - virtual bool GActive() {return active;} - - virtual EdgeType getEdgeType() const {return edgeType;} - - std::weak_ptr getNextNode(std::weak_ptr origin){ - return start.lock() == origin.lock() ? end : start; - } - - void draw(Vec2Df mouse_pos, Camera2D cam) {} + virtual bool GActive(); + virtual EdgeType getEdgeType() const; + std::weak_ptr getNextNode(std::weak_ptr origin); + void draw(Vec2Df mouse_pos, Camera2D cam); + friend void to_json(json& j, const Edge& E); + friend void from_json(const json& j, Edge& E); + friend void to_json(json& j, const std::shared_ptr& E); + friend void from_json(const json& j, std::shared_ptr& E); }; typedef std::shared_ptr SPTR_Edge; diff --git a/src/graph-manager.cpp b/src/graph-manager.cpp new file mode 100644 index 0000000..ce6731e --- /dev/null +++ b/src/graph-manager.cpp @@ -0,0 +1,471 @@ +#include "graph-manager.h" + +#include "config-node.h" +#include "datatypes.h" +#include "state.h" +#include "vmath.h" +#include "raylib.h" +#include "raymath.h" + +#include "button.h" +#include "midi-node.h" +#include "edge.h" +#include "node.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "state-io.h" + +namespace VISEQ::GRAPH{ + std::shared_ptr GraphManager::getPtr(){ + return shared_from_this(); + } + + int GraphManager::getNodesSize(){ + return nodes.size(); + } + + void GraphManager::createMidiNode(Vec2Df position){ + nodes.emplace_back(std::make_shared(position)); + nodes.back()->update(); + } + + void GraphManager::createConfigNode(Vec2Df position){ + nodes.emplace_back(std::make_shared(position)); + nodes.back()->update(); + } + + void GraphManager::setMidiPortForAll(int number, bool _state){ + for(auto n : nodes){ + if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast(n)->setMidiPort(number, _state); + } + } + + void GraphManager::addNode(SPTR_Node node){ + std::cout << "added node " << node->id << std::endl; + nodes.insert(nodes.end(), node); + node->update(); + } + + void GraphManager::addConnection(WPTR_Node from, WPTR_Node to){ + EdgeType et = EdgeType::MIDIEDGE; + if(from.lock()->getNodeType() == NodeType::CONFIGNODE || to.lock()->getNodeType() == NodeType::CONFIGNODE) et = EdgeType::CONFIGEDGE; + if(from.lock() != to.lock()) { + edges.emplace_back(std::make_shared()); + edges.back()->edgeType = et; + edges.back()->start = from; + edges.back()->end = to; + edges.back()->start_conn = from.lock()->connectors.back(); + edges.back()->end_conn = to.lock()->connectors.back(); + from.lock()->connections.push_back(edges.back()); + to.lock()->connections.push_back(edges.back()); + } + } + + void GraphManager::addToSelection(){ + auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); + selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); + } + + void GraphManager::select(){ + auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->select();}) | std::ranges::to(); + if(!KEYS_DOWN.shift) selected_nodes.clear(); + selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); + std::for_each(selected_nodes.begin(), selected_nodes.end(), [](WPTR_Node n){n.lock()->selected = true;}); + } + + void GraphManager::deselect(){ + std::cout << "deselect" << std::endl; + for(auto s : selected_nodes){ + s->selected = false; + } + selected_nodes.clear(); + } + + VEC_SPTR_Node GraphManager::getSelection(){ + return selected_nodes; + } + + void GraphManager::copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){ + for(auto n: _nodes){ + nodes.insert(nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + nodes.back()->setPosition(position + nodes.back()->drag_offset); + nodes.back()->selected = false; + } + } + + void GraphManager::copyNodes(std::vector> _nodes, Vec2Df position){ + for(auto n: _nodes){ + nodes.insert(nodes.end(), std::make_shared(*n)); + nodes.back()->setPosition(position + nodes.back()->drag_offset); + nodes.back()->selected = false; + } + } + + VEC_SPTR_Node GraphManager::getCopiesOfSelectedNodes(){ + VEC_SPTR_Node copied_nodes; + for(auto n : selected_nodes){ + copied_nodes.insert(copied_nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ + copied_nodes.back()->deselect(); + } + return copied_nodes; + } + + SPTR_Node GraphManager::copyNode(SPTR_Node n, Vec2Df position){ + std::cout << "copy basenode" << std::endl; + nodes.insert(nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + nodes.back()->setPosition(position); + return nodes.back(); + } + + SPTR_Node GraphManager::copyNode(std::shared_ptr n, Vec2Df position){ + std::cout << "copy midinode" << std::endl; + nodes.insert(nodes.end(), std::make_shared(*n)); + nodes.back()->clearConnectors(); + nodes.back()->setPosition(position); + return nodes.back(); + } + + void GraphManager::setNodeMode(int mode){ + for(auto n: nodes){ + n->setMode(mode); + } + } + + void GraphManager::reset(){ + counter.clear(); + } + WPTR_Node GraphManager::getNode(int i){ + return nodes.at(i); + } + + WPTR_Node GraphManager::getFirstNode(){ + return nodes.front(); + } + + WPTR_Node GraphManager::getLastNode(){ + return nodes.back(); + } + + VEC_WPTR_Node GraphManager::getMidiNodes(){ + return nodes | RANGE_FILTER([](NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to(); + } + + NODES::SPTR_Node GraphManager::getRandomNode(){ + VEC_SPTR_GraphItem o; + std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); + return std::dynamic_pointer_cast(o.front()); + } + + EDGES::SPTR_Edge GraphManager::getRandomEdge(){ + VEC_SPTR_GraphItem o; + std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); + return std::dynamic_pointer_cast(o.front()); + } + + SPTR_Node GraphManager::getClosestNode(Vec2Df position){ + auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->picked_up;}) | std::ranges::to(); + std::sort(neighbors.begin(), neighbors.end(), [position](SPTR_Node na, SPTR_Node nb){ return na->position.dist(position) < nb->position.dist(position);}); + return neighbors.front(); + } + VEC_WPTR_Node GraphManager::getNodesByID(int _id){ + return nodes | RANGE_FILTER([_id](SPTR_Node n){return n->id == _id;}) | std::ranges::to(); // TODO: Multiple nodes with the same ID? Why not return only one node? + } + + WPTR_Edge GraphManager::lastEdge(){ + return edges.back(); + } + + WPTR_Node GraphManager::next(WPTR_Node current, WPTR_Node last){ + VEC_WPTR_Edge possible_connections = current.lock()->connections | RANGE_FILTER([current](WPTR_Edge c){ + return (c.lock()->getEdgeType() == EdgeType::MIDIEDGE) && ((c.lock()->start.lock() == current.lock() && c.lock()->pass_forward) || (c.lock()->end.lock() == current.lock() && c.lock()->pass_backward)); + }) | std::ranges::to(); + + if(possible_connections.empty()) return SPTR_Node(nullptr); + + int return_path = 0; + float angle = -1; + if(current.lock()->distributionMode == Distribution::GEOMETRIC){ + Vec2Df coming_from = (current.lock()->position - last.lock()->position).norm(); + int i = 0; + for(auto n : possible_connections){ + WPTR_Node next_node = n.lock()->getNextNode(current); + float angle_v1_v2 = coming_from.norm().dot((next_node.lock()->position - current.lock()->position).norm()); + if(angle_v1_v2 > angle) { + return_path = i; + angle = angle_v1_v2; + } + ++i; + } + } + + if(current.lock()->distributionMode == Distribution::ROUNDROBIN){ + if(counter.find(current.lock()) == counter.end()) { + counter[current.lock()] = 0; + } else { + counter[current.lock()] = (counter[current.lock()] + 1) % possible_connections.size(); + return_path = counter[current.lock()]; + } + } + + if(current.lock()->distributionMode == Distribution::RANDOM){ + return_path = rand() % possible_connections.size(); + } + + return possible_connections.at(return_path).lock()->getNextNode(current); + } + + void GraphManager::splitEdge(SPTR_Edge edge){ + split_edges.push_back(edge); + } + + void GraphManager::splitEdges(){ + for(auto s : split_edges){ + Vec2Df center = s.lock()->end.lock()->position.center(s.lock()->start.lock()->position); + SPTR_Node cn = copyNode(std::dynamic_pointer_cast(s.lock()->start.lock()), center); // TODO: copy connectors + addConnection(s.lock()->start.lock(), cn); + addConnection(cn, s.lock()->end.lock()); + remove_edges.push_back(s); + } + split_edges.clear(); + } + + void GraphManager::removeEdge(SPTR_Edge edge){ + remove_edges.push_back(edge); + } + + void GraphManager::removeEdges(){ + for(auto edge : remove_edges){ + edge.lock()->start.lock()->removeConnector(edge.lock()->start_conn); + edge.lock()->end.lock()->removeConnector(edge.lock()->end_conn); + std::erase(edge.lock()->start.lock()->connections, edge.lock()); + std::erase(edge.lock()->end.lock()->connections, edge.lock()); + std::erase(edges, edge.lock()); + } + remove_edges.clear(); + } + + VEC_WPTR_Node GraphManager::getAdjacentNodes(VEC_SPTR_Node selected_nodes){ + for(auto n : nodes){ + n->highlighted = false; + } + + auto erase_connections = edges | RANGE_FILTER([selected_nodes](SPTR_Edge c){ return std::ranges::contains(selected_nodes, c->start.lock()) || std::ranges::contains(selected_nodes, c->end.lock());}); + + VEC_WPTR_Node adjacent_nodes; + for(auto c : erase_connections){ + if(!c->start.lock()->selected) adjacent_nodes.push_back(c->start); + if(!c->end.lock()->selected) adjacent_nodes.push_back(c->end); + } + + return adjacent_nodes; + } + + void GraphManager::removeNodesAndEdges(VEC_SPTR_Node selected_nodes){ + VEC_WPTR_Node adjacent_nodes = getAdjacentNodes(selected_nodes); + + for(auto n : adjacent_nodes){ + std::erase_if(n.lock()->connections, [n, selected_nodes](EDGES::WPTR_Edge c){ return std::ranges::contains(selected_nodes, c.lock()->start.lock()) || std::ranges::contains(selected_nodes, c.lock()->end.lock());}); + } + + for(auto n : selected_nodes){ + std::erase_if(this->edges, [n](WPTR_Edge c){ return c.lock()->start.lock() == n || c.lock()->end.lock() == n;}); + } + + std::erase_if(this->nodes, [selected_nodes](SPTR_Node n){ return std::ranges::contains(selected_nodes, n);}); + } + + void GraphManager::pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position){ + + VEC_SPTR_Edge copy_connections; + for(auto c : edges){ + if(std::ranges::contains(nodes, c->start.lock()) && std::ranges::contains(nodes, c->end.lock())) { + copy_connections.push_back(std::make_shared(*c)); + } + } + std::cout << copy_connections.size() << std::endl; + + for(auto old_node : nodes){ + SPTR_Node new_node; + + if(old_node->getNodeType() == NodeType::MIDINODE) { + new_node = copyNode(std::dynamic_pointer_cast(old_node), position + old_node->drag_offset); + } else { + new_node = copyNode(old_node, position + old_node->drag_offset); + } + + new_node->copied = false; + new_node->selected = true; + + //TODO copy connectors + + for(auto c : copy_connections){ + if(c->start.lock() == old_node) { + c->start = new_node; + c->start.lock()->connections.push_back(c); + } + if(c->end.lock() == old_node) { + c->end = new_node; + c->end.lock()->connections.push_back(c); + } + } + } + std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(edges)); + } + + void GraphManager::update(){ + if(!remove_edges.empty()) removeEdges(); + if(!split_edges.empty()) splitEdges(); + } + + void GraphManager::drawEdges(Vec2Df mouse_pos, Camera2D cam){ + for(auto c : edges){ + Vec2Df v1_position = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->connector_offset); + Vec2Df v2_position = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->connector_offset); + float v1_v2_distance = v1_position.dist(&v2_position); + + if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f; + Vec2Df from_to_handle = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->switches_offset); + Vec2Df to_from_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->switches_offset); + Vec2Df middle_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.4f; + Vec2Df middle_handle_2 = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.6f; + Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f); + Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f); + + // Dynamic opacity by distance to mouse + unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f); + unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f); + unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f); + + float midi_gate_v1 = 0.0f; + float midi_gate_v2 = 0.0f; + + if(c->start.lock()->getNodeType() == NodeType::MIDINODE){ + midi_gate_v1 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->start.lock())->getNotes().gate, 1.f, 4.f); + } + if(c->end.lock()->getNodeType() == NodeType::MIDINODE){ + midi_gate_v2 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->end.lock())->getNotes().gate, 1.f, 4.f); + } + // Line with small connector circles + if(c->end.lock()->getNodeType() == NodeType::MIDINODE && c->start.lock()->getNodeType() == NodeType::MIDINODE){ + + Color col = {229, 181, 103, 255}; + if(c->GActive()) col = {108, 153, 187, 255}; + DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, col); + { + float d = c->start.lock()->position.dist(c->end.lock()->position); + std::string frac = MIDI::Fractions.at(std::clamp(round(d/60) - 1, 0, 31)); + Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); + Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + + Vec2Df middle_handle = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length.x)*0.5f)/d; + Vec2Df middle_handle_frac = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length_frac.x)*0.5f)/d; + Vec2Df text_position = middle_handle + (c->start.lock()->position - c->end.lock()->position).norm().orth() * 12.0f; + Vec2Df text_position_frac = middle_handle_frac - (c->start.lock()->position - c->end.lock()->position).norm().orth() * 4.0f; + + float rotation = Vector2Angle((Vector2){1,0}, (c->end.lock()->position - c->start.lock()->position)) * RAD2DEG; + DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + if( (int)d % 60 == 0) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + } + if(midi_gate_v1 == 4.0f) col = VS_COLOR_RED; + DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, col); + + col = {229, 181, 103, 255}; + if(midi_gate_v2 == 4.0f) col = VS_COLOR_RED; + DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, col); + + if(!c->start.lock()->picked_up && !c->end.lock()->picked_up){ + // On/Off switches on both sides + if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, c->pass_forward ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) c->pass_forward = !c->pass_forward; + if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, c->pass_backward ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) c->pass_backward = !c->pass_backward; + + if(!c->pass_forward){ + Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle; + Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + from_to_handle; + DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_from}); + } + + if(!c->pass_backward){ + Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle; + Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + to_from_handle; + DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_to}); + } + + if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c); + } + } + if(c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE){ + DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, VS_COLOR_BLUE); + } + + if(ViButton(mouse_pos, flip_handle.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c); + } + } + + void GraphManager::drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera){ + float min_d = 480; + auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->picked_up && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to(); + std::sort(neighbors_local.begin(), neighbors_local.end(), [node, mouse_pos](SPTR_Node na, SPTR_Node nb){ return na->position.dist(mouse_pos) < nb->position.dist(mouse_pos);}); + if(!neighbors_local.empty()){ + min_d = neighbors_local.front()->position.dist(mouse_pos); + DrawCircleLinesV(neighbors_local.front()->position, 22.0f, VS_COLOR_PINK); + node->neighbors = neighbors_local | std::ranges::views::filter([mouse_pos, min_d](SPTR_Node n){return abs(n->position.dist(mouse_pos) - min_d) < 5;}) | std::ranges::to(); + for(auto nb : node->neighbors){ + DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED); + float d = nb->position.dist(mouse_pos); + float dn = nb->position.dist(node->position); + if(d > 0 && d < min_d*1.5 && d < 960) { + std::string frac = MIDI::Fractions.at(std::clamp(round(dn/60) - 1, 0, 31)); + Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",dn).c_str(), 10.0f, 2.0f); + Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + + Vec2Df middle_handle = nb->position + (node->position - nb->position) * ((dn - text_length.x)*0.5f)/dn; + Vec2Df middle_handle_frac = nb->position + (node->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn; + Vec2Df text_position = middle_handle + (nb->position - node->position).norm().orth() * 12.0f; + Vec2Df text_position_frac = middle_handle_frac - (nb->position - node->position).norm().orth() * 4.0f; + + float rotation = Vector2Angle((Vector2){1,0}, (node->position - nb->position)) * RAD2DEG; + DrawTextPro(GetFontDefault(), std::format("{:.1f}",dn).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + if(IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + Vec2Df n_position = node->position - ((node->position - nb->position)).norm() * 20.0f; + Vec2Df nb_position = nb->position - ((nb->position - node->position)).norm() * 20.0f; + DrawLineV(n_position, nb_position, VS_COLOR_PINK); + } + } + } + } + + void GraphManager::drawNodes(Vec2Df mouse_pos, Camera2D camera){ + for(auto n : nodes){ + if(KEYS_PRESSED.ctrl && n->hovering()) state->selection_origin = n->position; + } + BASE::Interactable::z_hovering = -1; + for(auto n : nodes){ + n->update(); + if(n->picked_up) drawNeighborLines(n, mouse_pos, camera); + n->draw(mouse_pos, camera); + } + } + + void to_json(json& j, const std::shared_ptr& G){ + j = *G; + } + + void from_json(const json& j, std::shared_ptr& G){ + G.reset(new GraphManager(j.get())); + } +} diff --git a/src/graph-manager.h b/src/graph-manager.h index fc64799..4e0c86d 100644 --- a/src/graph-manager.h +++ b/src/graph-manager.h @@ -1,33 +1,19 @@ #pragma once - -#include "config-node.h" -#include "datatypes.h" #include "state.h" #include "vmath.h" #include "raylib.h" -#include "raymath.h" #include "nlohmann/json.hpp" -#include "button.h" #include "midi-node.h" #include "edge.h" #include "node.h" -#include -#include -#include -#include #include #include -#include -#include -#include #include -#include "state-io.h" - using json = nlohmann::json; using namespace VISEQ::GRAPH::NODES; @@ -53,443 +39,50 @@ namespace VISEQ::GRAPH { GraphManager() {} - std::shared_ptr getPtr(){ - return shared_from_this(); - } - - int getNodesSize(){ - return nodes.size(); - } - - void createMidiNode(Vec2Df position){ - nodes.emplace_back(std::make_shared(position)); - nodes.back()->update(); - } - - void createConfigNode(Vec2Df position){ - nodes.emplace_back(std::make_shared(position)); - nodes.back()->update(); - } - - void setMidiPortForAll(int number, bool _state){ - for(auto n : nodes){ - if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast(n)->setMidiPort(number, _state); - } - } - - void addNode(SPTR_Node node){ - std::cout << "added node " << node->id << std::endl; - nodes.insert(nodes.end(), node); - node->update(); - } - - void addConnection(WPTR_Node from, WPTR_Node to){ - EdgeType et = EdgeType::MIDIEDGE; - if(from.lock()->getNodeType() == NodeType::CONFIGNODE || to.lock()->getNodeType() == NodeType::CONFIGNODE) et = EdgeType::CONFIGEDGE; - if(from.lock() != to.lock()) { - edges.emplace_back(std::make_shared()); - edges.back()->edgeType = et; - edges.back()->start = from; - edges.back()->end = to; - edges.back()->start_conn = from.lock()->connectors.back(); - edges.back()->end_conn = to.lock()->connectors.back(); - from.lock()->connections.push_back(edges.back()); - to.lock()->connections.push_back(edges.back()); - } - } - - void addToSelection(){ - auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); - selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); - } - - void select(){ - auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->select();}) | std::ranges::to(); - if(!KEYS_DOWN.shift) selected_nodes.clear(); - selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); - std::for_each(selected_nodes.begin(), selected_nodes.end(), [](WPTR_Node n){n.lock()->selected = true;}); - } - - void deselect(){ - std::cout << "deselect" << std::endl; - for(auto s : selected_nodes){ - s->selected = false; - } - selected_nodes.clear(); - } - - VEC_SPTR_Node getSelection(){ - return selected_nodes; - } - - void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){ - for(auto n: _nodes){ - nodes.insert(nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - nodes.back()->setPosition(position + nodes.back()->drag_offset); - nodes.back()->selected = false; - } - } - - void copyNodes(std::vector> _nodes, Vec2Df position){ - for(auto n: _nodes){ - nodes.insert(nodes.end(), std::make_shared(*n)); - nodes.back()->setPosition(position + nodes.back()->drag_offset); - nodes.back()->selected = false; - } - } - - VEC_SPTR_Node getCopiesOfSelectedNodes(){ - VEC_SPTR_Node copied_nodes; - for(auto n : selected_nodes){ - copied_nodes.insert(copied_nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ - copied_nodes.back()->deselect(); - } - return copied_nodes; - } - - SPTR_Node copyNode(SPTR_Node n, Vec2Df position){ - std::cout << "copy basenode" << std::endl; - nodes.insert(nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - nodes.back()->setPosition(position); - return nodes.back(); - } - - SPTR_Node copyNode(std::shared_ptr n, Vec2Df position){ - std::cout << "copy midinode" << std::endl; - nodes.insert(nodes.end(), std::make_shared(*n)); - nodes.back()->clearConnectors(); - nodes.back()->setPosition(position); - return nodes.back(); - } - - void setNodeMode(int mode){ - for(auto n: nodes){ - n->setMode(mode); - } - } - - void reset(){ - counter.clear(); - } - WPTR_Node getNode(int i){ - return nodes.at(i); - } - - WPTR_Node getFirstNode(){ - return nodes.front(); - } - - WPTR_Node getLastNode(){ - return nodes.back(); - } - - VEC_WPTR_Node getMidiNodes(){ - return nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to(); - } - - NODES::SPTR_Node getRandomNode(){ - VEC_SPTR_GraphItem o; - std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); - return std::dynamic_pointer_cast(o.front()); - } - - EDGES::SPTR_Edge getRandomEdge(){ - VEC_SPTR_GraphItem o; - std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); - return std::dynamic_pointer_cast(o.front()); - } - - SPTR_Node getClosestNode(Vec2Df position){ - auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->picked_up;}) | std::ranges::to(); - std::sort(neighbors.begin(), neighbors.end(), [position](SPTR_Node na, SPTR_Node nb){ return na->position.dist(position) < nb->position.dist(position);}); - return neighbors.front(); - } - VEC_WPTR_Node getNodesByID(int _id){ - return nodes | RANGE_FILTER([_id](SPTR_Node n){return n->id == _id;}) | std::ranges::to(); // TODO: Multiple nodes with the same ID? Why not return only one node? - } - - WPTR_Edge lastEdge(){ - return edges.back(); - } - - WPTR_Node next(WPTR_Node current, WPTR_Node last){ - VEC_WPTR_Edge possible_connections = current.lock()->connections | RANGE_FILTER([current](WPTR_Edge c){ - return (c.lock()->getEdgeType() == EdgeType::MIDIEDGE) && ((c.lock()->start.lock() == current.lock() && c.lock()->pass_forward) || (c.lock()->end.lock() == current.lock() && c.lock()->pass_backward)); - }) | std::ranges::to(); - - if(possible_connections.empty()) return SPTR_Node(nullptr); - - int return_path = 0; - float angle = -1; - if(current.lock()->distributionMode == Distribution::GEOMETRIC){ - Vec2Df coming_from = (current.lock()->position - last.lock()->position).norm(); - int i = 0; - for(auto n : possible_connections){ - WPTR_Node next_node = n.lock()->getNextNode(current); - float angle_v1_v2 = coming_from.norm().dot((next_node.lock()->position - current.lock()->position).norm()); - if(angle_v1_v2 > angle) { - return_path = i; - angle = angle_v1_v2; - } - ++i; - } - } - - if(current.lock()->distributionMode == Distribution::ROUNDROBIN){ - if(counter.find(current.lock()) == counter.end()) { - counter[current.lock()] = 0; - } else { - counter[current.lock()] = (counter[current.lock()] + 1) % possible_connections.size(); - return_path = counter[current.lock()]; - } - } - - if(current.lock()->distributionMode == Distribution::RANDOM){ - return_path = rand() % possible_connections.size(); - } - - return possible_connections.at(return_path).lock()->getNextNode(current); - } - - void splitEdge(SPTR_Edge edge){ - split_edges.push_back(edge); - } - - void splitEdges(){ - for(auto s : split_edges){ - Vec2Df center = s.lock()->end.lock()->position.center(s.lock()->start.lock()->position); - SPTR_Node cn = copyNode(std::dynamic_pointer_cast(s.lock()->start.lock()), center); // TODO: copy connectors - addConnection(s.lock()->start.lock(), cn); - addConnection(cn, s.lock()->end.lock()); - remove_edges.push_back(s); - } - split_edges.clear(); - } - - void removeEdge(SPTR_Edge edge){ - remove_edges.push_back(edge); - } - - void removeEdges(){ - for(auto edge : remove_edges){ - edge.lock()->start.lock()->removeConnector(edge.lock()->start_conn); - edge.lock()->end.lock()->removeConnector(edge.lock()->end_conn); - std::erase(edge.lock()->start.lock()->connections, edge.lock()); - std::erase(edge.lock()->end.lock()->connections, edge.lock()); - std::erase(edges, edge.lock()); - } - remove_edges.clear(); - } - - VEC_WPTR_Node getAdjacentNodes(VEC_SPTR_Node selected_nodes){ - for(auto n : nodes){ - n->highlighted = false; - } - - auto erase_connections = edges | RANGE_FILTER([selected_nodes](SPTR_Edge c){ return std::ranges::contains(selected_nodes, c->start.lock()) || std::ranges::contains(selected_nodes, c->end.lock());}); - - VEC_WPTR_Node adjacent_nodes; - for(auto c : erase_connections){ - if(!c->start.lock()->selected) adjacent_nodes.push_back(c->start); - if(!c->end.lock()->selected) adjacent_nodes.push_back(c->end); - } - - return adjacent_nodes; - } - - void removeNodesAndEdges(VEC_SPTR_Node selected_nodes){ - VEC_WPTR_Node adjacent_nodes = getAdjacentNodes(selected_nodes); - - for(auto n : adjacent_nodes){ - std::erase_if(n.lock()->connections, [n, selected_nodes](VISEQ::GRAPH::EDGES::WPTR_Edge c){ return std::ranges::contains(selected_nodes, c.lock()->start.lock()) || std::ranges::contains(selected_nodes, c.lock()->end.lock());}); - } - - for(auto n : selected_nodes){ - std::erase_if(this->edges, [n](WPTR_Edge c){ return c.lock()->start.lock() == n || c.lock()->end.lock() == n;}); - } - - std::erase_if(this->nodes, [selected_nodes](SPTR_Node n){ return std::ranges::contains(selected_nodes, n);}); - } - - void pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position){ - - VEC_SPTR_Edge copy_connections; - for(auto c : edges){ - if(std::ranges::contains(nodes, c->start.lock()) && std::ranges::contains(nodes, c->end.lock())) { - copy_connections.push_back(std::make_shared(*c)); - } - } - std::cout << copy_connections.size() << std::endl; - - for(auto old_node : nodes){ - SPTR_Node new_node; - - if(old_node->getNodeType() == NodeType::MIDINODE) { - new_node = copyNode(std::dynamic_pointer_cast(old_node), position + old_node->drag_offset); - } else { - new_node = copyNode(old_node, position + old_node->drag_offset); - } - - new_node->copied = false; - new_node->selected = true; - - //TODO copy connectors - - for(auto c : copy_connections){ - if(c->start.lock() == old_node) { - c->start = new_node; - c->start.lock()->connections.push_back(c); - } - if(c->end.lock() == old_node) { - c->end = new_node; - c->end.lock()->connections.push_back(c); - } - } - } - std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(edges)); - } - - void update(){ - if(!remove_edges.empty()) removeEdges(); - if(!split_edges.empty()) splitEdges(); - } - - void drawEdges(Vec2Df mouse_pos, Camera2D cam){ - for(auto c : edges){ - Vec2Df v1_position = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->connector_offset); - Vec2Df v2_position = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->connector_offset); - float v1_v2_distance = v1_position.dist(&v2_position); - - if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f; - Vec2Df from_to_handle = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->switches_offset); - Vec2Df to_from_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->switches_offset); - Vec2Df middle_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.4f; - Vec2Df middle_handle_2 = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.6f; - Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f); - Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f); - - // Dynamic opacity by distance to mouse - unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f); - unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f); - unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f); - - float midi_gate_v1 = 0.0f; - float midi_gate_v2 = 0.0f; - - if(c->start.lock()->getNodeType() == NodeType::MIDINODE){ - midi_gate_v1 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->start.lock())->getNotes().gate, 1.f, 4.f); - } - if(c->end.lock()->getNodeType() == NodeType::MIDINODE){ - midi_gate_v2 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->end.lock())->getNotes().gate, 1.f, 4.f); - } - // Line with small connector circles - if(c->end.lock()->getNodeType() == NodeType::MIDINODE && c->start.lock()->getNodeType() == NodeType::MIDINODE){ - - Color col = {229, 181, 103, 255}; - if(c->GActive()) col = {108, 153, 187, 255}; - DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, col); - { - float d = c->start.lock()->position.dist(c->end.lock()->position); - std::string frac = MIDI::Fractions.at(std::clamp(round(d/60) - 1, 0, 31)); - Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); - Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); - - Vec2Df middle_handle = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length.x)*0.5f)/d; - Vec2Df middle_handle_frac = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length_frac.x)*0.5f)/d; - Vec2Df text_position = middle_handle + (c->start.lock()->position - c->end.lock()->position).norm().orth() * 12.0f; - Vec2Df text_position_frac = middle_handle_frac - (c->start.lock()->position - c->end.lock()->position).norm().orth() * 4.0f; - - float rotation = Vector2Angle((Vector2){1,0}, (c->end.lock()->position - c->start.lock()->position)) * RAD2DEG; - DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - if( (int)d % 60 == 0) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - } - if(midi_gate_v1 == 4.0f) col = VS_COLOR_RED; - DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, col); - - col = {229, 181, 103, 255}; - if(midi_gate_v2 == 4.0f) col = VS_COLOR_RED; - DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, col); - - if(!c->start.lock()->picked_up && !c->end.lock()->picked_up){ - // On/Off switches on both sides - if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, c->pass_forward ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) c->pass_forward = !c->pass_forward; - if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, c->pass_backward ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) c->pass_backward = !c->pass_backward; - - if(!c->pass_forward){ - Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle; - Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + from_to_handle; - DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_from}); - } - - if(!c->pass_backward){ - Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle; - Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + to_from_handle; - DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_to}); - } - - if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c); - } - } - if(c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE){ - DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, VS_COLOR_BLUE); - } - - if(ViButton(mouse_pos, flip_handle.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c); - } - } - - void drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera){ - float min_d = 480; - auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->picked_up && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to(); - std::sort(neighbors_local.begin(), neighbors_local.end(), [node, mouse_pos](SPTR_Node na, SPTR_Node nb){ return na->position.dist(mouse_pos) < nb->position.dist(mouse_pos);}); - if(!neighbors_local.empty()){ - min_d = neighbors_local.front()->position.dist(mouse_pos); - DrawCircleLinesV(neighbors_local.front()->position, 22.0f, VS_COLOR_PINK); - node->neighbors = neighbors_local | std::ranges::views::filter([mouse_pos, min_d](SPTR_Node n){return abs(n->position.dist(mouse_pos) - min_d) < 5;}) | std::ranges::to(); - for(auto nb : node->neighbors){ - DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED); - float d = nb->position.dist(mouse_pos); - float dn = nb->position.dist(node->position); - if(d > 0 && d < min_d*1.5 && d < 960) { - std::string frac = MIDI::Fractions.at(std::clamp(round(dn/60) - 1, 0, 31)); - Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",dn).c_str(), 10.0f, 2.0f); - Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); - - Vec2Df middle_handle = nb->position + (node->position - nb->position) * ((dn - text_length.x)*0.5f)/dn; - Vec2Df middle_handle_frac = nb->position + (node->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn; - Vec2Df text_position = middle_handle + (nb->position - node->position).norm().orth() * 12.0f; - Vec2Df text_position_frac = middle_handle_frac - (nb->position - node->position).norm().orth() * 4.0f; - - float rotation = Vector2Angle((Vector2){1,0}, (node->position - nb->position)) * RAD2DEG; - DrawTextPro(GetFontDefault(), std::format("{:.1f}",dn).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - if(IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - Vec2Df n_position = node->position - ((node->position - nb->position)).norm() * 20.0f; - Vec2Df nb_position = nb->position - ((nb->position - node->position)).norm() * 20.0f; - DrawLineV(n_position, nb_position, VS_COLOR_PINK); - } - } - } - } - - void drawNodes(Vec2Df mouse_pos, Camera2D camera){ - for(auto n : nodes){ - if(KEYS_PRESSED.ctrl && n->hovering()) state->selection_origin = n->position; - } - BASE::Interactable::z_hovering = -1; - for(auto n : nodes){ - n->update(); - if(n->picked_up) drawNeighborLines(n, mouse_pos, camera); - n->draw(mouse_pos, camera); - } - } + std::shared_ptr getPtr(); + int getNodesSize(); + void createMidiNode(Vec2Df position); + void createConfigNode(Vec2Df position); + void setMidiPortForAll(int number, bool _state); + void addNode(SPTR_Node node); + void addConnection(WPTR_Node from, WPTR_Node to); + void addToSelection(); + void select(); + void deselect(); + VEC_SPTR_Node getSelection(); + void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position); + void copyNodes(std::vector> _nodes, Vec2Df position); + VEC_SPTR_Node getCopiesOfSelectedNodes(); + SPTR_Node copyNode(SPTR_Node n, Vec2Df position); + SPTR_Node copyNode(std::shared_ptr n, Vec2Df position); + void setNodeMode(int mode); + void reset(); + WPTR_Node getNode(int i); + WPTR_Node getFirstNode(); + WPTR_Node getLastNode(); + VEC_WPTR_Node getMidiNodes(); + NODES::SPTR_Node getRandomNode(); + EDGES::SPTR_Edge getRandomEdge(); + SPTR_Node getClosestNode(Vec2Df position); + VEC_WPTR_Node getNodesByID(int _id); + WPTR_Edge lastEdge(); + WPTR_Node next(WPTR_Node current, WPTR_Node last); + void splitEdge(SPTR_Edge edge); + void splitEdges(); + void removeEdge(SPTR_Edge edge); + void removeEdges(); + VEC_WPTR_Node getAdjacentNodes(VEC_SPTR_Node selected_nodes); + void removeNodesAndEdges(VEC_SPTR_Node selected_nodes); + void pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position); + void update(); + void drawEdges(Vec2Df mouse_pos, Camera2D cam); + void drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera); + void drawNodes(Vec2Df mouse_pos, Camera2D camera); NLOHMANN_DEFINE_TYPE_INTRUSIVE(GraphManager, nodes, edges, selected_nodes) + + friend void from_json(const json& j, std::shared_ptr& G); + friend void to_json(json& j, const std::shared_ptr& G); }; } diff --git a/src/interactable.cpp b/src/interactable.cpp new file mode 100644 index 0000000..7d16976 --- /dev/null +++ b/src/interactable.cpp @@ -0,0 +1,17 @@ +#include "interactable.h" + +namespace VISEQ::BASE { + + bool Interactable::clicked() { + return hovering() && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + } + + bool Interactable::held() { + return IsMouseButtonDown(MOUSE_BUTTON_LEFT); + } + + bool Interactable::released() { + return IsMouseButtonReleased(MOUSE_BUTTON_LEFT); + } + +} diff --git a/src/interactable.h b/src/interactable.h index dbc8a53..51f7b75 100644 --- a/src/interactable.h +++ b/src/interactable.h @@ -24,17 +24,11 @@ namespace VISEQ::BASE { virtual bool entered() {return false;}; virtual bool exited() {return false;}; - virtual bool clicked() { - return hovering() && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - } + virtual bool clicked(); - virtual bool held() { - return IsMouseButtonDown(MOUSE_BUTTON_LEFT); - } + virtual bool held(); - virtual bool released() { - return IsMouseButtonReleased(MOUSE_BUTTON_LEFT); - } + virtual bool released(); virtual GRAPH::GraphItemType getType() = 0; virtual void pickUp() = 0; diff --git a/src/interaction-event.h b/src/interaction-event.h index 5431f20..ff7b933 100644 --- a/src/interaction-event.h +++ b/src/interaction-event.h @@ -17,5 +17,4 @@ namespace VISEQ::APP{ return lhs.item == rhs.item; } - inline static std::deque event_queue; } diff --git a/src/interaction-manager.cpp b/src/interaction-manager.cpp new file mode 100644 index 0000000..8dca1a1 --- /dev/null +++ b/src/interaction-manager.cpp @@ -0,0 +1,258 @@ +#include "interaction-manager.h" + +#include "clipboard.h" +#include "connector.h" +#include "datatypes.h" +#include "node.h" +#include "raylib.h" +#include "state.h" +#include +#include + +namespace VISEQ::INTERACTION{ + + void InteractionManager::update(){ + if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_C)){ + VEC_SPTR_Node selected_nodes = gm->getCopiesOfSelectedNodes(); + if(!selected_nodes.empty()) cb.copyNodes(selected_nodes); + } + + if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_V)){ + VEC_SPTR_Node paste_nodes = cb.pasteNodes(); + if(!paste_nodes.empty()) gm->pasteNodesAndEdges(paste_nodes, MOD_MOUSE); + } + + if(IsKeyPressed(KEY_DELETE)){ + removeNodes(gm->getSelection()); + } + + if(!KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { + state->node_mode = (state->node_mode + 1) % 7; + } + + if(KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { + state->node_mode -= state->node_mode > 0 ? 1 : -6; + } + + } + + void InteractionManager::draw(){ + doInteraction(); + scaleSelection(); + if(!gm->getSelection().empty()){ + DrawRectangleRoundedLines(state->bounding_box, 0.1f, 3, VS_COLOR_LIGHTGREY); + scale_handle->draw(); + } + } + + void InteractionManager::removeNodes(VEC_SPTR_Node nodes){ + + VEC_WPTR_Node adjacent_nodes = gm->getAdjacentNodes(nodes); + + sm->rerouteSignal(nodes, adjacent_nodes); + sm->rebaseSignal(nodes); + + gm->removeNodesAndEdges(nodes); + } + + void InteractionManager::scaleSelection(){ + if(!gm->getSelection().empty()){ + float offset = 30; + Vec2Df corner_tl = {50000, 50000}; + Vec2Df corner_br = {-50000, -50000}; + std::shared_ptr node_tl; + std::shared_ptr node_br; + + for(auto n : gm->getSelection()){ + float x = n->position.x; + float y = n->position.y; + + if(x < corner_tl.x && y < corner_tl.y) node_tl = n; + if(x > corner_br.x && y > corner_br.y) node_br = n; + + if(x <= corner_tl.x) corner_tl.x = x; + if(x >= corner_br.x) corner_br.x = x; + if(y <= corner_tl.y) corner_tl.y = y; + if(y >= corner_br.y) corner_br.y = y; + } + corner_tl -= Vec2Df(offset, offset); + corner_br += Vec2Df(offset, offset); + state->bounding_box = (Rectangle){corner_tl.x, corner_tl.y, corner_br.x - corner_tl.x, corner_br.y - corner_tl.y}; + + scale_handle->update(); + + if(scale_handle->clicked()){ + scale_start = (corner_br - corner_tl); + for(auto n : gm->getSelection()){ + n->drag_offset = n->position - node_tl->position; + } + } + if(scale_handle->picked_up){ + Vec2Df scale = (scale_handle->position - corner_tl) / scale_start; + if(IsKeyDown(KEY_LEFT_SHIFT)) scale.y = scale.x; + DrawText(std::format("{:.2f} {:.2f}", scale.x, scale.y).c_str(), corner_br.x+5, corner_br.y+5, 10, RED); + for(auto n : gm->getSelection()){ + n->position = node_tl->position + (n->drag_offset * scale); + } + } else { + scale_handle->position = corner_br; + } + } + } + + void InteractionManager::snapPolar(std::shared_ptr dragNode){ + float steps = round((dragNode->neighbors.front()->position.dist(SCREEN_MOUSE)) / state->settings.grid) * state->settings.grid; + if(dragNode->neighbors.size() > 1){ + Vec2Df dn1 = dragNode->neighbors[0]->position; + Vec2Df dn2 = dragNode->neighbors[1]->position; + Vec2Df v1_orth = dn1.center(dn2); + + if(dn1.x < dn2.x) std::swap(dn1,dn2); + + float dist = dn1.dist(dn2); + float dist_orth = std::sqrt(std::pow(steps,2) - std::pow(dist/2,2)); + + if(SCREEN_MOUSE.y < v1_orth.y) dist_orth *= -1; + + Vec2Df v1_orth_t = v1_orth + (dn1-dn2).norm().orth() * dist_orth; + DrawLineV(dn1, dn2, VS_COLOR_RED); + DrawLineV(v1_orth, v1_orth_t, VS_COLOR_RED); + DrawCircleV(v1_orth, 3.0f, VS_COLOR_RED); + dragNode->position = v1_orth_t; + } else if(!dragNode->neighbors.empty()){ + float pol_r = steps; + float pol_theta = Vector2Angle(Vec2Df{1,0},(SCREEN_MOUSE - dragNode->neighbors.front()->position)); + Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta); + dragNode->position = dragNode->neighbors.front()->position + polar; + } + } + + void InteractionManager::doInteraction(){ + bool exit = false; + bool once = false; + bool connect_once = false; + + state->mouse_z = canvas->z_index; + + for(auto e : state->event_queue){ + if(!connect_once && e.action == APP::Interaction::CONNECT){ + APP::InteractionEvent partner; + bool found = false; + for(auto ei : state->event_queue){ + if(ei.action == APP::Interaction::HOVER && ei.item->getType() == GraphItemType::CONNECTOR) { + std::cout << "YEAH" << std::endl; + partner = ei; + found = true; + break; + } + } + + auto connector = std::dynamic_pointer_cast(e.item); + if(found && partner != e){ + auto connector_to = std::dynamic_pointer_cast(partner.item); + connector->partner = connector_to; + connector_to->partner = connector; + gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), std::dynamic_pointer_cast(connector_to->parent.lock())); + } else if (partner != e){ + if(KEYS_DOWN.alt){ + gm->createConfigNode(SCREEN_MOUSE); + } else { + gm->createMidiNode(SCREEN_MOUSE); + } + gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), gm->getLastNode()); + } + connect_once = true; + break; + } + } + + while(state->event_queue.size() > 0 && !exit){ + auto e = state->event_queue.front(); + if(e.action == APP::Interaction::CLICK){ + gm->addToSelection(); + } + if(e.action == APP::Interaction::RIGHTCLICK){ + gm->addToSelection(); + state->context_menu = !state->context_menu; + } + if(state->context_menu) break; + + if(e.action == APP::Interaction::HOVER){ + state->mouse_z = e.item->z_index; + } + + if(e.item->getType() == GraphItemType::CANVAS) { + if(e.action == APP::Interaction::SELECT) { + gm->select(); + canvas->clearSelectionRect(); + } + if(e.action == APP::Interaction::DESELECT) { + gm->deselect(); + } + } + + if(!once && e.item->getType() == GraphItemType::NODE){ + auto node = std::dynamic_pointer_cast(e.item); + if(e.action == APP::Interaction::PICKUP) { + node->pickUp(); + if(node->selected){ + for(auto s : gm->getSelection()){ + s->pickUp(node->position); + } + } + once = true; + } + if(e.action == APP::Interaction::DRAG) { + if(KEYS_DOWN.ctrl) node->drag(MOD_MOUSE); + else if(KEYS_DOWN.shift) { + snapPolar(node); + } + else node->drag(MOD_MOUSE + node->drag_offset); + + if(node->selected){ + for(auto s : gm->getSelection()){ + s->drag(MOD_MOUSE + s->drag_offset); + } + } + once = true; + } + if(e.action == APP::Interaction::PUTDOWN) { + node->putDown(); + if(node->selected){ + for(auto s : gm->getSelection()){ + s->putDown(); + } + } + } + } + if(exit) break; + + if(!once && (e.item->getType() == GraphItemType::CONNECTOR || e.item->getType() == GraphItemType::CANVAS || e.item->getType() == GraphItemType::HANDLE)) { + switch(e.action){ + case VISEQ::APP::Interaction::PICKUP: + { + e.item->pickUp(); + once = true; + break; + } + case VISEQ::APP::Interaction::DRAG: + { + e.item->drag(MOD_MOUSE); + once = true; + break; + } + case VISEQ::APP::Interaction::PUTDOWN: + { + e.item->putDown(); + break; + } + default: + {} + } + } + + state->event_queue.pop_front(); + } + state->event_queue.clear(); + } +} diff --git a/src/interaction-manager.h b/src/interaction-manager.h index 3838993..a1597d8 100644 --- a/src/interaction-manager.h +++ b/src/interaction-manager.h @@ -1,7 +1,6 @@ #pragma once #include "clipboard.h" -#include "connector.h" #include "datatypes.h" #include "draggable.h" #include "graph-item.h" @@ -12,7 +11,6 @@ #include "signal-manager.h" #include "state.h" #include "canvas.h" -#include #include namespace VISEQ::INTERACTION{ @@ -65,247 +63,11 @@ namespace VISEQ::INTERACTION{ Vec2Df scale_start; std::shared_ptr scale_handle = std::make_shared(); - void update(){ - if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_C)){ - VEC_SPTR_Node selected_nodes = gm->getCopiesOfSelectedNodes(); - if(!selected_nodes.empty()) cb.copyNodes(selected_nodes); - } - - if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_V)){ - VEC_SPTR_Node paste_nodes = cb.pasteNodes(); - if(!paste_nodes.empty()) gm->pasteNodesAndEdges(paste_nodes, MOD_MOUSE); - } - - if(IsKeyPressed(KEY_DELETE)){ - removeNodes(gm->getSelection()); - } - - if(!KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { - state->node_mode = (state->node_mode + 1) % 7; - } - - if(KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { - state->node_mode -= state->node_mode > 0 ? 1 : -6; - } - - } - - void draw(){ - doInteraction(); - scaleSelection(); - if(!gm->getSelection().empty()){ - DrawRectangleRoundedLines(state->bounding_box, 0.1f, 3, VS_COLOR_LIGHTGREY); - scale_handle->draw(); - } - } - - void removeNodes(VEC_SPTR_Node nodes){ - - VEC_WPTR_Node adjacent_nodes = gm->getAdjacentNodes(nodes); - - sm->rerouteSignal(nodes, adjacent_nodes); - sm->rebaseSignal(nodes); - - gm->removeNodesAndEdges(nodes); - } - - void scaleSelection(){ - if(!gm->getSelection().empty()){ - float offset = 30; - Vec2Df corner_tl = {50000, 50000}; - Vec2Df corner_br = {-50000, -50000}; - std::shared_ptr node_tl; - std::shared_ptr node_br; - - for(auto n : gm->getSelection()){ - float x = n->position.x; - float y = n->position.y; - - if(x < corner_tl.x && y < corner_tl.y) node_tl = n; - if(x > corner_br.x && y > corner_br.y) node_br = n; - - if(x <= corner_tl.x) corner_tl.x = x; - if(x >= corner_br.x) corner_br.x = x; - if(y <= corner_tl.y) corner_tl.y = y; - if(y >= corner_br.y) corner_br.y = y; - } - corner_tl -= Vec2Df(offset, offset); - corner_br += Vec2Df(offset, offset); - state->bounding_box = (Rectangle){corner_tl.x, corner_tl.y, corner_br.x - corner_tl.x, corner_br.y - corner_tl.y}; - - scale_handle->update(); - - if(scale_handle->clicked()){ - scale_start = (corner_br - corner_tl); - for(auto n : gm->getSelection()){ - n->drag_offset = n->position - node_tl->position; - } - } - if(scale_handle->picked_up){ - Vec2Df scale = (scale_handle->position - corner_tl) / scale_start; - if(IsKeyDown(KEY_LEFT_SHIFT)) scale.y = scale.x; - DrawText(std::format("{:.2f} {:.2f}", scale.x, scale.y).c_str(), corner_br.x+5, corner_br.y+5, 10, RED); - for(auto n : gm->getSelection()){ - n->position = node_tl->position + (n->drag_offset * scale); - } - } else { - scale_handle->position = corner_br; - } - } - } - - void snapPolar(std::shared_ptr dragNode){ - float steps = round((dragNode->neighbors.front()->position.dist(SCREEN_MOUSE)) / state->settings.grid) * state->settings.grid; - if(dragNode->neighbors.size() > 1){ - Vec2Df dn1 = dragNode->neighbors[0]->position; - Vec2Df dn2 = dragNode->neighbors[1]->position; - Vec2Df v1_orth = dn1.center(dn2); - - if(dn1.x < dn2.x) std::swap(dn1,dn2); - - float dist = dn1.dist(dn2); - float dist_orth = std::sqrt(std::pow(steps,2) - std::pow(dist/2,2)); - - if(SCREEN_MOUSE.y < v1_orth.y) dist_orth *= -1; - - Vec2Df v1_orth_t = v1_orth + (dn1-dn2).norm().orth() * dist_orth; - DrawLineV(dn1, dn2, VS_COLOR_RED); - DrawLineV(v1_orth, v1_orth_t, VS_COLOR_RED); - DrawCircleV(v1_orth, 3.0f, VS_COLOR_RED); - dragNode->position = v1_orth_t; - } else if(!dragNode->neighbors.empty()){ - float pol_r = steps; - float pol_theta = Vector2Angle(Vec2Df{1,0},(SCREEN_MOUSE - dragNode->neighbors.front()->position)); - Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta); - dragNode->position = dragNode->neighbors.front()->position + polar; - } - } - - void doInteraction(){ - bool exit = false; - bool once = false; - bool connect_once = false; - - state->mouse_z = canvas->z_index; - - for(auto e : APP::event_queue){ - if(!connect_once && e.action == APP::Interaction::CONNECT){ - APP::InteractionEvent partner; - bool found = false; - for(auto ei : APP::event_queue){ - if(ei.action == APP::Interaction::HOVER && ei.item->getType() == GraphItemType::CONNECTOR) { - std::cout << "YEAH" << std::endl; - partner = ei; - found = true; - break; - } - } - - auto connector = std::dynamic_pointer_cast(e.item); - if(found && partner != e){ - auto connector_to = std::dynamic_pointer_cast(partner.item); - connector->partner = connector_to; - connector_to->partner = connector; - gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), std::dynamic_pointer_cast(connector_to->parent.lock())); - } else if (partner != e){ - if(KEYS_DOWN.alt){ - gm->createConfigNode(SCREEN_MOUSE); - } else { - gm->createMidiNode(SCREEN_MOUSE); - } - gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), gm->getLastNode()); - } - connect_once = true; - break; - } - } - - while(APP::event_queue.size() > 0 && !exit){ - auto e = APP::event_queue.front(); - if(e.action == APP::Interaction::RIGHTCLICK){ - gm->addToSelection(); - state->context_menu = !state->context_menu; - } - if(state->context_menu) break; - - if(e.action == APP::Interaction::HOVER){ - state->mouse_z = e.item->z_index; - } - - if(e.item->getType() == GraphItemType::CANVAS) { - if(e.action == APP::Interaction::SELECT) { - gm->select(); - canvas->clearSelectionRect(); - } - if(e.action == APP::Interaction::DESELECT) { - gm->deselect(); - } - } - - if(!once && e.item->getType() == GraphItemType::NODE){ - auto node = std::dynamic_pointer_cast(e.item); - if(e.action == APP::Interaction::PICKUP) { - node->pickUp(); - if(node->selected){ - for(auto s : gm->getSelection()){ - s->pickUp(node->position); - } - } - once = true; - } - if(e.action == APP::Interaction::DRAG) { - if(KEYS_DOWN.ctrl) node->drag(MOD_MOUSE); - else if(KEYS_DOWN.shift) { - snapPolar(node); - } - else node->drag(MOD_MOUSE + node->drag_offset); - - if(node->selected){ - for(auto s : gm->getSelection()){ - s->drag(MOD_MOUSE + s->drag_offset); - } - } - once = true; - } - if(e.action == APP::Interaction::PUTDOWN) { - node->putDown(); - if(node->selected){ - for(auto s : gm->getSelection()){ - s->putDown(); - } - } - } - } - if(exit) break; - - if(!once && (e.item->getType() == GraphItemType::CONNECTOR || e.item->getType() == GraphItemType::CANVAS || e.item->getType() == GraphItemType::HANDLE)) { - switch(e.action){ - case VISEQ::APP::Interaction::PICKUP: - { - e.item->pickUp(); - once = true; - break; - } - case VISEQ::APP::Interaction::DRAG: - { - e.item->drag(MOD_MOUSE); - once = true; - break; - } - case VISEQ::APP::Interaction::PUTDOWN: - { - e.item->putDown(); - break; - } - default: - {} - } - } - - APP::event_queue.pop_front(); - } - APP::event_queue.clear(); - } - + void update(); + void draw(); + void removeNodes(VEC_SPTR_Node nodes); + void scaleSelection(); + void snapPolar(std::shared_ptr dragNode); + void doInteraction(); }; } diff --git a/src/main.cpp b/src/main.cpp index e27e3ce..e98f515 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,10 +15,8 @@ #include #include -#include #include - VISEQ::APP::AppManager app; std::shared_ptr state = VISEQ::APP::State::get(); @@ -46,8 +44,8 @@ int main(){ state->settings.speed = (state->settings.grid * 16.0f) * (state->settings.bpm / 120.f); - state->settings.width = monitor_width - 300; - state->settings.height = monitor_height - 300; + state->settings.width = GetScreenWidth(); + state->settings.height = GetScreenHeight(); SetTargetFPS(state->settings.fps); diff --git a/src/midi-node.cpp b/src/midi-node.cpp new file mode 100644 index 0000000..6a5a95d --- /dev/null +++ b/src/midi-node.cpp @@ -0,0 +1,399 @@ +#include "midi-node.h" + +#include "config-node.h" +#include "datatypes.h" +#include "edge.h" +#include "node-handle.h" +#include "node.h" +#include "raylib.h" +#include "state.h" +#include +#include +#include + +namespace VISEQ::GRAPH::NODES { + + void MidiNode::init(){ + float orbit_1 = radius + 25.0f; + float orbit_2 = orbit_1 + 20.0f; + float orbit_3 = orbit_2 + 20.0f; + float orbit_4 = orbit_3 + 20.0f; + float orbit_5 = orbit_4 + 30.0f; + + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.note, position, VS_COLOR_GREEN, "NOTE", orbit_5, 12)); + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 16, 1, 16, 40, 150)); + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.gate, position, VS_COLOR_RED, "GATE", orbit_3, 30, 10, 30000, 40, 300)); + midiHandles[NOTE].back()->logScale = true; + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.velocity, position, VS_COLOR_PURPLE, "VELOCITY", orbit_4, 32)); + + midiHandles[CC].emplace_back(std::make_shared>(midiCC.control, position, VS_COLOR_SUN, "CONTROL", orbit_3, 32)); + midiHandles[CC].emplace_back(std::make_shared>(midiCC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[CC].emplace_back(std::make_shared>(midiCC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); + + midiHandles[PC].emplace_back(std::make_shared>(midiPC.program, position, VS_COLOR_SUN, "PROGRAM", orbit_3, 32)); + midiHandles[PC].emplace_back(std::make_shared>(midiPC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[PC].emplace_back(std::make_shared>(midiPC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); + + midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 256, -8192, 8192, 40, 300)); + + midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 32)); + + midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); + midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.note, position, VS_COLOR_SUN, "NOTE", orbit_5, 12)); + } + + std::shared_ptr MidiNode::getPtr(){ + return std::static_pointer_cast(shared_from_this()); + } + + NodeType MidiNode::getNodeType() const { + return nodeType; + } + + void MidiNode::setMidiPort(int midi_port, bool _state){ + midi_outs[midi_port].enabled = _state; + } + + void MidiNode::trigger() { + triggered = true; + start = GetTime(); + fade = getNotes().gate; + } + + void MidiNode::setMode(int _mode) { + mode = _mode; + } + + MIDI::MidiNote MidiNode::getNotes() const { + MIDI::MidiNote cNote; + for(auto cn : getConnectedConfigNodes()){ + cNote += cn.lock()->getNotes(); + } + return midiNote + cNote; + } + + MIDI::MidiCC MidiNode::getCC() const { + MIDI::MidiCC cCC; + for(auto cn : getConnectedConfigNodes()){ + cCC += cn.lock()->getCC(); + } + return midiCC + cCC; + } + + MIDI::MidiPC MidiNode::getPC() const { + MIDI::MidiPC cPC; + for(auto cn : getConnectedConfigNodes()){ + cPC += cn.lock()->getPC(); + } + return midiPC + cPC; + } + + MIDI::MidiPitchBend MidiNode::getPitchBend() const { + MIDI::MidiPitchBend cPitchBend; + for(auto cn : getConnectedConfigNodes()){ + cPitchBend += cn.lock()->getPitchBend(); + } + return midiPitchBend + cPitchBend; + } + + MIDI::MidiAftertouch MidiNode::getAftertouch() const { + MIDI::MidiAftertouch cAftertouch; + for(auto cn : getConnectedConfigNodes()){ + cAftertouch += cn.lock()->getAftertouch(); + } + return midiAftertouch + cAftertouch; + } + + MIDI::MidiPolyPressure MidiNode::getPolyPressure() const { + MIDI::MidiPolyPressure cPolyPressure; + for(auto cn : getConnectedConfigNodes()){ + cPolyPressure += cn.lock()->getPolyPressure(); + } + return midiPolyPressure + cPolyPressure; + } + + std::vector> MidiNode::getConnectedConfigNodes() const { + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + if(e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + void MidiNode::update() { + Node::update(); + mode = state->node_mode; + for(auto mh : midiHandles[NOTE]){ + mh->update(position); + } + for(auto mh : midiHandles[CC]){ + mh->update(position); + } + for(auto mh : midiHandles[PC]){ + mh->update(position); + } + for(auto mh : midiHandles[PB]){ + mh->update(position); + } + for(auto mh : midiHandles[AT]){ + mh->update(position); + } + for(auto mh : midiHandles[PP]){ + mh->update(position); + } + } + + void MidiNode::modMidiNote(int incr){ + midiNote.note += incr; + if(midiNote.note < 0 ) midiNote.note = VISEQ::MIDI::Notes.size()-1; + if(midiNote.note >= (int)VISEQ::MIDI::Notes.size()) midiNote.note = 0; + } + + void MidiNode::modMidiCC(int incr){ + midiCC.control += incr; + if(midiCC.control < 0 ) midiCC.control = 127; + if(midiCC.control > 127 ) midiCC.control = 0; + } + + void MidiNode::modMidiPC(int incr){ + midiPC.program += incr; + if(midiPC.program < 0 ) midiPC.program = 127; + if(midiPC.program > 127 ) midiPC.program = 0; + } + + void MidiNode::modMidiPitchBend(int incr){ + midiPitchBend.value += incr; + if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + } + + void MidiNode::modMidiAftertouch(int incr){ + midiAftertouch.value += incr; + if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; + if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; + } + + void MidiNode::modMidiPolyPressure(int incr){ + midiPolyPressure.value += incr; + if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; + if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; + } + + void MidiNode::interact(){ + float wheel = GetMouseWheelMove(); + + if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + switch(state->node_mode){ + case 0:{ + modMidiNote(wheel); + break;} + case 1:{ + modMidiCC(wheel); + break;} + case 2:{ + modMidiPC(wheel); + break;} + case 3:{ + modMidiPitchBend(wheel); + break;} + case 4:{ + modMidiAftertouch(wheel); + break;} + case 5:{ + modMidiPolyPressure(wheel); + break;} + }; + } else if(KEYS_DOWN.shift && !KEYS_DOWN.alt) { + midiNote.velocity += wheel; + if(midiNote.velocity < 0 ) midiNote.velocity = 127; + if(midiNote.velocity > 127 ) midiNote.velocity = 0; + } else if(KEYS_DOWN.alt) { + switch(state->node_mode){ + case 0:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiNote.gate += incr; + if(midiNote.gate < 0 ) midiNote.gate = 0; + break;} + case 1:{ + midiCC.value += wheel; + if(midiCC.value < 0 ) midiCC.value = 0; + if(midiCC.value > 127 ) midiCC.value = 127; + break;} + case 2:{ + midiPC.value += wheel; + if(midiPC.value < 0 ) midiPC.value = 0; + if(midiPC.value > 127 ) midiPC.value = 127; + break;} + case 3:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiPitchBend.value += incr; + if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + break;} + case 4:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiAftertouch.value += incr; + if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; + if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; + break;} + case 5:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiPolyPressure.value += incr; + if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; + if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; + break;} + }; + } + } + + void MidiNode::draw(Vec2Df mouse, Camera2D cam) { + Node::draw(); + + for(int i = 0; i < 6; ++i){ + for(auto mh : midiHandles[(Handles)i]){ + mh->lock(); + } + } + + if(hovering() || selected) interact(); + + last = time; + time = GetTime(); + if(time - start >= (float)getNotes().gate / 1000.0f){ + triggered = false; + fade = 0; + } else { + fade -= (time - last)*1000.0f; + } + + DrawRing(position, radius, radius + 4, 0, 360, 24, (Color){60, 60, 60, 255}); + DrawRing(position, radius, radius + 4, 0.0f - 90.0f, (360.0f/127.0f) * getNotes().velocity - 90.0f, 24, (Color){158, 134, 200, 255}); + DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); + + float trigger_alpha = (1.f / getNotes().gate) * fade; + + if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, ColorLerp(state->mode_color, VS_COLOR_RED, trigger_alpha)); + if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); + if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); + + if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); + if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); + + if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); + bool modified = false; + if(selected && mode >= 0 && mode < 6){ + for(auto mh : midiHandles[(Handles)mode]){ + mh->unlock(); + mh->draw(); + std::string text = ""; + if(mh->hovering() || mh->picked_up){ + text = std::format("{}", mh->assignedValueRef); + if(mh->name == "NOTE") text = MIDI::Notes[mh->assignedValueRef]; + if(mh->name == "GATE") text = std::format("{:.2f}", mh->assignedValueRef / 1000.0f); + if(mh->name == "VALUE" && (Handles)mode == PB) text = std::format("{}", round(((float)mh->assignedValueRef / 8192.0f) * 100)); + DrawCenteredText(text, position, 12, WHITE); + modified = true; + } + } + } + + if(!modified){ + switch((Handles)mode){ + case NOTE:{ + DrawCenteredText(MIDI::Notes[getNotes().note], position, 12, WHITE); + break;} + case CC:{ + DrawCenteredText(std::format("{}", getCC().value), position, 12, WHITE); + break;} + case PC:{ + DrawCenteredText(std::format("{}", getPC().value), position, 12, WHITE); + break;} + case PB:{ + DrawCenteredText(std::format("{}", round(((float)getPitchBend().value / 8192.0f) * 100)), position, 12, WHITE); + break;} + case AT:{ + DrawCenteredText(std::format("{}", getAftertouch().value), position, 12, WHITE); + break;} + case PP:{ + DrawCenteredText(std::format("{}", getPolyPressure().value), position, 12, WHITE); + break;} + } + + } + + if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); + } + + void to_json(json& j, const MidiNode& N){ + std::vector conns; + std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); + + std::vector> handles; + + for(auto i : N.midiHandles){ + handles.emplace_back(); + for(auto n : i.second){ + handles.at(i.first).push_back(n->position); + } + } + + j = json{ + {"id", N.id}, + {"position", N.position}, + {"distributionMode", N.distributionMode}, + {"nodeType", N.nodeType}, + {"midiNote", N.midiNote}, + {"midiCC", N.midiCC}, + {"midiPC", N.midiPC}, + {"midiPitchBend", N.midiPitchBend}, + {"midiAftertouch", N.midiAftertouch}, + {"midiPolyPressure", N.midiPolyPressure}, + {"handles", handles}, + {"connections",conns}}; + + } + + void from_json(const json& j, MidiNode& N){ + std::cout << "MidiNode" << std::endl; + + j.at("id").get_to(N.id); + j.at("position").get_to(N.position); + j.at("distributionMode").get_to(N.distributionMode); + j.at("nodeType").get_to(N.nodeType); + j.at("midiNote").get_to(N.midiNote); + j.at("midiCC").get_to(N.midiCC); + j.at("midiPC").get_to(N.midiPC); + j.at("midiPitchBend").get_to(N.midiPitchBend); + j.at("midiAftertouch").get_to(N.midiAftertouch); + j.at("midiPolyPressure").get_to(N.midiPolyPressure); + + std::vector> handles; + + j.at("handles").get_to(handles); + + int i = 0; + for(auto n : handles){ + int j = 0; + for(auto nn : n){ + N.midiHandles[(Handles)i][j]->position = nn; + j++; + } + i++; + } + } + + void to_json(json& j, const std::shared_ptr& N){ + j = *N; + } + + void from_json(const json& j, std::shared_ptr& N){ + N.reset(new MidiNode(j.get())); + } + +} diff --git a/src/midi-node.h b/src/midi-node.h index 72b4ece..e9cc84a 100644 --- a/src/midi-node.h +++ b/src/midi-node.h @@ -1,13 +1,10 @@ #pragma once -#include "button.h" #include "config-node.h" #include "datatypes.h" -#include "edge.h" #include "node-handle.h" #include "node.h" #include "raylib.h" -#include "state.h" #include #include @@ -16,6 +13,8 @@ namespace VISEQ::APP::UI{ } namespace VISEQ::GRAPH::NODES { + + enum Handles {NOTE = 0, CC, PC, PB, AT, PP}; class MidiNode : public Node{ friend class APP::UI::AppUI; @@ -28,8 +27,6 @@ namespace VISEQ::GRAPH::NODES { MIDI::MidiAftertouch midiAftertouch; MIDI::MidiPolyPressure midiPolyPressure; - enum Handles {NOTE = 0, CC, PC, PB, AT, PP}; - std::map>>> midiHandles; public: @@ -66,378 +63,33 @@ namespace VISEQ::GRAPH::NODES { float time = 0; float last = 0; - void init(){ - float orbit_1 = radius + 25.0f; - float orbit_2 = orbit_1 + 20.0f; - float orbit_3 = orbit_2 + 20.0f; - float orbit_4 = orbit_3 + 20.0f; - float orbit_5 = orbit_4 + 30.0f; + void init(); + std::shared_ptr getPtr(); + virtual NodeType getNodeType() const override; + void setMidiPort(int midi_port, bool _state); + void trigger() override ; + void setMode(int _mode) override ; + MIDI::MidiNote getNotes() const ; + MIDI::MidiCC getCC() const ; + MIDI::MidiPC getPC() const ; + MIDI::MidiPitchBend getPitchBend() const ; + MIDI::MidiAftertouch getAftertouch() const ; + MIDI::MidiPolyPressure getPolyPressure() const ; + std::vector> getConnectedConfigNodes() const ; + void update() override ; + void modMidiNote(int incr); + void modMidiCC(int incr); + void modMidiPC(int incr); + void modMidiPitchBend(int incr); + void modMidiAftertouch(int incr); + void modMidiPolyPressure(int incr); + void interact(); + void draw(Vec2Df mouse, Camera2D cam) override ; - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.note, position, VS_COLOR_GREEN, "NOTE", orbit_5, 12)); - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 16, 1, 16, 40, 150)); - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.gate, position, VS_COLOR_RED, "GATE", orbit_3, 30, 10, 30000, 40, 300)); - midiHandles[NOTE].back()->logScale = true; - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.velocity, position, VS_COLOR_PURPLE, "VELOCITY", orbit_4, 32)); - - midiHandles[CC].emplace_back(std::make_shared>(midiCC.control, position, VS_COLOR_SUN, "CONTROL", orbit_3, 32)); - midiHandles[CC].emplace_back(std::make_shared>(midiCC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[CC].emplace_back(std::make_shared>(midiCC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); - - midiHandles[PC].emplace_back(std::make_shared>(midiPC.program, position, VS_COLOR_SUN, "PROGRAM", orbit_3, 32)); - midiHandles[PC].emplace_back(std::make_shared>(midiPC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[PC].emplace_back(std::make_shared>(midiPC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); - - midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 256, -8192, 8192, 40, 300)); - - midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 32)); - - midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); - midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.note, position, VS_COLOR_SUN, "NOTE", orbit_5, 12)); - } - - std::shared_ptr getPtr(){ - return std::static_pointer_cast(shared_from_this()); - } - - virtual NodeType getNodeType() const override {return nodeType;} - - void setMidiPort(int midi_port, bool _state){ - midi_outs[midi_port].enabled = _state; - } - - void trigger() override { - triggered = true; - start = GetTime(); - fade = getNotes().gate; - } - - void setMode(int _mode) override { - mode = _mode; - } - - MIDI::MidiNote getNotes() const { - MIDI::MidiNote cNote; - for(auto cn : getConnectedConfigNodes()){ - cNote += cn.lock()->getNotes(); - } - return midiNote + cNote; - } - - MIDI::MidiCC getCC() const { - MIDI::MidiCC cCC; - for(auto cn : getConnectedConfigNodes()){ - cCC += cn.lock()->getCC(); - } - return midiCC + cCC; - } - - MIDI::MidiPC getPC() const { - MIDI::MidiPC cPC; - for(auto cn : getConnectedConfigNodes()){ - cPC += cn.lock()->getPC(); - } - return midiPC + cPC; - } - - MIDI::MidiPitchBend getPitchBend() const { - MIDI::MidiPitchBend cPitchBend; - for(auto cn : getConnectedConfigNodes()){ - cPitchBend += cn.lock()->getPitchBend(); - } - return midiPitchBend + cPitchBend; - } - - MIDI::MidiAftertouch getAftertouch() const { - MIDI::MidiAftertouch cAftertouch; - for(auto cn : getConnectedConfigNodes()){ - cAftertouch += cn.lock()->getAftertouch(); - } - return midiAftertouch + cAftertouch; - } - - MIDI::MidiPolyPressure getPolyPressure() const { - MIDI::MidiPolyPressure cPolyPressure; - for(auto cn : getConnectedConfigNodes()){ - cPolyPressure += cn.lock()->getPolyPressure(); - } - return midiPolyPressure + cPolyPressure; - } - - std::vector> getConnectedConfigNodes() const { - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - if(e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - void update() override { - Node::update(); - mode = state->node_mode; - for(auto mh : midiHandles[NOTE]){ - mh->update(position); - } - for(auto mh : midiHandles[CC]){ - mh->update(position); - } - for(auto mh : midiHandles[PC]){ - mh->update(position); - } - for(auto mh : midiHandles[PB]){ - mh->update(position); - } - for(auto mh : midiHandles[AT]){ - mh->update(position); - } - for(auto mh : midiHandles[PP]){ - mh->update(position); - } - } - - void modMidiNote(int incr){ - midiNote.note += incr; - if(midiNote.note < 0 ) midiNote.note = VISEQ::MIDI::Notes.size()-1; - if(midiNote.note >= (int)VISEQ::MIDI::Notes.size()) midiNote.note = 0; - } - - void modMidiCC(int incr){ - midiCC.control += incr; - if(midiCC.control < 0 ) midiCC.control = 127; - if(midiCC.control > 127 ) midiCC.control = 0; - } - - void modMidiPC(int incr){ - midiPC.program += incr; - if(midiPC.program < 0 ) midiPC.program = 127; - if(midiPC.program > 127 ) midiPC.program = 0; - } - - void modMidiPitchBend(int incr){ - midiPitchBend.value += incr; - if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - } - - void modMidiAftertouch(int incr){ - midiAftertouch.value += incr; - if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; - if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; - } - - void modMidiPolyPressure(int incr){ - midiPolyPressure.value += incr; - if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; - if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; - } - - void interact(){ - float wheel = GetMouseWheelMove(); - - if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - switch(state->node_mode){ - case 0:{ - modMidiNote(wheel); - break;} - case 1:{ - modMidiCC(wheel); - break;} - case 2:{ - modMidiPC(wheel); - break;} - case 3:{ - modMidiPitchBend(wheel); - break;} - case 4:{ - modMidiAftertouch(wheel); - break;} - case 5:{ - modMidiPolyPressure(wheel); - break;} - }; - } else if(KEYS_DOWN.shift && !KEYS_DOWN.alt) { - midiNote.velocity += wheel; - if(midiNote.velocity < 0 ) midiNote.velocity = 127; - if(midiNote.velocity > 127 ) midiNote.velocity = 0; - } else if(KEYS_DOWN.alt) { - switch(state->node_mode){ - case 0:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiNote.gate += incr; - if(midiNote.gate < 0 ) midiNote.gate = 0; - break;} - case 1:{ - midiCC.value += wheel; - if(midiCC.value < 0 ) midiCC.value = 0; - if(midiCC.value > 127 ) midiCC.value = 127; - break;} - case 2:{ - midiPC.value += wheel; - if(midiPC.value < 0 ) midiPC.value = 0; - if(midiPC.value > 127 ) midiPC.value = 127; - break;} - case 3:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiPitchBend.value += incr; - if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - break;} - case 4:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiAftertouch.value += incr; - if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; - if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; - break;} - case 5:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiPolyPressure.value += incr; - if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; - if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; - break;} - }; - } - } - - void draw(Vec2Df mouse, Camera2D cam) override { - Node::draw(); - - for(int i = 0; i < 6; ++i){ - for(auto mh : midiHandles[(Handles)i]){ - mh->lock(); - } - } - - if(hovering() || selected) interact(); - - last = time; - time = GetTime(); - if(time - start >= (float)getNotes().gate / 1000.0f){ - triggered = false; - fade = 0; - } else { - fade -= (time - last)*1000.0f; - } - - DrawRing(position, radius, radius + 4, 0, 360, 24, (Color){60, 60, 60, 255}); - DrawRing(position, radius, radius + 4, 0.0f - 90.0f, (360.0f/127.0f) * getNotes().velocity - 90.0f, 24, (Color){158, 134, 200, 255}); - DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); - - float trigger_alpha = (1.f / getNotes().gate) * fade; - - if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, ColorLerp(state->mode_color, VS_COLOR_RED, trigger_alpha)); - if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); - if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); - - if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); - if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); - - if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); - bool modified = false; - - if(selected && mode >= 0 && mode < 6){ - for(auto mh : midiHandles[(Handles)mode]){ - mh->unlock(); - mh->draw(); - std::string text = ""; - if(mh->hovering() || mh->picked_up){ - text = std::format("{}", mh->assignedValueRef); - if(mh->name == "NOTE") text = MIDI::Notes[mh->assignedValueRef]; - if(mh->name == "GATE") text = std::format("{:.2f}", mh->assignedValueRef / 1000.0f); - if(mh->name == "VALUE" && (Handles)mode == PB) text = std::format("{}", round(((float)mh->assignedValueRef / 8192.0f) * 100)); - DrawCenteredText(text, position, 12, WHITE); - modified = true; - } - } - } - - if(!modified){ - switch((Handles)mode){ - case NOTE:{ - DrawCenteredText(MIDI::Notes[getNotes().note], position, 12, WHITE); - break;} - case CC:{ - DrawCenteredText(std::format("{}", getCC().value), position, 12, WHITE); - break;} - case PC:{ - DrawCenteredText(std::format("{}", getPC().value), position, 12, WHITE); - break;} - case PB:{ - DrawCenteredText(std::format("{}", round(((float)getPitchBend().value / 8192.0f) * 100)), position, 12, WHITE); - break;} - case AT:{ - DrawCenteredText(std::format("{}", getAftertouch().value), position, 12, WHITE); - break;} - case PP:{ - DrawCenteredText(std::format("{}", getPolyPressure().value), position, 12, WHITE); - break;} - } - - } - - if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); - } - - friend void to_json(json& j, const MidiNode& N){ - std::vector conns; - std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); - - std::vector> handles; - - for(auto i : N.midiHandles){ - handles.emplace_back(); - for(auto n : i.second){ - handles.at(i.first).push_back(n->position); - } - } - - j = json{ - {"id", N.id}, - {"position", N.position}, - {"distributionMode", N.distributionMode}, - {"nodeType", N.nodeType}, - {"midiNote", N.midiNote}, - {"midiCC", N.midiCC}, - {"midiPC", N.midiPC}, - {"midiPitchBend", N.midiPitchBend}, - {"midiAftertouch", N.midiAftertouch}, - {"midiPolyPressure", N.midiPolyPressure}, - {"handles", handles}, - {"connections",conns}}; - - } - - friend void from_json(const json& j, MidiNode& N){ - std::cout << "MidiNode" << std::endl; - - j.at("id").get_to(N.id); - j.at("position").get_to(N.position); - j.at("distributionMode").get_to(N.distributionMode); - j.at("nodeType").get_to(N.nodeType); - j.at("midiNote").get_to(N.midiNote); - j.at("midiCC").get_to(N.midiCC); - j.at("midiPC").get_to(N.midiPC); - j.at("midiPitchBend").get_to(N.midiPitchBend); - j.at("midiAftertouch").get_to(N.midiAftertouch); - j.at("midiPolyPressure").get_to(N.midiPolyPressure); - - std::vector> handles; - - j.at("handles").get_to(handles); - - int i = 0; - for(auto n : handles){ - int j = 0; - for(auto nn : n){ - N.midiHandles[(Handles)i][j]->position = nn; - j++; - } - i++; - } - } + friend void to_json(json& j, const MidiNode& N); + friend void from_json(const json& j, MidiNode& N); + friend void to_json(json& j, const std::shared_ptr& N); + friend void from_json(const json& j, std::shared_ptr& N); }; + } diff --git a/src/midi.cpp b/src/midi.cpp new file mode 100644 index 0000000..ccccba0 --- /dev/null +++ b/src/midi.cpp @@ -0,0 +1,91 @@ +#include "midi.h" + +#include "libremidi/libremidi.hpp" +#include "raylib.h" + +#include +#include +#include "datatypes.h" + +namespace VISEQ::MIDI { + + void Midi::enumerate(){ + std::cout << (int)obs.get_current_api() << std::endl; + + + int i = 0; + midi_out_ports.clear(); + for(const libremidi::output_port& port : obs.get_output_ports()){ + midi_out_ports.push_back({port, i, false}); + i++; + } + + auto out_conf = libremidi::midi1::out_default_configuration(); + virtual_midi_out = libremidi::midi_out(libremidi::output_configuration(), out_conf); + virtual_midi_out.open_virtual_port("viseq out"); + + //midi_out_ports.push_back({&virtual_midi_out, 99, false}); + } + + void Midi::open_midi_out(MidiOutPort *out_port){ + if(!midi_outs.contains(out_port->number)){ + auto out = std::make_shared(); + out->open_port(out_port->port); + midi_outs[out_port->number] = out; + } + } + + void Midi::close_midi_out(MidiOutPort *out_port){ + if(midi_outs.contains(out_port->number)){ + midi_outs[out_port->number]->close_port(); + midi_outs.erase(out_port->number); + } + } + + void Midi::note(int midi_out, int channel, int note, int velocity, int gate){ + if(midi_outs.contains(midi_out)){ + if(midi_outs[midi_out]->is_port_open()){ + midi_outs[midi_out]->send_message(libremidi::channel_events::note_on(channel, note, velocity)); + notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate); + } + } + virtual_midi_out.send_message(libremidi::channel_events::note_on(channel, note, velocity)); + notes.emplace_back(99, channel, note, velocity, GetTime(), gate); + } + + void Midi::cc(int midi_out, int channel, int cc, int value){ + if(midi_outs.contains(midi_out)){ + if(midi_outs[midi_out]->is_port_open()){ + midi_outs[midi_out]->send_message(libremidi::channel_events::control_change(channel, cc, value)); + } + } + } + + void Midi::pitch_bend(int midi_out, int channel, int pitch_value){ + if(midi_outs.contains(midi_out)){ + if(midi_outs[midi_out]->is_port_open()){ + midi_outs[midi_out]->send_message(libremidi::channel_events::pitch_bend(channel, pitch_value)); + } + } + } + + void Midi::update(){ + float time = GetTime(); + for(auto &n : notes){ + if(time - n.start >= (float)n.gate / 1000.0f){ + if(midi_outs.contains(n.midi_out) && midi_outs[n.midi_out]->is_port_open()) midi_outs[n.midi_out]->send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); + virtual_midi_out.send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); + n.erase = true; + } + } + std::erase_if(notes, [](const auto& item){return item.erase;}); + + for(auto& p : midi_out_ports){ + if(p.enabled) { + open_midi_out(&p); + } else if(!p.enabled) { + close_midi_out(&p); + } + } + } +} diff --git a/src/midi.h b/src/midi.h index ed4c672..b2442fc 100644 --- a/src/midi.h +++ b/src/midi.h @@ -1,11 +1,11 @@ #pragma once #include "libremidi/libremidi.hpp" -#include "libremidi/message.hpp" #include "libremidi/observer_configuration.hpp" -#include "raylib.h" -#include +#include +#include +#include #include #include #include "datatypes.h" @@ -34,79 +34,20 @@ namespace VISEQ::MIDI { std::map> midi_outs; std::vector midi_in_ports; std::vector midi_out_ports; + + libremidi::midi_out virtual_midi_out; std::vector notes; float timer; - void enumerate(){ - std::cout << (int)obs.get_current_api() << std::endl; - int i = 0; - midi_out_ports.clear(); - for(const libremidi::output_port& port : obs.get_output_ports()){ - midi_out_ports.push_back({port, i, false}); - i++; - } - } - - void open_midi_out(MidiOutPort *out_port){ - if(!midi_outs.contains(out_port->number)){ - auto out = std::make_shared(); - out->open_port(out_port->port); - midi_outs[out_port->number] = out; - } - } - - void close_midi_out(MidiOutPort *out_port){ - if(midi_outs.contains(out_port->number)){ - midi_outs[out_port->number]->close_port(); - midi_outs.erase(out_port->number); - } - } - - void note(int midi_out, int channel, int note, int velocity, int gate){ - if(midi_outs.contains(midi_out)){ - if(midi_outs[midi_out]->is_port_open()){ - midi_outs[midi_out]->send_message(libremidi::channel_events::note_on(channel, note, velocity)); - notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate); - } - } - } - - void cc(int midi_out, int channel, int cc, int value){ - if(midi_outs.contains(midi_out)){ - if(midi_outs[midi_out]->is_port_open()){ - midi_outs[midi_out]->send_message(libremidi::channel_events::control_change(channel, cc, value)); - } - } - } - - void pitch_bend(int midi_out, int channel, int pitch_value){ - if(midi_outs.contains(midi_out)){ - if(midi_outs[midi_out]->is_port_open()){ - midi_outs[midi_out]->send_message(libremidi::channel_events::pitch_bend(channel, pitch_value)); - } - } - } - - void update(){ - float time = GetTime(); - for(auto &n : notes){ - if(time - n.start >= (float)n.gate / 1000.0f){ - if(midi_outs.contains(n.midi_out) && midi_outs[n.midi_out]->is_port_open()) midi_outs[n.midi_out]->send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); - n.erase = true; - } - } - std::erase_if(notes, [](const auto& item){return item.erase;}); - - for(auto& p : midi_out_ports){ - if(p.enabled) { - open_midi_out(&p); - } else if(!p.enabled) { - close_midi_out(&p); - } - } - } + void enumerate(); + void open_midi_out(MidiOutPort *out_port); + void close_midi_out(MidiOutPort *out_port); + void note(int midi_out, int channel, int note, int velocity, int gate); + void cc(int midi_out, int channel, int cc, int value); + void pitch_bend(int midi_out, int channel, int pitch_value); + void update(); }; } diff --git a/src/modifier-node.cpp b/src/modifier-node.cpp new file mode 100644 index 0000000..87c0234 --- /dev/null +++ b/src/modifier-node.cpp @@ -0,0 +1,289 @@ +#include "modifier-node.h" + +namespace VISEQ::GRAPH::NODES { + std::shared_ptr ModNode::getPtr() { + return std::static_pointer_cast(shared_from_this()); + } + + NodeType ModNode::getNodeType() const {return nodeType;} + + void ModNode::trigger() {} + + void ModNode::setMode(int _mode) { + mode = _mode; + } + + MIDI::MidiNote ModNode::getNotes() const { + MIDI::MidiNote cNote; + for(auto cn : getConnectedModNodes()){ + cNote += cn.lock()->getNotes(); + } + return midiNote + cNote; + } + + MIDI::MidiCC ModNode::getCC() const { + MIDI::MidiCC cCC; + for(auto cn : getConnectedModNodes()){ + cCC += cn.lock()->getCC(); + } + return midiCC + cCC; + } + + MIDI::MidiPC ModNode::getPC() const { + MIDI::MidiPC cPC; + for(auto cn : getConnectedModNodes()){ + cPC += cn.lock()->getPC(); + } + return midiPC + cPC; + } + + MIDI::MidiPitchBend ModNode::getPitchBend() const { + MIDI::MidiPitchBend cPitchBend; + for(auto cn : getConnectedModNodes()){ + cPitchBend += cn.lock()->getPitchBend(); + } + return midiPitchBend + cPitchBend; + } + + MIDI::MidiAftertouch ModNode::getAftertouch() const { + MIDI::MidiAftertouch cAftertouch; + for(auto cn : getConnectedModNodes()){ + cAftertouch += cn.lock()->getAftertouch(); + } + return midiAftertouch + cAftertouch; + } + + MIDI::MidiPolyPressure ModNode::getPolyPressure() const { + MIDI::MidiPolyPressure cPolyPressure; + for(auto cn : getConnectedModNodes()){ + cPolyPressure += cn.lock()->getPolyPressure(); + } + return midiPolyPressure + cPolyPressure; + } + + std::vector> ModNode::getConnectedModNodes() const { // TODO: check for cyclic connections of config nodes (crashes) + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + std::vector> ModNode::getConnectedMidiNodes() const { + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::MIDINODE || c->end.lock()->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + void ModNode::update() { + Node::update(); + mode = state->node_mode; + } + + void ModNode::interact(){ + float wheel = GetMouseWheelMove(); + + if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + switch(state->node_mode){ + case 0:{ + midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 1:{ + midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 2:{ + midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + }; + + if(midiNote.note < -127 ) midiNote.note = -127; + if(midiNote.note > 127 ) midiNote.note = 127; + + if(midiCC.value < -127 ) midiCC.value = -127; + if(midiCC.value > 127 ) midiCC.value = 127; + + if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + + } else if(KEYS_DOWN.alt) { + switch(state->node_mode){ + case 0:{ + midiNote.channel += wheel; + if(midiNote.channel < 0 ) midiNote.channel = 0; + if(midiNote.channel > 15) midiNote.channel = 15; + break;} + case 1:{ + midiCC.channel += wheel; + if(midiCC.channel < 0 ) midiCC.channel = 0; + if(midiCC.channel > 15 ) midiCC.channel = 15; + break;} + case 2:{ + midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; + if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; + if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; + break;} + }; + } + } + + void ModNode::draw(Vec2Df mouse, Camera2D cam) { + Node::draw(); + if(hovering() || selected) interact(); + + DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); + DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); + + Color gate_color = VS_COLOR_GREEN; + + switch(mode){ + case 0:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 1:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 2:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + } + if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); + if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); + if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); + + if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); + if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); + + if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); + + if(KEYS_DOWN.alt){ + float value = 0; + int offset_gate = 0; + switch(mode){ + case 0: + value = (float)getNotes().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 1: + value = (float)getCC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 2: + value = (float)getPC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 3: + value = (float)getPitchBend().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 4: + value = (float)getAftertouch().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 5: + value = (float)getPolyPressure().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + } + } else { + switch(mode){ + case 0:{ + int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); + DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 1:{ + int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); + DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 2:{ + int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); + DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 3:{ + int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); + DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 4:{ + int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); + DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 5:{ + int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); + DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + } + } + + if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); + //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); + } + + void to_json(json& j, const ModNode& N){ + std::vector conns; + std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); + + j = json{ + {"id", N.id}, + {"position", N.position}, + {"distributionMode", N.distributionMode}, + {"nodeType", N.nodeType}, + {"midi_note_channel",N.midiNote.channel}, + {"midi_note_note",N.midiNote.note}, + {"midi_note_velocity",N.midiNote.velocity}, + {"midi_note_gate",N.midiNote.gate}, + {"midi_cc_channel",N.midiCC.channel}, + {"midi_cc_control",N.midiCC.control}, + {"midi_cc_value",N.midiCC.value}, + {"midi_pc_channel",N.midiPC.channel}, + {"midi_pc_program",N.midiPC.program}, + {"midi_pc_value",N.midiPC.value}, + {"midi_pitch_bend_channel",N.midiPitchBend.channel}, + {"midi_pitch_bend_value",N.midiPitchBend.value}, + {"midi_aftertouch_channel",N.midiAftertouch.channel}, + {"midi_aftertouch_value",N.midiAftertouch.value}, + {"midi_polypressure_channel",N.midiPolyPressure.channel}, + {"midi_polypressure_value",N.midiPolyPressure.value}, + {"connections",conns}}; + } + + void from_json(const json& j, ModNode& N){ + std::cout << "configNode from_json" << std::endl; + + j.at("id").get_to(N.id); + j.at("position").get_to(N.position); + j.at("distributionMode").get_to(N.distributionMode); + j.at("nodeType").get_to(N.nodeType); + j.at("midi_note_channel").get_to(N.midiNote.channel); + j.at("midi_note_note").get_to(N.midiNote.note); + j.at("midi_note_velocity").get_to(N.midiNote.velocity); + j.at("midi_note_gate").get_to(N.midiNote.gate); + j.at("midi_cc_channel").get_to(N.midiCC.channel); + j.at("midi_cc_control").get_to(N.midiCC.control); + j.at("midi_cc_value").get_to(N.midiCC.value); + j.at("midi_pc_channel").get_to(N.midiPC.channel); + j.at("midi_pc_program").get_to(N.midiPC.program); + j.at("midi_pc_value").get_to(N.midiPC.value); + j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); + j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); + j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); + j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); + j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); + j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); + + //j.at("connections").get_to(N.connections); + } +} + diff --git a/src/modifier-node.h b/src/modifier-node.h index 236a251..2130a82 100644 --- a/src/modifier-node.h +++ b/src/modifier-node.h @@ -15,9 +15,7 @@ namespace VISEQ::GRAPH::NODES { ModNode() {std::cout << "ModNode Constructor std" << std::endl;} ModNode(Vec2Df position) : Node(position) {} - std::shared_ptr getPtr() { - return std::static_pointer_cast(shared_from_this()); - } + std::shared_ptr getPtr(); NodeType nodeType = NodeType::CONFIGNODE; @@ -31,285 +29,24 @@ namespace VISEQ::GRAPH::NODES { int mode = 0; float radius = 20.0f; - virtual NodeType getNodeType() const override {return nodeType;} + virtual NodeType getNodeType() const override; - virtual void trigger() override {} + virtual void trigger() override; - virtual void setMode(int _mode) override { - mode = _mode; - } + virtual void setMode(int _mode) override ; - MIDI::MidiNote getNotes() const { - MIDI::MidiNote cNote; - for(auto cn : getConnectedModNodes()){ - cNote += cn.lock()->getNotes(); - } - return midiNote + cNote; - } - - MIDI::MidiCC getCC() const { - MIDI::MidiCC cCC; - for(auto cn : getConnectedModNodes()){ - cCC += cn.lock()->getCC(); - } - return midiCC + cCC; - } - - MIDI::MidiPC getPC() const { - MIDI::MidiPC cPC; - for(auto cn : getConnectedModNodes()){ - cPC += cn.lock()->getPC(); - } - return midiPC + cPC; - } - - MIDI::MidiPitchBend getPitchBend() const { - MIDI::MidiPitchBend cPitchBend; - for(auto cn : getConnectedModNodes()){ - cPitchBend += cn.lock()->getPitchBend(); - } - return midiPitchBend + cPitchBend; - } - - MIDI::MidiAftertouch getAftertouch() const { - MIDI::MidiAftertouch cAftertouch; - for(auto cn : getConnectedModNodes()){ - cAftertouch += cn.lock()->getAftertouch(); - } - return midiAftertouch + cAftertouch; - } - - MIDI::MidiPolyPressure getPolyPressure() const { - MIDI::MidiPolyPressure cPolyPressure; - for(auto cn : getConnectedModNodes()){ - cPolyPressure += cn.lock()->getPolyPressure(); - } - return midiPolyPressure + cPolyPressure; - } - - std::vector> getConnectedModNodes() const { // TODO: check for cyclic connections of config nodes (crashes) - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - std::vector> getConnectedMidiNodes() const { - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::MIDINODE || c->end.lock()->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - void update() override { - Node::update(); - mode = state->node_mode; - } - - void interact(){ - float wheel = GetMouseWheelMove(); - - if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - switch(state->node_mode){ - case 0:{ - midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 1:{ - midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 2:{ - midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - }; - - if(midiNote.note < -127 ) midiNote.note = -127; - if(midiNote.note > 127 ) midiNote.note = 127; - - if(midiCC.value < -127 ) midiCC.value = -127; - if(midiCC.value > 127 ) midiCC.value = 127; - - if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - - } else if(KEYS_DOWN.alt) { - switch(state->node_mode){ - case 0:{ - midiNote.channel += wheel; - if(midiNote.channel < 0 ) midiNote.channel = 0; - if(midiNote.channel > 15) midiNote.channel = 15; - break;} - case 1:{ - midiCC.channel += wheel; - if(midiCC.channel < 0 ) midiCC.channel = 0; - if(midiCC.channel > 15 ) midiCC.channel = 15; - break;} - case 2:{ - midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; - if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; - if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; - break;} - }; - } - } - - void draw(Vec2Df mouse, Camera2D cam) override { - Node::draw(); - if(hovering() || selected) interact(); - - DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); - DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); - - Color gate_color = VS_COLOR_GREEN; - - switch(mode){ - case 0:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 1:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 2:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - } - if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); - if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); - if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); - - if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); - if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); - - if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); - - if(KEYS_DOWN.alt){ - float value = 0; - int offset_gate = 0; - switch(mode){ - case 0: - value = (float)getNotes().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 1: - value = (float)getCC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 2: - value = (float)getPC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 3: - value = (float)getPitchBend().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 4: - value = (float)getAftertouch().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 5: - value = (float)getPolyPressure().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - } - } else { - switch(mode){ - case 0:{ - int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); - DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 1:{ - int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); - DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 2:{ - int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); - DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 3:{ - int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); - DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 4:{ - int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); - DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 5:{ - int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); - DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - } - } - - if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); - //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); - } - - friend void to_json(json& j, const ModNode& N){ - std::vector conns; - std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); - - j = json{ - {"id", N.id}, - {"position", N.position}, - {"distributionMode", N.distributionMode}, - {"nodeType", N.nodeType}, - {"midi_note_channel",N.midiNote.channel}, - {"midi_note_note",N.midiNote.note}, - {"midi_note_velocity",N.midiNote.velocity}, - {"midi_note_gate",N.midiNote.gate}, - {"midi_cc_channel",N.midiCC.channel}, - {"midi_cc_control",N.midiCC.control}, - {"midi_cc_value",N.midiCC.value}, - {"midi_pc_channel",N.midiPC.channel}, - {"midi_pc_program",N.midiPC.program}, - {"midi_pc_value",N.midiPC.value}, - {"midi_pitch_bend_channel",N.midiPitchBend.channel}, - {"midi_pitch_bend_value",N.midiPitchBend.value}, - {"midi_aftertouch_channel",N.midiAftertouch.channel}, - {"midi_aftertouch_value",N.midiAftertouch.value}, - {"midi_polypressure_channel",N.midiPolyPressure.channel}, - {"midi_polypressure_value",N.midiPolyPressure.value}, - {"connections",conns}}; - } - - friend void from_json(const json& j, ModNode& N){ - std::cout << "configNode from_json" << std::endl; - - j.at("id").get_to(N.id); - j.at("position").get_to(N.position); - j.at("distributionMode").get_to(N.distributionMode); - j.at("nodeType").get_to(N.nodeType); - j.at("midi_note_channel").get_to(N.midiNote.channel); - j.at("midi_note_note").get_to(N.midiNote.note); - j.at("midi_note_velocity").get_to(N.midiNote.velocity); - j.at("midi_note_gate").get_to(N.midiNote.gate); - j.at("midi_cc_channel").get_to(N.midiCC.channel); - j.at("midi_cc_control").get_to(N.midiCC.control); - j.at("midi_cc_value").get_to(N.midiCC.value); - j.at("midi_pc_channel").get_to(N.midiPC.channel); - j.at("midi_pc_program").get_to(N.midiPC.program); - j.at("midi_pc_value").get_to(N.midiPC.value); - j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); - j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); - j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); - j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); - j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); - j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); - - //j.at("connections").get_to(N.connections); - } + MIDI::MidiNote getNotes() const; + MIDI::MidiCC getCC() const; + MIDI::MidiPC getPC() const; + MIDI::MidiPitchBend getPitchBend() const; + MIDI::MidiAftertouch getAftertouch() const; + MIDI::MidiPolyPressure getPolyPressure() const; + std::vector> getConnectedModNodes() const; + std::vector> getConnectedMidiNodes() const; + void update() override; + void interact(); + void draw(Vec2Df mouse, Camera2D cam) override; + friend void to_json(json& j, const ModNode& N); + friend void from_json(const json& j, ModNode& N); }; } diff --git a/src/mouse.h b/src/mouse.h deleted file mode 100644 index dcd2e99..0000000 --- a/src/mouse.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "draggable.h" -#include "graph-manager.h" -#include "interaction-event.h" -#include -namespace VISEQ::APP { - class Mouse { - public: - Mouse() {} - Mouse(GraphManager *gm) : gm(gm) {} - - GraphManager *gm; - - void update(){ - /*auto hovering_nodes = gm->nodes | RANGE_FILTER([](SPTR_Node n) { return n->hovering() || n->picked_up;}) | std::ranges::to>>();*/ - /**/ - /*if(!hovering_nodes.empty()){*/ - /* hovering_nodes.front()->is_hovering = true;*/ - /**/ - /* if(hovering_nodes.front()->pickedUp()) hovering_nodes.front()->drag();*/ - /* if(hovering_nodes.front()->released()) hovering_nodes.front()->putDown();*/ - /**/ - /*}*/ - - } - - void interact(InteractionEvent e){ - - } - - /*virtual void select() override {*/ - /* if(STATE == IState::DRAW_SELECTION) in_selection_rect = CheckCollisionPointRec(position, state->selection);*/ - /* if(MOUSE_LEFT.released) selected = (KEYS_DOWN.shift && selected) || in_selection_rect;*/ - /*}*/ - /**/ - /*virtual void update() override {*/ - /* if(STATE != IState::HOVER_NODE) select();*/ - /* if((pickedUp() || hovering()) && z_hovering == -1) z_hovering = z_index;*/ - /**/ - /* if(z_index == z_hovering || selected){*/ - /* if(pickedUp()){*/ - /* drag();*/ - /* STATE = IState::DRAG_NODE;*/ - /* }*/ - /* }*/ - /* if(STATE != IState::DRAG_NODE){*/ - /* if(entered()){*/ - /* STATE = IState::HOVER_NODE;*/ - /* std::cout << "z_index=" << z_index << " z_hovering=" << z_hovering << std::endl;*/ - /* is_hovering = true;*/ - /* }*/ - /* if(exited()){*/ - /* STATE = IState::HOVER_CANVAS;*/ - /* is_hovering = false;*/ - /* }*/ - /* }*/ - /* if(released() && picked_up){*/ - /* putDown();*/ - /* STATE = IState::HOVER_CANVAS;*/ - /* if(hovering()) STATE = IState::HOVER_NODE;*/ - /* }*/ - /*}*/ - }; -} diff --git a/src/node-handle.h b/src/node-handle.h index 69489cd..21b1d7f 100644 --- a/src/node-handle.h +++ b/src/node-handle.h @@ -17,8 +17,25 @@ namespace VISEQ::GRAPH::NODES{ class NodeHandle : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this>{ public: NodeHandle() {} - NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps) : assignedValueRef(_assignedValue), parentPosition(parentPos), col(_col), name(name), orbit(orbit), steps(steps) {init();} - NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps, float min, float max, float minDist, float maxDist) : assignedValueRef(_assignedValue), parentPosition(parentPos), col(_col), name(name), minValue(min), maxValue(max), minDistance(minDist), maxDistance(maxDist), orbit(orbit), steps(steps) {init();} + NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps) : + assignedValueRef(_assignedValue), + parentPosition(parentPos), + col(_col), + name(name), + orbit(orbit), + steps(steps) {init();} + + NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps, float min, float max, float minDist, float maxDist) : + assignedValueRef(_assignedValue), + parentPosition(parentPos), + col(_col), + name(name), + minValue(min), + maxValue(max), + minDistance(minDist), + maxDistance(maxDist), + orbit(orbit), + steps(steps) {init();} virtual GRAPH::GraphItemType getType() override { return GraphItemType::HANDLE; diff --git a/src/node.cpp b/src/node.cpp new file mode 100644 index 0000000..54b686a --- /dev/null +++ b/src/node.cpp @@ -0,0 +1,108 @@ +#include "node.h" + +#include +#include + +#include "connector.h" +#include "datatypes.h" +#include "state.h" +#include "vmath.h" + +#include "midi-node.h" +#include "config-node.h" + +namespace VISEQ::GRAPH::NODES { + void Node::setMode(int _mode) {mode = _mode;} + + std::shared_ptr Node::getPtr(){ + return shared_from_this(); + } + + bool Node::hovering() { + return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius); + } + + void Node::addConnector() { + connectors.emplace_back(std::make_shared(getPtr(), position, radius + 5.0f)); + } + + void Node::removeConnector(std::weak_ptr connector){ + std::erase(connectors, connector.lock()); + } + + NodeType Node::getNodeType() const {return nodeType;} + + void Node::deselect() { + selected = false; + } + + GraphItemType Node::getType() { + return GraphItemType::NODE; + } + + void Node::clearConnectors(){ + connectors.clear(); + update(); + } + + void Node::update() { + if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); + if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); + if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); + if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); + if(hovering() && MOUSE_RIGHT.clicked) { + selected = true; + config = true; + state->pushEvent({getPtr(), APP::Interaction::RIGHTCLICK}); + } + int free_connectors = 0; + for(auto c : connectors){ + if(c->partner.expired()) free_connectors++; + c->setPosition(position); + c->setOrbit(radius + 8.0f); + c->update(); + } + if(free_connectors == 0) addConnector(); + } + + void Node::draw(Vec2Df mp, Camera2D cam) {} + + void Node::draw() { + for(auto c : connectors){ + c->draw(); + } + } + + void to_json(json& j, const Node& N){ + if(N.getNodeType() == NodeType::MIDINODE){ + j = dynamic_cast(N); + } + if(N.getNodeType() == NodeType::CONFIGNODE){ + j = dynamic_cast(N); + } + } + + void from_json(const json& j, Node& N){ + + if(j.at("nodeType") == NodeType::MIDINODE){ + N = j.template get(); + } + if(j.at("nodeType") == NodeType::CONFIGNODE){ + N = j.template get(); + } + } + + void to_json(json& j, const SPTR_Node& N){ + j = *N; + } + + void from_json(const json& j, std::shared_ptr& N){ + std::cout << "SPTR_Node" << std::endl; + if(N->getNodeType() == NodeType::MIDINODE){ + N.reset(new MidiNode(j.get())); + } + if(N->getNodeType() == NodeType::CONFIGNODE){ + N.reset(new ConfigNode(j.get())); + } + } +} diff --git a/src/node.h b/src/node.h index 3bab18f..5693408 100644 --- a/src/node.h +++ b/src/node.h @@ -7,8 +7,6 @@ #include "datatypes.h" #include "draggable.h" #include "graph-item.h" -#include "interaction-event.h" -#include "state.h" #include "vmath.h" namespace VISEQ::GRAPH::EDGES{ @@ -20,8 +18,7 @@ namespace VISEQ::GRAPH::NODES { class Node : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this { public: - - Node() {std::cout << "Node constructor" << std::endl;} + Node() {} Node(float x, float y) : Draggable(Vec2Df(x, y)) {} Node(Vec2Df p) : Draggable(p) {} @@ -43,66 +40,23 @@ namespace VISEQ::GRAPH::NODES { int mode = 0; virtual void trigger() {} - virtual void setMode(int _mode) {mode = _mode;} + virtual void setMode(int _mode); + std::shared_ptr getPtr(); + virtual bool hovering() override; + void addConnector(); + void removeConnector(std::weak_ptr connector); + virtual NodeType getNodeType() const; + void deselect() override; + GraphItemType getType() override; + void clearConnectors(); + void update() override; + void draw(Vec2Df mp, Camera2D cam) override; + void draw() override; - std::shared_ptr getPtr(){ - return shared_from_this(); - } - - virtual bool hovering() override { - return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius); - } - - void addConnector() { - connectors.emplace_back(std::make_shared(getPtr(), position, radius + 5.0f)); - } - - void removeConnector(std::weak_ptr connector){ - std::erase(connectors, connector.lock()); - } - - virtual NodeType getNodeType() const {return nodeType;} - - void deselect() override { - selected = false; - } - - GraphItemType getType() override{ - return GraphItemType::NODE; - } - - void clearConnectors(){ - connectors.clear(); - update(); - } - - void update() override{ - if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); - if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); - if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); - if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); - if(hovering() && MOUSE_RIGHT.clicked) { - selected = true; - config = true; - state->pushEvent({getPtr(), APP::Interaction::RIGHTCLICK}); - } - int free_connectors = 0; - for(auto c : connectors){ - if(c->partner.expired()) free_connectors++; - c->setPosition(position); - c->setOrbit(radius + 8.0f); - c->update(); - } - if(free_connectors == 0) addConnector(); - } - - void draw(Vec2Df mp, Camera2D cam) override {} - - void draw() override{ - for(auto c : connectors){ - c->draw(); - } - } + friend void to_json(json& j, const Node& N); + friend void from_json(const json& j, Node& N); + friend void to_json(json& j, const std::shared_ptr& N); + friend void from_json(const json& j, std::shared_ptr& N); }; diff --git a/src/selectable.cpp b/src/selectable.cpp new file mode 100644 index 0000000..9ed3150 --- /dev/null +++ b/src/selectable.cpp @@ -0,0 +1,7 @@ +#include "selectable.h" + +namespace VISEQ::BASE { + bool Selectable::clicked() { + return (selected || hovering()) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + } +} diff --git a/src/selectable.h b/src/selectable.h index 391d344..9213a48 100644 --- a/src/selectable.h +++ b/src/selectable.h @@ -16,9 +16,7 @@ namespace VISEQ::BASE { bool erase = false; Vec2Df selection_offset; - virtual bool clicked() override { - return (selected || hovering()) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - } + virtual bool clicked() override; virtual bool select() = 0; virtual void deselect() = 0; diff --git a/src/signal-manager.cpp b/src/signal-manager.cpp new file mode 100644 index 0000000..ff5c9a3 --- /dev/null +++ b/src/signal-manager.cpp @@ -0,0 +1,143 @@ +#include "signal-manager.h" + +#include "datatypes.h" +#include "midi-node.h" +#include "node.h" +#include "signal.h" +#include "midi.h" + +#include +#include +#include +#include + +namespace VISEQ::SIGNAL { + + void SignalManager::pause(){ + play = false; + } + + void SignalManager::stop(){ + play = false; + reset(); + } + + void SignalManager::start(){ + play = true; + } + + void SignalManager::reset(){ + for(auto &s : signals){ + s->position = s->start.lock()->position + Vec2Df{0.1, 0.1}; + s->next = s->start; + s->last = s->start; + } + gm->reset(); + } + + void SignalManager::startSignalThread(){ + std::thread t1(&SignalManager::updateSignals, this); + t1.detach(); + } + + void SignalManager::stopSignalThread(){ + loop = false; + } + + void SignalManager::addSignalAtNode(WPTR_Node node){ + Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200}; + signals.emplace_back(std::make_shared(node.lock()->position + Vec2Df{0.5f, 0.5f}, 8.0f, 480, c)); // position + 1 or else it does not render. why? no idea! + signals.back()->next = node; + signals.back()->last = node; + signals.back()->start = node; + } + + void SignalManager::rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){ + auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());}); + + for(auto s : reroute_signals){ + float dist = 10000000; + SPTR_Node next_node; + for(auto n : adjacent_nodes){ + if(n.lock()->position.dist(s->position) < dist) { + dist = n.lock()->position.dist(s->position); + next_node = n.lock(); + } + } + s->next = next_node; + s->last = next_node; + } + + } + + void SignalManager::rebaseSignal(VEC_SPTR_Node nodes){ + auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());}); + + for(auto s : rebase_signals){ + s->start = s->next; + } + } + + void SignalManager::removeSignal(std::shared_ptr s){ + erase_signals.push_back(s); + } + + void SignalManager::updateSignals(){ + auto last = std::chrono::high_resolution_clock::now(); + while(loop){ + std::chrono::duration diff = std::chrono::high_resolution_clock::now() - last; + if((int)diff.count() >= 250){ + mutex->lock(); + if(play){ + for(auto& s : signals){ + s->direction = (s->next.lock()->position - s->position).norm(); + s->update(diff.count()); + if(s->position.dist(s->next.lock()->position) < 0.6f) { + WPTR_Node next_node = gm->next(s->next, s->last); + s->next.lock()->trigger(); + + if(next_node.lock() != s->next.lock() && !next_node.expired() && s->next.lock()->getNodeType() == NodeType::MIDINODE) { + auto midinode = std::dynamic_pointer_cast(s->next.lock()); + for(auto &m : midinode->midi_outs){ + if(m.second.enabled) { + if(midinode->send_note) midi.note(m.second.i, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); + if(midinode->send_cc) midi.cc(m.second.i, midinode->getCC().channel, midinode->getCC().control, (int)Clamp(midinode->getCC().value + s->midi_cc_offset, 0, 127)); + if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); + } + } + if(midinode->send_note) midi.note(99, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); + s->last = s->next; + s->next = next_node; + } + } + } + } + midi.update(); + mutex->unlock(); + last = std::chrono::high_resolution_clock::now(); + } + } + } + + void SignalManager::drawSignals(Vec2Df mouse_pos, Camera2D camera){ + mutex->lock(); + if(!erase_signals.empty()){ + for(auto s: erase_signals){ + std::erase(signals, s); + } + erase_signals.clear(); + } + for(auto s : signals){ + s->draw(mouse_pos, camera); + } + mutex->unlock(); + } + + void to_json(json& j, const std::shared_ptr& G){ + j = *G; + } + + void from_json(const json& j, std::shared_ptr& G){ + G.reset(new SignalManager(j.get())); + } +} diff --git a/src/signal-manager.h b/src/signal-manager.h index 92aa129..91f85c0 100644 --- a/src/signal-manager.h +++ b/src/signal-manager.h @@ -1,15 +1,12 @@ #pragma once -#include "datatypes.h" #include "graph-manager.h" -#include "midi-node.h" #include "node.h" #include "signal.h" #include "midi.h" -#include #include -#include +#include using namespace VISEQ::GRAPH; @@ -40,126 +37,23 @@ namespace VISEQ::SIGNAL { bool loop = true; bool play = false; - void pause(){ - play = false; - } - - void stop(){ - play = false; - reset(); - } - - void start(){ - play = true; - } - - void reset(){ - for(auto &s : signals){ - s->position = s->start.lock()->position + Vec2Df{0.1, 0.1}; - s->next = s->start; - s->last = s->start; - } - gm->reset(); - } - - void startSignalThread(){ - std::thread t1(&SignalManager::updateSignals, this); - t1.detach(); - } - - void stopSignalThread(){ - loop = false; - } - - void addSignalAtNode(WPTR_Node node){ - Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200}; - signals.emplace_back(std::make_shared(node.lock()->position + Vec2Df{0.5f, 0.5f}, 8.0f, 480, c)); // position + 1 or else it does not render. why? no idea! - signals.back()->next = node; - signals.back()->last = node; - signals.back()->start = node; - } - - void rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){ - auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());}); - - for(auto s : reroute_signals){ - float dist = 10000000; - SPTR_Node next_node; - for(auto n : adjacent_nodes){ - if(n.lock()->position.dist(s->position) < dist) { - dist = n.lock()->position.dist(s->position); - next_node = n.lock(); - } - } - s->next = next_node; - s->last = next_node; - } - - } - - void rebaseSignal(VEC_SPTR_Node nodes){ - auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());}); - - for(auto s : rebase_signals){ - s->start = s->next; - } - } - - void removeSignal(std::shared_ptr s){ - erase_signals.push_back(s); - } - - void updateSignals(){ - auto last = std::chrono::high_resolution_clock::now(); - while(loop){ - std::chrono::duration diff = std::chrono::high_resolution_clock::now() - last; - if((int)diff.count() >= 250){ - mutex->lock(); - if(play){ - for(auto& s : signals){ - s->direction = (s->next.lock()->position - s->position).norm(); - s->update(diff.count()); - if(s->position.dist(s->next.lock()->position) < 0.6f) { - WPTR_Node next_node = gm->next(s->next, s->last); - s->next.lock()->trigger(); - - if(next_node.lock() != s->next.lock() && !next_node.expired() && s->next.lock()->getNodeType() == NodeType::MIDINODE) { - auto midinode = std::dynamic_pointer_cast(s->next.lock()); - for(auto &m : midinode->midi_outs){ - if(m.second.enabled) { - if(midinode->send_note) midi.note(m.second.i, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); - if(midinode->send_cc) midi.cc(m.second.i, midinode->getCC().channel, midinode->getCC().control, (int)Clamp(midinode->getCC().value + s->midi_cc_offset, 0, 127)); - if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); - } - } - s->last = s->next; - s->next = next_node; - } - } - } - } - midi.update(); - mutex->unlock(); - last = std::chrono::high_resolution_clock::now(); - } - } - } - - void drawSignals(Vec2Df mouse_pos, Camera2D camera){ - mutex->lock(); - if(!erase_signals.empty()){ - for(auto s: erase_signals){ - std::erase(signals, s); - } - erase_signals.clear(); - } - for(auto s : signals){ - s->draw(mouse_pos, camera); - } - mutex->unlock(); - } + void pause(); + void stop(); + void start(); + void reset(); + void startSignalThread(); + void stopSignalThread(); + void addSignalAtNode(WPTR_Node node); + void rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes); + void rebaseSignal(VEC_SPTR_Node nodes); + void removeSignal(std::shared_ptr s); + void updateSignals(); + void drawSignals(Vec2Df mouse_pos, Camera2D camera); NLOHMANN_DEFINE_TYPE_INTRUSIVE(SignalManager, signals) - + + friend void from_json(const json& j, std::shared_ptr& G); + friend void to_json(json& j, const std::shared_ptr& G); + }; } diff --git a/src/signal.cpp b/src/signal.cpp new file mode 100644 index 0000000..6058eb5 --- /dev/null +++ b/src/signal.cpp @@ -0,0 +1,55 @@ +#include "signal.h" + +#include +#include "node.h" + +namespace VISEQ::SIGNAL { + void Signal::reset(){ + toStart(); + } + + void Signal::toStart(){ + position = start.lock()->position; + } + + void Signal::update(double t_delta){ + // 120px / 16th = 1920px / whole note + // whole note = 2s at 120bpm + // 960px / s + if(run) position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f; + } + + void Signal::draw(Vec2Df mouse, Camera2D cam) { + DrawCircle(position.x, position.y, radius, color); + } + + void to_json(json& j, const Signal& S){ + std::map fc = {{"r",S.color.r},{"g",S.color.g},{"b",S.color.b},{"a",S.color.a}}; + j = json{{"color",fc},{"position", S.position},{"radius",S.radius},{"start",S.start.lock()->id},{"next",S.next.lock()->id},{"last",S.last.lock()->id},{"speed",S.speed},{"midi_note_offset",S.midi_note_offset},{"midi_cc_offset",S.midi_cc_offset},{"multiplier",S.multiplier}}; + } + + void from_json(const json& j, Signal& S){ + + Color c; + c.r = j.at("color")["r"].get(); + c.g = j.at("color")["g"].get(); + c.b = j.at("color")["b"].get(); + c.a = j.at("color")["a"].get(); + + j.at("position").get_to(S.position); + j.at("speed").get_to(S.speed); + j.at("radius").get_to(S.radius); + j.at("midi_note_offset").get_to(S.midi_note_offset); + j.at("midi_cc_offset").get_to(S.midi_cc_offset); + + S.color = c; + } + + void to_json(json& j, const std::shared_ptr& S){ + j = *S; + } + + void from_json(const json& j, std::shared_ptr& S){ + S.reset(new Signal(j.get())); + } +} diff --git a/src/signal.h b/src/signal.h index 35ed1f6..587faf5 100644 --- a/src/signal.h +++ b/src/signal.h @@ -35,24 +35,16 @@ namespace VISEQ::SIGNAL { int midi_note_offset = 0; int midi_cc_offset = 0; - void reset(){ - toStart(); - } + void reset(); + void toStart(); + void update(double t_delta); + void draw(Vec2Df mouse, Camera2D cam); - void toStart(){ - position = start.lock()->position; - } + friend void to_json(json& j, const Signal& S); + friend void from_json(const json& j, Signal& S); + friend void to_json(json& j, const std::shared_ptr& S); + friend void from_json(const json& j, std::shared_ptr& S); - void update(double t_delta){ - // 120px / 16th = 1920px / whole note - // whole note = 2s at 120bpm - // 960px / s - if(run) position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f; - } - - void draw(Vec2Df mouse, Camera2D cam) { - DrawCircle(position.x, position.y, radius, color); - } }; } diff --git a/src/state-io.h b/src/state-io.h index 3eb260d..1bc3b39 100644 --- a/src/state-io.h +++ b/src/state-io.h @@ -3,7 +3,6 @@ #include "config-node.h" #include "midi-node.h" #include "graph-item.h" -#include "node-handle.h" #include "node.h" #include "edge.h" @@ -16,139 +15,26 @@ typedef std::shared_ptr SPTR_GraphItem; typedef std::vector VEC_SPTR_GraphItem; -namespace nlohmann { - template - struct adl_serializer> { - static void to_json(json& j, const std::shared_ptr& opt) { - if (opt) { - j = *opt; - } else { - j = nullptr; - } - } - - static void from_json(const json& j, std::shared_ptr& opt) { - if (j.is_null()) { - opt = nullptr; - } else { - opt.reset(new T(j.get())); - } - } - }; - - /*template */ - /*struct adl_serializer> {*/ - /* template */ - /* static void to_json(J& j, const std::shared_ptr& opt) {*/ - /* if (opt) {*/ - /* j = *opt;*/ - /* } else {*/ - /* j = nullptr;*/ - /* }*/ - /* }*/ - /**/ - /* template */ - /* static void from_json(const J& j, std::shared_ptr& opt) {*/ - /* if (j.is_null()) {*/ - /* opt = nullptr;*/ - /* } else {*/ - /* opt.reset(new T(j.template get()));*/ - /* }*/ - /* }*/ - /*};*/ -} - -namespace VISEQ::GRAPH::EDGES { - using namespace VISEQ::GRAPH::NODES; - - inline void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){ - j = json{{"id",E.id}, {"start", E.start.lock()->id}, {"end",E.end.lock()->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}}; - } - - inline void from_json(const json& j, Edge& E){ - j.at("id").get_to(E.id); - j.at("pass_forward").get_to(E.pass_forward); - j.at("pass_backward").get_to(E.pass_backward); - } - - inline void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){ - j = *E; - } - - inline void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){ - E.reset(new Edge(j.get())); - } -} - -namespace VISEQ::GRAPH::NODES { - - /* ------------------- MIDINODE ---------------------- */ - - /*inline void to_json(json& j, const std::shared_ptr& N){*/ - /* j = *N;*/ - /*}*/ - /**/ - /*inline void from_json(const json& j, std::shared_ptr& N){*/ - /* N.reset(new MidiNode(j.get()));*/ - /*}*/ - - inline void to_json(json& j, const MidiNode::NodeMidiOut& MO){ - j = json{{"port_id", MO.i}, {"enabled", MO.enabled}, {"name", MO.name}}; - } - - /*template< typename T>*/ - /*inline void to_json(json& j, const std::shared_ptr>& NH){*/ - /* j = json{{"position", NH->position}};*/ - /*}*/ - /* ----------------- CONFIG NODE -----------------*/ - - - /*inline void to_json(json& j, const std::shared_ptr& N){*/ - /* j = *N;*/ - /*}*/ - /**/ - /*inline void from_json(const json& j, std::shared_ptr& N){*/ - /* N.reset(new ConfigNode(j.get()));*/ - /*}*/ - - /* ------------------- NODE ---------------------- */ - - - inline void to_json(json& j, const Node& N){ - if(N.getNodeType() == NodeType::MIDINODE){ - j = dynamic_cast(N); - } - if(N.getNodeType() == NodeType::CONFIGNODE){ - j = dynamic_cast(N); - } - } - - inline void from_json(const json& j, Node& N){ - - if(j.at("nodeType") == NodeType::MIDINODE){ - N = j.template get(); - } - if(j.at("nodeType") == NodeType::CONFIGNODE){ - N = j.template get(); - } - } - - inline void to_json(json& j, const SPTR_Node& N){ - j = *N; - } - - inline void from_json(const json& j, std::shared_ptr& N){ - std::cout << "SPTR_Node" << std::endl; - if(N->getNodeType() == NodeType::MIDINODE){ - N.reset(new MidiNode(j.get())); - } - if(N->getNodeType() == NodeType::CONFIGNODE){ - N.reset(new ConfigNode(j.get())); - } - } - -} - +//namespace nlohmann { +// template +// struct adl_serializer> { +// static void to_json(json& j, const std::shared_ptr& opt) { +// if (opt) { +// j = *opt; +// } else { +// j = nullptr; +// } +// } +// +// static void from_json(const json& j, std::shared_ptr& opt) { +// if (j.is_null()) { +// opt = nullptr; +// } else { +// opt.reset(new T(j.get())); +// } +// } +// }; +//} namespace VISEQ::GRAPH { inline void to_json(json& j, const GraphItem& G){ @@ -182,35 +68,35 @@ namespace VISEQ::BASE{ } namespace VISEQ::SIGNAL{ - inline void to_json(json& j, const Signal& G){ - std::map fc = {{"r",G.color.r},{"g",G.color.g},{"b",G.color.b},{"a",G.color.a}}; - j = json{{"color",fc},{"position", G.position},{"radius",G.radius},{"start",G.start.lock()->id},{"next",G.next.lock()->id},{"last",G.last.lock()->id},{"speed",G.speed},{"midi_note_offset",G.midi_note_offset},{"midi_cc_offset",G.midi_cc_offset},{"multiplier",G.multiplier}}; - } - - inline void from_json(const json& j, Signal& S){ - - Color c; - c.r = j.at("color")["r"].get(); - c.g = j.at("color")["g"].get(); - c.b = j.at("color")["b"].get(); - c.a = j.at("color")["a"].get(); - - j.at("position").get_to(S.position); - j.at("speed").get_to(S.speed); - j.at("radius").get_to(S.radius); - j.at("midi_note_offset").get_to(S.midi_note_offset); - j.at("midi_cc_offset").get_to(S.midi_cc_offset); - - S.color = c; - } - - inline void to_json(json& j, const std::shared_ptr& G){ - j = *G; - } - - inline void from_json(const json& j, std::shared_ptr& S){ - S.reset(new Signal(j.get())); - } +// inline void to_json(json& j, const Signal& G){ +// std::map fc = {{"r",G.color.r},{"g",G.color.g},{"b",G.color.b},{"a",G.color.a}}; +// j = json{{"color",fc},{"position", G.position},{"radius",G.radius},{"start",G.start.lock()->id},{"next",G.next.lock()->id},{"last",G.last.lock()->id},{"speed",G.speed},{"midi_note_offset",G.midi_note_offset},{"midi_cc_offset",G.midi_cc_offset},{"multiplier",G.multiplier}}; +// } +// +// inline void from_json(const json& j, Signal& S){ +// +// Color c; +// c.r = j.at("color")["r"].get(); +// c.g = j.at("color")["g"].get(); +// c.b = j.at("color")["b"].get(); +// c.a = j.at("color")["a"].get(); +// +// j.at("position").get_to(S.position); +// j.at("speed").get_to(S.speed); +// j.at("radius").get_to(S.radius); +// j.at("midi_note_offset").get_to(S.midi_note_offset); +// j.at("midi_cc_offset").get_to(S.midi_cc_offset); +// +// S.color = c; +// } +// +// inline void to_json(json& j, const std::shared_ptr& G){ +// j = *G; +// } +// +// inline void from_json(const json& j, std::shared_ptr& S){ +// S.reset(new Signal(j.get())); +// } } diff --git a/src/state.cpp b/src/state.cpp new file mode 100644 index 0000000..4dd3f83 --- /dev/null +++ b/src/state.cpp @@ -0,0 +1,76 @@ +#include "state.h" + +#include "interaction-event.h" +#include "raylib.h" +#include "vmath.h" +#include + +namespace VISEQ::APP{ + + void State::pushEvent(InteractionEvent e){ + event_queue.push_back(e); + } + + InteractionEvent State::pullEvent(){ + if(!event_queue.empty()){ + InteractionEvent e = event_queue.front(); + event_queue.pop_front(); + return e; + } + return {nullptr, Interaction::NONE}; + } + + void State::update(Camera2D camera){ + + settings.width = GetScreenWidth(); + settings.height = GetScreenHeight(); + + if(istate != old_istate) std::cout << state_string[istate] << std::endl; + + mouse_delta = {GetMouseDelta().x, GetMouseDelta().y}; + + settings.speed = (settings.grid * 16.0f) * (settings.bpm / 120.f); + + Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera); + Vec2Df mouse_pos = {(float)mp.x, (float)mp.y}; + + mouse_left.down = IsMouseButtonDown(MOUSE_BUTTON_LEFT); + mouse_left.clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + mouse_left.released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT); + + mouse_right.down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); + mouse_right.clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); + mouse_right.released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT); + + keys_down.ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); + keys_down.shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); + keys_down.alt = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); + + keys_pressed.ctrl = IsKeyPressed(KEY_LEFT_CONTROL) || IsKeyPressed(KEY_RIGHT_CONTROL); + keys_pressed.shift = IsKeyPressed(KEY_LEFT_SHIFT) || IsKeyPressed(KEY_RIGHT_SHIFT); + keys_pressed.alt = IsKeyPressed(KEY_LEFT_ALT) || IsKeyPressed(KEY_RIGHT_ALT); + + keys_released.ctrl = IsKeyReleased(KEY_LEFT_CONTROL) || IsKeyReleased(KEY_RIGHT_CONTROL); + keys_released.shift = IsKeyReleased(KEY_LEFT_SHIFT) || IsKeyReleased(KEY_RIGHT_SHIFT); + keys_released.alt = IsKeyReleased(KEY_LEFT_ALT) || IsKeyReleased(KEY_RIGHT_ALT); + + screen_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; + raw_mouse = {GetMousePosition().x, GetMousePosition().y}; + mod_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; + + mode_color = settings.mode_colors[node_mode]; + + if(keys_down.ctrl) { + mod_mouse.x = round(mouse_pos.x / settings.grid) * settings.grid; + mod_mouse.y = round(mouse_pos.y / settings.grid) * settings.grid; + } + old_istate = istate; + } + + std::shared_ptr State::get() { + if(!State::instance){ + State::instance.reset(new State()); + } + return State::instance; + } +} diff --git a/src/state.h b/src/state.h index 5f8944a..49b71b2 100644 --- a/src/state.h +++ b/src/state.h @@ -5,7 +5,6 @@ #include "raylib.h" #include "vmath.h" #include -#include #define STATE state->istate #define SETTINGS state->settings @@ -88,70 +87,14 @@ namespace VISEQ::APP{ Color mode_color = settings.mode_colors[0]; - void pushEvent(InteractionEvent e){ - event_queue.push_back(e); - } - - InteractionEvent pullEvent(){ - if(!event_queue.empty()){ - InteractionEvent e = event_queue.front(); - event_queue.pop_front(); - return e; - } - return {nullptr, Interaction::NONE}; - } - - void update(Camera2D camera){ - - if(istate != old_istate) std::cout << state_string[istate] << std::endl; - - mouse_delta = {GetMouseDelta().x, GetMouseDelta().y}; - - settings.speed = (settings.grid * 16.0f) * (settings.bpm / 120.f); - - Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera); - Vec2Df mouse_pos = {(float)mp.x, (float)mp.y}; - - mouse_left.down = IsMouseButtonDown(MOUSE_BUTTON_LEFT); - mouse_left.clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - mouse_left.released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT); - - mouse_right.down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); - mouse_right.clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); - mouse_right.released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT); - - keys_down.ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); - keys_down.shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); - keys_down.alt = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); - - keys_pressed.ctrl = IsKeyPressed(KEY_LEFT_CONTROL) || IsKeyPressed(KEY_RIGHT_CONTROL); - keys_pressed.shift = IsKeyPressed(KEY_LEFT_SHIFT) || IsKeyPressed(KEY_RIGHT_SHIFT); - keys_pressed.alt = IsKeyPressed(KEY_LEFT_ALT) || IsKeyPressed(KEY_RIGHT_ALT); - - keys_released.ctrl = IsKeyReleased(KEY_LEFT_CONTROL) || IsKeyReleased(KEY_RIGHT_CONTROL); - keys_released.shift = IsKeyReleased(KEY_LEFT_SHIFT) || IsKeyReleased(KEY_RIGHT_SHIFT); - keys_released.alt = IsKeyReleased(KEY_LEFT_ALT) || IsKeyReleased(KEY_RIGHT_ALT); - - screen_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; - raw_mouse = {GetMousePosition().x, GetMousePosition().y}; - mod_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; - - mode_color = settings.mode_colors[node_mode]; - - if(keys_down.ctrl) { - mod_mouse.x = round(mouse_pos.x / settings.grid) * settings.grid; - mod_mouse.y = round(mouse_pos.y / settings.grid) * settings.grid; - } - old_istate = istate; - } + void pushEvent(InteractionEvent e); + InteractionEvent pullEvent(); + void update(Camera2D camera); inline static std::shared_ptr instance; - static std::shared_ptr get() { - if(!instance){ - instance.reset(new State()); - } - return instance; - } + static std::shared_ptr get(); + + std::deque event_queue; }; } diff --git a/src/ui.cpp b/src/ui.cpp new file mode 100644 index 0000000..5ce199d --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,195 @@ +#include "ui.h" + +#include "state.h" +#include "midi-node.h" +#include "raylib.h" +#include "raygui/raygui.h" + +#include "datatypes.h" + +#include +#include + +namespace VISEQ::APP::UI{ + void AppUI::init(){ + GuiLoadStyleDark(); + } + + void AppUI::drawSideBar(){ + + GuiPanel((Rectangle){-1,-30,280,(float)state->settings.height + 31}, ""); + const int s_controls = 25; + //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); + if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); + if(GuiButton((Rectangle){120, (float)s_controls, 60, 30}, "STOP")) sm->stop(); + if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset(); + + const int s_bpm = s_controls + 45; + GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); + GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",state->settings.bpm).c_str()); + int _bpm = state->settings.bpm; +// GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); + if(GuiSpinner((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", &_bpm, 1, 300, bpm_spinner_edit_mode)) bpm_spinner_edit_mode = !bpm_spinner_edit_mode; + state->settings.bpm = _bpm; + + int s_midi = s_bpm + 75; + GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); + if(GuiButton((Rectangle){200, (float)s_midi, 60, 20}, "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.substr(0,32).c_str(), &mo.enabled); + i++; + } + + if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { + for(auto &midis : sm->midi.midi_out_ports){ + if(midis.enabled){ + gm->setMidiPortForAll(midis.number, true); + } + } + } + + int s_signal = s_midi + 100 + i*20; + GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS"); + if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){ + std::shared_ptr ni = gm->getRandomNode(); + sm->addSignalAtNode(ni); + } + + int signal_configs = 0; + for(auto &s : sm->signals){ + if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(s); + DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s->color); + + int s_speed = round(s->speed); + s->multiplier = state->settings.bpm / 120.0f; + float signal_bpm = round(state->settings.bpm * s->multiplier * (s->speed / state->settings.speed)); + if(GuiSpinner((Rectangle){70, (float)s_signal+70 + signal_configs*20, 100, 20}, "", &s_speed, 1, 16 * state->settings.grid * 4, s->ui_edit_speed)) s->ui_edit_speed = !s->ui_edit_speed; + if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20}, "1/2")) s_speed = state->settings.speed / 2; + //if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20},"1/4")) s_speed = state->settings.speed / 4; + GuiLabel((Rectangle){195, (float)s_signal+70 + signal_configs*20, 30, 20}, std::format("{}",signal_bpm).c_str()); + GuiToggle((Rectangle){240, (float)s_signal+70 + signal_configs*20, 20, 20},"P", &s->run); + s->speed = s_speed; + signal_configs++; + } + + int s_nodes = s_signal + 70 + signal_configs*20; + GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES"); + if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){ + gm->createMidiNode(Vec2Df{200.0f, 200.0f}); + } + DrawText(std::format("{}", gm->getNodesSize()).c_str(), 20, state->settings.height - 70, 15, VS_COLOR_WHITE); + DrawFPS(20, state->settings.height - 30); + + std::string shorts[6] = {"NT","CC","PC","PB","AT","PP"}; + for(int i = 0; i < 6; ++i){ + Color col = state->settings.mode_colors[i]; + Vec2Df pos = {state->settings.width - (6.0f*75.0f) + i*75.0f, state->settings.height - 75.0f}; + DrawRing(pos, 30.0f, 34.0f, 0.0f, 360.0f, 24, state->node_mode == i ? VS_COLOR_WHITE : (Color){0,0,0,0}); + DrawCircleV(pos, 30.0f, col); + DrawCenteredText(shorts[i], pos, 20, WHITE); + } + } + + void AppUI::DrawCenteredText(std::string text, Vec2Df position, int size, Color color){ + float offset = MeasureText(text.c_str(), size); + DrawText(text.c_str(), position.x-offset/2.0f, position.y-(float)size/2.0f+1, size, color ); + } + + void AppUI::drawContextMenu(Camera2D camera){ + for(auto n : gm->getSelection() | std::views::filter([](std::shared_ptr n){ return n->config; })){ + Vector2 np = GetWorldToScreen2D((Vector2){n->position.x, n->position.y}, camera); + + state->context_menu = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config"); + n->config = state->context_menu; + + int option = n->distributionMode; + GuiToggleGroup((Rectangle){ np.x + 20, np.y + 40, 73, 20 }, "GEO;RR;RAND", &option); + n->distributionMode = (Distribution)option; + + GuiLabel((Rectangle){np.x + 20, np.y + 65, 220, 20}, "SIGNALS"); + if(GuiButton((Rectangle){np.x + 20, np.y + 90, 220, 20}, "ADD SIGNAL")){ + sm->addSignalAtNode(n); + } + + int y_midi = 120; + GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT"); + + int i = 0; + if(n->getNodeType() == NodeType::MIDINODE){ + std::shared_ptr mn = std::dynamic_pointer_cast(n); + for(auto& mo : mn->midi_outs){ + GuiCheckBox((Rectangle){np.x + 20, np.y + y_midi + 20 + i*20.0f, 20, 20}, mo.second.name.c_str(), &mo.second.enabled); + i++; + } + + int y_channel = y_midi + 30 + i*20; + GuiCheckBox((Rectangle){np.x + 20, np.y + y_channel, 20, 20}, "NOTE", &mn->send_note); + GuiCheckBox((Rectangle){np.x + 90, np.y + y_channel, 20, 20}, "CC", &mn->send_cc); + GuiCheckBox((Rectangle){np.x + 160, np.y + y_channel, 20, 20}, "PITCH", &mn->send_pitch_bend); + + int y_cc_note = y_channel + 120; + switch(state->node_mode){ + case 0:{ + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "NOTE CHANNEL"); + GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiNote.channel); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "NOTE"); + float v = mn->midiNote.note; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note , 20, 20}, MIDI::Notes[(int)round(v)].c_str()); + GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiNote.note).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); + mn->midiNote.note = round(v); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "VELOCITY"); + v = mn->midiNote.velocity; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiNote.velocity).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); + mn->midiNote.velocity = round(v); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 120, 100, 20}, "GATE"); + v = mn->midiNote.gate; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 120, 20, 20}, std::format("{}",(float)mn->midiNote.gate/1000.0f).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 150, 220, 20}, "", "", &v, 0, 30000); + mn->midiNote.gate= round(v); + break;} + case 1: + { + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "CC CHANNEL"); + GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiCC.channel); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); + float v = mn->midiCC.control; + GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiCC.control).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); + mn->midiCC.control = round(v); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "CC VALUE"); + v = mn->midiCC.value; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiCC.value).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); + mn->midiCC.value = round(v); + + break;} + case 2: + { + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "PITCH BEND CHANNEL"); + GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiPitchBend.channel); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); + float v = mn->midiPitchBend.value; + GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiPitchBend.value).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 16383); + mn->midiPitchBend.value = round(v); + break;} + } + } + } + } + + void AppUI::draw(Vec2Df mouse_pos, Camera2D camera){ + drawSideBar(); + if(state->context_menu) drawContextMenu(camera); + } +} diff --git a/src/ui.h b/src/ui.h index 7bdc049..06d443d 100644 --- a/src/ui.h +++ b/src/ui.h @@ -1,17 +1,14 @@ #pragma once #include "state.h" -#include "midi-node.h" #include "raylib.h" #include "raygui/raygui.h" #include "raygui/dark/style_dark.h" -#include "datatypes.h" #include "graph-manager.h" #include "signal-manager.h" #include -#include namespace VISEQ::APP::UI{ class AppUI{ @@ -26,187 +23,10 @@ namespace VISEQ::APP::UI{ bool bpm_spinner_edit_mode = false; - void init(){ - GuiLoadStyleDark(); - } - - void drawSideBar(){ - - GuiPanel((Rectangle){-1,-30,380,(float)state->settings.height + 31}, ""); - const int s_controls = 25; - //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); - if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); - if(GuiButton((Rectangle){120, (float)s_controls, 60, 30}, "STOP")) sm->stop(); - if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset(); - - const int s_bpm = s_controls + 45; - GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); - GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",state->settings.bpm).c_str()); - int _bpm = state->settings.bpm; -// GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); - if(GuiSpinner((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", &_bpm, 1, 300, bpm_spinner_edit_mode)) bpm_spinner_edit_mode = !bpm_spinner_edit_mode; - state->settings.bpm = _bpm; - - int s_midi = s_bpm + 75; - GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); - if(GuiButton((Rectangle){200, (float)s_midi, 60, 20}, "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.substr(0,32).c_str(), &mo.enabled); - i++; - } - - if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { - for(auto &midis : sm->midi.midi_out_ports){ - if(midis.enabled){ - gm->setMidiPortForAll(midis.number, true); - } - } - } - - int s_signal = s_midi + 100 + i*20; - GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS"); - if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){ - std::shared_ptr ni = gm->getRandomNode(); - sm->addSignalAtNode(ni); - } - - int signal_configs = 0; - for(auto &s : sm->signals){ - if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(s); - DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s->color); - - int s_speed = round(s->speed); - s->multiplier = state->settings.bpm / 120.0f; - float signal_bpm = round(state->settings.bpm * s->multiplier * (s->speed / state->settings.speed)); - if(GuiSpinner((Rectangle){70, (float)s_signal+70 + signal_configs*20, 100, 20}, "", &s_speed, 1, 16 * state->settings.grid * 4, s->ui_edit_speed)) s->ui_edit_speed = !s->ui_edit_speed; - if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20}, "1/2")) s_speed = state->settings.speed / 2; - //if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20},"1/4")) s_speed = state->settings.speed / 4; - GuiLabel((Rectangle){195, (float)s_signal+70 + signal_configs*20, 30, 20}, std::format("{}",signal_bpm).c_str()); - GuiToggle((Rectangle){240, (float)s_signal+70 + signal_configs*20, 20, 20},"P", &s->run); - s->speed = s_speed; - signal_configs++; - } - - int s_nodes = s_signal + 70 + signal_configs*20; - GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES"); - if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){ - gm->createMidiNode(Vec2Df{200.0f, 200.0f}); - } - DrawText(std::format("{}", gm->getNodesSize()).c_str(), 20, state->settings.height - 70, 15, VS_COLOR_WHITE); - DrawFPS(20, state->settings.height - 30); - - std::string shorts[6] = {"NT","CC","PC","PB","AT","PP"}; - for(int i = 0; i < 6; ++i){ - Color col = state->settings.mode_colors[i]; - Vec2Df pos = {state->settings.width - (6.0f*75.0f) + i*75.0f, state->settings.height - 75.0f}; - DrawRing(pos, 30.0f, 34.0f, 0.0f, 360.0f, 24, state->node_mode == i ? VS_COLOR_WHITE : (Color){0,0,0,0}); - DrawCircleV(pos, 30.0f, col); - DrawCenteredText(shorts[i], pos, 20, WHITE); - } - } - - void DrawCenteredText(std::string text, Vec2Df position, int size, Color color){ - float offset = MeasureText(text.c_str(), size); - DrawText(text.c_str(), position.x-offset/2.0f, position.y-(float)size/2.0f+1, size, color ); - } - - void drawContextMenu(Camera2D camera){ - for(auto n : gm->getSelection() | std::views::filter([](std::shared_ptr n){ return n->config; })){ - Vector2 np = GetWorldToScreen2D((Vector2){n->position.x, n->position.y}, camera); - - state->context_menu = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config"); - n->config = state->context_menu; - - int option = n->distributionMode; - GuiToggleGroup((Rectangle){ np.x + 20, np.y + 40, 73, 20 }, "GEO;RR;RAND", &option); - n->distributionMode = (Distribution)option; - - GuiLabel((Rectangle){np.x + 20, np.y + 65, 220, 20}, "SIGNALS"); - if(GuiButton((Rectangle){np.x + 20, np.y + 90, 220, 20}, "ADD SIGNAL")){ - sm->addSignalAtNode(n); - } - - int y_midi = 120; - GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT"); - - int i = 0; - if(n->getNodeType() == NodeType::MIDINODE){ - std::shared_ptr mn = std::dynamic_pointer_cast(n); - for(auto& mo : mn->midi_outs){ - GuiCheckBox((Rectangle){np.x + 20, np.y + y_midi + 20 + i*20.0f, 20, 20}, mo.second.name.c_str(), &mo.second.enabled); - i++; - } - - int y_channel = y_midi + 30 + i*20; - GuiCheckBox((Rectangle){np.x + 20, np.y + y_channel, 20, 20}, "NOTE", &mn->send_note); - GuiCheckBox((Rectangle){np.x + 90, np.y + y_channel, 20, 20}, "CC", &mn->send_cc); - GuiCheckBox((Rectangle){np.x + 160, np.y + y_channel, 20, 20}, "PITCH", &mn->send_pitch_bend); - - int y_cc_note = y_channel + 120; - switch(state->node_mode){ - case 0:{ - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "NOTE CHANNEL"); - GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiNote.channel); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "NOTE"); - float v = mn->midiNote.note; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note , 20, 20}, MIDI::Notes[(int)round(v)].c_str()); - GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiNote.note).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); - mn->midiNote.note = round(v); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "VELOCITY"); - v = mn->midiNote.velocity; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiNote.velocity).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); - mn->midiNote.velocity = round(v); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 120, 100, 20}, "GATE"); - v = mn->midiNote.gate; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 120, 20, 20}, std::format("{}",(float)mn->midiNote.gate/1000.0f).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 150, 220, 20}, "", "", &v, 0, 30000); - mn->midiNote.gate= round(v); - break;} - case 1: - { - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "CC CHANNEL"); - GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiCC.channel); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); - float v = mn->midiCC.control; - GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiCC.control).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); - mn->midiCC.control = round(v); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "CC VALUE"); - v = mn->midiCC.value; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiCC.value).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); - mn->midiCC.value = round(v); - - break;} - case 2: - { - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "PITCH BEND CHANNEL"); - GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiPitchBend.channel); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); - float v = mn->midiPitchBend.value; - GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiPitchBend.value).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 16383); - mn->midiPitchBend.value = round(v); - break;} - } - } - } - } - - void draw(Vec2Df mouse_pos, Camera2D camera){ - drawSideBar(); - if(state->context_menu) drawContextMenu(camera); - } - + void init(); + void drawSideBar(); + void drawContextMenu(Camera2D camera); + void draw(Vec2Df mouse_pos, Camera2D camera); + void DrawCenteredText(std::string text, Vec2Df position, int size, Color color); }; }