upgrade to libremidi 5.1, raylib and raygui latest

This commit is contained in:
Sebastian @ HfG
2025-06-13 19:06:09 +02:00
parent bd642576bb
commit 96bd5f30e6
112 changed files with 6484 additions and 1946 deletions
+3 -2
View File
@@ -3,6 +3,7 @@
#include <libremidi/backends/alsa_raw/midi_out.hpp>
#include <libremidi/backends/alsa_raw/observer.hpp>
#include <string_view>
// Credits: greatly inspired from
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
@@ -19,8 +20,8 @@ struct backend
using midi_out_configuration = alsa_raw_output_configuration;
using midi_observer_configuration = alsa_raw_observer_configuration;
static const constexpr auto API = libremidi::API::ALSA_RAW;
static const constexpr auto name = "alsa_raw";
static const constexpr auto display_name = "ALSA (raw)";
static const constexpr std::string_view name = "alsa_raw";
static const constexpr std::string_view display_name = "ALSA (raw)";
static inline bool available() noexcept
{
@@ -2,6 +2,7 @@
#include <libremidi/config.hpp>
#include <chrono>
#include <cstdint>
#include <functional>
#include <optional>
#include <span>
@@ -2,10 +2,17 @@
#include <libremidi/backends/linux/alsa.hpp>
#include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp>
#include <libremidi/observer_configuration.hpp>
#include <functional>
#include <alsa/asoundlib.h>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <string_view>
#include <system_error>
#include <vector>
// Credits: greatly inspired from
@@ -27,14 +34,14 @@ struct alsa_raw_port_id
};
inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept
{
return (uint64_t(id.card) << 32) + (uint64_t(id.dev) << 16) + id.port;
return (uint64_t(id.port) << 32) + (uint64_t(id.dev) << 16) + id.card;
}
inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept
{
alsa_raw_port_id ret;
ret.card = (p & 0x00'00'FF'FF'00'00'00'00) >> 32;
ret.port = (p & 0x00'00'FF'FF'00'00'00'00) >> 32;
ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16;
ret.port = (p & 0x00'00'00'00'00'00'FF'FF);
ret.card = (p & 0x00'00'00'00'00'00'FF'FF);
return ret;
}
static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102);
+12 -12
View File
@@ -200,7 +200,7 @@ public:
midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
: midi_in_impl{std::move(conf), std::move(apiconf)}
{
if (this->termination_event < 0)
if (this->m_termination_event < 0)
{
libremidi_handle_error(this->configuration, "error creating eventfd.");
return;
@@ -220,7 +220,7 @@ public:
private:
void run_thread(auto parse_func)
{
fds_.push_back(this->termination_event);
fds_.push_back(this->m_termination_event);
const auto period
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
.count();
@@ -228,12 +228,12 @@ private:
for (;;)
{
// Poll
ssize_t err = poll(fds_.data(), fds_.size(), period);
ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
if (err == -EAGAIN)
continue;
else if (err < 0)
return;
else if (termination_event.ready(fds_.back()))
else if (m_termination_event.ready(fds_.back()))
break;
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
@@ -250,11 +250,11 @@ private:
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
}
else
{
this->thread_ = std::thread{
this->m_thread = std::thread{
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
}
return stdx::error{};
@@ -282,16 +282,16 @@ private:
stdx::error close_port() override
{
termination_event.notify();
if (thread_.joinable())
thread_.join();
termination_event.consume(); // Reset to zero
m_termination_event.notify();
if (m_thread.joinable())
m_thread.join();
m_termination_event.consume(); // Reset to zero
return midi_in_impl::close_port();
}
std::thread thread_;
eventfd_notifier termination_event{};
std::thread m_thread;
eventfd_notifier m_termination_event{};
};
class midi_in_alsa_raw_manual : public midi_in_impl
@@ -6,7 +6,8 @@
#if LIBREMIDI_HAS_UDEV
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/backends/linux/udev.hpp>
#include <libremidi/detail/observer.hpp>
#endif
#include <libremidi/detail/observer.hpp>
namespace libremidi::alsa_raw
{
@@ -31,24 +32,30 @@ public:
if (!configuration.has_callbacks())
return;
fds[0] = this->udev;
fds[1] = termination_event;
fds[2] = timer_fd;
#if LIBREMIDI_HAS_UDEV
m_fds[0] = this->m_udev;
m_fds[1] = m_termination_event;
m_fds[2] = m_timer_fd;
#endif
// Set-up initial state
if (configuration.notify_in_constructor)
this->check_devices();
#if LIBREMIDI_HAS_UDEV
// Start thread
thread = std::thread{[this] { this->run(); }};
m_thread = std::thread{[this] { this->run(); }};
#endif
}
~observer_impl_base()
{
termination_event.notify();
#if LIBREMIDI_HAS_UDEV
m_termination_event.notify();
if (thread.joinable())
thread.join();
if (m_thread.joinable())
m_thread.join();
#endif
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
@@ -78,11 +85,12 @@ public:
}
private:
#if LIBREMIDI_HAS_UDEV
void run()
{
for (;;)
{
if (int err = poll(fds, 3, -1); err < 0)
if (int err = poll(m_fds, 3, -1); err < 0)
{
if (err == -EAGAIN)
continue;
@@ -91,58 +99,77 @@ private:
}
// Check udev
if (fds[0].revents & POLLIN)
if (m_fds[0].revents & POLLIN)
{
udev_device* dev = udev.udev.monitor_receive_device(udev.monitor);
udev_device* dev = m_udev.udev.monitor_receive_device(m_udev.monitor);
if (!dev)
continue;
std::string_view act = udev.udev.device_get_action(dev);
std::string_view ss = udev.udev.device_get_subsystem(dev);
std::string_view act = m_udev.udev.device_get_action(dev);
std::string_view ss = m_udev.udev.device_get_subsystem(dev);
if (!act.empty() && ss == "snd_seq")
{
if (act == "add" || act == "remove")
{
// Check every 100 milliseconds for ten seconds
this->timer_fd.restart(configuration.poll_period.count());
timer_check_counts = 100;
this->m_timer_fd.restart(configuration.poll_period.count());
m_timer_check_counts = 100;
}
}
udev.udev.device_unref(dev);
m_udev.udev.device_unref(dev);
fds[0].revents = 0;
m_fds[0].revents = 0;
}
// Check eventfd
if (fds[1].revents & POLLIN)
if (m_fds[1].revents & POLLIN)
{
break;
}
// Check timer
if (fds[2].revents & POLLIN)
if (m_fds[2].revents & POLLIN)
{
if (this->timer_check_counts-- <= 0)
this->timer_fd.cancel();
fds[2].revents = 0;
if (this->m_timer_check_counts-- <= 0)
this->m_timer_fd.cancel();
m_fds[2].revents = 0;
check_devices();
}
}
}
#endif
template <bool Input>
auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept
auto to_port_info(const alsa_raw::alsa_raw_port_info& p) const noexcept
-> std::conditional_t<Input, input_port, output_port>
{
#if LIBREMIDI_HAS_UDEV
auto [container, device, type] = get_udev_soundcard_info(m_udev, p.card);
#else
container_identifier container{};
device_identifier device{};
libremidi::port_information::port_type type{};
#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 = "unknown";
return {
{.client = 0,
.container = container,
.device = device,
.port = raw_to_port_handle({p.card, p.dev, p.sub}),
.manufacturer = p.card_name,
.device_name = p.device_name,
.port_name = p.subdevice_name,
.display_name = p.subdevice_name}};
.display_name = std::move(display_name),
.type = type}};
}
void check_devices()
@@ -151,7 +178,7 @@ private:
new_devs.enumerate_cards();
for (auto& in_prev : current_inputs)
for (auto& in_prev : m_current_inputs)
{
if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);
it == new_devs.inputs.end())
@@ -165,8 +192,8 @@ private:
for (auto& in_next : new_devs.inputs)
{
if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next);
it == current_inputs.end())
if (auto it = std::find(m_current_inputs.begin(), m_current_inputs.end(), in_next);
it == m_current_inputs.end())
{
if (auto& cb = this->configuration.input_added)
{
@@ -175,7 +202,7 @@ private:
}
}
for (auto& out_prev : current_outputs)
for (auto& out_prev : m_current_outputs)
{
if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);
it == new_devs.outputs.end())
@@ -189,8 +216,8 @@ private:
for (auto& out_next : new_devs.outputs)
{
if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next);
it == current_outputs.end())
if (auto it = std::find(m_current_outputs.begin(), m_current_outputs.end(), out_next);
it == m_current_outputs.end())
{
if (auto& cb = this->configuration.output_added)
{
@@ -198,38 +225,24 @@ private:
}
}
}
current_inputs = std::move(new_devs.inputs);
current_outputs = std::move(new_devs.outputs);
m_current_inputs = std::move(new_devs.inputs);
m_current_outputs = std::move(new_devs.outputs);
}
udev_helper udev{};
eventfd_notifier termination_event{};
timerfd_timer timer_fd{};
int timer_check_counts = 0;
std::thread thread;
std::vector<alsa_raw_port_info> current_inputs;
std::vector<alsa_raw_port_info> current_outputs;
pollfd fds[3]{};
};
}
#else
#include <libremidi/backends/dummy.hpp>
namespace libremidi::alsa_raw
{
template <typename Enumerator>
struct observer_impl_base : observer_dummy
{
explicit observer_impl_base(
[[maybe_unused]] observer_configuration&& conf,
[[maybe_unused]] alsa_raw_observer_configuration&& apiconf)
: observer_dummy{dummy_configuration{}, dummy_configuration{}}
{
}
};
}
#if LIBREMIDI_HAS_UDEV
udev_helper m_udev{};
eventfd_notifier m_termination_event{};
timerfd_timer m_timer_fd{};
int m_timer_check_counts = 0;
std::thread m_thread;
pollfd m_fds[3]{};
#endif
std::vector<alsa_raw_port_info> m_current_inputs;
std::vector<alsa_raw_port_info> m_current_outputs;
};
}
namespace libremidi::alsa_raw
{
struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>
+6 -5
View File
@@ -5,6 +5,8 @@
#include <libremidi/backends/alsa_raw_ump/observer.hpp>
#include <libremidi/backends/dummy.hpp>
#include <string_view>
namespace libremidi::alsa_raw_ump
{
struct backend
@@ -14,12 +16,11 @@ struct backend
using midi_observer = observer_impl;
using midi_in_configuration = alsa_raw_ump::input_configuration;
using midi_out_configuration = alsa_raw_ump::output_configuration;
struct midi_observer_configuration : alsa_raw_observer_configuration
{
};
using midi_observer_configuration = alsa_raw_ump::observer_configuration;
static const constexpr auto API = libremidi::API::ALSA_RAW_UMP;
static const constexpr auto name = "alsa_raw_ump";
static const constexpr auto display_name = "ALSA (raw UMP)";
static const constexpr std::string_view name = "alsa_raw_ump";
static const constexpr std::string_view display_name = "ALSA (raw UMP)";
static inline bool available() noexcept
{
@@ -1,6 +1,9 @@
#pragma once
#include <libremidi/backends/alsa_raw/config.hpp>
#include <chrono>
#include <functional>
namespace libremidi::alsa_raw_ump
{
struct input_configuration
@@ -18,8 +21,7 @@ struct output_configuration
std::optional<chunking_parameters> chunking;
};
struct observer_configuration
struct observer_configuration : public alsa_raw_observer_configuration
{
std::chrono::milliseconds poll_period{100};
};
}
@@ -3,6 +3,8 @@
#include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp>
#include <cstdio>
namespace libremidi::alsa_raw_ump
{
struct midi2_enumerator : alsa_raw::enumerator
@@ -194,7 +194,7 @@ public:
libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
: midi_in_impl{std::move(conf), std::move(apiconf)}
{
if (this->termination_event < 0)
if (this->m_termination_event < 0)
{
libremidi_handle_error(this->configuration, "error creating eventfd.");
return;
@@ -206,14 +206,14 @@ public:
~midi_in_impl_threaded()
{
// Close a connection if it exists.
this->close_port();
midi_in_impl_threaded::close_port();
client_open_ = std::errc::not_connected;
}
private:
void run_thread(auto parse_func)
{
fds_.push_back(this->termination_event);
fds_.push_back(this->m_termination_event);
const auto period
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
.count();
@@ -221,12 +221,12 @@ private:
for (;;)
{
// Poll
ssize_t err = poll(fds_.data(), fds_.size(), period);
ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
if (err == -EAGAIN)
continue;
else if (err < 0)
return;
else if (termination_event.ready(fds_.back()))
else if (m_termination_event.ready(fds_.back()))
break;
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
@@ -243,11 +243,11 @@ private:
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
}
else
{
this->thread_ = std::thread{
this->m_thread = std::thread{
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
}
return stdx::error{};
@@ -275,16 +275,16 @@ private:
stdx::error close_port() override
{
termination_event.notify();
if (thread_.joinable())
thread_.join();
termination_event.consume(); // Reset to zero
m_termination_event.notify();
if (m_thread.joinable())
m_thread.join();
m_termination_event.consume(); // Reset to zero
return midi_in_impl::close_port();
}
std::thread thread_;
eventfd_notifier termination_event{};
std::thread m_thread;
eventfd_notifier m_termination_event{};
};
class midi_in_impl_manual : public midi_in_impl
@@ -299,7 +299,7 @@ public:
~midi_in_impl_manual()
{
// Close a connection if it exists.
this->close_port();
midi_in_impl_manual::close_port();
client_open_ = std::errc::not_connected;
}
@@ -1,12 +1,17 @@
#pragma once
#include <libremidi/backends/alsa_raw_ump/config.hpp>
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
#include <libremidi/backends/linux/alsa.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <libremidi/error_handler.hpp>
#include <libremidi/output_configuration.hpp>
#include <alsa/asoundlib.h>
#include <atomic>
#include <thread>
#include <cassert>
#include <cstdint>
#include <system_error>
#include <utility>
namespace libremidi::alsa_raw_ump
{
+10 -3
View File
@@ -16,6 +16,10 @@
#include <libremidi/backends/alsa_seq/midi_out.hpp>
#include <libremidi/backends/alsa_seq/observer.hpp>
#include <unistd.h>
#include <string_view>
namespace libremidi::alsa_seq
{
@@ -29,13 +33,16 @@ struct backend
using midi_out_configuration = alsa_seq::output_configuration;
using midi_observer_configuration = alsa_seq::observer_configuration;
static const constexpr auto API = libremidi::API::ALSA_SEQ;
static const constexpr auto name = "alsa_seq";
static const constexpr auto display_name = "ALSA (sequencer)";
static const constexpr std::string_view name = "alsa_seq";
static const constexpr std::string_view display_name = "ALSA (sequencer)";
static inline bool available() noexcept
{
static const libasound& snd = libasound::instance();
return snd.available && snd.seq.available;
if (!snd.available || !snd.seq.available)
return false;
return ::access("/dev/snd/seq", F_OK) == 0;
}
};
@@ -46,19 +46,19 @@ namespace
{
inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept
{
return (client << 32) + port;
return (port << 32) + client;
}
inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept
{
int client = p >> 32;
int port = p & 0xFFFFFFFF;
int port = p >> 32;
int client = p & 0xFFFFFFFF;
return {client, port};
}
inline void for_all_ports(
const libasound& snd, snd_seq_t* seq,
std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)> func)
const std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)>& func)
{
snd_seq_client_info_t* cinfo{};
snd_seq_client_info_alloca(&cinfo);
+18 -18
View File
@@ -212,7 +212,7 @@ public:
.count();
}
int process_event(const snd_seq_event_t& ev)
int64_t process_event(const snd_seq_event_t& ev)
{
if constexpr (ConfigurationImpl::midi_version == 1)
{
@@ -258,17 +258,17 @@ public:
return 0;
}
int process_events()
int64_t process_events()
{
if constexpr (ConfigurationImpl::midi_version == 1)
{
snd_seq_event_t* ev{};
event_handle handle{snd};
int result = 0;
int64_t result = 0;
if ((result = snd.seq.event_input(seq, &ev)) > 0)
{
handle.reset(ev);
if (int err = process_event(*ev); err < 0)
if (auto err = process_event(*ev); err < 0)
{
return err;
}
@@ -353,7 +353,7 @@ public:
midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)
: midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}
{
if (this->termination_event < 0)
if (this->m_termination_event < 0)
{
this->libremidi_handle_error(this->configuration, "error creating eventfd.");
return;
@@ -364,7 +364,7 @@ public:
~midi_in_alsa_threaded()
{
this->close_port();
midi_in_alsa_threaded::close_port();
this->client_open_ = std::errc::not_connected;
}
@@ -399,7 +399,7 @@ private:
{
try
{
this->thread = std::thread([this] { thread_handler(); });
this->m_thread = std::thread([this] { thread_handler(); });
return stdx::error{};
}
catch (const std::system_error& e)
@@ -415,12 +415,12 @@ private:
stdx::error stop_thread()
{
termination_event.notify();
m_termination_event.notify();
if (this->thread.joinable())
this->thread.join();
if (this->m_thread.joinable())
this->m_thread.join();
termination_event.consume();
m_termination_event.consume();
return stdx::error{};
}
@@ -428,7 +428,7 @@ private:
{
int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1;
auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd));
poll_fds[0] = this->termination_event;
poll_fds[0] = this->m_termination_event;
alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);
const auto period
@@ -439,10 +439,10 @@ private:
if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)
{
// No data pending
if (poll(poll_fds, poll_fd_count, period) >= 0)
if (poll(poll_fds, poll_fd_count, static_cast<int32_t>(period)) >= 0)
{
// We got our stop-thread signal
if (termination_event.ready(poll_fds[0]))
if (m_termination_event.ready(poll_fds[0]))
{
break;
}
@@ -450,7 +450,7 @@ private:
continue;
}
int res{};
int64_t res{};
if constexpr (ConfigurationImpl::midi_version == 1)
{
res = this->process_events();
@@ -467,8 +467,8 @@ private:
}
}
std::thread thread{};
eventfd_notifier termination_event{};
std::thread m_thread{};
eventfd_notifier m_termination_event{};
};
template <typename ConfigurationBase, typename ConfigurationImpl>
@@ -497,7 +497,7 @@ public:
return 0;
}
~midi_in_alsa_manual() { this->close_port(); }
~midi_in_alsa_manual() { midi_in_alsa_manual::close_port(); }
stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
{
@@ -30,7 +30,7 @@ public:
return;
}
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
if (snd.midi.event_new(this->m_bufferSize, &this->coder) < 0)
{
libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");
return;
@@ -68,8 +68,9 @@ public:
stdx::error open_port(const output_port& p, std::string_view portName) override
{
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (nSrc < 1)
unsigned int n_src
= this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (n_src < 1)
{
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
return make_error_code(std::errc::no_such_device);
@@ -122,9 +123,9 @@ public:
stdx::error send_message(const unsigned char* message, std::size_t size) override
{
int64_t result{};
if (size > this->bufferSize)
if (size > this->m_bufferSize)
{
this->bufferSize = size;
this->m_bufferSize = size;
result = snd.midi.event_resize_buffer(this->coder, size);
if (result != 0)
{
@@ -146,8 +147,8 @@ public:
// FIXME direct is set but snd_seq_event_output_direct is not used...
snd_seq_ev_set_direct(&ev);
const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below
result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev);
const int64_t n_bytes = size; // signed to avoir potential overflow with size - offset below
result = snd.midi.event_encode(this->coder, message + offset, (long)(n_bytes - offset), &ev);
if (result < 0)
{
libremidi_handle_warning(this->configuration, "event parsing error!");
@@ -174,6 +175,6 @@ public:
}
private:
uint64_t bufferSize{32};
uint64_t m_bufferSize{32};
};
}
@@ -7,7 +7,10 @@
#include <alsa/asoundlib.h>
#include <bitset>
#if LIBREMIDI_HAS_UDEV
#include <libremidi/backends/linux/udev.hpp>
#endif
#include <map>
namespace libremidi::alsa_seq
@@ -21,6 +24,8 @@ struct port_info
int port{};
bool isInput{};
bool isOutput{};
std::optional<int> card{};
libremidi::port_information::port_type type{};
};
template <typename ConfigurationImpl>
@@ -107,13 +112,19 @@ public:
const auto tp = snd.seq.port_info_get_type(pinfo);
bool ok = this->configuration.track_any;
static constexpr auto virtual_port
= SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER;
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 {};
@@ -123,7 +134,10 @@ public:
if (auto name = snd.seq.port_info_get_name(pinfo))
p.port_name = name;
auto cap = snd.seq.port_info_get_capability(pinfo);
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);
@@ -131,18 +145,34 @@ public:
}
template <bool Input>
auto to_port_info(port_info p) const noexcept
auto to_port_info(const port_info& p) const noexcept
-> std::conditional_t<Input, input_port, output_port>
{
static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle));
static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle));
container_identifier container{};
device_identifier device{};
libremidi::port_information::port_type type = p.type;
#if LIBREMIDI_HAS_UDEV
if (p.card)
{
auto res = get_udev_soundcard_info(m_udev, *p.card);
container = res.container;
device = res.path;
type = res.type;
}
#endif
return {
{.client = std::uintptr_t(this->seq),
.container = container,
.device = device,
.port = alsa_seq::seq_to_port_handle(p.client, p.port),
.manufacturer = "",
.device_name = p.client_name,
.port_name = p.port_name,
.display_name = p.port_name}};
.display_name = p.port_name,
.type = type}};
}
void init_all_ports()
@@ -200,7 +230,7 @@ public:
if (p.client == snd.seq.client_id(seq))
return;
knownClients_[{p.client, p.port}] = p;
m_knownClients[{p.client, p.port}] = p;
if (p.isInput && configuration.input_added)
{
configuration.input_added(to_port_info<true>(p));
@@ -214,11 +244,11 @@ public:
void unregister_port(int client, int port)
{
auto it = knownClients_.find({client, port});
if (it != knownClients_.end())
auto it = m_knownClients.find({client, port});
if (it != m_knownClients.end())
{
auto p = it->second;
knownClients_.erase(it);
m_knownClients.erase(it);
if (p.isInput && configuration.input_removed)
{
@@ -263,7 +293,11 @@ public:
}
private:
std::map<std::pair<int, int>, port_info> knownClients_;
std::map<std::pair<int, int>, port_info> m_knownClients;
#if LIBREMIDI_HAS_UDEV
udev_helper m_udev{};
#endif
};
template <typename ConfigurationImpl>
@@ -275,9 +309,9 @@ public:
{
// Create relevant descriptors
auto& snd = alsa_data::snd;
const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN);
descriptors_.resize(N + 1);
snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN);
const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN);
descriptors_.resize(n + 1);
snd.seq.poll_descriptors(this->seq, descriptors_.data(), n, POLLIN);
descriptors_.back() = this->termination_event;
// Start the listening thread
@@ -288,7 +322,7 @@ public:
.count();
for (;;)
{
int err = poll(descriptors_.data(), descriptors_.size(), period);
int err = poll(descriptors_.data(), descriptors_.size(), static_cast<int32_t>(period));
if (err >= 0)
{
// We got our stop-thread signal
@@ -61,7 +61,7 @@ struct shared_handler : public libremidi::shared_context
{
auto clt = std::make_shared<shared_handler>(client_name);
auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) {
auto cb = [client = std::weak_ptr{clt}](libremidi::alsa_seq::poll_parameters params) {
if (auto clt = client.lock())
{
clt->events.push(
@@ -74,8 +74,7 @@ struct shared_handler : public libremidi::shared_context
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->events.push({.type = shared_handler::event_type::callback_removed, .payload = id});
clt->queue_event.notify();
}
return true;
@@ -134,7 +133,7 @@ struct shared_handler : public libremidi::shared_context
break;
}
case callback_removed:
auto addr = std::move(*std::get_if<snd_seq_addr_t>(&ev.payload));
auto addr = *std::get_if<snd_seq_addr_t>(&ev.payload);
if (auto index = index_of_address(addr); index >= 0)
{
addresses.erase(addresses.begin() + index);
@@ -146,15 +145,14 @@ struct shared_handler : public libremidi::shared_context
}
// Look for who's ready
for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++)
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)
while (snd.seq.event_input(client, &ev) > 0)
{
handle.reset(ev);
+10 -3
View File
@@ -5,6 +5,10 @@
#include <libremidi/backends/alsa_seq_ump/config.hpp>
#include <libremidi/backends/alsa_seq_ump/midi_out.hpp>
#include <unistd.h>
#include <string_view>
namespace libremidi
{
template <>
@@ -50,13 +54,16 @@ struct backend
using midi_out_configuration = alsa_seq_ump::output_configuration;
using midi_observer_configuration = alsa_seq_ump::observer_configuration;
static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP;
static const constexpr auto name = "alsa_seq_ump";
static const constexpr auto display_name = "ALSA (sequencer, UMP)";
static const constexpr std::string_view name = "alsa_seq_ump";
static const constexpr std::string_view display_name = "ALSA (sequencer, UMP)";
static inline bool available() noexcept
{
static const libasound& snd = libasound::instance();
return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available;
if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available)
return false;
return ::access("/dev/snd/seq", F_OK) == 0;
}
};
}
@@ -64,8 +64,9 @@ public:
stdx::error open_port(const output_port& p, std::string_view portName) override
{
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (nSrc < 1)
unsigned int n_src
= this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (n_src < 1)
{
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
return make_error_code(std::errc::no_such_device);
@@ -133,7 +134,8 @@ public:
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
return static_cast<std::errc>(-ret);
}
return std::errc{0};
static_assert(std::errc{0} == std::errc{});
return std::errc{};
};
segment_ump_stream(ump_stream, count, write_func, []() {});
+4 -2
View File
@@ -3,6 +3,8 @@
#include <libremidi/backends/coremidi/midi_out.hpp>
#include <libremidi/backends/coremidi/observer.hpp>
#include <string_view>
namespace libremidi
{
struct core_backend
@@ -14,8 +16,8 @@ struct core_backend
using midi_out_configuration = coremidi_output_configuration;
using midi_observer_configuration = coremidi_observer_configuration;
static const constexpr auto API = libremidi::API::COREMIDI;
static const constexpr auto name = "core";
static const constexpr auto display_name = "CoreMIDI";
static const constexpr std::string_view name = "core";
static const constexpr std::string_view display_name = "CoreMIDI";
static constexpr inline bool available() noexcept { return true; }
};
@@ -71,20 +71,22 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)
return static_cast<uint64_t>(res);
}
#endif
static inline 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.
inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
{
CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);
static constexpr auto getProp = [](MIDIObjectRef prop) {
CFStringRef str = nullptr;
MIDIObjectGetStringProperty(prop, kMIDIPropertyName, &str);
return CFString_handle{str};
};
// Begin with the endpoint's name.
if (auto endpoint_name = getProp(endpoint))
if (auto endpoint_name = get_cfstring_property(endpoint, kMIDIPropertyName))
{
CFStringAppend(result, endpoint_name.get());
}
@@ -101,7 +103,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
if (CFStringGetLength(result) == 0)
{
// endpoint name has zero length -- try the entity
if (auto entity_name = getProp(entity))
if (auto entity_name = get_cfstring_property(entity, kMIDIPropertyName))
{
CFStringAppend(result, entity_name.get());
}
@@ -112,7 +114,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
if (device == 0)
goto finish;
if (auto dev_name = getProp(device))
if (auto dev_name = get_cfstring_property(device, kMIDIPropertyName))
{
const auto dev_strlen = CFStringGetLength(dev_name.get());
@@ -78,13 +78,55 @@ public:
if (!ok)
return {};
// Get the MIDI device from the entity
libremidi::port_information::port_type type{};
libremidi::container_identifier usb_location_id{};
libremidi::device_identifier usb_vendor_product{};
{
MIDIDeviceRef device{};
if (MIDIEntityGetDevice(e, &device) == noErr)
{
if (device)
{
using enum libremidi::port_information::port_type;
SInt32 devid_res{};
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr)
{
type = (libremidi::port_information::port_type)(hardware | usb);
usb_location_id = (uint64_t)devid_res;
}
SInt32 vendor_res{};
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res)
== noErr)
{
type = (libremidi::port_information::port_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);
if (driver == "com.apple.AppleMIDIBluetoothDriver")
type = (libremidi::port_information::port_type)(hardware | bluetooth);
if (driver == "com.apple.AppleMIDIIACDriver")
type = (libremidi::port_information::port_type)(software);
if (driver == "com.apple.AppleMIDIRTPDriver")
type = (libremidi::port_information::port_type)(network);
}
}
}
return std::conditional_t<Input, input_port, output_port>{
{.client = (std::uintptr_t)this->client,
.container = usb_location_id,
.device = usb_vendor_product,
.port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),
.manufacturer = get_string_property(obj, kMIDIPropertyManufacturer),
.device_name = get_string_property(obj, kMIDIPropertyModel),
.port_name = get_string_property(obj, kMIDIPropertyName),
.display_name = get_string_property(obj, kMIDIPropertyDisplayName)}};
.display_name = get_string_property(obj, kMIDIPropertyDisplayName),
.type = type}};
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
+4 -2
View File
@@ -3,6 +3,8 @@
#include <libremidi/backends/coremidi_ump/midi_out.hpp>
#include <libremidi/backends/coremidi_ump/observer.hpp>
#include <string_view>
namespace libremidi::coremidi_ump
{
struct backend
@@ -14,8 +16,8 @@ struct backend
using midi_out_configuration = coremidi_ump::output_configuration;
using midi_observer_configuration = coremidi_ump::observer_configuration;
static const constexpr auto API = libremidi::API::COREMIDI_UMP;
static const constexpr auto name = "core_ump";
static const constexpr auto display_name = "CoreMIDI UMP";
static const constexpr std::string_view name = "core_ump";
static const constexpr std::string_view display_name = "CoreMIDI UMP";
static constexpr inline bool available() noexcept { return true; /* todo? */ }
};
+9 -5
View File
@@ -5,6 +5,8 @@
#include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/observer.hpp>
#include <string_view>
namespace libremidi
{
class observer_dummy : public observer_api
@@ -23,11 +25,12 @@ class midi_in_dummy final
, public error_handler
{
public:
explicit midi_in_dummy(const input_configuration& configuration, std::any)
explicit midi_in_dummy(const input_configuration& configuration, const input_api_configuration&)
{
libremidi_handle_warning(configuration, "This class provides no functionality.");
}
explicit midi_in_dummy(const ump_input_configuration& configuration, std::any)
explicit midi_in_dummy(
const ump_input_configuration& configuration, const input_api_configuration&)
{
libremidi_handle_warning(configuration, "This class provides no functionality.");
}
@@ -58,7 +61,8 @@ class midi_out_dummy final
, public error_handler
{
public:
explicit midi_out_dummy(const output_configuration& configuration, std::any)
explicit midi_out_dummy(
const output_configuration& configuration, const output_api_configuration&)
{
libremidi_handle_warning(configuration, "This class provides no functionality.");
}
@@ -97,8 +101,8 @@ struct dummy_backend
using midi_out_configuration = dummy_configuration;
using midi_observer_configuration = dummy_configuration;
static const constexpr auto API = libremidi::API::DUMMY;
static const constexpr auto name = "dummy";
static const constexpr auto display_name = "Dummy";
static const constexpr std::string_view name = "dummy";
static const constexpr std::string_view display_name = "Dummy";
static constexpr inline bool available() noexcept { return true; }
};
+4 -2
View File
@@ -7,6 +7,8 @@
#include <libremidi/backends/emscripten/midi_out.hpp>
#include <libremidi/backends/emscripten/observer.hpp>
#include <string_view>
namespace libremidi
{
@@ -19,8 +21,8 @@ struct emscripten_backend
using midi_out_configuration = emscripten_output_configuration;
using midi_observer_configuration = emscripten_observer_configuration;
static const constexpr auto API = libremidi::API::WEBMIDI;
static const constexpr auto name = "webmidi";
static const constexpr auto display_name = "WebMIDI";
static const constexpr std::string_view name = "webmidi";
static const constexpr std::string_view display_name = "WebMIDI";
static constexpr inline bool available() noexcept { return true; }
};
@@ -38,7 +38,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std::
}
midi.open_input(portNumber, *this);
portNumber_ = portNumber;
m_portNumber = portNumber;
return stdx::error{};
}
@@ -52,7 +52,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port()
{
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
midi.close_input(portNumber_, *this);
midi.close_input(m_portNumber, *this);
return stdx::error{};
}
@@ -31,7 +31,7 @@ public:
void on_input(double ts, unsigned char* begin, unsigned char* end);
private:
int portNumber_{};
int m_portNumber{};
midi1::input_state_machine m_processing{this->configuration};
};
@@ -34,7 +34,7 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std:
return std::errc::invalid_argument;
}
portNumber_ = portNumber;
m_portNumber = portNumber;
return stdx::error{};
}
@@ -49,14 +49,14 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() {
LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size)
{
if (portNumber_ < 0)
if (m_portNumber < 0)
libremidi_handle_error(
this->configuration,
"trying to send a message without an open "
"port.");
webmidi_helpers::midi_access_emscripten::instance().send_message(
portNumber_, reinterpret_cast<const char*>(message), size);
m_portNumber, reinterpret_cast<const char*>(message), size);
return stdx::error{};
}
}
@@ -28,6 +28,6 @@ public:
stdx::error send_message(const unsigned char* message, size_t size) override;
private:
int portNumber_{-1};
int m_portNumber{-1};
};
}
+4 -2
View File
@@ -3,6 +3,8 @@
#include <libremidi/backends/jack/midi_out.hpp>
#include <libremidi/backends/jack/observer.hpp>
#include <string_view>
//*********************************************************************//
// API: UNIX JACK
//
@@ -22,8 +24,8 @@ struct jack_backend
using midi_out_configuration = jack_output_configuration;
using midi_observer_configuration = jack_observer_configuration;
static const constexpr auto API = libremidi::API::JACK_MIDI;
static const constexpr auto name = "jack";
static const constexpr auto display_name = "JACK";
static const constexpr std::string_view name = "jack";
static const constexpr std::string_view display_name = "JACK";
static inline bool available() noexcept
{
+89 -11
View File
@@ -5,7 +5,9 @@
#include <libremidi/detail/semaphore.hpp>
#include <atomic>
#include <semaphore>
#include <cstdint>
#include <cstring>
#include <memory>
namespace libremidi
{
@@ -54,16 +56,16 @@ struct jack_client
}
template <bool Input>
static auto
get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept
-> std::vector<std::conditional_t<Input, input_port, output_port>>
static auto get_ports(
jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags,
bool midi2) noexcept -> std::vector<std::conditional_t<Input, input_port, output_port>>
{
std::vector<std::conditional_t<Input, input_port, output_port>> ret;
if (!client)
return {};
const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags);
const char** ports = jack_get_ports(client, pattern, type, flags);
if (ports == nullptr)
return {};
@@ -73,7 +75,11 @@ struct jack_client
{
// FIXME this does not take into account filtering sw / hw ports
auto port = jack_port_by_name(client, ports[i]);
ret.push_back(to_port_info<Input>(client, port));
if (port)
{
if (bool(midi2) == bool(jack_port_flags(port) & 0x20))
ret.push_back(to_port_info<Input>(client, port));
}
i++;
}
@@ -132,7 +138,7 @@ struct jack_helpers : jack_client
{.token = this_instance,
.callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {
if (auto pt = p.lock())
if (auto ppt = pt->load())
if (pt->load())
self.process(nf);
self.thread_lock.check_client_released();
@@ -192,8 +198,8 @@ struct jack_helpers : jack_client
self.client_open_ = std::errc::not_connected;
}
stdx::error
create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)
stdx::error create_local_port(
const auto& self, std::string_view portName, const char* type, JackPortFlags flags)
{
// full name: "client_name:port_name\0"
if (portName.empty())
@@ -209,8 +215,7 @@ struct jack_helpers : jack_client
if (!this->port)
{
this->port
= jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0);
this->port = jack_port_register(this->client, portName.data(), type, flags, 0);
}
if (!this->port)
@@ -238,4 +243,77 @@ struct jack_helpers : jack_client
return from_errc(err);
}
};
struct jack_queue
{
public:
static constexpr auto size_sz = sizeof(int32_t);
jack_queue() = default;
jack_queue(const jack_queue&) = delete;
jack_queue(jack_queue&&) = delete;
jack_queue& operator=(const jack_queue&) = delete;
jack_queue& operator=(jack_queue&& other) noexcept
{
ringbuffer = other.ringbuffer;
ringbuffer_space = other.ringbuffer_space;
other.ringbuffer = nullptr;
return *this;
}
explicit jack_queue(int64_t sz) noexcept
{
ringbuffer = jack_ringbuffer_create(sz);
ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);
}
~jack_queue() noexcept
{
if (ringbuffer)
jack_ringbuffer_free(ringbuffer);
}
stdx::error write(const unsigned char* data, int64_t sz) const noexcept
{
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
return std::errc::no_buffer_space;
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
sched_yield();
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
return stdx::error{};
}
void read(void* jack_events) const noexcept
{
int32_t sz;
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
{
jack_ringbuffer_read_advance(ringbuffer, size_sz);
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
else
jack_ringbuffer_read_advance(ringbuffer, sz);
}
}
jack_ringbuffer_t* ringbuffer{};
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
};
struct jack_midi1
{
static constexpr const char* port_type = "8 bit raw midi";
};
struct jack_midi2
{
static constexpr const char* port_type = "32 bit raw UMP";
};
}
+6 -6
View File
@@ -4,13 +4,12 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <chrono>
namespace libremidi
{
class midi_in_jack final
: public midi1::in_api
, public jack_helpers
, public jack_midi1
, public error_handler
{
public:
@@ -46,7 +45,8 @@ public:
stdx::error open_port(const input_port& port, std::string_view portName) override
{
if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{})
if (auto err = create_local_port(*this, portName, port_type, JackPortIsInput);
err != stdx::error{})
return err;
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
@@ -62,7 +62,7 @@ public:
stdx::error open_virtual_port(std::string_view portName) override
{
return create_local_port(*this, portName, JackPortIsInput);
return create_local_port(*this, portName, port_type, JackPortIsInput);
}
stdx::error close_port() override { return do_close_port(); }
@@ -96,8 +96,8 @@ public:
this->client, &current_frames, &current_usecs, &next_usecs, &period_usecs);
// We have midi events in buffer
uint32_t evCount = jack_midi_get_event_count(buff);
for (uint32_t j = 0; j < evCount; j++)
uint32_t ev_count = jack_midi_get_event_count(buff);
for (uint32_t j = 0; j < ev_count; j++)
{
jack_midi_event_t event{};
jack_midi_event_get(&event, buff, j);
+8 -71
View File
@@ -3,76 +3,12 @@
#include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <semaphore>
namespace libremidi
{
struct jack_queue
{
public:
static constexpr auto size_sz = sizeof(int32_t);
jack_queue() = default;
jack_queue(const jack_queue&) = delete;
jack_queue(jack_queue&&) = delete;
jack_queue& operator=(const jack_queue&) = delete;
jack_queue& operator=(jack_queue&& other) noexcept
{
ringbuffer = other.ringbuffer;
ringbuffer_space = other.ringbuffer_space;
other.ringbuffer = nullptr;
return *this;
}
explicit jack_queue(int64_t sz) noexcept
{
ringbuffer = jack_ringbuffer_create(sz);
ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);
}
~jack_queue() noexcept
{
if (ringbuffer)
jack_ringbuffer_free(ringbuffer);
}
stdx::error write(const unsigned char* data, int64_t sz) const noexcept
{
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
return std::errc::no_buffer_space;
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
sched_yield();
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
return stdx::error{};
}
void read(void* jack_events) const noexcept
{
int32_t sz;
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
{
jack_ringbuffer_read_advance(ringbuffer, size_sz);
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
else
jack_ringbuffer_read_advance(ringbuffer, sz);
}
}
jack_ringbuffer_t* ringbuffer{};
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
};
class midi_out_jack
: public midi1::out_api
, public jack_helpers
, public jack_midi1
, public error_handler
{
public:
@@ -94,7 +30,8 @@ public:
stdx::error open_port(const output_port& port, std::string_view portName) override
{
if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{})
if (auto err = create_local_port(*this, portName, port_type, JackPortIsOutput);
err != stdx::error{})
return err;
// Connecting to the output
@@ -111,7 +48,7 @@ public:
stdx::error open_virtual_port(std::string_view portName) override
{
return create_local_port(*this, portName, JackPortIsOutput);
return create_local_port(*this, portName, port_type, JackPortIsOutput);
}
stdx::error close_port() override { return do_close_port(); }
@@ -128,7 +65,7 @@ class midi_out_jack_queued final : public midi_out_jack
public:
midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf)
: midi_out_jack{std::move(conf), std::move(apiconf)}
, queue{configuration.ringbuffer_size}
, m_queue{configuration.ringbuffer_size}
{
auto status = connect(*this);
if (!this->client)
@@ -150,7 +87,7 @@ public:
stdx::error send_message(const unsigned char* message, std::size_t size) override
{
return queue.write(message, size);
return m_queue.write(message, size);
}
int process(jack_nframes_t nframes)
@@ -158,13 +95,13 @@ public:
void* buff = jack_port_get_buffer(this->port, nframes);
jack_midi_clear_buffer(buff);
this->queue.read(buff);
this->m_queue.read(buff);
return 0;
}
private:
jack_queue queue;
jack_queue m_queue;
};
class midi_out_jack_direct final : public midi_out_jack
+6 -7
View File
@@ -10,6 +10,7 @@ namespace libremidi
class observer_jack final
: public observer_api
, private jack_client
, public jack_midi1
, private error_handler
{
public:
@@ -48,8 +49,7 @@ public:
void initial_callback()
{
{
const char** ports
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
if (ports != nullptr)
{
@@ -80,8 +80,7 @@ public:
}
{
const char** ports
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
if (ports != nullptr)
{
@@ -119,7 +118,7 @@ public:
if (reg)
{
std::string_view type = jack_port_type(port);
if (type != JACK_DEFAULT_MIDI_TYPE)
if (type != port_type)
return;
bool physical = flags & JackPortIsPhysical;
@@ -199,12 +198,12 @@ public:
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<true>(this->client, nullptr, JackPortIsOutput);
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, false);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<false>(this->client, nullptr, JackPortIsInput);
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, false);
}
~observer_jack()
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <libremidi/backends/jack_ump/midi_in.hpp>
#include <libremidi/backends/jack_ump/midi_out.hpp>
#include <libremidi/backends/jack_ump/observer.hpp>
#include <string_view>
namespace libremidi::jack_ump
{
struct backend
{
using midi_in = midi_in_jack;
using midi_out = midi_out_jack;
using midi_observer = observer_jack;
using midi_in_configuration = jack_ump::input_configuration;
using midi_out_configuration = jack_ump::output_configuration;
using midi_observer_configuration = jack_ump::observer_configuration;
static const constexpr auto API = libremidi::API::JACK_UMP;
static const constexpr std::string_view name = "jack_ump";
static const constexpr std::string_view display_name = "JACK (UMP)";
static inline bool available() noexcept
{
#if LIBREMIDI_WEAKJACK
if (WeakJack::instance().available() != 0)
return false;
#endif
int major, minor, micro, patch;
jack_get_version(&major, &minor, &micro, &patch);
return (major >= 3 && minor >= 1 && micro >= 4);
}
};
}
@@ -0,0 +1,33 @@
#pragma once
#include <libremidi/backends/jack/config.hpp>
namespace libremidi::jack_ump
{
struct input_configuration
{
std::string client_name = "libremidi client";
jack_client_t* context{};
std::function<void(libremidi::jack_callback)> set_process_func{};
std::function<void(int64_t)> clear_process_func{};
};
struct output_configuration
{
std::string client_name = "libremidi client";
jack_client_t* context{};
std::function<void(libremidi::jack_callback)> set_process_func{};
std::function<void(int64_t)> clear_process_func{};
int32_t ringbuffer_size = 16384;
bool direct = false;
};
struct observer_configuration
{
std::string client_name = "libremidi client";
jack_client_t* context{};
};
}
@@ -0,0 +1,119 @@
#pragma once
#include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/backends/jack_ump/config.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi::jack_ump
{
class midi_in_jack final
: public midi2::in_api
, public jack_helpers
, private jack_midi1
, public error_handler
{
public:
using midi_api::client_open_;
struct
: libremidi::ump_input_configuration
, jack_ump::input_configuration
{
} configuration;
explicit midi_in_jack(
libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
auto status = connect(*this);
if (!this->client)
{
libremidi_handle_error(configuration, "Could not create JACK client");
client_open_ = from_jack_status(status);
return;
}
client_open_ = stdx::error{};
}
~midi_in_jack() override
{
midi_in_jack::close_port();
disconnect(*this);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
stdx::error open_port(const input_port& port, std::string_view portName) override
{
if (auto err
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
err != stdx::error{})
return err;
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
err != 0 && err != EEXIST)
{
libremidi_handle_error(
configuration,
"could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port));
return from_errc(err);
}
return stdx::error{};
}
stdx::error open_virtual_port(std::string_view portName) override
{
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
}
stdx::error close_port() override { return do_close_port(); }
stdx::error set_port_name(std::string_view portName) override
{
int ret = jack_port_rename(this->client, this->port, portName.data());
return from_errc(ret);
}
timestamp absolute_timestamp() const noexcept override
{
return 1000 * jack_frames_to_time(client, jack_frame_time(client));
}
int process(jack_nframes_t nframes)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = true,
.has_samples = true,
};
void* buff = jack_port_get_buffer(this->port, nframes);
// Timing
jack_nframes_t current_frames{};
jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC
jack_time_t next_usecs{};
float period_usecs{};
jack_get_cycle_times(
this->client, &current_frames, &current_usecs, &next_usecs, &period_usecs);
// We have midi events in buffer
uint32_t ev_count = jack_midi_get_event_count(buff);
for (uint32_t j = 0; j < ev_count; j++)
{
jack_midi_event_t event{};
jack_midi_event_get(&event, buff, j);
const auto to_ns
= [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); };
m_processing.on_bytes(
{(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)},
m_processing.timestamp<timestamp_info>(to_ns, event.time));
}
return 0;
}
midi2::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,184 @@
#pragma once
#include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/backends/jack_ump/config.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi::jack_ump
{
class midi_out_jack
: public midi2::out_api
, public jack_helpers
, private jack_midi1
, public error_handler
{
public:
using midi_api::client_open_;
struct
: libremidi::output_configuration
, jack_ump::output_configuration
{
} configuration;
midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
~midi_out_jack() override { }
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
stdx::error open_port(const output_port& port, std::string_view portName) override
{
if (auto err
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
err != stdx::error{})
return err;
// Connecting to the output
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
err != 0 && err != EEXIST)
{
libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
return from_errc(err);
}
return stdx::error{};
}
stdx::error open_virtual_port(std::string_view portName) override
{
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
}
stdx::error close_port() override { return do_close_port(); }
stdx::error set_port_name(std::string_view portName) override
{
int ret = jack_port_rename(this->client, this->port, portName.data());
return from_errc(ret);
}
};
class midi_out_jack_queued final : public midi_out_jack
{
public:
midi_out_jack_queued(
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
: midi_out_jack{std::move(conf), std::move(apiconf)}
, m_queue{configuration.ringbuffer_size}
{
auto status = connect(*this);
if (!this->client)
{
libremidi_handle_error(configuration, "Could not create JACK client");
client_open_ = from_jack_status(status);
return;
}
client_open_ = stdx::error{};
}
~midi_out_jack_queued() override
{
midi_out_jack::close_port();
disconnect(*this);
}
stdx::error send_ump(const uint32_t* message, std::size_t size) override
{
return m_queue.write((unsigned char*)message, size * sizeof(uint32_t));
}
int process(jack_nframes_t nframes)
{
void* buff = jack_port_get_buffer(this->port, nframes);
jack_midi_clear_buffer(buff);
this->m_queue.read(buff);
return 0;
}
private:
libremidi::jack_queue m_queue;
};
class midi_out_jack_direct final : public midi_out_jack
{
public:
midi_out_jack_direct(
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
: midi_out_jack{std::move(conf), std::move(apiconf)}
{
auto status = connect(*this);
if (!this->client)
{
libremidi_handle_error(configuration, "Could not create JACK client");
client_open_ = from_jack_status(status);
return;
}
buffer_size = jack_get_buffer_size(this->client);
client_open_ = stdx::error{};
}
~midi_out_jack_direct() override
{
midi_out_jack::close_port();
disconnect(*this);
}
int process(jack_nframes_t nframes)
{
void* buff = jack_port_get_buffer(this->port, nframes);
jack_midi_clear_buffer(buff);
return 0;
}
stdx::error send_ump(const uint32_t* message, std::size_t size) override
{
void* buff = jack_port_get_buffer(this->port, buffer_size);
int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t));
return from_errc(ret);
}
int convert_timestamp(int64_t user) const noexcept
{
switch (configuration.timestamps)
{
case timestamp_mode::AudioFrame:
return static_cast<int>(user);
default:
// TODO
return 0;
}
}
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
{
void* buff = jack_port_get_buffer(this->port, buffer_size);
int ret = jack_midi_event_write(
buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t));
return from_errc(ret);
}
int buffer_size{};
};
}
namespace libremidi
{
template <>
inline std::unique_ptr<midi_out_api>
make<jack_ump::midi_out_jack>(output_configuration&& conf, jack_ump::output_configuration&& api)
{
if (api.direct)
return std::make_unique<jack_ump::midi_out_jack_direct>(std::move(conf), std::move(api));
else
return std::make_unique<jack_ump::midi_out_jack_queued>(std::move(conf), std::move(api));
}
}
@@ -0,0 +1,235 @@
#pragma once
#include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/backends/jack_ump/config.hpp>
#include <libremidi/detail/observer.hpp>
#include <unordered_set>
namespace libremidi::jack_ump
{
class observer_jack final
: public observer_api
, private jack_client
, private jack_midi1 // seems like a bug in PipeWire? MIDI 2 flag is set but the MIDI 1 type string is used
, private error_handler
{
public:
struct
: libremidi::observer_configuration
, jack_ump::observer_configuration
{
} configuration;
explicit observer_jack(
libremidi::observer_configuration&& conf, jack_ump::observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
// Initialize JACK client
if (configuration.context)
{
this->client = configuration.context;
set_callbacks();
}
else
{
jack_status_t status{};
this->client
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
if (status != jack_status_t{})
libremidi_handle_error(configuration, std::to_string((int)status));
if (this->client != nullptr)
{
set_callbacks();
jack_activate(this->client);
}
}
}
void initial_callback()
{
{
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
if (ports != nullptr)
{
int i = 0;
while (ports[i] != nullptr)
{
const auto port = jack_port_by_name(client, ports[i]);
const auto flags = jack_port_flags(port);
if (!(flags & 0x20)) // midi 2 check
{
i++;
continue;
}
bool physical = flags & JackPortIsPhysical;
bool ok = configuration.track_any;
if (configuration.track_hardware)
ok |= physical;
if (configuration.track_virtual)
ok |= !physical;
if (ok)
{
seen_input_ports.insert(ports[i]);
if (this->configuration.input_added && configuration.notify_in_constructor)
this->configuration.input_added(to_port_info<true>(client, port));
}
i++;
}
}
jack_free(ports);
}
{
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
if (ports != nullptr)
{
int i = 0;
while (ports[i] != nullptr)
{
const auto port = jack_port_by_name(client, ports[i]);
const auto flags = jack_port_flags(port);
if (!(flags & 0x20)) // midi 2 check
{
i++;
continue;
}
bool physical = flags & JackPortIsPhysical;
bool ok = configuration.track_any;
if (configuration.track_hardware)
ok |= physical;
if (configuration.track_virtual)
ok |= !physical;
if (ok)
{
seen_output_ports.insert(ports[i]);
if (this->configuration.output_added && configuration.notify_in_constructor)
this->configuration.output_added(to_port_info<false>(client, port));
}
i++;
}
}
jack_free(ports);
}
}
void on_port_callback(jack_port_t* port, bool reg)
{
auto flags = jack_port_flags(port);
std::string name = jack_port_name(port);
if (reg)
{
std::string_view type = jack_port_type(port);
if (type != port_type)
return;
if (!(flags & 0x20)) // midi 2 check
return;
bool physical = flags & JackPortIsPhysical;
bool ok = configuration.track_any;
if (configuration.track_hardware)
ok |= physical;
if (configuration.track_virtual)
ok |= !physical;
if (!ok)
return;
// Note: we keep track of the ports as
// when disconnecting, jack_port_type and jack_port_flags aren't correctly
// set anymore.
if (flags & JackPortIsOutput)
{
seen_input_ports.insert(name);
if (this->configuration.input_added)
this->configuration.input_added(to_port_info<true>(client, port));
}
else if (flags & JackPortIsInput)
{
seen_output_ports.insert(name);
if (this->configuration.output_added)
this->configuration.output_added(to_port_info<false>(client, port));
}
}
else
{
if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())
{
if (this->configuration.input_removed)
this->configuration.input_removed(to_port_info<true>(client, port));
seen_input_ports.erase(it);
}
if (auto it = seen_output_ports.find(name); it != seen_output_ports.end())
{
if (this->configuration.output_removed)
this->configuration.output_removed(to_port_info<false>(client, port));
seen_output_ports.erase(it);
}
}
}
void set_callbacks()
{
initial_callback();
if (!configuration.has_callbacks())
return;
jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) {
auto& self = *(observer_jack*)arg;
if (auto port = jack_port_by_id(self.client, p))
{
self.on_port_callback(port, r != 0);
}
}, this);
jack_set_port_rename_callback(
this->client,
+[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) {
const auto& self = *static_cast<observer_jack*>(arg);
auto port = jack_port_by_id(self.client, p);
if (!port)
return;
}, this);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, true);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, true);
}
~observer_jack()
{
if (client && !configuration.context)
{
// If we own the client, deactivate it
jack_deactivate(this->client);
jack_client_close(this->client);
this->client = nullptr;
}
}
std::unordered_set<std::string> seen_input_ports;
std::unordered_set<std::string> seen_output_ports;
};
}
+4 -2
View File
@@ -2,6 +2,8 @@
#include <libremidi/backends/dummy.hpp>
#include <libremidi/backends/keyboard/midi_in.hpp>
#include <string_view>
namespace libremidi
{
struct kbd_backend
@@ -13,8 +15,8 @@ struct kbd_backend
using midi_out_configuration = dummy_configuration;
using midi_observer_configuration = dummy_configuration;
static const constexpr auto API = libremidi::API::KEYBOARD;
static const constexpr auto name = "keyboard";
static const constexpr auto display_name = "Computer keyboard";
static const constexpr std::string_view name = "keyboard";
static const constexpr std::string_view display_name = "Computer keyboard";
static inline bool available() noexcept { return true; }
};
+133 -71
View File
@@ -6,6 +6,128 @@
namespace libremidi
{
enum kbd_event
{
NOTE_0 = 0x0, // C
VEL_0 = NOTE_0 + 128, // Set velocity to 0
OCT_0 = VEL_0 + 128, // Set octave to 0
OCTAVE_PLUS = OCT_0 + 128,
OCTAVE_MINUS,
VELOCITY_PLUS,
VELOCITY_MINUS,
};
// Default map
//
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
// | ctrl | | alt | |altgr | | ctrl |
// '------' '-----'--------------------------'------' '------'
// clang-format off
// 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<int, int>& scancode_map_macos(){
static const std::map<int, int> ret{
{ 0, NOTE_0}, // C0
{ 13, NOTE_0 + 1},
{ 1, NOTE_0 + 2},
{ 14, NOTE_0 + 3},
{ 2, NOTE_0 + 4},
{ 3, NOTE_0 + 5},
{ 17, NOTE_0 + 6},
{ 5, NOTE_0 + 7},
{ 16, NOTE_0 + 8},
{ 4, NOTE_0 + 9},
{ 32, NOTE_0 + 10},
{ 38, NOTE_0 + 11},
{ 40, NOTE_0 + 12}, // C1
{ 31, NOTE_0 + 13},
{ 37, NOTE_0 + 14},
{ 35, NOTE_0 + 15},
{ 41, NOTE_0 + 16},
{ 39, NOTE_0 + 17},
{ 30, NOTE_0 + 18},
{ 42, NOTE_0 + 19},
{ 50, VEL_0 + int(0)},
{ 18, VEL_0 + int(1 * 127 / 12)},
{ 19, VEL_0 + int(2 * 127 / 12)},
{ 20, VEL_0 + int(3 * 127 / 12)},
{ 21, VEL_0 + int(4 * 127 / 12)},
{ 23, VEL_0 + int(5 * 127 / 12)},
{ 22, VEL_0 + int(6 * 127 / 12)},
{ 26, VEL_0 + int(7 * 127 / 12)},
{ 28, VEL_0 + int(8 * 127 / 12)},
{ 25, VEL_0 + int(9 * 127 / 12)},
{ 29, VEL_0 + int(10 * 127 / 12)},
{ 27, VEL_0 + int(11 * 127 / 12)},
{ 24, VEL_0 + int(127)},
{ 6, OCTAVE_MINUS },
{ 7, OCTAVE_PLUS },
{ 8, VELOCITY_MINUS },
{ 9, VELOCITY_PLUS},
};
return ret;
}
// Use: https://kbdlayout.info/KBDUSX/scancodes
// Also valid for windows
static inline const std::map<int, int>& scancode_map_linux() {
static const std::map<int, int> ret{
{ 0x1E, NOTE_0}, // C0
{ 0x11, NOTE_0 + 1},
{ 0x1F, NOTE_0 + 2},
{ 0x12, NOTE_0 + 3},
{ 0x20, NOTE_0 + 4},
{ 0x21, NOTE_0 + 5},
{ 0x14, NOTE_0 + 6},
{ 0x22, NOTE_0 + 7},
{ 0x15, NOTE_0 + 8},
{ 0x23, NOTE_0 + 9},
{ 0x16, NOTE_0 + 10},
{ 0x24, NOTE_0 + 11},
{ 0x25, NOTE_0 + 12}, // C1
{ 0x18, NOTE_0 + 13},
{ 0x26, NOTE_0 + 14},
{ 0x19, NOTE_0 + 15},
{ 0x27, NOTE_0 + 16},
{ 0x28, NOTE_0 + 17},
{ 0x1B, NOTE_0 + 18},
{ 0x2B, NOTE_0 + 19},
{ 0x29, VEL_0 + int(0)},
{ 0x02, VEL_0 + int(1 * 127 / 12)},
{ 0x03, VEL_0 + int(2 * 127 / 12)},
{ 0x04, VEL_0 + int(3 * 127 / 12)},
{ 0x05, VEL_0 + int(4 * 127 / 12)},
{ 0x06, VEL_0 + int(5 * 127 / 12)},
{ 0x07, VEL_0 + int(6 * 127 / 12)},
{ 0x08, VEL_0 + int(7 * 127 / 12)},
{ 0x09, VEL_0 + int(8 * 127 / 12)},
{ 0x0A, VEL_0 + int(9 * 127 / 12)},
{ 0x0B, VEL_0 + int(10 * 127 / 12)},
{ 0x0C, VEL_0 + int(11 * 127 / 12)},
{ 0x0D, VEL_0 + int(127)},
{ 0x2C, OCTAVE_MINUS },
{ 0x2D, OCTAVE_PLUS },
{ 0x2E, VELOCITY_MINUS },
{ 0x2F, VELOCITY_PLUS},
};
return ret;
}
// clang-format on
/**
* Used to set up keyboard input.
* Your app should pass a function that will give you a callback
@@ -13,80 +135,20 @@ namespace libremidi
*/
struct kbd_input_configuration
{
// Note that on Linux with X11 scancodes should be offset by 8 (an X11 legacy quirk)
// ex.: pass QKeyEvent::nativeScanCode() - 8 to this API
using scancode_callback = std::function<void(int)>;
// First argument is on key press, second on key release
std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks
= [](scancode_callback, scancode_callback) {};
= [](const scancode_callback&, const scancode_callback&) { };
enum kbd_event
{
NOTE_0 = 0x0, // C
VEL_0 = NOTE_0 + 128, // Set velocity to 0
OCT_0 = VEL_0 + 128, // Set octave to 0
OCTAVE_PLUS = OCT_0 + 128,
OCTAVE_MINUS,
VELOCITY_PLUS,
VELOCITY_MINUS,
};
// Default map
//
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
// | ctrl | | alt | |altgr | | ctrl |
// '------' '-----'--------------------------'------' '------'
// clang-format off
// Use: https://kbdlayout.info/KBDUSX/scancodes
std::map<int, int> scancode_map{
{ 0x1E, NOTE_0}, // C0
{ 0x11, NOTE_0 + 1},
{ 0x1F, NOTE_0 + 2},
{ 0x12, NOTE_0 + 3},
{ 0x20, NOTE_0 + 4},
{ 0x21, NOTE_0 + 5},
{ 0x14, NOTE_0 + 6},
{ 0x22, NOTE_0 + 7},
{ 0x15, NOTE_0 + 8},
{ 0x23, NOTE_0 + 9},
{ 0x16, NOTE_0 + 10},
{ 0x24, NOTE_0 + 11},
{ 0x25, NOTE_0 + 12}, // C1
{ 0x18, NOTE_0 + 13},
{ 0x26, NOTE_0 + 14},
{ 0x19, NOTE_0 + 15},
{ 0x27, NOTE_0 + 16},
{ 0x28, NOTE_0 + 17},
{ 0x1B, NOTE_0 + 18},
{ 0x2B, NOTE_0 + 19},
{ 0x29, VEL_0 + int(0)},
{ 0x02, VEL_0 + int(1 * 127 / 12)},
{ 0x03, VEL_0 + int(2 * 127 / 12)},
{ 0x04, VEL_0 + int(3 * 127 / 12)},
{ 0x05, VEL_0 + int(4 * 127 / 12)},
{ 0x06, VEL_0 + int(5 * 127 / 12)},
{ 0x07, VEL_0 + int(6 * 127 / 12)},
{ 0x08, VEL_0 + int(7 * 127 / 12)},
{ 0x09, VEL_0 + int(8 * 127 / 12)},
{ 0x0A, VEL_0 + int(9 * 127 / 12)},
{ 0x0B, VEL_0 + int(10 * 127 / 12)},
{ 0x0C, VEL_0 + int(11 * 127 / 12)},
{ 0x0D, VEL_0 + int(127)},
{ 0x2C, OCTAVE_MINUS },
{ 0x2D, OCTAVE_PLUS },
{ 0x2E, VELOCITY_MINUS },
{ 0x2F, VELOCITY_PLUS},
};
// clang-format on
std::map<int, int> scancode_map
#if defined(__APPLE__)
= scancode_map_macos()
#else
= scancode_map_linux()
#endif
;
};
}
@@ -22,22 +22,22 @@ public:
explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
configuration.set_input_scancode_callbacks(
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
}
~midi_in_kbd() override { }
libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }
stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; }
stdx::error open_port(const input_port&, std::string_view) override
{
configuration.set_input_scancode_callbacks(
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; }
return stdx::error{};
}
stdx::error close_port() override { return stdx::error{}; }
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
@@ -45,7 +45,7 @@ public:
void on_keypress(int scancode)
{
using kevent = kbd_input_configuration::kbd_event;
using kevent = libremidi::kbd_event;
auto it = configuration.scancode_map.find(scancode);
if (it == configuration.scancode_map.end())
@@ -88,7 +88,7 @@ public:
void on_keyrelease(int scancode)
{
using kevent = kbd_input_configuration::kbd_event;
using kevent = libremidi::kbd_event;
auto it = configuration.scancode_map.find(scancode);
if (it == configuration.scancode_map.end())
+3 -1
View File
@@ -2,7 +2,7 @@
#include <libremidi/backends/linux/dylib_loader.hpp>
#include <alsa/asoundlib.h>
#include <alsa/asoundlib.h> // IWYU pragma: export
#if defined(SND_LIB_VERSION)
#if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)
@@ -241,6 +241,7 @@ struct libasound
LIBREMIDI_SYMBOL_INIT(snd_seq, client_id)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_card)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, close)
@@ -298,6 +299,7 @@ struct libasound
LIBREMIDI_SYMBOL_DEF(snd_seq, client_id)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_card)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, close)
+43 -34
View File
@@ -12,6 +12,8 @@ namespace libremidi
class libpipewire
{
public:
decltype(&::pw_get_library_version) get_library_version{};
decltype(&::pw_init) init{};
decltype(&::pw_deinit) deinit{};
@@ -67,12 +69,12 @@ public:
bool available{true};
private:
dylib_loader library;
dylib_loader m_library;
libpipewire()
: library("libpipewire-0.3.so.0")
: m_library("libpipewire-0.3.so.0")
{
if (!library)
if (!m_library)
{
available = false;
return;
@@ -81,52 +83,59 @@ private:
// in terms of regex:
// decltype\(&::([a-z_]+)\) [a-z_]+{};
// \1 = library.symbol<decltype(&::\1)>("\1");
init = library.symbol<decltype(&::pw_init)>("pw_init");
deinit = library.symbol<decltype(&::pw_deinit)>("pw_deinit");
get_library_version
= m_library.symbol<decltype(&::pw_get_library_version)>("pw_get_library_version");
context_new = library.symbol<decltype(&::pw_context_new)>("pw_context_new");
context_connect = library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");
context_destroy = library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
init = m_library.symbol<decltype(&::pw_init)>("pw_init");
deinit = m_library.symbol<decltype(&::pw_deinit)>("pw_deinit");
core_disconnect = library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");
context_new = m_library.symbol<decltype(&::pw_context_new)>("pw_context_new");
context_connect = m_library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");
context_destroy = m_library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
core_disconnect = m_library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");
proxy_add_listener
= library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");
= m_library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
proxy_destroy = m_library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");
main_loop_new = library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");
main_loop_destroy = library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");
main_loop_quit = library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");
main_loop_run = library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");
main_loop_new = m_library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");
main_loop_destroy
= m_library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");
main_loop_quit = m_library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");
main_loop_run = m_library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");
main_loop_get_loop
= library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");
= m_library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");
properties_new = library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
properties_free = library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
properties_get = library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");
properties_new = m_library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
properties_free = m_library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
properties_get = m_library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");
filter_new_simple = library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");
filter_new_simple
= m_library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");
filter_get_node_id
= library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");
= m_library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");
filter_get_properties
= library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");
filter_add_port = library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");
= m_library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");
filter_add_port = m_library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");
filter_remove_port
= library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");
filter_update_properties
= library.symbol<decltype(&::pw_filter_update_properties)>("pw_filter_update_properties");
= m_library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");
filter_update_properties = m_library.symbol<decltype(&::pw_filter_update_properties)>(
"pw_filter_update_properties");
filter_update_params
= library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");
filter_get_time = library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");
filter_destroy = library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");
filter_connect = library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");
= m_library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");
filter_get_time = m_library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");
filter_destroy = m_library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");
filter_connect = m_library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");
filter_get_dsp_buffer
= library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");
= m_library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");
filter_dequeue_buffer
= library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");
= m_library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");
filter_queue_buffer
= library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");
filter_flush = library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");
= m_library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");
filter_flush = m_library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");
assert(get_library_version);
assert(init);
assert(deinit);
+102
View File
@@ -1,10 +1,12 @@
#pragma once
#include <libremidi/backends/linux/dylib_loader.hpp>
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/observer_configuration.hpp>
#include <libudev.h>
#include <cassert>
#include <optional>
namespace libremidi
{
@@ -25,12 +27,28 @@ struct libudev
LIBREMIDI_SYMBOL_INIT(udev, device_get_action)
LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem)
LIBREMIDI_SYMBOL_INIT(udev, device_get_property_value)
LIBREMIDI_SYMBOL_INIT(udev, device_new_from_syspath)
LIBREMIDI_SYMBOL_INIT(udev, device_get_parent_with_subsystem_devtype)
LIBREMIDI_SYMBOL_INIT(udev, device_unref)
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref)
LIBREMIDI_SYMBOL_INIT(udev, enumerate_new)
LIBREMIDI_SYMBOL_INIT(udev, enumerate_unref)
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_is_initialized)
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_subsystem)
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_sysname)
LIBREMIDI_SYMBOL_INIT(udev, enumerate_scan_devices)
LIBREMIDI_SYMBOL_INIT(udev, enumerate_get_list_entry)
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_name)
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_next)
LIBREMIDI_SYMBOL_INIT2(udev, new, create)
LIBREMIDI_SYMBOL_INIT(udev, unref)
}
@@ -46,12 +64,28 @@ struct libudev
LIBREMIDI_SYMBOL_DEF(udev, device_get_action)
LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem)
LIBREMIDI_SYMBOL_DEF(udev, device_new_from_syspath)
LIBREMIDI_SYMBOL_DEF(udev, device_get_property_value)
LIBREMIDI_SYMBOL_DEF(udev, device_get_parent_with_subsystem_devtype)
LIBREMIDI_SYMBOL_DEF(udev, device_unref)
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref)
LIBREMIDI_SYMBOL_DEF(udev, enumerate_new)
LIBREMIDI_SYMBOL_DEF(udev, enumerate_unref)
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_is_initialized)
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_subsystem)
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_sysname)
LIBREMIDI_SYMBOL_DEF(udev, enumerate_scan_devices)
LIBREMIDI_SYMBOL_DEF(udev, enumerate_get_list_entry)
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_name)
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_next)
LIBREMIDI_SYMBOL_DEF2(udev, new, create)
LIBREMIDI_SYMBOL_DEF(udev, unref)
};
@@ -89,4 +123,72 @@ struct udev_helper
udev_monitor* monitor{};
};
struct udev_enumerator
{
explicit udev_enumerator(const udev_helper& h)
: helper{h}
{
auto& udev = helper.udev;
auto& instance = helper.instance;
enumerator = udev.enumerate_new(instance);
assert(enumerator);
}
~udev_enumerator()
{
auto& udev = helper.udev;
udev.enumerate_unref(enumerator);
}
const udev_helper& helper;
udev_enumerate* enumerator{};
operator udev_enumerate*() const noexcept { return enumerator; }
};
struct udev_soundcard_info
{
container_identifier container;
device_identifier path;
port_information::port_type type{};
};
inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card)
{
auto& udev = helper.udev;
udev_enumerator e{helper};
udev.enumerate_add_match_subsystem(e, "sound");
udev.enumerate_add_match_sysname(e, ("controlC" + std::to_string(card)).c_str());
if (udev.enumerate_scan_devices(e) != 0)
return {};
for (auto entry = udev.enumerate_get_list_entry(e); entry;
entry = udev.list_entry_get_next(entry))
{
auto path = udev.list_entry_get_name(entry);
if (!path)
continue;
auto dev = udev.device_new_from_syspath(helper.instance, path);
if (!dev)
continue;
const auto usb_device
= udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
port_information::port_type type = port_information::port_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);
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};
udev.device_unref(dev);
return ret;
}
return {};
}
}
+15 -12
View File
@@ -42,31 +42,34 @@ struct optionally_owned
{
public:
explicit optionally_owned(T* maybe_existing)
: storage{.ref = maybe_existing}
: m_storage{.ref = maybe_existing}
{
if (storage.ref)
if (m_storage.ref)
{
ownership = unowned;
m_ownership = unowned;
return;
}
else
{
std::destroy_at(&storage.ref);
std::construct_at<T>(reinterpret_cast<T*>(storage.object));
ownership = owned;
std::destroy_at(&m_storage.ref);
std::construct_at<T>(reinterpret_cast<T*>(m_storage.object));
m_ownership = owned;
}
}
~optionally_owned()
{
if (is_owned())
std::destroy_at(reinterpret_cast<T*>(storage.object));
std::destroy_at(reinterpret_cast<T*>(m_storage.object));
}
T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); }
T& get() noexcept
{
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
}
const T& get() const noexcept
{
return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref);
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
}
optionally_owned(const optionally_owned&) = delete;
@@ -74,18 +77,18 @@ public:
optionally_owned& operator=(const optionally_owned&) = delete;
optionally_owned& operator=(optionally_owned&&) noexcept = delete;
bool is_owned() const noexcept { return ownership == owned; }
bool is_owned() const noexcept { return m_ownership == owned; }
private:
union
{
alignas(T) unsigned char object[sizeof(T)];
T* ref;
} storage;
} m_storage;
enum
{
owned,
unowned
} ownership{};
} m_ownership{};
};
}
+1 -4
View File
@@ -4,6 +4,7 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <thread>
namespace libremidi
{
template <typename Impl, typename F>
@@ -219,8 +220,6 @@ public:
return {};
}
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
@@ -394,8 +393,6 @@ public:
return {};
}
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
+4 -2
View File
@@ -5,6 +5,8 @@
#include <libremidi/backends/net/midi_out.hpp>
#include <libremidi/backends/net/observer.hpp>
#include <string_view>
namespace libremidi::net
{
struct backend
@@ -16,8 +18,8 @@ struct backend
using midi_out_configuration = libremidi::net::dgram_output_configuration;
using midi_observer_configuration = libremidi::net::net_observer_configuration;
static const constexpr auto API = libremidi::API::NETWORK;
static const constexpr auto name = "network";
static const constexpr auto display_name = "Network";
static const constexpr std::string_view name = "network";
static const constexpr std::string_view display_name = "Network";
static inline bool available() noexcept { return true; }
};
+4 -2
View File
@@ -4,6 +4,8 @@
#include <libremidi/backends/net/midi_out.hpp>
#include <libremidi/backends/net/observer.hpp>
#include <string_view>
namespace libremidi::net_ump
{
struct backend
@@ -15,8 +17,8 @@ struct backend
using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;
using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;
static const constexpr auto API = libremidi::API::NETWORK_UMP;
static const constexpr auto name = "network (UMP)";
static const constexpr auto display_name = "Network (UMP)";
static const constexpr std::string_view name = "network (UMP)";
static const constexpr std::string_view display_name = "Network (UMP)";
static inline bool available() noexcept { return true; }
};
+4 -2
View File
@@ -7,6 +7,8 @@
#include <libremidi/backends/pipewire/midi_out.hpp>
#include <libremidi/backends/pipewire/observer.hpp>
#include <string_view>
namespace libremidi::pipewire
{
struct backend
@@ -18,8 +20,8 @@ struct backend
using midi_out_configuration = pipewire_output_configuration;
using midi_observer_configuration = pipewire_observer_configuration;
static const constexpr auto API = libremidi::API::PIPEWIRE;
static const constexpr auto name = "pipewire";
static const constexpr auto display_name = "PipeWire";
static const constexpr std::string_view name = "pipewire";
static const constexpr std::string_view display_name = "PipeWire";
static inline bool available() noexcept
{
+18 -12
View File
@@ -18,6 +18,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <spa/param/audio/format-utils.h>
@@ -79,11 +80,11 @@ struct pipewire_context
struct graph
{
mutable std::mutex mtx;
libremidi::hash_map<uint32_t, node> physical_audio;
libremidi::hash_map<uint32_t, node> physical_midi;
libremidi::hash_map<uint32_t, node> software_audio;
libremidi::hash_map<uint32_t, node> software_midi;
libremidi::hash_map<uint32_t, port_info> port_cache;
libremidi::hash_map<uint64_t, node> physical_audio;
libremidi::hash_map<uint64_t, node> physical_midi;
libremidi::hash_map<uint64_t, node> software_audio;
libremidi::hash_map<uint64_t, node> software_midi;
libremidi::hash_map<uint64_t, port_info> port_cache;
void for_each_port(auto func)
{
@@ -123,7 +124,7 @@ struct pipewire_context
}
explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)
: global_instance{inst}
: global_instance{std::move(inst)}
, owns_main_loop{true}
{
this->main_loop = pw.main_loop_new(nullptr);
@@ -287,7 +288,7 @@ struct pipewire_context
spa_hook_remove(&core_listener);
}
[[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_t in_port)
[[nodiscard]] pw_proxy* link_ports(uint64_t out_port, uint64_t in_port)
{
auto props = pw.properties_new(
PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT,
@@ -361,6 +362,8 @@ struct pipewire_context
return &this->current_graph.physical_audio[nid];
else if (p.format.find("midi") != p.format.npos)
return &this->current_graph.physical_midi[nid];
else if (p.format.find("UMP") != p.format.npos)
return &this->current_graph.physical_midi[nid];
}
else
{
@@ -368,6 +371,8 @@ struct pipewire_context
return &this->current_graph.software_audio[nid];
else if (p.format.find("midi") != p.format.npos)
return &this->current_graph.software_midi[nid];
else if (p.format.find("UMP") != p.format.npos)
return &this->current_graph.software_midi[nid];
}
return nullptr;
};
@@ -453,12 +458,12 @@ struct pipewire_filter
}* port{};
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop)
: loop{loop}
: loop{std::move(loop)}
{
}
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)
: loop{loop}
: loop{std::move(loop)}
, filter{filter}
{
}
@@ -498,7 +503,8 @@ struct pipewire_filter
pw.filter_destroy(this->filter);
}
stdx::error create_local_port(std::string_view port_name, spa_direction direction)
stdx::error
create_local_port(std::string_view port_name, spa_direction direction, const char* format)
{
// clang-format off
this->port = (struct port*)pw.filter_add_port(
@@ -507,7 +513,7 @@ struct pipewire_filter
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(struct port),
pw.properties_new(
PW_KEY_FORMAT_DSP, "8 bit raw midi",
PW_KEY_FORMAT_DSP, format,
PW_KEY_PORT_NAME, port_name.data(),
nullptr),
nullptr, 0);
@@ -517,7 +523,7 @@ struct pipewire_filter
return stdx::error{};
}
void set_port_buffer(int bytes)
void set_port_buffer(int64_t bytes)
{
uint8_t buffer[1024];
struct spa_pod_builder builder;
+20 -17
View File
@@ -9,8 +9,6 @@
#include <libremidi/detail/semaphore.hpp>
#include <atomic>
#include <semaphore>
#include <stop_token>
#include <thread>
namespace libremidi
@@ -66,14 +64,14 @@ struct pipewire_helpers
{
this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);
pipewire_callback cbs{
libremidi::pipewire_callback cbs{
.token = this_instance,
.callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void {
if (auto pt = p.lock())
self.process(nf);
if (auto pt = p.lock())
self.process(nf);
self.thread_lock.check_client_released();
}};
self.thread_lock.check_client_released();
}};
configuration.set_process_func(cbs);
}
else
@@ -197,7 +195,8 @@ struct pipewire_helpers
}
template <typename Self>
stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction)
stdx::error create_local_port(
Self& self, std::string_view portName, spa_direction direction, const char* format)
{
assert(this->global_context);
assert(this->filter);
@@ -207,7 +206,7 @@ struct pipewire_helpers
if (!this->filter->port)
{
auto ret = this->filter->create_local_port(portName.data(), direction);
auto ret = this->filter->create_local_port(portName.data(), direction, format);
if (ret != stdx::error{})
{
self.libremidi_handle_error(self.configuration, "error creating port");
@@ -218,11 +217,11 @@ struct pipewire_helpers
return stdx::error{};
}
void add_callbacks(const observer_configuration& conf)
void add_callbacks(std::string format, const observer_configuration& conf)
{
assert(global_context);
global_context->on_port_added = [&conf](const pipewire_context::port_info& port) {
if (port.format.find("midi") == std::string::npos)
global_context->on_port_added = [format, &conf](const pipewire_context::port_info& port) {
if (port.format.find(format) == std::string::npos)
return;
bool unfiltered = conf.track_any;
@@ -243,8 +242,8 @@ struct pipewire_helpers
}
};
global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) {
if (port.format.find("midi") == std::string::npos)
global_context->on_port_removed = [format, &conf](const pipewire_context::port_info& port) {
if (port.format.find(format) == std::string::npos)
return;
bool unfiltered = conf.track_any;
@@ -437,7 +436,9 @@ 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 <spa_direction Direction>
static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept
static auto get_ports(
std::string_view format, const observer_configuration& conf,
const pipewire_context& ctx) noexcept
-> std::vector<
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
{
@@ -452,7 +453,8 @@ struct pipewire_helpers
for (auto& port :
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{
ret.push_back(to_port_info<Direction>(port));
if (port.format.find(format) != std::string::npos)
ret.push_back(to_port_info<Direction>(port));
}
}
@@ -462,7 +464,8 @@ struct pipewire_helpers
for (auto& port :
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{
ret.push_back(to_port_info<Direction>(port));
if (port.format.find(format) != std::string::npos)
ret.push_back(to_port_info<Direction>(port));
}
}
}
@@ -47,7 +47,8 @@ public:
stdx::error open_port(const input_port& in_port, std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi");
err != stdx::error{})
return err;
if (auto err = link_ports(*this, in_port); err != stdx::error{})
@@ -59,7 +60,8 @@ public:
stdx::error open_virtual_port(std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi");
err != stdx::error{})
return err;
start_thread();
@@ -5,8 +5,6 @@
#include <readerwriterqueue.h>
#include <semaphore>
namespace libremidi
{
class midi_out_pipewire
@@ -50,7 +48,8 @@ public:
stdx::error open_port(const output_port& out_port, std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi");
err != stdx::error{})
return err;
this->filter->set_port_buffer(configuration.output_buffer_size);
@@ -64,7 +63,8 @@ public:
stdx::error open_virtual_port(std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi");
err != stdx::error{})
return err;
this->filter->set_port_buffer(configuration.output_buffer_size);
@@ -121,8 +121,9 @@ public:
continue;
}
spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi);
int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size());
spa_pod_builder_control(&build, static_cast<int32_t>(m.timestamp), SPA_CONTROL_Midi);
int res
= spa_pod_builder_bytes(&build, m.bytes.data(), static_cast<uint32_t>(m.bytes.size()));
// Try again next buffer
if (res == -ENOSPC)
@@ -24,19 +24,9 @@ public:
{
create_context(*this);
// FIXME notify_in_constructor
// FIXME port rename callback
#if 0
// Initialize PipeWire client
if (configuration.context)
{
this->client = configuration.context;
set_callbacks();
}
else
#endif
{
this->add_callbacks(configuration);
this->add_callbacks("midi", configuration);
this->start_thread();
}
@@ -56,27 +46,18 @@ public:
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_OUTPUT>(this->configuration, *this->global_context);
return get_ports<SPA_DIRECTION_OUTPUT>("midi", this->configuration, *this->global_context);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_INPUT>(this->configuration, *this->global_context);
return get_ports<SPA_DIRECTION_INPUT>("midi", this->configuration, *this->global_context);
}
~observer_pipewire()
{
stop_thread();
destroy_context();
#if 0
if (client && !configuration.context)
{
// If we own the client, deactivate it
pipewire_deactivate(this->client);
pipewire_client_close(this->client);
this->client = nullptr;
}
#endif
}
std::unordered_set<std::string> seen_input_ports;
@@ -0,0 +1,39 @@
#pragma once
#include <libremidi/backends/dummy.hpp>
#include <libremidi/backends/linux/pipewire.hpp>
#include <libremidi/backends/pipewire_ump/config.hpp>
#include <libremidi/backends/pipewire_ump/midi_in.hpp>
#include <libremidi/backends/pipewire_ump/midi_out.hpp>
#include <libremidi/backends/pipewire_ump/observer.hpp>
#include <string_view>
namespace libremidi::pipewire_ump
{
struct backend
{
using midi_in = libremidi::pipewire_ump::midi_in_pipewire;
using midi_out = libremidi::pipewire_ump::midi_out_pipewire;
using midi_observer = libremidi::pipewire_ump::observer_pipewire;
using midi_in_configuration = libremidi::pipewire_ump::input_configuration;
using midi_out_configuration = libremidi::pipewire_ump::output_configuration;
using midi_observer_configuration = libremidi::pipewire_ump::observer_configuration;
static const constexpr auto API = libremidi::API::PIPEWIRE_UMP;
static const constexpr std::string_view name = "pipewire_ump";
static const constexpr std::string_view display_name = "PipeWire (UMP)";
static inline bool available() noexcept
{
static const libpipewire& pw = libpipewire::instance();
if (!pw.available)
return false;
const std::string_view version = pw.get_library_version();
if (version.size() >= 3)
{
if (version[0] > '1' || version[2] >= '4')
return true;
}
return false;
}
};
}
@@ -0,0 +1,36 @@
#pragma once
#include <libremidi/backends/pipewire/config.hpp>
#include <libremidi/config.hpp>
namespace libremidi::pipewire_ump
{
struct input_configuration
{
std::string client_name = "libremidi client";
pw_main_loop* context{};
pw_filter* filter{};
std::function<void(libremidi::pipewire_callback)> set_process_func{};
std::function<void(int64_t)> clear_process_func{};
};
struct output_configuration
{
std::string client_name = "libremidi client";
pw_main_loop* context{};
pw_filter* filter{};
std::function<void(libremidi::pipewire_callback)> set_process_func{};
std::function<void(int64_t)> clear_process_func{};
int64_t output_buffer_size{65536};
};
struct observer_configuration
{
std::string client_name = "libremidi client";
pw_main_loop* context{};
};
}
@@ -0,0 +1,135 @@
#pragma once
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/backends/pipewire_ump/config.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi::pipewire_ump
{
class midi_in_pipewire final
: public midi2::in_api
, public pipewire_helpers
, public error_handler
{
public:
struct
: ump_input_configuration
, libremidi::pipewire_ump::input_configuration
{
} configuration;
explicit midi_in_pipewire(
ump_input_configuration&& conf, libremidi::pipewire_ump::input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (auto ret = create_context(*this); ret != stdx::error{})
{
client_open_ = ret;
return;
}
if (auto ret = create_filter(*this); ret != stdx::error{})
{
client_open_ = ret;
return;
}
client_open_ = stdx::error{};
}
~midi_in_pipewire() override
{
stop_thread();
do_close_port();
destroy_filter(*this);
destroy_context();
client_open_ = std::errc::not_connected;
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
stdx::error open_port(const input_port& in_port, std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP");
err != stdx::error{})
return err;
if (auto err = link_ports(*this, in_port); err != stdx::error{})
return err;
start_thread();
return stdx::error{};
}
stdx::error open_virtual_port(std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP");
err != stdx::error{})
return err;
start_thread();
return stdx::error{};
}
stdx::error close_port() override
{
stop_thread();
return do_close_port();
}
stdx::error set_port_name(std::string_view port_name) override
{
return rename_port(port_name);
}
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
void process(struct spa_io_position* position)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = true,
.has_samples = true,
};
assert(this->filter);
assert(this->filter->port);
const auto b = pw.filter_dequeue_buffer(this->filter->port);
if (!b)
return;
const auto buf = b->buffer;
const auto d = &buf->datas[0];
if (d->data == nullptr)
return;
const auto pod
= (spa_pod*)spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size);
if (!pod)
return;
if (!spa_pod_is_sequence(pod))
return;
struct spa_pod_control* c{};
SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c)
{
if (c->type != SPA_CONTROL_UMP)
continue;
auto data = (uint8_t*)SPA_POD_BODY(&c->value);
auto size = SPA_POD_BODY_SIZE(&c->value);
const auto to_ns = [=, clk = position->clock] {
return 1e9 * ((clk.position + c->offset) / (double)clk.rate.denom);
};
m_processing.on_bytes(
{(uint32_t*)data, (uint32_t*)(data + size)},
m_processing.timestamp<timestamp_info>(to_ns, c->offset));
}
pw.filter_queue_buffer(this->filter->port, b);
}
midi2::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,185 @@
#pragma once
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/backends/pipewire_ump/config.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <readerwriterqueue.h>
namespace libremidi::pipewire_ump
{
class midi_out_pipewire
: public midi2::out_api
, public pipewire_helpers
, public error_handler
{
public:
struct
: libremidi::output_configuration
, libremidi::pipewire_ump::output_configuration
{
} configuration;
midi_out_pipewire(
libremidi::output_configuration&& conf,
libremidi::pipewire_ump::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (auto ret = create_context(*this); ret != stdx::error{})
{
client_open_ = ret;
return;
}
if (auto ret = create_filter(*this); ret != stdx::error{})
{
client_open_ = ret;
return;
}
client_open_ = stdx::error{};
}
~midi_out_pipewire() override
{
stop_thread();
do_close_port();
destroy_filter(*this);
destroy_context();
client_open_ = std::errc::not_connected;
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
stdx::error open_port(const output_port& out_port, std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP");
err != stdx::error{})
return err;
this->filter->set_port_buffer(configuration.output_buffer_size);
if (auto err = link_ports(*this, out_port); err != stdx::error{})
return err;
start_thread();
return stdx::error{};
}
stdx::error open_virtual_port(std::string_view name) override
{
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP");
err != stdx::error{})
return err;
this->filter->set_port_buffer(configuration.output_buffer_size);
start_thread();
return stdx::error{};
}
stdx::error close_port() override
{
stop_thread();
return do_close_port();
}
stdx::error set_port_name(std::string_view port_name) override
{
return rename_port(port_name);
}
int process(spa_io_position* pos)
{
m_process_clock.store(pos->clock.nsec, std::memory_order_relaxed);
const auto b = pw.filter_dequeue_buffer(this->filter->port);
if (!b)
return 1;
const auto buf = b->buffer;
const auto d = &buf->datas[0];
if (d->data == nullptr)
return 1;
spa_pod_builder build;
spa_zero(build);
spa_pod_builder_init(&build, d->data, d->maxsize);
spa_pod_frame f;
spa_pod_builder_push_sequence(&build, &f, 0);
// for all events
while (auto m_ptr = m_queue.peek())
{
auto& m = *m_ptr;
spa_pod_builder_control(&build, static_cast<int32_t>(m.timestamp), SPA_CONTROL_UMP);
int res = spa_pod_builder_bytes(&build, m.data, cmidi2_ump_get_message_size_bytes(m.data));
// Try again next buffer
if (res == -ENOSPC)
break;
// Recycle the memory
m_queue.pop();
}
spa_pod_builder_pop(&build, &f);
int n_fill_frames = build.state.offset;
if (n_fill_frames > 0)
{
d->chunk->offset = 0;
d->chunk->stride = 1;
d->chunk->size = n_fill_frames;
b->size = n_fill_frames;
pw.filter_queue_buffer(this->filter->port, b);
return 0;
}
pw.filter_flush(this->filter->filter, true);
return 0;
}
stdx::error send_ump(const uint32_t* message, size_t size) override
{
// FIXME
if (size > 4)
return std::errc::message_size;
libremidi::ump m;
std::copy_n(message, size, m.data);
m.timestamp = 0;
m_queue.enqueue(std::move(m));
return stdx::error{};
}
int convert_timestamp(int64_t user) const noexcept
{
switch (configuration.timestamps)
{
case timestamp_mode::AudioFrame:
return static_cast<int>(user);
default:
// TODO
return 0;
}
}
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
{
// FIXME
if (size > 4)
return std::errc::message_size;
libremidi::ump m;
std::copy_n(message, size, m.data);
m.timestamp = convert_timestamp(ts);
m_queue.enqueue(std::move(m)); // FIXME actually send them scheudled
return stdx::error{};
}
moodycamel::ReaderWriterQueue<libremidi::ump> m_queue;
std::atomic_int64_t m_process_clock = 0;
};
}
@@ -0,0 +1,68 @@
#pragma once
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/backends/pipewire_ump/config.hpp>
#include <libremidi/detail/observer.hpp>
#include <unordered_set>
namespace libremidi::pipewire_ump
{
class observer_pipewire final
: public observer_api
, private pipewire_helpers
, private error_handler
{
public:
struct
: libremidi::observer_configuration
, libremidi::pipewire_ump::observer_configuration
{
} configuration;
explicit observer_pipewire(
libremidi::observer_configuration&& conf,
libremidi::pipewire_ump::observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
create_context(*this);
// FIXME port rename callback
{
this->add_callbacks("UMP", configuration);
this->start_thread();
}
if (configuration.notify_in_constructor)
{
if (configuration.input_added)
for (const auto& p : get_input_ports())
configuration.input_added(p);
if (configuration.output_added)
for (const auto& p : get_output_ports())
configuration.output_added(p);
}
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_OUTPUT>("UMP", this->configuration, *this->global_context);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_INPUT>("UMP", this->configuration, *this->global_context);
}
~observer_pipewire()
{
stop_thread();
destroy_context();
}
std::unordered_set<std::string> seen_input_ports;
std::unordered_set<std::string> seen_output_ports;
};
}
+4 -2
View File
@@ -3,6 +3,8 @@
#include <libremidi/backends/winmidi/midi_out.hpp>
#include <libremidi/backends/winmidi/observer.hpp>
#include <string_view>
namespace libremidi::winmidi
{
struct backend
@@ -14,8 +16,8 @@ struct backend
using midi_out_configuration = winmidi::output_configuration;
using midi_observer_configuration = winmidi::observer_configuration;
static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES;
static const constexpr auto name = "winmidi";
static const constexpr auto display_name = "Windows MIDI Services";
static 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; }
};
@@ -1,19 +1,34 @@
#pragma once
#include <libremidi/config.hpp>
#include <string>
namespace winrt::Microsoft::Windows::Devices::Midi2
{
class MidiSession;
class MidiEndpointDeviceInformation;
class MidiEndpointConnection;
}
// TODO allow to share midi session and endpoints
namespace libremidi::winmidi
{
struct input_configuration
{
std::string client_name = "libremidi input";
winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{};
};
struct output_configuration
{
std::string client_name = "libremidi output";
winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{};
};
struct observer_configuration
{
std::string client_name = "libremidi observer";
};
}
+51 -15
View File
@@ -3,58 +3,94 @@
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN 1
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/detail/memory.hpp>
#include <mutex>
#include <cctype>
#include <string>
#include <thread>
#include <vector>
#include <guiddef.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Midi2.h>
#include <winrt/Microsoft.Windows.Devices.Midi2.h>
#include <libremidi/cmidi2.hpp>
// MinGW support
#if !defined(_MSC_VER)
namespace Microsoft::Windows::Devices::Midi2::Initialization
{
struct IMidiClientInitializer;
struct MidiClientInitializerUuid;
}
namespace midi2 = winrt::Windows::Devices::Midi2;
namespace foundation = winrt::Windows::Foundation;
namespace collections = winrt::Windows::Foundation::Collections;
__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);
#endif
#include <init/Microsoft.Windows.Devices.Midi2.Initialization.hpp>
// clang-format on
namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2;
namespace foundation = winrt::Windows::Foundation;
namespace libremidi::winmidi
{
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Storage::Streams;
using namespace winrt::Windows::Devices::Midi2;
using namespace 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<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(b));
}
inline bool iequals(std::string_view lhs, std::string_view rhs)
{
return std::ranges::equal(lhs, rhs, ichar_equals);
}
inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation
get_port_by_name(const std::string& port) {
inline std::pair<
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointDeviceInformation,
winrt::Microsoft::Windows::Devices::Midi2::MidiGroupTerminalBlock>
get_port(const std::string& device_name, int group_terminal_block)
{
auto eps = MidiEndpointDeviceInformation::FindAll();
for (const auto& ep : eps)
{
auto str = to_string(ep.Id());
auto str = to_string(ep.EndpointDeviceId());
if (str.empty())
continue;
if (iequals(str, port))
return ep;
if (iequals(str, device_name))
{
for (const auto& gp : ep.GetGroupTerminalBlocks())
{
if (gp.Number() == group_terminal_block)
{
return std::make_pair(ep, gp);
}
}
}
}
return {nullptr};
return {nullptr, nullptr};
}
struct winmidi_shared_data
{
std::shared_ptr<MidiDesktopAppSdkInitializer> initializer;
bool m_ready{false};
winmidi_shared_data()
: initializer{libremidi::instance<MidiDesktopAppSdkInitializer>()}
, m_ready{
initializer && initializer->InitializeSdkRuntime()
&& initializer->EnsureServiceAvailable()}
{
}
};
}
+28 -11
View File
@@ -5,14 +5,12 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <iostream>
namespace libremidi::winmidi
{
class midi_in_impl final
: public midi2::in_api
, public error_handler
, public winmidi_shared_data
{
public:
struct
@@ -24,7 +22,9 @@ public:
explicit midi_in_impl(
libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, m_session{MidiSession::CreateSession(L"libremidi session")}
, m_session{
configuration.context ? *configuration.context
: MidiSession::Create(to_hstring(configuration.client_name))}
{
this->client_open_ = stdx::error{};
}
@@ -42,15 +42,23 @@ public:
stdx::error open_port(const input_port& port, std::string_view) override
{
auto ep = get_port_by_name(port.port_name);
if (!ep)
auto device_id = std::get_if<std::string>(&port.device);
if (!device_id)
return std::errc::invalid_argument;
auto [ep, gp] = get_port(*device_id, port.port);
if (!ep || !gp)
return std::errc::address_not_available;
m_endpoint = m_session.CreateEndpointConnection(ep.Id());
m_group_filter = port.port - 1;
// TODO use a MidiGroupEndpointListener for the filtering
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
m_revoke_token = m_endpoint.MessageReceived(
[&](const foundation::IInspectable& sender,
const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
[this](
const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&,
const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
process_message(args);
});
@@ -59,7 +67,7 @@ public:
return stdx::error{};
}
void process_message(const winrt::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,
@@ -68,6 +76,14 @@ public:
};
const auto& ump = msg.GetMessagePacket();
auto pk = msg.PeekFirstWord();
if (m_group_filter >= 0)
{
int group = cmidi2_ump_get_group(&pk);
if (group != m_group_filter)
return;
}
const auto& b = ump.GetAllWords();
uint32_t ump_space[64];
@@ -91,7 +107,8 @@ public:
private:
MidiSession m_session;
winrt::event_token m_revoke_token{};
winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
midi2::input_state_machine m_processing{this->configuration};
int m_group_filter = -1;
};
}
@@ -11,6 +11,7 @@ namespace libremidi::winmidi
class midi_out_impl final
: public midi2::out_api
, public error_handler
, public winmidi_shared_data
{
public:
struct
@@ -21,7 +22,9 @@ public:
midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, m_session{MidiSession::CreateSession(L"libremidi session")}
, m_session{
configuration.context ? *configuration.context
: MidiSession::Create(to_hstring(configuration.client_name))}
{
this->client_open_ = stdx::error{};
}
@@ -35,11 +38,15 @@ public:
stdx::error open_port(const output_port& port, std::string_view) override
{
auto ep = get_port_by_name(port.port_name);
if (!ep)
auto device_id = std::get_if<std::string>(&port.device);
if (!device_id)
return std::errc::invalid_argument;
auto [ep, gp] = get_port(*device_id, port.port);
if (!ep || !gp)
return std::errc::address_not_available;
m_endpoint = m_session.CreateEndpointConnection(ep.Id());
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
m_endpoint.Open();
return stdx::error{};
@@ -83,7 +90,7 @@ public:
private:
MidiSession m_session;
winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
};
}
+180 -54
View File
@@ -2,7 +2,7 @@
#include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmidi/helpers.hpp>
#include <libremidi/detail/observer.hpp>
;
namespace libremidi::winmidi
{
struct port_info
@@ -11,7 +11,9 @@ struct port_info
hstring name;
};
class observer_impl final : public observer_api
class observer_impl final
: public observer_api
, public winmidi_shared_data
{
public:
struct
@@ -20,11 +22,17 @@ public:
{
} configuration;
MidiEndpointDeviceWatcher watcher = nullptr;
MidiEndpointDeviceWatcher::Added_revoker m_addHandler;
MidiEndpointDeviceWatcher::Updated_revoker m_updHandler;
MidiEndpointDeviceWatcher::Removed_revoker m_delHandler;
std::map<hstring, std::vector<input_port>> m_known_input_devices;
std::map<hstring, std::vector<output_port>> m_known_output_devices;
std::mutex m_devices_mtx;
explicit observer_impl(
libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, session{MidiSession::CreateSession(L"libremidi session")}
{
if (!configuration.has_callbacks())
return;
@@ -40,26 +48,35 @@ public:
configuration.output_added(p);
}
/*
evTokenOnInputAdded_
= internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); });
evTokenOnInputRemoved_
= internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); });
evTokenOnOutputAdded_
= internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); });
evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved(
[this](const port_info& p) { on_output_removed(p); });
*/
if ((watcher = MidiEndpointDeviceWatcher::Create()))
{
namespace enumeration = winrt::Windows::Devices::Enumeration;
auto addHandler = foundation::TypedEventHandler<
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationAddedEventArgs>(
this, &observer_impl::on_device_added);
auto updHandler = foundation::TypedEventHandler<
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationUpdatedEventArgs>(
this, &observer_impl::on_device_updated);
auto delHandler = foundation::TypedEventHandler<
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationRemovedEventArgs>(
this, &observer_impl::on_device_removed);
m_addHandler = watcher.Added(winrt::auto_revoke, addHandler);
m_updHandler = watcher.Updated(winrt::auto_revoke, updHandler);
m_delHandler = watcher.Removed(winrt::auto_revoke, delHandler);
watcher.Start();
}
}
~observer_impl()
{
if (!configuration.has_callbacks())
return;
// internalInPortObserver_.PortAdded(evTokenOnInputAdded_);
// internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);
// internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);
// internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);
m_addHandler.revoke();
m_updHandler.revoke();
m_delHandler.revoke();
}
libremidi::API get_current_api() const noexcept override
@@ -67,33 +84,56 @@ public:
return libremidi::API::WINDOWS_MIDI_SERVICES;
}
template <bool Input>
auto to_port_info(const DeviceInformation& p) const noexcept
-> std::conditional_t<Input, input_port, output_port>
static port_information::port_type code_to_type(std::string_view str) noexcept
{
using enum port_information::port_type;
if (str.starts_with("KS"))
return hardware;
if (str == "BLE")
return port_information::port_type(hardware | bluetooth);
if (str == "VPB" || str == "APP")
return software;
if (str == "LOOP")
return port_information::port_type(software | loopback);
if (str.starts_with("NET"))
return network;
return unknown;
}
template <bool Input>
auto to_port_info(const MidiEndpointDeviceInformation& p, const MidiGroupTerminalBlock& gp)
const noexcept -> std::conditional_t<Input, input_port, output_port>
{
const auto& tinfo = p.GetTransportSuppliedInfo();
return {
{.client = 0,
.port = 0,
.manufacturer = "",
.device_name = "",
.port_name = to_string(p.Id()),
.display_name = to_string(p.Name())}};
.container = std::bit_cast<libremidi::uuid>(p.ContainerId()),
.device = to_string(p.EndpointDeviceId()),
.port = gp.Number(),
.manufacturer = to_string(tinfo.ManufacturerName),
.device_name = to_string(p.Name()),
.port_name = to_string(gp.Name()),
.display_name = to_string(gp.Name()) + " " + std::to_string(gp.Number()),
.type = code_to_type(to_string(p.GetTransportSuppliedInfo().TransportCode))}};
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
std::vector<libremidi::input_port> ret;
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
for (const auto& ep : endpointDevices)
for (const auto& ep : MidiEndpointDeviceInformation::FindAll())
{
if(ep.Name().starts_with(L"Diagnostics")) {
continue;
}
// FIXME if(has input...)
ret.emplace_back(to_port_info<true>(ep));
for (const auto& gp : ep.GetGroupTerminalBlocks())
{
if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockOutput)
ret.emplace_back(to_port_info<true>(ep, gp));
}
}
return ret;
@@ -103,46 +143,132 @@ public:
{
std::vector<libremidi::output_port> ret;
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
for (const auto& ep : endpointDevices)
for (const auto& ep : MidiEndpointDeviceInformation::FindAll())
{
if(ep.Name().starts_with(L"Diagnostics")) {
continue;
}
// FIXME if(has output...)
ret.emplace_back(to_port_info<false>(ep));
for (const auto& gp : ep.GetGroupTerminalBlocks())
{
if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockInput)
ret.emplace_back(to_port_info<false>(ep, gp));
}
}
return ret;
}
void on_input_added(const DeviceInformation& name)
// Note: these callbacks are called from some random thread!
void on_device_added(
const MidiEndpointDeviceWatcher&, const MidiEndpointDeviceInformationAddedEventArgs& result)
{
if (configuration.input_added)
configuration.input_added(to_port_info<true>(name));
add_device(result.AddedDevice());
}
void on_input_removed(const DeviceInformation& name)
void on_device_updated(
const MidiEndpointDeviceWatcher& e,
const MidiEndpointDeviceInformationUpdatedEventArgs& result)
{
// OPTIMIZEME
remove_device(result.EndpointDeviceId());
add_device(
MidiEndpointDeviceInformation::CreateFromEndpointDeviceId(result.EndpointDeviceId()));
}
void on_device_removed(
const MidiEndpointDeviceWatcher&,
const MidiEndpointDeviceInformationRemovedEventArgs& result)
{
remove_device(result.EndpointDeviceId());
}
void add_device(const MidiEndpointDeviceInformation& ep)
{
for (const auto& gp : ep.GetGroupTerminalBlocks())
{
MidiGroupTerminalBlockDirection direction = gp.Direction();
switch (direction)
{
case MidiGroupTerminalBlockDirection::Bidirectional: {
if (configuration.input_added)
{
auto ip = to_port_info<true>(ep, gp);
{
std::lock_guard _{m_devices_mtx};
m_known_input_devices[ep.EndpointDeviceId()].push_back(ip);
}
configuration.input_added(std::move(ip));
}
if (configuration.output_added)
{
auto op = to_port_info<false>(ep, gp);
{
std::lock_guard _{m_devices_mtx};
m_known_output_devices[ep.EndpointDeviceId()].push_back(op);
}
configuration.output_added(std::move(op));
}
break;
}
case MidiGroupTerminalBlockDirection::BlockInput:
if (configuration.input_added)
{
auto ip = to_port_info<true>(ep, gp);
{
std::lock_guard _{m_devices_mtx};
m_known_input_devices[ep.EndpointDeviceId()].push_back(ip);
}
configuration.input_added(std::move(ip));
}
break;
case MidiGroupTerminalBlockDirection::BlockOutput:
if (configuration.output_added)
{
auto op = to_port_info<false>(ep, gp);
{
std::lock_guard _{m_devices_mtx};
m_known_output_devices[ep.EndpointDeviceId()].push_back(op);
}
configuration.output_added(std::move(op));
}
break;
}
}
}
void remove_device(hstring eid)
{
std::vector<input_port> to_remove_in;
std::vector<output_port> to_remove_out;
{
std::lock_guard _{m_devices_mtx};
if (auto it = m_known_input_devices.find(eid); it != m_known_input_devices.end())
{
for (auto& ip : it->second)
{
to_remove_in.push_back(ip);
}
m_known_input_devices.erase(it);
}
if (auto it = m_known_output_devices.find(eid); it != m_known_output_devices.end())
{
for (auto& op : it->second)
{
to_remove_out.push_back(op);
}
m_known_output_devices.erase(it);
}
}
if (configuration.input_removed)
configuration.input_removed(to_port_info<true>(name));
}
void on_output_added(const DeviceInformation& name)
{
if (configuration.output_added)
configuration.output_added(to_port_info<false>(name));
}
void on_output_removed(const DeviceInformation& name)
{
for (auto& port : to_remove_in)
configuration.input_removed(port);
if (configuration.output_removed)
configuration.output_removed(to_port_info<false>(name));
for (auto& port : to_remove_out)
configuration.output_removed(port);
}
private:
MidiSession session;
};
}
+4 -2
View File
@@ -3,6 +3,8 @@
#include <libremidi/backends/winmm/midi_out.hpp>
#include <libremidi/backends/winmm/observer.hpp>
#include <string_view>
// Default for Windows is to add an identifier to the port names; this
// flag can be defined (e.g. in your project file) to disable this behaviour.
// #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES
@@ -28,8 +30,8 @@ struct winmm_backend
using midi_out_configuration = winmm_output_configuration;
using midi_observer_configuration = winmm_observer_configuration;
static const constexpr auto API = libremidi::API::WINDOWS_MM;
static const constexpr auto name = "winmm";
static const constexpr auto display_name = "Windows Multimedia";
static const constexpr std::string_view name = "winmm";
static const constexpr std::string_view display_name = "Windows Multimedia";
static constexpr inline bool available() noexcept { return true; }
};
+2 -2
View File
@@ -1,7 +1,8 @@
#pragma once
#include <libremidi/backends/winmm/error_domain.hpp>
#include <libremidi/detail/midi_api.hpp>
#include <algorithm>
#include <string>
namespace libremidi
{
@@ -70,5 +71,4 @@ inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumbe
deviceName += " ";
deviceName += std::to_string(x);
}
}
@@ -3,9 +3,6 @@
#include <libremidi/backends/winmm/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <condition_variable>
#include <mutex>
#include <ranges>
#include <thread>
namespace libremidi
+4 -2
View File
@@ -3,6 +3,8 @@
#include <libremidi/backends/winuwp/midi_out.hpp>
#include <libremidi/backends/winuwp/observer.hpp>
#include <string_view>
namespace libremidi
{
struct winuwp_backend
@@ -14,8 +16,8 @@ struct winuwp_backend
using midi_out_configuration = winuwp_output_configuration;
using midi_observer_configuration = winuwp_observer_configuration;
static const constexpr auto API = libremidi::API::WINDOWS_UWP;
static const constexpr auto name = "winuwp";
static const constexpr auto display_name = "Windows UWP";
static const constexpr std::string_view name = "winuwp";
static const constexpr std::string_view display_name = "Windows UWP";
static constexpr inline bool available() noexcept { return true; }
};
+15 -17
View File
@@ -197,13 +197,13 @@ public:
configuration.output_added(p);
}
evTokenOnInputAdded_
= internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); });
evTokenOnInputRemoved_
= internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); });
evTokenOnOutputAdded_
= internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); });
evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved(
evTokenOnInputAdded_ = get_internal_in_port_observer().PortAdded(
[this](const port_info& p) { on_input_added(p); });
evTokenOnInputRemoved_ = get_internal_in_port_observer().PortRemoved(
[this](const port_info& p) { on_input_removed(p); });
evTokenOnOutputAdded_ = get_internal_out_port_observer().PortAdded(
[this](const port_info& p) { on_output_added(p); });
evTokenOnOutputRemoved_ = get_internal_out_port_observer().PortRemoved(
[this](const port_info& p) { on_output_removed(p); });
}
@@ -211,10 +211,10 @@ public:
{
if (!configuration.has_callbacks())
return;
internalInPortObserver_.PortAdded(evTokenOnInputAdded_);
internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);
internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);
internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);
get_internal_in_port_observer().PortAdded(evTokenOnInputAdded_);
get_internal_in_port_observer().PortRemoved(evTokenOnInputRemoved_);
get_internal_out_port_observer().PortAdded(evTokenOnOutputAdded_);
get_internal_out_port_observer().PortRemoved(evTokenOnOutputRemoved_);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
@@ -235,7 +235,7 @@ public:
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
std::vector<libremidi::input_port> ret;
for (auto& port : internalInPortObserver_.get_ports())
for (auto& port : get_internal_in_port_observer().get_ports())
ret.push_back(to_port_info<true>(port));
return ret;
}
@@ -243,18 +243,20 @@ public:
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
std::vector<libremidi::output_port> ret;
for (auto& port : internalOutPortObserver_.get_ports())
for (auto& port : get_internal_out_port_observer().get_ports())
ret.push_back(to_port_info<false>(port));
return ret;
}
static observer_winuwp_internal& get_internal_in_port_observer()
{
static observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};
return internalInPortObserver_;
}
static observer_winuwp_internal& get_internal_out_port_observer()
{
static observer_winuwp_internal internalOutPortObserver_{MidiOutPort::GetDeviceSelector()};
return internalOutPortObserver_;
}
@@ -283,10 +285,6 @@ public:
}
private:
static inline observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};
static inline observer_winuwp_internal internalOutPortObserver_{
MidiOutPort::GetDeviceSelector()};
int evTokenOnInputAdded_{-1};
int evTokenOnInputRemoved_{-1};
int evTokenOnOutputAdded_{-1};