#pragma once #if __has_include() #include #include #include #include #include #include namespace libremidi::alsa_seq { struct shared_handler : public libremidi::shared_context { const libasound& snd = libasound::instance(); struct equals_addr { constexpr bool operator()(const snd_seq_addr_t& lhs, const snd_seq_addr_t& rhs) noexcept { return lhs.client == rhs.client && lhs.port == rhs.port; } }; explicit shared_handler(std::string_view v) { if (int err = snd.seq.open(&client, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); err < 0) { client = nullptr; // fixme throw? return; } if (!v.empty()) snd.seq.set_client_name(client, v.data()); // Last descriptor is the eventfd one int fds_size = snd.seq.poll_descriptors_count(client, POLLIN); fds.reserve(fds_size + 2); fds.resize(fds_size); snd.seq.poll_descriptors(client, fds.data(), fds_size, POLLIN); fds.push_back(termination_event); fds.push_back(queue_event); } void start_processing() override { thread = std::thread{[this] { process(); }}; } void stop_processing() override { termination_event.notify(); if (thread.joinable()) thread.join(); termination_event.consume(); } static shared_configurations make(std::string_view client_name) { auto clt = std::make_shared(client_name); auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) { if (auto clt = client.lock()) { clt->events.push( {.type = shared_handler::event_type::callback_added, .payload = std::move(params)}); clt->queue_event.notify(); } return true; }; auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) { if (auto clt = client.lock()) { clt->events.push( {.type = shared_handler::event_type::callback_removed, .payload = std::move(id)}); clt->queue_event.notify(); } return true; }; return { .context = clt, .observer = alsa_seq:: observer_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb}, .in = alsa_seq:: input_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb}, .out = alsa_seq::output_configuration{.context = clt->client}, }; } int64_t index_of_address(snd_seq_addr_t addr) { auto it = std::find_if(addresses.begin(), addresses.end(), [=](snd_seq_addr_t other) { return equals_addr{}(addr, other); }); if (it != addresses.end()) { return std::distance(addresses.begin(), it); } else { return -1; } } void process() { for (;;) { int err = poll(fds.data(), fds.size(), -1); if (err < 0) return; // Check for termination signal if (termination_event.ready(fds[fds.size() - 2])) return; // Check for queue processing signal if (queue_event.ready(fds[fds.size() - 1])) { this->queue_event.consume(); event ev; while (this->events.pop(ev)) { switch (ev.type) { case callback_added: { auto [addr, cb] = std::move(*std::get_if(&ev.payload)); addresses.push_back(addr); callbacks.push_back(std::move(cb)); break; } case callback_removed: auto addr = std::move(*std::get_if(&ev.payload)); if (auto index = index_of_address(addr); index >= 0) { addresses.erase(addresses.begin() + index); callbacks.erase(callbacks.begin() + index); } break; } } } // Look for who's ready for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++) { if (fds[i].revents & POLLIN) { // Read alsa event snd_seq_event_t* ev{}; event_handle handle{snd}; int result = 0; while ((result = snd.seq.event_input(client, &ev)) > 0) { handle.reset(ev); if (auto index = index_of_address(ev->dest); index >= 0) { // Dispatch the event to the correct observer or midi_in object int err = callbacks[index](*ev); if (err < 0 && err != -EAGAIN) return; } } } } } } ~shared_handler() { snd.seq.close(client); } enum event_type { callback_added, callback_removed, }; struct event { event_type type; std::variant payload; }; snd_seq_t* client{}; boost::lockfree::spsc_queue events{16}; std::vector addresses; std::vector> callbacks; std::vector fds; eventfd_notifier termination_event, queue_event{false}; std::thread thread; }; } #endif