diff --git a/cmake/libremidi.examples.cmake b/cmake/libremidi.examples.cmake index f136fe38..4f1ba4bc 100644 --- a/cmake/libremidi.examples.cmake +++ b/cmake/libremidi.examples.cmake @@ -21,7 +21,7 @@ add_example(midi1_to_midi2) add_example(midiclock_in) add_example(midiclock_out) add_example(midiout) -add_example(client) +# add_example(client) add_example(midiprobe) add_example(qmidiin) add_example(sysextest) diff --git a/cmake/libremidi.sources.cmake b/cmake/libremidi.sources.cmake index 3f95ac20..6695351a 100644 --- a/cmake/libremidi.sources.cmake +++ b/cmake/libremidi.sources.cmake @@ -126,8 +126,8 @@ target_sources(libremidi PRIVATE include/libremidi/detail/ump_stream.hpp include/libremidi/api.hpp - include/libremidi/client.cpp - include/libremidi/client.hpp + # include/libremidi/client.cpp + # include/libremidi/client.hpp include/libremidi/config.hpp include/libremidi/configurations.hpp include/libremidi/error.hpp diff --git a/examples/utils.hpp b/examples/utils.hpp index 34fc43a4..8bce2a8e 100644 --- a/examples/utils.hpp +++ b/examples/utils.hpp @@ -59,6 +59,10 @@ inline std::ostream& operator<<(std::ostream& s, const libremidi::device_identif { std::ostream& s; void operator()(const std::string& u) { s << u; } + void operator()(libremidi::usb_device_identifier u) + { + s << u.vendor_id << ":" << u.product_id; + } void operator()(uint64_t u) { auto res = static_cast(u); @@ -160,23 +164,27 @@ inline std::ostream& operator<<(std::ostream& s, const libremidi::port_informati { s << "[ client: " << rhs.client; if (rhs.container.index() > 0) - s << ", container: " << rhs.container; + s << "\n , container: " << rhs.container; if (rhs.device.index() > 0) - s << ", device_id: " << rhs.device; + s << "\n , device_id: " << rhs.device; - s << ", port: " << rhs.port; + s << "\n , port: " << rhs.port; if (!rhs.manufacturer.empty()) - s << ", manufacturer: " << rhs.manufacturer; + s << "\n , manufacturer: " << rhs.manufacturer; + if (!rhs.product.empty()) + s << "\n , product: " << rhs.product; + if (!rhs.serial.empty()) + s << "\n , serial: " << rhs.serial; if (!rhs.device_name.empty()) - s << ", device_name: " << rhs.device_name; + s << "\n , device_name: " << rhs.device_name; if (!rhs.port_name.empty()) - s << ", port_name: " << rhs.port_name; + s << "\n , port_name: " << rhs.port_name; if (!rhs.display_name.empty()) - s << ", display_name: " << rhs.display_name; - if (rhs.type != libremidi::port_information::unknown) - s << ", type: " << rhs.type; - return s << "]"; + s << "\n , display_name: " << rhs.display_name; + if (rhs.type != libremidi::transport_type::unknown) + s << "\n , type: " << rhs.type; + return s << "\n ]"; } namespace libremidi::examples diff --git a/include/libremidi/backends/alsa_raw/helpers.hpp b/include/libremidi/backends/alsa_raw/helpers.hpp index 5b0cf4ff..95412893 100644 --- a/include/libremidi/backends/alsa_raw/helpers.hpp +++ b/include/libremidi/backends/alsa_raw/helpers.hpp @@ -53,6 +53,7 @@ namespace alsa_raw { struct alsa_raw_port_info { + // hw:1,2,0 std::string device; std::string card_name; std::string device_name; diff --git a/include/libremidi/backends/alsa_raw/observer.hpp b/include/libremidi/backends/alsa_raw/observer.hpp index f7559b59..22876304 100644 --- a/include/libremidi/backends/alsa_raw/observer.hpp +++ b/include/libremidi/backends/alsa_raw/observer.hpp @@ -28,6 +28,21 @@ class observer_impl_base 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 @@ class observer_impl_base #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 @@ class observer_impl_base return ret; } -private: #if LIBREMIDI_HAS_UDEV void run() { @@ -98,6 +102,12 @@ class observer_impl_base 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 @@ class observer_impl_base 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 @@ class observer_impl_base -> 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), @@ -247,7 +260,12 @@ 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/observer.hpp b/include/libremidi/backends/alsa_raw_ump/observer.hpp index 03e32869..d43fc835 100644 --- a/include/libremidi/backends/alsa_raw_ump/observer.hpp +++ b/include/libremidi/backends/alsa_raw_ump/observer.hpp @@ -4,11 +4,16 @@ 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/helpers.hpp b/include/libremidi/backends/alsa_seq/helpers.hpp index 42cc6041..80bcb5b5 100644 --- a/include/libremidi/backends/alsa_seq/helpers.hpp +++ b/include/libremidi/backends/alsa_seq/helpers.hpp @@ -56,6 +56,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 +100,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( @@ -159,15 +192,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; } } diff --git a/include/libremidi/backends/alsa_seq/observer.hpp b/include/libremidi/backends/alsa_seq/observer.hpp index eb0b093d..5296dd53 100644 --- a/include/libremidi/backends/alsa_seq/observer.hpp +++ b/include/libremidi/backends/alsa_seq/observer.hpp @@ -16,16 +16,23 @@ 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,23 +100,34 @@ class observer_impl } } - 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 { - port_info p; - p.client = client; - p.port = port; + client_info p; + p.client_id = client; - snd_seq_client_info_t* cinfo; - snd_seq_client_info_alloca(&cinfo); - if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0) - return std::nullopt; + if (auto name = snd.seq.client_info_get_name(&cinfo)) + p.client_name = name; - snd_seq_port_info_t* pinfo; - snd_seq_port_info_alloca(&pinfo); - if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0) + 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; - const auto tp = snd.seq.port_info_get_type(pinfo); + 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 @@ -117,33 +135,47 @@ class observer_impl if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) { - p.type = libremidi::port_information::port_type::hardware; + p.type = libremidi::transport_type::hardware; ok = true; } else if ((tp & virtual_port) && this->configuration.track_virtual) { - p.type = libremidi::port_information::port_type::software; + p.type = libremidi::transport_type::software; ok = true; } if (!ok) return {}; - if (auto name = snd.seq.client_info_get_name(cinfo)) + if (auto name = snd.seq.client_info_get_name(&cinfo)) p.client_name = name; - if (auto name = snd.seq.port_info_get_name(pinfo)) + 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) + 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); + 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) + return std::nullopt; + + snd_seq_port_info_t* pinfo; + snd_seq_port_info_alloca(&pinfo); + if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0) + return std::nullopt; + + return get_info(client, port, *cinfo, *pinfo); + } + template auto to_port_info(const port_info& p) const noexcept -> std::conditional_t @@ -153,7 +185,10 @@ class observer_impl 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 @@ class observer_impl 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, @@ -200,8 +241,8 @@ class observer_impl 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) + if (auto p = get_info(clt, pt, client, port)) + if (p->is_input) ret.push_back(to_port_info(*p)); }); return ret; @@ -214,8 +255,8 @@ class observer_impl 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) + 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 @@ class observer_impl 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 @@ class observer_impl 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 @@ class observer_threaded : public observer_impl { // 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 @@ class observer_threaded : public observer_impl eventfd_notifier termination_event{}; std::thread thread; - std::vector descriptors_; + std::vector descriptors; + std::vector> queued_events; }; template @@ -363,7 +478,7 @@ class observer_manual : public observer_impl { this->configuration.manual_poll( poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) { - this->handle_event(v); + this->handle_event_direct(v); return 0; }}); } diff --git a/include/libremidi/backends/android/observer.hpp b/include/libremidi/backends/android/observer.hpp index 1c9b84ce..654004a7 100644 --- a/include/libremidi/backends/android/observer.hpp +++ b/include/libremidi/backends/android/observer.hpp @@ -53,6 +53,7 @@ class observer final 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)); diff --git a/include/libremidi/backends/coremidi/observer.hpp b/include/libremidi/backends/coremidi/observer.hpp index 305f3219..bcd2d2a1 100644 --- a/include/libremidi/backends/coremidi/observer.hpp +++ b/include/libremidi/backends/coremidi/observer.hpp @@ -22,6 +22,10 @@ class observer_core 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"; @@ -77,7 +81,7 @@ class observer_core 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{}; { @@ -86,11 +90,11 @@ class observer_core { 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; } @@ -98,25 +102,26 @@ class observer_core 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)), @@ -213,3 +218,17 @@ class observer_core 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/observer.hpp b/include/libremidi/backends/coremidi_ump/observer.hpp index 085a0fc4..835fc1c4 100644 --- a/include/libremidi/backends/coremidi_ump/observer.hpp +++ b/include/libremidi/backends/coremidi_ump/observer.hpp @@ -14,6 +14,7 @@ class observer_impl final : public libremidi::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/emscripten/observer.cpp b/include/libremidi/backends/emscripten/observer.cpp index 9cdda2d3..31ef49e3 100644 --- a/include/libremidi/backends/emscripten/observer.cpp +++ b/include/libremidi/backends/emscripten/observer.cpp @@ -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/jack/helpers.hpp b/include/libremidi/backends/jack/helpers.hpp index 2a639f93..9ce74311 100644 --- a/include/libremidi/backends/jack/helpers.hpp +++ b/include/libremidi/backends/jack/helpers.hpp @@ -41,11 +41,12 @@ 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 = "", @@ -55,7 +56,7 @@ struct jack_client }}; } - 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++; } diff --git a/include/libremidi/backends/jack/observer.hpp b/include/libremidi/backends/jack/observer.hpp index fdbddf82..b8059bcd 100644 --- a/include/libremidi/backends/jack/observer.hpp +++ b/include/libremidi/backends/jack/observer.hpp @@ -70,7 +70,8 @@ class observer_jack final { 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 @@ class observer_jack final { 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 @@ class observer_jack final { 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 @@ class observer_jack final 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); } } @@ -194,12 +200,14 @@ class observer_jack final 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_ump/observer.hpp b/include/libremidi/backends/jack_ump/observer.hpp index 699166a2..4fb8a887 100644 --- a/include/libremidi/backends/jack_ump/observer.hpp +++ b/include/libremidi/backends/jack_ump/observer.hpp @@ -77,7 +77,8 @@ class observer_jack final { 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 @@ class observer_jack final { 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 @@ class observer_jack final { 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 @@ class observer_jack final 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 @@ class observer_jack final 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/linux/alsa.hpp b/include/libremidi/backends/linux/alsa.hpp index a98a18ed..d68060c1 100644 --- a/include/libremidi/backends/linux/alsa.hpp +++ b/include/libremidi/backends/linux/alsa.hpp @@ -13,6 +13,10 @@ #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 @@ -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/udev.hpp b/include/libremidi/backends/linux/udev.hpp index bffa8571..4ea52e25 100644 --- a/include/libremidi/backends/linux/udev.hpp +++ b/include/libremidi/backends/linux/udev.hpp @@ -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/pipewire/helpers.hpp b/include/libremidi/backends/pipewire/helpers.hpp index 66af9ded..56484d32 100644 --- a/include/libremidi/backends/pipewire/helpers.hpp +++ b/include/libremidi/backends/pipewire/helpers.hpp @@ -217,6 +217,7 @@ struct pipewire_helpers return stdx::error{}; } + template void add_callbacks(std::string format, const observer_configuration& conf) { assert(global_context); @@ -232,12 +233,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 +255,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 +408,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 +425,7 @@ struct pipewire_helpers } return {{ + .api = Api, .client = 0, .port = port.id, .manufacturer = "", @@ -435,7 +437,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 +456,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 +467,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)); } } } diff --git a/include/libremidi/backends/pipewire/observer.hpp b/include/libremidi/backends/pipewire/observer.hpp index 1d119094..4e065da8 100644 --- a/include/libremidi/backends/pipewire/observer.hpp +++ b/include/libremidi/backends/pipewire/observer.hpp @@ -27,7 +27,7 @@ class observer_pipewire final // FIXME port rename callback { - this->add_callbacks("midi", configuration); + this->add_callbacks("midi", configuration); this->start_thread(); } @@ -47,12 +47,14 @@ class observer_pipewire final 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_ump/observer.hpp b/include/libremidi/backends/pipewire_ump/observer.hpp index 7d52db10..c5249e95 100644 --- a/include/libremidi/backends/pipewire_ump/observer.hpp +++ b/include/libremidi/backends/pipewire_ump/observer.hpp @@ -28,7 +28,7 @@ class observer_pipewire final // FIXME port rename callback { - this->add_callbacks("UMP", configuration); + this->add_callbacks("UMP", configuration); this->start_thread(); } @@ -48,12 +48,14 @@ class observer_pipewire final 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/observer.hpp b/include/libremidi/backends/winmidi/observer.hpp index ec8faf95..e1a45aea 100644 --- a/include/libremidi/backends/winmidi/observer.hpp +++ b/include/libremidi/backends/winmidi/observer.hpp @@ -84,18 +84,18 @@ class observer_impl final 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,7 +108,8 @@ class observer_impl final 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(), diff --git a/include/libremidi/backends/winmm/observer.hpp b/include/libremidi/backends/winmm/observer.hpp index 075f4ee5..11d30b05 100644 --- a/include/libremidi/backends/winmm/observer.hpp +++ b/include/libremidi/backends/winmm/observer.hpp @@ -103,8 +103,12 @@ class observer_winmm : public observer_api 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 @@ class observer_winmm : public observer_api 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 = "", diff --git a/include/libremidi/backends/winuwp/observer.hpp b/include/libremidi/backends/winuwp/observer.hpp index 83e7a97f..0ae116c2 100644 --- a/include/libremidi/backends/winuwp/observer.hpp +++ b/include/libremidi/backends/winuwp/observer.hpp @@ -225,7 +225,8 @@ class observer_winuwp final : public observer_api -> std::conditional_t { return { - {.client = 0, + {.api = libremidi::API::WINDOWS_UWP, + .client = 0, .port = 0, .manufacturer = "", .device_name = "", diff --git a/include/libremidi/midi_in.cpp b/include/libremidi/midi_in.cpp index e5ca766c..711e06ed 100644 --- a/include/libremidi/midi_in.cpp +++ b/include/libremidi/midi_in.cpp @@ -310,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 d26bfb8f..513b96e8 100644 --- a/include/libremidi/midi_out.cpp +++ b/include/libremidi/midi_out.cpp @@ -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_configuration.hpp b/include/libremidi/observer_configuration.hpp index 891cb28d..61f5d6d1 100644 --- a/include/libremidi/observer_configuration.hpp +++ b/include/libremidi/observer_configuration.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include #include +#include #include #include @@ -9,35 +11,14 @@ 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), + // Compat + using port_type = libremidi::transport_type; - hardware = (1 << 3), - usb = (1 << 4), - bluetooth = (1 << 5), - pci = (1 << 6), - - network = (1 << 7), - }; + /// 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 // ALSA Raw: unused @@ -46,6 +27,7 @@ struct LIBREMIDI_EXPORT port_information // WebMIDI: unused // JACK: jack_client_t* // PipeWire: unused // FIXME: pw_context? pw_main_loop? + // WinMIDI: TODO // WinMM: unused // WinUWP: unused client_handle client = static_cast(-1); @@ -58,7 +40,7 @@ struct LIBREMIDI_EXPORT port_information container_identifier container = std::monostate{}; /// Device identifier if the API provides one - // WinMM: { uint16_t manufacturer_id, uint16_t product_id; } + // WinMM: MIDI{IN,OUT}CAPS mId / pId { 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) @@ -68,25 +50,42 @@ struct LIBREMIDI_EXPORT port_information // 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 + // WebMIDI: index of the MIDI device in the list provided by the browser. // 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 + // WinUWP: index of the MIDI device in the list provided by the OS. port_handle port = static_cast(-1); /// User-readable information + // ALSA Raw: ID_VENDOR_FROM_DATABASE if provided by udev + // ALSA Seq: ID_VENDOR_FROM_DATABASE if provided by udev // CoreMIDI: kMIDIPropertyManufacturer // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName // WinMM: unavailable std::string manufacturer{}; + // ALSA Raw: ID_MODEL_FROM_DATABASE if provided by udev + // ALSA Seq: ID_MODEL_FROM_DATABASE if provided by udev + 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. + // ALSA Raw: ID_USB_SERIAL if provided by udev. + // ALSA Seq: ID_USB_SERIAL if provided by udev. + std::string serial{}; + + // ALSA Raw: Name returned by snd_rawmidi_info_get_name + // ALSA Seq: Name returned by snd_seq_client_info_get_name // CoreMIDI: kMIDIPropertyModel // WinMIDI: MidiEndpointDeviceInformation::Name // WinMM: unavailable std::string device_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 // WinMIDI: MidiGroupTerminalBlock::Name // WinMM: szPname @@ -103,18 +102,18 @@ struct LIBREMIDI_EXPORT port_information 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; + std::strong_ordering operator<=>(const port_information& other) const noexcept = delete; }; 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; + std::strong_ordering operator<=>(const input_port& other) const noexcept = delete; }; 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; + std::strong_ordering operator<=>(const output_port& other) const noexcept = delete; }; using input_port_callback = std::function; @@ -135,6 +134,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/types.hpp b/include/libremidi/types.hpp new file mode 100644 index 00000000..b4ef74bb --- /dev/null +++ b/include/libremidi/types.hpp @@ -0,0 +1,67 @@ +#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 = std::variant; +using device_identifier + = std::variant; +using endpoint_identifier = std::variant; +}