upgrade to libremidi 5.1, raylib and raygui latest
This commit is contained in:
@@ -30,6 +30,8 @@ typedef enum libremidi_api
|
||||
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
|
||||
KEYBOARD_UMP, /*!< Computer keyboard input */
|
||||
NETWORK_UMP, /*!< MIDI2 over IP */
|
||||
JACK_UMP, /*!< MIDI2 over JACK, type "32 bit raw UMP". Requires PipeWire v1.4+. */
|
||||
PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */
|
||||
|
||||
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
|
||||
} libremidi_api;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <libremidi/api-c.h>
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <any>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
@@ -31,9 +30,6 @@ LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;
|
||||
*/
|
||||
LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
libremidi::API midi_api(const std::any& conf);
|
||||
|
||||
//! A static function to determine the current version.
|
||||
LIBREMIDI_EXPORT std::string_view get_version() noexcept;
|
||||
|
||||
@@ -64,7 +60,7 @@ inline constexpr libremidi::API default_api() noexcept
|
||||
return API::COREMIDI;
|
||||
#elif defined(_WIN32)
|
||||
return API::WINDOWS_MM;
|
||||
#elif defined(__linux__)
|
||||
#elif defined(LIBREMIDI_ALSA)
|
||||
return API::ALSA_SEQ;
|
||||
#elif defined(__emscripten__)
|
||||
return API::EMSCRIPTEN_WEBMIDI;
|
||||
@@ -83,7 +79,7 @@ inline constexpr libremidi::API default_api() noexcept
|
||||
return API::COREMIDI_UMP;
|
||||
#elif defined(_WIN32)
|
||||
return API::WINDOWS_MIDI_SERVICES;
|
||||
#elif defined(__linux__)
|
||||
#elif defined(LIBREMIDI_ALSA)
|
||||
return API::ALSA_SEQ_UMP;
|
||||
#elif defined(__emscripten__)
|
||||
return API::DUMMY;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <any>
|
||||
#include <tuple>
|
||||
|
||||
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
|
||||
@@ -30,10 +29,14 @@
|
||||
|
||||
#if defined(LIBREMIDI_JACK)
|
||||
#include <libremidi/backends/jack.hpp>
|
||||
#include <libremidi/backends/jack_ump.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_PIPEWIRE)
|
||||
#include <libremidi/backends/pipewire.hpp>
|
||||
#if defined(LIBREMIDI_PIPEWIRE_UMP)
|
||||
#include <libremidi/backends/pipewire_ump.hpp>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_COREMIDI)
|
||||
@@ -163,6 +166,14 @@ static constexpr auto available_backends = make_tl(
|
||||
#if defined(LIBREMIDI_NETWORK)
|
||||
,
|
||||
net_ump::backend{}
|
||||
#endif
|
||||
#if defined(LIBREMIDI_JACK)
|
||||
,
|
||||
jack_ump::backend{}
|
||||
#endif
|
||||
#if defined(LIBREMIDI_PIPEWIRE_UMP)
|
||||
,
|
||||
pipewire_ump::backend{}
|
||||
#endif
|
||||
,
|
||||
dummy_backend{});
|
||||
@@ -173,7 +184,7 @@ static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);
|
||||
template <typename F>
|
||||
auto for_all_backends(F&& f)
|
||||
{
|
||||
std::apply([&](auto&&... x) { (f(x), ...); }, available_backends);
|
||||
std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
@@ -202,10 +213,10 @@ auto for_backend(libremidi::API api, F&& f)
|
||||
midi2::for_backend(api, f);
|
||||
}
|
||||
|
||||
void for_input_configuration(auto f, std::any& api_conf)
|
||||
void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
|
||||
if (auto conf = std::get_if<typename T::midi_in_configuration>(&api_conf))
|
||||
{
|
||||
f(*conf);
|
||||
return true;
|
||||
@@ -217,10 +228,10 @@ void for_input_configuration(auto f, std::any& api_conf)
|
||||
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||
}
|
||||
|
||||
void for_output_configuration(auto f, std::any& api_conf)
|
||||
void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))
|
||||
if (auto conf = std::get_if<typename T::midi_out_configuration>(&api_conf))
|
||||
{
|
||||
f(*conf);
|
||||
return true;
|
||||
@@ -232,10 +243,10 @@ void for_output_configuration(auto f, std::any& api_conf)
|
||||
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||
}
|
||||
|
||||
void for_observer_configuration(auto f, std::any& api_conf)
|
||||
void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))
|
||||
if (auto conf = std::get_if<typename T::midi_observer_configuration>(&api_conf))
|
||||
{
|
||||
f(*conf);
|
||||
return true;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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, []() {});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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? */ }
|
||||
};
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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, ¤t_frames, ¤t_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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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, µ, &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, ¤t_frames, ¤t_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;
|
||||
};
|
||||
}
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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; }
|
||||
};
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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()}
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <libremidi/shared_context.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace libremidi::midi1
|
||||
{
|
||||
@@ -66,8 +67,8 @@ public:
|
||||
}
|
||||
|
||||
explicit client(const client_configuration& conf, shared_configurations ctx)
|
||||
: configuration{conf}
|
||||
, context{ctx}
|
||||
: m_configuration{conf}
|
||||
, m_context{std::move(ctx)}
|
||||
, m_observer{
|
||||
observer_configuration{
|
||||
.on_error = conf.on_error,
|
||||
@@ -81,10 +82,10 @@ public:
|
||||
.track_hardware = conf.track_hardware,
|
||||
.track_virtual = conf.track_virtual,
|
||||
.notify_in_constructor = false},
|
||||
context.observer}
|
||||
m_context.observer}
|
||||
{
|
||||
if (context.context)
|
||||
context.context->start_processing();
|
||||
if (m_context.context)
|
||||
m_context.context->start_processing();
|
||||
}
|
||||
|
||||
~client()
|
||||
@@ -92,8 +93,8 @@ public:
|
||||
m_inputs.clear();
|
||||
m_outputs.clear();
|
||||
|
||||
if (context.context)
|
||||
context.context->stop_processing();
|
||||
if (m_context.context)
|
||||
m_context.context->stop_processing();
|
||||
}
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept
|
||||
@@ -116,19 +117,19 @@ public:
|
||||
input_configuration{
|
||||
.on_message
|
||||
= [this,
|
||||
port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); },
|
||||
port](libremidi::message&& m) { m_configuration.on_message(port, std::move(m)); },
|
||||
.on_raw_data = {},
|
||||
.get_timestamp = {},
|
||||
|
||||
.on_error = configuration.on_error,
|
||||
.on_warning = configuration.on_warning,
|
||||
.on_error = m_configuration.on_error,
|
||||
.on_warning = m_configuration.on_warning,
|
||||
|
||||
.ignore_sysex = configuration.ignore_sysex,
|
||||
.ignore_timing = configuration.ignore_timing,
|
||||
.ignore_sensing = configuration.ignore_sensing,
|
||||
.ignore_sysex = m_configuration.ignore_sysex,
|
||||
.ignore_timing = m_configuration.ignore_timing,
|
||||
.ignore_sensing = m_configuration.ignore_sensing,
|
||||
|
||||
.timestamps = configuration.timestamps},
|
||||
context.in);
|
||||
.timestamps = m_configuration.timestamps},
|
||||
m_context.in);
|
||||
|
||||
res.first->second.open_port(port, name);
|
||||
}
|
||||
@@ -141,11 +142,11 @@ public:
|
||||
auto res = m_outputs.try_emplace(
|
||||
port,
|
||||
output_configuration{
|
||||
.on_error = configuration.on_error,
|
||||
.on_warning = configuration.on_warning,
|
||||
.on_error = m_configuration.on_error,
|
||||
.on_warning = m_configuration.on_warning,
|
||||
|
||||
.timestamps = configuration.timestamps},
|
||||
context.out);
|
||||
.timestamps = m_configuration.timestamps},
|
||||
m_context.out);
|
||||
|
||||
res.first->second.open_port(port, name);
|
||||
}
|
||||
@@ -190,8 +191,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
client_configuration configuration;
|
||||
shared_configurations context;
|
||||
client_configuration m_configuration;
|
||||
shared_configurations m_context;
|
||||
|
||||
std::map<input_port, midi_in> m_inputs;
|
||||
std::map<output_port, midi_out> m_outputs;
|
||||
|
||||
@@ -311,7 +311,7 @@ enum cmidi2_ump_chord_name_tonic_note
|
||||
CMIDI2_UMP_CHORD_NAME_G = 7,
|
||||
};
|
||||
|
||||
enum cmidi2_ump_chor_name_chord_type
|
||||
enum cmidi2_ump_chord_name_chord_type
|
||||
{
|
||||
CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0,
|
||||
CMIDI2_UMP_CHORD_TYPE_MAJOR = 1,
|
||||
@@ -1494,7 +1494,7 @@ static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump)
|
||||
{
|
||||
return cmidi2_ump_get_32_to_64(ump);
|
||||
}
|
||||
static inline uint32_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump)
|
||||
static inline uint8_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump)
|
||||
{
|
||||
return cmidi2_ump_get_byte_at(ump, 2);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(MSC_VER)
|
||||
#if defined(_MSC_VER)
|
||||
#define NOMINMAX 1
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
@@ -62,3 +62,9 @@ using midi_bytes = std::vector<unsigned char>;
|
||||
#else
|
||||
#define LIBREMIDI_INLINE
|
||||
#endif
|
||||
|
||||
#if __cpp_contracts >= 202502L
|
||||
#define LIBREMIDI_PRECONDITION(...) pre(__VA_ARGS__)
|
||||
#else
|
||||
#define LIBREMIDI_PRECONDITION(...)
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <libremidi/api.hpp>
|
||||
#include <libremidi/backends/alsa_raw/config.hpp>
|
||||
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
||||
#include <libremidi/backends/alsa_seq/config.hpp>
|
||||
@@ -8,17 +9,60 @@
|
||||
#include <libremidi/backends/coremidi_ump/config.hpp>
|
||||
#include <libremidi/backends/emscripten/config.hpp>
|
||||
#include <libremidi/backends/jack/config.hpp>
|
||||
#include <libremidi/backends/jack_ump/config.hpp>
|
||||
#include <libremidi/backends/keyboard/config.hpp>
|
||||
#include <libremidi/backends/net/config.hpp>
|
||||
#include <libremidi/backends/pipewire/config.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||
#include <libremidi/backends/winmidi/config.hpp>
|
||||
#include <libremidi/backends/winmm/config.hpp>
|
||||
#include <libremidi/backends/winuwp/config.hpp>
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <variant>
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
struct unspecified_configuration
|
||||
{
|
||||
};
|
||||
struct dummy_configuration
|
||||
{
|
||||
};
|
||||
|
||||
using input_api_configuration = std::variant<
|
||||
unspecified_configuration, dummy_configuration, alsa_raw_input_configuration,
|
||||
alsa_raw_ump::input_configuration, alsa_seq::input_configuration,
|
||||
alsa_seq_ump::input_configuration, coremidi_input_configuration,
|
||||
coremidi_ump::input_configuration, emscripten_input_configuration, jack_input_configuration,
|
||||
kbd_input_configuration, libremidi::net::dgram_input_configuration,
|
||||
libremidi::net_ump::dgram_input_configuration, pipewire_input_configuration,
|
||||
winmidi::input_configuration, winmm_input_configuration, winuwp_input_configuration,
|
||||
jack_ump::input_configuration, pipewire_ump::input_configuration, libremidi::API>;
|
||||
|
||||
using output_api_configuration = std::variant<
|
||||
unspecified_configuration, dummy_configuration, alsa_raw_output_configuration,
|
||||
alsa_raw_ump::output_configuration, alsa_seq::output_configuration,
|
||||
alsa_seq_ump::output_configuration, coremidi_output_configuration,
|
||||
coremidi_ump::output_configuration, emscripten_output_configuration, jack_output_configuration,
|
||||
libremidi::net::dgram_output_configuration, libremidi::net_ump::dgram_output_configuration,
|
||||
pipewire_output_configuration, winmidi::output_configuration, winmm_output_configuration,
|
||||
winuwp_output_configuration, jack_ump::output_configuration,
|
||||
pipewire_ump::output_configuration, libremidi::API>;
|
||||
|
||||
using observer_api_configuration = std::variant<
|
||||
unspecified_configuration, dummy_configuration, alsa_raw_observer_configuration,
|
||||
alsa_raw_ump::observer_configuration, alsa_seq::observer_configuration,
|
||||
alsa_seq_ump::observer_configuration, coremidi_observer_configuration,
|
||||
coremidi_ump::observer_configuration, emscripten_observer_configuration,
|
||||
jack_observer_configuration, libremidi::net::dgram_observer_configuration,
|
||||
libremidi::net_ump::dgram_observer_configuration, pipewire_observer_configuration,
|
||||
winmidi::observer_configuration, winmm_observer_configuration, winuwp_observer_configuration,
|
||||
jack_ump::observer_configuration, pipewire_ump::observer_configuration, libremidi::API>;
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
libremidi::API midi_api(const input_api_configuration& conf);
|
||||
LIBREMIDI_EXPORT
|
||||
libremidi::API midi_api(const output_api_configuration& conf);
|
||||
LIBREMIDI_EXPORT
|
||||
libremidi::API midi_api(const observer_api_configuration& conf);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#pragma once
|
||||
#include <libremidi/api.hpp>
|
||||
#include <libremidi/config.hpp>
|
||||
#include <libremidi/configurations.hpp>
|
||||
#include <libremidi/input_configuration.hpp>
|
||||
#include <libremidi/observer_configuration.hpp>
|
||||
#include <libremidi/output_configuration.hpp>
|
||||
|
||||
#include <any>
|
||||
#include <optional>
|
||||
|
||||
namespace libremidi
|
||||
@@ -16,22 +16,22 @@ class observer;
|
||||
|
||||
//! Get the default midi in configuration object for a given API
|
||||
LIBREMIDI_EXPORT
|
||||
std::any midi_in_configuration_for(libremidi::API);
|
||||
input_api_configuration midi_in_configuration_for(libremidi::API);
|
||||
|
||||
//! Get the default midi out configuration object for a given API
|
||||
LIBREMIDI_EXPORT
|
||||
std::any midi_out_configuration_for(libremidi::API);
|
||||
output_api_configuration midi_out_configuration_for(libremidi::API);
|
||||
|
||||
//! Get the default observer configuration object for a given API
|
||||
LIBREMIDI_EXPORT
|
||||
std::any observer_configuration_for(libremidi::API);
|
||||
observer_api_configuration observer_configuration_for(libremidi::API);
|
||||
|
||||
//! Get a matching midi in configuration object for a given observer instance.
|
||||
LIBREMIDI_EXPORT
|
||||
std::any midi_in_configuration_for(const libremidi::observer&);
|
||||
input_api_configuration midi_in_configuration_for(const libremidi::observer&);
|
||||
//! Get a matching midi out configuration object for a given observer instance.
|
||||
LIBREMIDI_EXPORT
|
||||
std::any midi_out_configuration_for(const libremidi::observer&);
|
||||
output_api_configuration midi_out_configuration_for(const libremidi::observer&);
|
||||
|
||||
//! Get the default port for midi input (if any) in a given API.
|
||||
//! Note that this will only return a hardware / physical port.
|
||||
@@ -47,15 +47,15 @@ namespace midi1
|
||||
{
|
||||
//! Get a default configuration for creating a MIDI 1 input
|
||||
LIBREMIDI_EXPORT
|
||||
std::any in_default_configuration();
|
||||
input_api_configuration in_default_configuration();
|
||||
|
||||
//! Get a default configuration for creating a MIDI 1 output
|
||||
LIBREMIDI_EXPORT
|
||||
std::any out_default_configuration();
|
||||
output_api_configuration out_default_configuration();
|
||||
|
||||
//! Get a default configuration for creating a MIDI 1 observer
|
||||
LIBREMIDI_EXPORT
|
||||
std::any observer_default_configuration();
|
||||
observer_api_configuration observer_default_configuration();
|
||||
|
||||
//! Get a default MIDI 1 input port for the default API of the system
|
||||
inline std::optional<input_port> in_default_port() noexcept
|
||||
@@ -74,15 +74,15 @@ namespace midi2
|
||||
{
|
||||
//! Get a default configuration for creating a MIDI 2 input
|
||||
LIBREMIDI_EXPORT
|
||||
std::any in_default_configuration();
|
||||
input_api_configuration in_default_configuration();
|
||||
|
||||
//! Get a default configuration for creating a MIDI 2 output
|
||||
LIBREMIDI_EXPORT
|
||||
std::any out_default_configuration();
|
||||
output_api_configuration out_default_configuration();
|
||||
|
||||
//! Get a default configuration for creating a MIDI 2 observer
|
||||
LIBREMIDI_EXPORT
|
||||
std::any observer_default_configuration();
|
||||
observer_api_configuration observer_default_configuration();
|
||||
|
||||
//! Get a default MIDI 2 input port for the default API of the system
|
||||
inline std::optional<input_port> in_default_port() noexcept
|
||||
|
||||
@@ -7,8 +7,193 @@
|
||||
#include <libremidi/error.hpp>
|
||||
#include <libremidi/message.hpp>
|
||||
#include <libremidi/ump.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
static inline void cmidi2_reverse(int64_t v, cmidi2_ump* output)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint32_t u[2];
|
||||
int64_t i;
|
||||
} e0, e1;
|
||||
e0.i = v;
|
||||
e1.u[0] = e0.u[1];
|
||||
e1.u[1] = e0.u[0];
|
||||
memcpy(output, &e1, 8);
|
||||
}
|
||||
|
||||
static inline auto u7_to_u16(uint8_t in) -> uint16_t
|
||||
{
|
||||
static constexpr auto ratio = float(std::numeric_limits<uint16_t>::max()) / 127.f;
|
||||
return std::clamp(std::round(in * ratio), 0.f, float(std::numeric_limits<uint16_t>::max()));
|
||||
};
|
||||
|
||||
static inline auto u7_to_u32(uint8_t in) -> uint32_t
|
||||
{
|
||||
static constexpr auto ratio = double(std::numeric_limits<uint32_t>::max()) / 127.;
|
||||
return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits<uint32_t>::max()));
|
||||
};
|
||||
|
||||
static inline auto u16_to_u32(uint16_t in) -> uint32_t
|
||||
{
|
||||
static constexpr auto ratio = double(std::numeric_limits<uint32_t>::max())
|
||||
/ double(std::numeric_limits<uint16_t>::max());
|
||||
return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits<uint32_t>::max()));
|
||||
};
|
||||
|
||||
static inline bool
|
||||
cmidi2_midi1_channel_voice_to_midi2(const uint8_t* bytes, std::size_t sz, cmidi2_ump* output)
|
||||
{
|
||||
if (sz < 2)
|
||||
return false;
|
||||
if (sz > 3)
|
||||
return false;
|
||||
const auto status = libremidi::message_type(bytes[0] & 0xF0);
|
||||
const int channel = bytes[0] & 0x0F;
|
||||
const int group = 0;
|
||||
switch (status)
|
||||
{
|
||||
case libremidi::message_type::NOTE_OFF: {
|
||||
const int64_t msg
|
||||
= cmidi2_ump_midi2_note_off(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0);
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case libremidi::message_type::NOTE_ON: {
|
||||
const int64_t msg
|
||||
= cmidi2_ump_midi2_note_on(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0);
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case libremidi::message_type::POLY_PRESSURE: {
|
||||
const int64_t msg = cmidi2_ump_midi2_paf(group, channel, bytes[1], u7_to_u32(bytes[2]));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case libremidi::message_type::CONTROL_CHANGE: {
|
||||
const int64_t msg = cmidi2_ump_midi2_cc(group, channel, bytes[1], u7_to_u32(bytes[2]));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case libremidi::message_type::PROGRAM_CHANGE: {
|
||||
const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, bytes[1], 0, 0);
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case libremidi::message_type::AFTERTOUCH: {
|
||||
const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(bytes[1]));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case libremidi::message_type::PITCH_BEND: {
|
||||
const auto pb = bytes[1] + bytes[2] * 0x80;
|
||||
const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(const cmidi2_ump* input, cmidi2_ump* output)
|
||||
{
|
||||
if (cmidi2_ump_get_message_type(input) != CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL)
|
||||
return false;
|
||||
|
||||
const auto group = cmidi2_ump_get_group(input);
|
||||
const auto channel = cmidi2_ump_get_channel(input);
|
||||
const auto status = cmidi2_ump_get_status_code(input);
|
||||
const auto b1 = cmidi2_ump_get_midi1_byte2(input);
|
||||
const auto b2 = cmidi2_ump_get_midi1_byte3(input);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
// Not in midi 1:
|
||||
case CMIDI2_STATUS_PER_NOTE_RCC:
|
||||
case CMIDI2_STATUS_PER_NOTE_ACC:
|
||||
case CMIDI2_STATUS_RPN:
|
||||
case CMIDI2_STATUS_NRPN:
|
||||
case CMIDI2_STATUS_RELATIVE_RPN:
|
||||
case CMIDI2_STATUS_RELATIVE_NRPN:
|
||||
case CMIDI2_STATUS_PER_NOTE_PITCH_BEND:
|
||||
case CMIDI2_STATUS_PER_NOTE_MANAGEMENT:
|
||||
return false;
|
||||
|
||||
case CMIDI2_STATUS_NOTE_OFF: {
|
||||
const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, b1, 0, u7_to_u16(b2), 0);
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case CMIDI2_STATUS_NOTE_ON: {
|
||||
const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, b1, 0, u7_to_u16(b2), 0);
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case CMIDI2_STATUS_PAF: {
|
||||
const int64_t msg = cmidi2_ump_midi2_paf(group, channel, b1, u7_to_u32(b2));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case CMIDI2_STATUS_CC: {
|
||||
int64_t msg = cmidi2_ump_midi2_cc(group, channel, b1, u7_to_u32(b2));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case CMIDI2_STATUS_PROGRAM: {
|
||||
const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, b1, 0, 0);
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case CMIDI2_STATUS_CAF: {
|
||||
const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(b1));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
case CMIDI2_STATUS_PITCH_BEND: {
|
||||
const auto pb = b1 + b2 * 0x80;
|
||||
const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb));
|
||||
cmidi2_reverse(msg, output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
static inline libremidi::message midi1_from_ump(libremidi::ump u)
|
||||
{
|
||||
libremidi::message ret;
|
||||
ret.bytes.resize(4);
|
||||
if (auto n = cmidi2_convert_single_ump_to_midi1((uint8_t*)ret.bytes.data(), 4, u.data))
|
||||
{
|
||||
ret.timestamp = u.timestamp;
|
||||
ret.bytes.resize(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.bytes.clear();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline libremidi::ump ump_from_midi1(libremidi::message u)
|
||||
{
|
||||
libremidi::ump ret;
|
||||
if (!u.bytes.empty())
|
||||
{
|
||||
ret.timestamp = u.timestamp;
|
||||
cmidi2_midi1_channel_voice_to_midi2(u.bytes.data(), u.bytes.size(), ret.data);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct midi1_to_midi2
|
||||
{
|
||||
stdx::error
|
||||
@@ -20,11 +205,31 @@ struct midi1_to_midi2
|
||||
context.ump = ump;
|
||||
context.ump_num_bytes = sizeof(ump);
|
||||
context.ump_proceeded_bytes = 0;
|
||||
context.skip_delta_time = true;
|
||||
|
||||
if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK)
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp);
|
||||
switch (cmidi2_convert_midi1_to_ump(&context))
|
||||
{
|
||||
case CMIDI2_CONVERSION_RESULT_OK: {
|
||||
if (auto n = context.ump_proceeded_bytes; n > 0)
|
||||
return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp);
|
||||
else
|
||||
return std::errc::operation_not_supported;
|
||||
}
|
||||
case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE:
|
||||
return std::errc::no_buffer_space;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_STATUS:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_INPUT:
|
||||
return std::errc::invalid_argument;
|
||||
default:
|
||||
return std::errc::operation_not_supported;
|
||||
}
|
||||
}
|
||||
|
||||
cmidi2_midi_conversion_context context = [] {
|
||||
@@ -32,20 +237,45 @@ struct midi1_to_midi2
|
||||
cmidi2_midi_conversion_context_initialize(&tmp);
|
||||
return tmp;
|
||||
}();
|
||||
|
||||
uint32_t ump[65536 / 4];
|
||||
};
|
||||
|
||||
struct midi2_to_midi1
|
||||
{
|
||||
stdx::error
|
||||
convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi)
|
||||
stdx::error convert(const uint32_t* message, std::size_t size, int64_t timestamp, auto on_midi)
|
||||
{
|
||||
auto n
|
||||
= cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));
|
||||
if (n > 0)
|
||||
return on_midi(midi, n, timestamp);
|
||||
else
|
||||
return std::errc::no_buffer_space;
|
||||
context.midi1 = static_cast<unsigned char*>(midi);
|
||||
context.midi1_num_bytes = sizeof(midi);
|
||||
context.midi1_proceeded_bytes = 0;
|
||||
context.ump = const_cast<uint32_t*>(message);
|
||||
context.ump_num_bytes = size * sizeof(uint32_t);
|
||||
context.ump_proceeded_bytes = 0;
|
||||
context.skip_delta_time = true;
|
||||
|
||||
switch (cmidi2_convert_ump_to_midi1(&context))
|
||||
{
|
||||
case CMIDI2_CONVERSION_RESULT_OK: {
|
||||
if (auto n = context.midi1_proceeded_bytes; n > 0)
|
||||
return on_midi(midi, n, timestamp);
|
||||
else
|
||||
return std::errc::operation_not_supported;
|
||||
}
|
||||
case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE:
|
||||
return std::errc::no_buffer_space;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_STATUS:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7:
|
||||
return std::errc::invalid_argument;
|
||||
case CMIDI2_CONVERSION_RESULT_INVALID_INPUT:
|
||||
return std::errc::invalid_argument;
|
||||
default:
|
||||
return std::errc::operation_not_supported;
|
||||
}
|
||||
}
|
||||
|
||||
cmidi2_midi_conversion_context context = [] {
|
||||
@@ -53,6 +283,7 @@ struct midi2_to_midi1
|
||||
cmidi2_midi_conversion_context_initialize(&tmp);
|
||||
return tmp;
|
||||
}();
|
||||
|
||||
uint8_t midi[65536];
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <libremidi/detail/conversion.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <cmath>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cinttypes>
|
||||
#include <span>
|
||||
|
||||
namespace libremidi
|
||||
@@ -107,12 +109,12 @@ struct input_state_machine : input_state_machine_base<input_configuration>
|
||||
{
|
||||
message.bytes.clear();
|
||||
message.timestamp = {};
|
||||
state = main;
|
||||
m_state = main;
|
||||
}
|
||||
|
||||
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
|
||||
{
|
||||
return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));
|
||||
return (((bytes.front() == 0xF0) || (m_state == in_sysex)) && (bytes.back() == 0xF7));
|
||||
}
|
||||
|
||||
// Function to process a byte stream which may contain multiple successive
|
||||
@@ -139,22 +141,22 @@ private:
|
||||
void on_bytes_multi_segmented(
|
||||
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
|
||||
{
|
||||
int64_t nBytes = bytes.size();
|
||||
int64_t iByte = 0;
|
||||
int64_t n_bytes = bytes.size();
|
||||
int64_t i_byte = 0;
|
||||
|
||||
const bool finished_sysex = has_finished_sysex(bytes);
|
||||
switch (state)
|
||||
switch (m_state)
|
||||
{
|
||||
case in_sysex: {
|
||||
return on_continue_sysex(cb, bytes, finished_sysex);
|
||||
}
|
||||
case main: {
|
||||
while (iByte < nBytes)
|
||||
while (i_byte < n_bytes)
|
||||
{
|
||||
int64_t size = 1;
|
||||
// We are expecting that the next byte in the packet is a status
|
||||
// byte.
|
||||
const auto status = bytes[iByte];
|
||||
const auto status = bytes[i_byte];
|
||||
if (!(status & 0x80))
|
||||
break;
|
||||
|
||||
@@ -170,17 +172,17 @@ private:
|
||||
if (configuration.ignore_sysex)
|
||||
{
|
||||
size = 0;
|
||||
iByte = nBytes;
|
||||
i_byte = n_bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = nBytes - iByte;
|
||||
size = n_bytes - i_byte;
|
||||
}
|
||||
|
||||
if (bytes[nBytes - 1] != 0xF7)
|
||||
if (bytes[n_bytes - 1] != 0xF7)
|
||||
{
|
||||
// We know per CoreMIDI API there can't be anything else in this packet
|
||||
state = in_sysex;
|
||||
m_state = in_sysex;
|
||||
message.assign(bytes.begin(), bytes.begin() + size);
|
||||
message.timestamp = timestamp;
|
||||
return;
|
||||
@@ -192,7 +194,7 @@ private:
|
||||
if (configuration.ignore_timing)
|
||||
{
|
||||
size = 0;
|
||||
iByte += 2;
|
||||
i_byte += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -209,7 +211,7 @@ private:
|
||||
if (configuration.ignore_timing)
|
||||
{
|
||||
size = 0;
|
||||
iByte += 1;
|
||||
i_byte += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -222,7 +224,7 @@ private:
|
||||
if (configuration.ignore_sensing)
|
||||
{
|
||||
size = 0;
|
||||
iByte += 1;
|
||||
i_byte += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -238,14 +240,14 @@ private:
|
||||
// Now process the actual bytes of the message
|
||||
if (size > 0)
|
||||
{
|
||||
auto begin = bytes.begin() + iByte;
|
||||
auto begin = bytes.begin() + i_byte;
|
||||
message.assign(begin, begin + size);
|
||||
message.timestamp = timestamp;
|
||||
|
||||
cb(std::move(message));
|
||||
message.clear();
|
||||
|
||||
iByte += size;
|
||||
i_byte += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +258,7 @@ private:
|
||||
const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)
|
||||
{
|
||||
if (finished_sysex)
|
||||
state = main;
|
||||
m_state = main;
|
||||
|
||||
if (configuration.ignore_sysex)
|
||||
{
|
||||
@@ -283,7 +285,7 @@ private:
|
||||
// SYSEX start
|
||||
case 0xF0: {
|
||||
if (!finished_sysex)
|
||||
state = in_sysex;
|
||||
m_state = in_sysex;
|
||||
|
||||
if (!this->configuration.ignore_sysex)
|
||||
{
|
||||
@@ -328,7 +330,7 @@ private:
|
||||
return;
|
||||
|
||||
const bool finished_sysex = has_finished_sysex(bytes);
|
||||
switch (state)
|
||||
switch (m_state)
|
||||
{
|
||||
case in_sysex:
|
||||
return on_continue_sysex(cb, bytes, finished_sysex);
|
||||
@@ -346,7 +348,7 @@ private:
|
||||
{
|
||||
main,
|
||||
in_sysex
|
||||
} state{main};
|
||||
} m_state{main};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -453,6 +455,18 @@ private:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: {
|
||||
if (this->configuration.midi1_channel_events_to_midi2)
|
||||
{
|
||||
libremidi::ump msg;
|
||||
cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(bytes.data(), msg.data);
|
||||
msg.timestamp = timestamp;
|
||||
cb(std::move(msg));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
libremidi::ump msg;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <semaphore>
|
||||
|
||||
struct semaphore_pair_lock
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <libremidi/error.hpp>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
|
||||
namespace libremidi
|
||||
|
||||
@@ -28,7 +28,7 @@ struct source_location {
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
inline auto from_errc(int ret) noexcept
|
||||
inline auto from_errc(int64_t ret) noexcept
|
||||
{
|
||||
return static_cast<std::errc>(-ret);
|
||||
}
|
||||
|
||||
@@ -123,5 +123,10 @@ struct ump_input_configuration
|
||||
uint32_t ignore_sensing : 1 = true;
|
||||
|
||||
uint32_t timestamps : 3 = timestamp_mode::Absolute;
|
||||
|
||||
//! Upscale MIDI 1 channel events to MIDI 2 channel events.
|
||||
//! Note that this only has an effect on Windows with MIDI Services
|
||||
//! as other platforms already do this by default.
|
||||
uint32_t midi1_channel_events_to_midi2 : 1 = true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,14 +44,14 @@ const char* libremidi_get_version(void)
|
||||
return LIBREMIDI_VERSION;
|
||||
}
|
||||
|
||||
void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||
void libremidi_midi1_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||
{
|
||||
if (!cb)
|
||||
return;
|
||||
libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); });
|
||||
}
|
||||
|
||||
void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||
void libremidi_midi2_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||
{
|
||||
if (!cb)
|
||||
return;
|
||||
@@ -276,11 +276,11 @@ int libremidi_midi_in_new(
|
||||
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
||||
}
|
||||
|
||||
if (c->on_midi1_message.callback)
|
||||
if (c->version == libremidi_midi_configuration::MIDI1 && c->on_midi1_message.callback)
|
||||
conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) {
|
||||
cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size());
|
||||
};
|
||||
else if (c->on_midi1_raw_data.callback)
|
||||
else if (c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback)
|
||||
{
|
||||
conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span<const uint8_t> msg, int64_t ts) {
|
||||
cb.callback(cb.context, ts, msg.data(), msg.size());
|
||||
@@ -319,11 +319,11 @@ int libremidi_midi_in_new(
|
||||
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
||||
}
|
||||
|
||||
if (c->on_midi2_message.callback)
|
||||
if (c->version == libremidi_midi_configuration::MIDI2 && c->on_midi2_message.callback)
|
||||
conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) {
|
||||
cb.callback(cb.context, msg.timestamp, msg.data, msg.size());
|
||||
};
|
||||
else if (c->on_midi2_raw_data.callback)
|
||||
else if (c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback)
|
||||
{
|
||||
conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span<const uint32_t> msg, int64_t ts) {
|
||||
cb.callback(cb.context, ts, msg.data(), msg.size());
|
||||
|
||||
@@ -53,14 +53,13 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name)
|
||||
midi2::for_all_backends([&](auto b) { apis.push_back(b.API); });
|
||||
return apis;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
libremidi::API midi_api(const std::any& conf)
|
||||
libremidi::API midi_api(const input_api_configuration& conf)
|
||||
{
|
||||
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
||||
midi_any::for_all_backends([&]<typename T>(T) {
|
||||
if (std::any_cast<typename T::midi_in_configuration>(&conf)
|
||||
|| std::any_cast<typename T::midi_out_configuration>(&conf)
|
||||
|| std::any_cast<typename T::midi_observer_configuration>(&conf))
|
||||
if (std::get_if<typename T::midi_in_configuration>(&conf))
|
||||
{
|
||||
ret = T::API;
|
||||
}
|
||||
@@ -68,9 +67,34 @@ libremidi::API midi_api(const std::any& conf)
|
||||
return ret;
|
||||
}
|
||||
LIBREMIDI_INLINE
|
||||
std::any midi_in_configuration_for(libremidi::API api)
|
||||
libremidi::API midi_api(const output_api_configuration& conf)
|
||||
{
|
||||
std::any ret;
|
||||
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
||||
midi_any::for_all_backends([&]<typename T>(T) {
|
||||
if (std::get_if<typename T::midi_out_configuration>(&conf))
|
||||
{
|
||||
ret = T::API;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
LIBREMIDI_INLINE
|
||||
libremidi::API midi_api(const observer_api_configuration& conf)
|
||||
{
|
||||
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
||||
midi_any::for_all_backends([&]<typename T>(T) {
|
||||
if (std::get_if<typename T::midi_observer_configuration>(&conf))
|
||||
{
|
||||
ret = T::API;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
input_api_configuration midi_in_configuration_for(libremidi::API api)
|
||||
{
|
||||
input_api_configuration ret;
|
||||
midi_any::for_backend(api, [&]<typename T>(T) {
|
||||
using conf_type = typename T::midi_in_configuration;
|
||||
ret = conf_type{};
|
||||
@@ -79,9 +103,9 @@ std::any midi_in_configuration_for(libremidi::API api)
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any midi_out_configuration_for(libremidi::API api)
|
||||
output_api_configuration midi_out_configuration_for(libremidi::API api)
|
||||
{
|
||||
std::any ret;
|
||||
output_api_configuration ret;
|
||||
midi_any::for_backend(api, [&]<typename T>(T) {
|
||||
using conf_type = typename T::midi_out_configuration;
|
||||
ret = conf_type{};
|
||||
@@ -90,9 +114,9 @@ std::any midi_out_configuration_for(libremidi::API api)
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any observer_configuration_for(libremidi::API api)
|
||||
observer_api_configuration observer_configuration_for(libremidi::API api)
|
||||
{
|
||||
std::any ret;
|
||||
observer_api_configuration ret;
|
||||
midi_any::for_backend(api, [&]<typename T>(T) {
|
||||
using conf_type = typename T::midi_observer_configuration;
|
||||
ret = conf_type{};
|
||||
@@ -101,13 +125,13 @@ std::any observer_configuration_for(libremidi::API api)
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any midi_in_configuration_for(const libremidi::observer& obs)
|
||||
input_api_configuration midi_in_configuration_for(const libremidi::observer& obs)
|
||||
{
|
||||
return midi_in_configuration_for(obs.get_current_api());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any midi_out_configuration_for(const libremidi::observer& obs)
|
||||
output_api_configuration midi_out_configuration_for(const libremidi::observer& obs)
|
||||
{
|
||||
// FIXME reuse context when meaningful, e.g. ALSA, JACK...
|
||||
return midi_out_configuration_for(obs.get_current_api());
|
||||
@@ -144,19 +168,19 @@ catch (const std::exception& e)
|
||||
namespace midi1
|
||||
{
|
||||
LIBREMIDI_INLINE
|
||||
std::any in_default_configuration()
|
||||
input_api_configuration in_default_configuration()
|
||||
{
|
||||
return midi_in_configuration_for(default_api());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any out_default_configuration()
|
||||
output_api_configuration out_default_configuration()
|
||||
{
|
||||
return midi_out_configuration_for(default_api());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any observer_default_configuration()
|
||||
observer_api_configuration observer_default_configuration()
|
||||
{
|
||||
return observer_configuration_for(default_api());
|
||||
}
|
||||
@@ -165,19 +189,19 @@ std::any observer_default_configuration()
|
||||
namespace midi2
|
||||
{
|
||||
LIBREMIDI_INLINE
|
||||
std::any in_default_configuration()
|
||||
input_api_configuration in_default_configuration()
|
||||
{
|
||||
return midi_in_configuration_for(default_api());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any out_default_configuration()
|
||||
output_api_configuration out_default_configuration()
|
||||
{
|
||||
return midi_out_configuration_for(default_api());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::any observer_default_configuration()
|
||||
observer_api_configuration observer_default_configuration()
|
||||
{
|
||||
return observer_configuration_for(default_api());
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
*/
|
||||
|
||||
#include <libremidi/api.hpp>
|
||||
#include <libremidi/configurations.hpp>
|
||||
#include <libremidi/defaults.hpp>
|
||||
#include <libremidi/input_configuration.hpp>
|
||||
#include <libremidi/message.hpp>
|
||||
@@ -68,7 +69,6 @@
|
||||
#include <midi/sysex.h>
|
||||
#include <midi/universal_packet.h>
|
||||
#endif
|
||||
#include <any>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
//! such as jack_observer_configuration, winmm_observer_configuration, etc...
|
||||
//! * if no callbacks are passed, no secondary thread will be created unless absolutely necessary
|
||||
explicit observer(const observer_configuration& conf = {}) noexcept;
|
||||
explicit observer(observer_configuration conf, std::any api_conf);
|
||||
explicit observer(const observer_configuration& conf, observer_api_configuration api_conf);
|
||||
observer(const observer&) = delete;
|
||||
observer(observer&& other) noexcept;
|
||||
observer& operator=(const observer&) = delete;
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
[[nodiscard]] std::vector<libremidi::output_port> get_output_ports() const noexcept;
|
||||
|
||||
private:
|
||||
std::unique_ptr<class observer_api> impl_;
|
||||
std::unique_ptr<class observer_api> m_impl;
|
||||
};
|
||||
|
||||
//! Main class for receiving MIDI 1.0 and 2.0 messages.
|
||||
@@ -111,15 +111,15 @@ public:
|
||||
//! Construct a midi_in object with a configuration object for a specific MIDI 1 back-end
|
||||
//! see configuration.hpp for the available configuration types.
|
||||
//! An exception will be thrown if the requested back-end cannot be opened.
|
||||
explicit midi_in(input_configuration conf, std::any api_conf);
|
||||
explicit midi_in(const input_configuration& conf, const input_api_configuration& api_conf);
|
||||
|
||||
//! Construct a midi_in object with the default MIDI 2 back-end for the platform
|
||||
explicit midi_in(ump_input_configuration conf) noexcept;
|
||||
explicit midi_in(const ump_input_configuration& conf) noexcept;
|
||||
|
||||
//! Construct a midi_in object with a configuration object for a specific MIDI 2 back-end
|
||||
//! see configuration.hpp for the available configuration types.
|
||||
//! An exception will be thrown if the requested back-end cannot be opened.
|
||||
explicit midi_in(ump_input_configuration conf, std::any api_conf);
|
||||
explicit midi_in(const ump_input_configuration& conf, const input_api_configuration& api_conf);
|
||||
|
||||
midi_in(const midi_in&) = delete;
|
||||
midi_in(midi_in&& other) noexcept;
|
||||
@@ -157,7 +157,7 @@ public:
|
||||
timestamp absolute_timestamp() const noexcept;
|
||||
|
||||
private:
|
||||
std::unique_ptr<class midi_in_api> impl_;
|
||||
std::unique_ptr<class midi_in_api> m_impl;
|
||||
};
|
||||
|
||||
//! Main class for sending MIDI 1.0 and 2.0 messages.
|
||||
@@ -170,7 +170,7 @@ public:
|
||||
//! Construct a midi_out object with a configuration object for a specific back-end
|
||||
//! see configuration.hpp for the available configuration types.
|
||||
//! An exception will be thrown if the requested back-end cannot be opened.
|
||||
explicit midi_out(output_configuration conf, std::any api_conf);
|
||||
explicit midi_out(const output_configuration& conf, const output_api_configuration& api_conf);
|
||||
|
||||
midi_out(const midi_out&) = delete;
|
||||
midi_out(midi_out&& other) noexcept;
|
||||
@@ -260,7 +260,7 @@ public:
|
||||
stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<class midi_out_api> impl_;
|
||||
std::unique_ptr<class midi_out_api> m_impl;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#if defined(__cpp_exceptions)
|
||||
@@ -121,12 +122,8 @@ struct message
|
||||
auto rbegin() noexcept { return bytes.rbegin(); }
|
||||
auto rend() noexcept { return bytes.rend(); }
|
||||
|
||||
bool uses_channel(int channel) const
|
||||
bool uses_channel(int channel) const LIBREMIDI_PRECONDITION(channel > 0 && channel <= 16)
|
||||
{
|
||||
#if defined(__cpp_exceptions)
|
||||
if (channel <= 0 || channel > 16)
|
||||
throw std::range_error("out of range");
|
||||
#endif
|
||||
return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0);
|
||||
}
|
||||
|
||||
@@ -250,12 +247,12 @@ struct meta_events
|
||||
int notated_32nd_notes_per_beat = 96)
|
||||
{
|
||||
int n = 1;
|
||||
int powTwo = 0;
|
||||
int pow_two = 0;
|
||||
|
||||
while (n < denominator)
|
||||
{
|
||||
n <<= 1;
|
||||
++powTwo;
|
||||
++pow_two;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -263,7 +260,7 @@ struct meta_events
|
||||
0x58,
|
||||
0x04,
|
||||
static_cast<uint8_t>(numerator),
|
||||
static_cast<uint8_t>(powTwo),
|
||||
static_cast<uint8_t>(pow_two),
|
||||
static_cast<uint8_t>(clocks_per_click),
|
||||
static_cast<uint8_t>(notated_32nd_notes_per_beat)};
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi_in(auto base_conf, std::any api_conf, auto backends)
|
||||
make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends)
|
||||
{
|
||||
std::unique_ptr<midi_in_api> ptr;
|
||||
|
||||
assert(base_conf.on_message || base_conf.on_raw_data);
|
||||
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
|
||||
if (auto conf = std::get_if<typename T::midi_in_configuration>(&api_conf))
|
||||
{
|
||||
ptr = libremidi::make<typename T::midi_in>(std::move(base_conf), std::move(*conf));
|
||||
return true;
|
||||
@@ -117,8 +117,9 @@ make_midi1_in(const input_configuration& base_conf)
|
||||
return std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi1_in(
|
||||
const input_configuration& base_conf, const input_api_configuration& api_conf,
|
||||
libremidi::API api)
|
||||
{
|
||||
if (libremidi::is_midi1(api))
|
||||
{
|
||||
@@ -133,13 +134,13 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, li
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf)
|
||||
make_midi1_in(const input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||
{
|
||||
if (!api_conf.has_value())
|
||||
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||
{
|
||||
return make_midi1_in(base_conf);
|
||||
}
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
@@ -161,19 +162,19 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf)
|
||||
|
||||
/// MIDI 1 constructors
|
||||
LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept
|
||||
: impl_{make_midi1_in(base_conf)}
|
||||
: m_impl{make_midi1_in(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
midi_in::midi_in(input_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_midi1_in(base_conf, api_conf)}
|
||||
midi_in::midi_in(const input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||
: m_impl{make_midi1_in(base_conf, api_conf)}
|
||||
{
|
||||
if (!impl_)
|
||||
if (!m_impl)
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
m_impl = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +215,8 @@ make_midi2_in(const ump_input_configuration& base_conf)
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(
|
||||
const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
|
||||
const ump_input_configuration& base_conf, const input_api_configuration& api_conf,
|
||||
libremidi::API api)
|
||||
{
|
||||
if (is_midi2(api))
|
||||
{
|
||||
@@ -230,13 +232,13 @@ static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf)
|
||||
make_midi2_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||
{
|
||||
if (!api_conf.has_value())
|
||||
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||
{
|
||||
return make_midi2_in(base_conf);
|
||||
}
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
@@ -257,45 +259,45 @@ make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf
|
||||
}
|
||||
|
||||
/// MIDI 2 constructors
|
||||
LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept
|
||||
: impl_{make_midi2_in(base_conf)}
|
||||
LIBREMIDI_INLINE midi_in::midi_in(const ump_input_configuration& base_conf) noexcept
|
||||
: m_impl{make_midi2_in(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_midi2_in(base_conf, api_conf)}
|
||||
midi_in::midi_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||
: m_impl{make_midi2_in(base_conf, api_conf)}
|
||||
{
|
||||
if (!impl_)
|
||||
if (!m_impl)
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
m_impl = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_in::~midi_in() = default;
|
||||
|
||||
LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept
|
||||
: impl_{std::move(other.impl_)}
|
||||
: m_impl{std::move(other.m_impl)}
|
||||
{
|
||||
other.impl_
|
||||
other.m_impl
|
||||
= std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_in::set_port_name(std::string_view portName)
|
||||
{
|
||||
if(impl_->is_port_open())
|
||||
return impl_->set_port_name(portName);
|
||||
if (m_impl->is_port_open())
|
||||
return m_impl->set_port_name(portName);
|
||||
|
||||
return std::errc::not_connected;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept
|
||||
{
|
||||
this->impl_ = std::move(other.impl_);
|
||||
other.impl_
|
||||
this->m_impl = std::move(other.m_impl);
|
||||
other.m_impl
|
||||
= std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
return *this;
|
||||
}
|
||||
@@ -303,23 +305,23 @@ LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept
|
||||
LIBREMIDI_INLINE
|
||||
libremidi::API midi_in::get_current_api() const noexcept
|
||||
{
|
||||
return impl_->get_current_api();
|
||||
return m_impl->get_current_api();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_in::open_port(const input_port& port, std::string_view portName)
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->is_port_open())
|
||||
if (m_impl->is_port_open())
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
auto ret = impl_->open_port(port, portName);
|
||||
auto ret = m_impl->open_port(port, portName);
|
||||
if (ret == stdx::error{})
|
||||
{
|
||||
impl_->connected_ = true;
|
||||
impl_->port_open_ = true;
|
||||
m_impl->connected_ = true;
|
||||
m_impl->port_open_ = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -327,28 +329,28 @@ stdx::error midi_in::open_port(const input_port& port, std::string_view portName
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_in::open_virtual_port(std::string_view portName)
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->is_port_open())
|
||||
if (m_impl->is_port_open())
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
auto ret = impl_->open_virtual_port(portName);
|
||||
auto ret = m_impl->open_virtual_port(portName);
|
||||
if (ret == stdx::error{})
|
||||
impl_->port_open_ = true;
|
||||
m_impl->port_open_ = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_in::close_port()
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
auto ret = impl_->close_port();
|
||||
auto ret = m_impl->close_port();
|
||||
|
||||
impl_->connected_ = false;
|
||||
impl_->port_open_ = false;
|
||||
m_impl->connected_ = false;
|
||||
m_impl->port_open_ = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -356,18 +358,18 @@ stdx::error midi_in::close_port()
|
||||
LIBREMIDI_INLINE
|
||||
bool midi_in::is_port_open() const noexcept
|
||||
{
|
||||
return impl_->is_port_open();
|
||||
return m_impl->is_port_open();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
bool midi_in::is_port_connected() const noexcept
|
||||
{
|
||||
return impl_->is_port_connected();
|
||||
return m_impl->is_port_connected();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
int64_t midi_in::absolute_timestamp() const noexcept
|
||||
{
|
||||
return impl_->absolute_timestamp();
|
||||
return m_impl->absolute_timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
namespace libremidi
|
||||
{
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
||||
make_midi_out_impl(auto base_conf, std::any api_conf)
|
||||
make_midi_out_impl(auto base_conf, output_api_configuration api_conf)
|
||||
{
|
||||
std::unique_ptr<midi_out_api> ptr;
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))
|
||||
if (auto conf = std::get_if<typename T::midi_out_configuration>(&api_conf))
|
||||
{
|
||||
ptr = libremidi::make<typename T::midi_out>(std::move(base_conf), std::move(*conf));
|
||||
return true;
|
||||
@@ -23,7 +23,8 @@ make_midi_out_impl(auto base_conf, std::any api_conf)
|
||||
return false;
|
||||
};
|
||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);
|
||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
||||
if (!ptr)
|
||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -58,13 +59,13 @@ make_midi_out(const output_configuration& base_conf)
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
||||
make_midi_out(const output_configuration& base_conf, const std::any& api_conf)
|
||||
make_midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf)
|
||||
{
|
||||
if (!api_conf.has_value())
|
||||
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||
{
|
||||
return make_midi_out(base_conf);
|
||||
}
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
@@ -85,35 +86,35 @@ make_midi_out(const output_configuration& base_conf, const std::any& api_conf)
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept
|
||||
: impl_{make_midi_out(base_conf)}
|
||||
: m_impl{make_midi_out(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
midi_out::midi_out(output_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_midi_out(base_conf, api_conf)}
|
||||
midi_out::midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf)
|
||||
: m_impl{make_midi_out(base_conf, api_conf)}
|
||||
{
|
||||
if (!impl_)
|
||||
if (!m_impl)
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open midi out for the given api");
|
||||
impl_ = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
m_impl = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out::~midi_out() = default;
|
||||
|
||||
LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept
|
||||
: impl_{std::move(other.impl_)}
|
||||
: m_impl{std::move(other.m_impl)}
|
||||
{
|
||||
other.impl_
|
||||
other.m_impl
|
||||
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
|
||||
{
|
||||
this->impl_ = std::move(other.impl_);
|
||||
other.impl_
|
||||
this->m_impl = std::move(other.m_impl);
|
||||
other.m_impl
|
||||
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
return *this;
|
||||
}
|
||||
@@ -121,8 +122,8 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::set_port_name(std::string_view portName) const
|
||||
{
|
||||
if(impl_->is_port_open())
|
||||
return impl_->set_port_name(portName);
|
||||
if (m_impl->is_port_open())
|
||||
return m_impl->set_port_name(portName);
|
||||
|
||||
return std::errc::not_connected;
|
||||
}
|
||||
@@ -130,23 +131,23 @@ stdx::error midi_out::set_port_name(std::string_view portName) const
|
||||
LIBREMIDI_INLINE
|
||||
libremidi::API midi_out::get_current_api() const noexcept
|
||||
{
|
||||
return impl_->get_current_api();
|
||||
return m_impl->get_current_api();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->is_port_open())
|
||||
if (m_impl->is_port_open())
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
auto ret = impl_->open_port(port, portName);
|
||||
auto ret = m_impl->open_port(port, portName);
|
||||
if (ret == stdx::error{})
|
||||
{
|
||||
impl_->connected_ = true;
|
||||
impl_->port_open_ = true;
|
||||
m_impl->connected_ = true;
|
||||
m_impl->port_open_ = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -154,40 +155,40 @@ stdx::error midi_out::open_port(const output_port& port, std::string_view portNa
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::open_virtual_port(std::string_view portName) const
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->is_port_open())
|
||||
if (m_impl->is_port_open())
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
auto ret = impl_->open_virtual_port(portName);
|
||||
auto ret = m_impl->open_virtual_port(portName);
|
||||
if (ret == stdx::error{})
|
||||
impl_->port_open_ = true;
|
||||
m_impl->port_open_ = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::close_port() const
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
auto ret = impl_->close_port();
|
||||
impl_->connected_ = false;
|
||||
impl_->port_open_ = false;
|
||||
auto ret = m_impl->close_port();
|
||||
m_impl->connected_ = false;
|
||||
m_impl->port_open_ = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
bool midi_out::is_port_open() const noexcept
|
||||
{
|
||||
return impl_->is_port_open();
|
||||
return m_impl->is_port_open();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
bool midi_out::is_port_connected() const noexcept
|
||||
{
|
||||
return impl_->is_port_connected();
|
||||
return m_impl->is_port_connected();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
@@ -227,13 +228,13 @@ stdx::error midi_out::send_message(const unsigned char* message, size_t size) co
|
||||
assert(size > 0);
|
||||
#endif
|
||||
|
||||
return impl_->send_message(message, size);
|
||||
return m_impl->send_message(message, size);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
int64_t midi_out::current_time()
|
||||
{
|
||||
return impl_->current_time();
|
||||
return m_impl->current_time();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
@@ -243,7 +244,7 @@ stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message,
|
||||
assert(size > 0);
|
||||
#endif
|
||||
|
||||
return impl_->schedule_message(ts, message, size);
|
||||
return m_impl->schedule_message(ts, message, size);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
@@ -254,7 +255,7 @@ stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const
|
||||
assert(size <= 4);
|
||||
#endif
|
||||
|
||||
return impl_->send_ump(message, size);
|
||||
return m_impl->send_ump(message, size);
|
||||
}
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::send_ump(const libremidi::ump& message) const
|
||||
@@ -321,6 +322,6 @@ stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t s
|
||||
assert(size > 0);
|
||||
#endif
|
||||
|
||||
return impl_->schedule_ump(ts, message, size);
|
||||
return m_impl->schedule_ump(ts, message, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,19 @@
|
||||
#include <libremidi/backends.hpp>
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf)
|
||||
static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(const auto& base_conf)
|
||||
{
|
||||
for (const auto& api : available_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
|
||||
return impl_;
|
||||
if (auto impl = make_observer(base_conf, observer_configuration_for(api)))
|
||||
return impl;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
@@ -26,8 +28,8 @@ static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_co
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
|
||||
return impl_;
|
||||
if (auto impl = make_observer(base_conf, observer_configuration_for(api)))
|
||||
return impl;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
@@ -37,11 +39,11 @@ static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_co
|
||||
return std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf)
|
||||
LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configuration api_conf)
|
||||
{
|
||||
std::unique_ptr<observer_api> ptr;
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))
|
||||
if (auto conf = std::get_if<typename T::midi_observer_configuration>(&api_conf))
|
||||
{
|
||||
ptr = libremidi::make<typename T::midi_observer>(std::move(base_conf), std::move(*conf));
|
||||
return true;
|
||||
@@ -49,17 +51,19 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf)
|
||||
return false;
|
||||
};
|
||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);
|
||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
||||
if (!ptr)
|
||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf, std::any api_conf)
|
||||
LIBREMIDI_INLINE std::unique_ptr<observer_api>
|
||||
make_observer(const auto& base_conf, observer_api_configuration api_conf)
|
||||
{
|
||||
if (!api_conf.has_value())
|
||||
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||
{
|
||||
return make_observer(base_conf);
|
||||
}
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
@@ -80,32 +84,33 @@ LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf, std
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept
|
||||
: impl_{make_observer(base_conf)}
|
||||
: m_impl{make_observer(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_observer(base_conf, api_conf)}
|
||||
LIBREMIDI_INLINE
|
||||
observer::observer(const observer_configuration& base_conf, observer_api_configuration api_conf)
|
||||
: m_impl{make_observer(base_conf, std::move(api_conf))}
|
||||
{
|
||||
if (!impl_)
|
||||
if (!m_impl)
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open observer for the given api");
|
||||
impl_ = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||
m_impl = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE observer::observer(observer&& other) noexcept
|
||||
: impl_{std::move(other.impl_)}
|
||||
: m_impl{std::move(other.m_impl)}
|
||||
{
|
||||
other.impl_ = std::make_unique<libremidi::observer_dummy>(
|
||||
other.m_impl = std::make_unique<libremidi::observer_dummy>(
|
||||
observer_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE observer& observer::operator=(observer&& other) noexcept
|
||||
{
|
||||
this->impl_ = std::move(other.impl_);
|
||||
other.impl_ = std::make_unique<libremidi::observer_dummy>(
|
||||
this->m_impl = std::move(other.m_impl);
|
||||
other.m_impl = std::make_unique<libremidi::observer_dummy>(
|
||||
observer_configuration{}, dummy_configuration{});
|
||||
return *this;
|
||||
}
|
||||
@@ -116,18 +121,18 @@ observer::~observer() = default;
|
||||
LIBREMIDI_INLINE
|
||||
libremidi::API observer::get_current_api() const noexcept
|
||||
{
|
||||
return impl_->get_current_api();
|
||||
return m_impl->get_current_api();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::vector<libremidi::input_port> observer::get_input_ports() const noexcept
|
||||
{
|
||||
return impl_->get_input_ports();
|
||||
return m_impl->get_input_ports();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
std::vector<libremidi::output_port> observer::get_output_ports() const noexcept
|
||||
{
|
||||
return impl_->get_output_ports();
|
||||
return m_impl->get_output_ports();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,43 +2,106 @@
|
||||
#include <libremidi/config.hpp>
|
||||
#include <libremidi/error.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <compare>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
using client_handle = std::uint64_t;
|
||||
using port_handle = std::uint64_t;
|
||||
|
||||
struct uuid
|
||||
{
|
||||
std::array<uint8_t, 16> bytes;
|
||||
|
||||
bool operator==(const uuid& other) const noexcept = default;
|
||||
std::strong_ordering operator<=>(const uuid& other) const noexcept = default;
|
||||
};
|
||||
using container_identifier = std::variant<std::monostate, uuid, std::string, std::uint64_t>;
|
||||
using device_identifier = std::variant<std::monostate, std::string, std::uint64_t>;
|
||||
|
||||
struct LIBREMIDI_EXPORT port_information
|
||||
{
|
||||
// Handle to the client object:
|
||||
enum port_type : uint8_t
|
||||
{
|
||||
unknown = 0,
|
||||
|
||||
software = (1 << 1),
|
||||
loopback = (1 << 2),
|
||||
|
||||
hardware = (1 << 3),
|
||||
usb = (1 << 4),
|
||||
bluetooth = (1 << 5),
|
||||
pci = (1 << 6),
|
||||
|
||||
network = (1 << 7),
|
||||
};
|
||||
|
||||
/// Handle to the API client object if the API provides one
|
||||
// ALSA Raw: unused
|
||||
// ALSA Seq: snd_seq_t*
|
||||
// CoreMIDI: MidiClientRef
|
||||
// WebMIDI: unused
|
||||
// JACK: jack_client_t*
|
||||
// PipeWire: unused
|
||||
// PipeWire: unused // FIXME: pw_context? pw_main_loop?
|
||||
// WinMM: unused
|
||||
// WinUWP: unused
|
||||
client_handle client = static_cast<client_handle>(-1);
|
||||
|
||||
// ALSA Raw: { uint16_t card, device, sub, padding; }
|
||||
// ALSA Seq: { uint32_t client, uint32_t port; }
|
||||
/// Container identifier if the API provides one
|
||||
// WinMIDI: ContainerID GUID (bit_cast to a winapi or winrt::GUID ;
|
||||
// this is not the string but the binary representation).
|
||||
// ALSA: device id (std::string), e.g. ID_PATH as returned by udev: "pci-0000:00:14.0-usb-0:12:1.0"
|
||||
// CoreMIDI: USBLocationID (int32_t)
|
||||
container_identifier container = std::monostate{};
|
||||
|
||||
/// Device identifier if the API provides one
|
||||
// WinMM: { uint16_t manufacturer_id, uint16_t product_id; }
|
||||
// WinMIDI: EndpointDeviceId (std::string), e.g. "\\?\swd#midisrv#midiu_ksa..."
|
||||
// ALSA: sysfs path (std::string), e.g. "/sys/devices/pci0000:00/0000:00:02.2/0000:02:00.0/sound/card0/controlC0"
|
||||
// CoreMIDI: USBVendorProduct (int32_t)
|
||||
device_identifier device = std::monostate{};
|
||||
|
||||
/// Handle to the port identifier if the API provides one
|
||||
// ALSA Raw: bit_cast to struct { uint16_t card, device, sub, padding; }.
|
||||
// ALSA Seq: bit_cast to struct { uint32_t client, port; }
|
||||
// CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t)
|
||||
// WebMIDI: unused
|
||||
// JACK: jack_port_id_t
|
||||
// PipeWire: port.id
|
||||
// WinMM: unset, identified by port_name
|
||||
// WinMIDI: uint64_t terminal_block_number; (MidiGroupTerminalBlock::Number(), index is 1-based)
|
||||
// WinMM: port index between 0 and midi{In,Out}GetNumDevs()
|
||||
// WinUWP: unused
|
||||
port_handle port = static_cast<port_handle>(-1);
|
||||
|
||||
/// User-readable information
|
||||
// CoreMIDI: kMIDIPropertyManufacturer
|
||||
// WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName
|
||||
// WinMM: unavailable
|
||||
std::string manufacturer{};
|
||||
|
||||
// CoreMIDI: kMIDIPropertyModel
|
||||
// WinMIDI: MidiEndpointDeviceInformation::Name
|
||||
// WinMM: unavailable
|
||||
std::string device_name{};
|
||||
|
||||
// CoreMIDI: kMIDIPropertyName
|
||||
// WinMIDI: MidiGroupTerminalBlock::Name
|
||||
// WinMM: szPname
|
||||
std::string port_name{};
|
||||
|
||||
// CoreMIDI: kMIDIPropertyDisplayName
|
||||
// Otherwise: the closest to a unique name we can get
|
||||
std::string display_name{};
|
||||
|
||||
/// Port type
|
||||
// CoreMIDI: available
|
||||
// WinMM: unavailable
|
||||
// WinMIDI: available
|
||||
port_type type = port_type::unknown;
|
||||
|
||||
bool operator==(const port_information& other) const noexcept = default;
|
||||
std::strong_ordering operator<=>(const port_information& other) const noexcept = default;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,858 @@
|
||||
#pragma once
|
||||
|
||||
#include <libremidi/error_handler.hpp>
|
||||
#include <libremidi/message.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
static constexpr auto to_underlying(auto e)
|
||||
{
|
||||
return static_cast<std::underlying_type_t<decltype(e)>>(e);
|
||||
}
|
||||
|
||||
// A clean-room reverse-engineered remote control protocol compatible with many hardware devices.
|
||||
// Thanks https://github.com/NicoG60/TouchMCU !
|
||||
struct remote_control_protocol
|
||||
{
|
||||
enum class device_type : uint8_t
|
||||
{
|
||||
logic_control = 0x10,
|
||||
logic_control_xt = 0x11,
|
||||
mackie_control = 0x14
|
||||
};
|
||||
|
||||
enum class command_to_device : uint8_t
|
||||
{
|
||||
device_query = 0x00,
|
||||
host_connection_reply = 0x02,
|
||||
|
||||
transport_click = 0x0A,
|
||||
lcd_backlight_save = 0x0B,
|
||||
touchless_movable_fader = 0x0C,
|
||||
|
||||
faders_touch_sensitivity = 0x0E,
|
||||
go_offline = 0x0F,
|
||||
update_tc_display = 0x10,
|
||||
update_assignment_display = 0x11,
|
||||
update_lcd = 0x12,
|
||||
firmware_version_request = 0x13,
|
||||
version_reply = 0x14,
|
||||
|
||||
firmware_update = 0x18,
|
||||
|
||||
channel_meter_mode = 0x20,
|
||||
global_lcd_meter_mode = 0x21,
|
||||
|
||||
faders_to_minimum = 0x61,
|
||||
all_leds_off = 0x62,
|
||||
reset = 0x63,
|
||||
};
|
||||
|
||||
enum class command_from_device : uint8_t
|
||||
{
|
||||
host_connection_query = 0x01,
|
||||
host_connection_confirmation = 0x03,
|
||||
host_connection_error = 0x04,
|
||||
version_reply = 0x14,
|
||||
};
|
||||
|
||||
enum class lcd_meter_mode : uint8_t
|
||||
{
|
||||
horizontal = 0x00,
|
||||
vertical = 0x01,
|
||||
};
|
||||
|
||||
enum class fader_sensitivity : uint8_t
|
||||
{
|
||||
sensitivity_0 = 0x00,
|
||||
sensitivity_1 = 0x01,
|
||||
sensitivity_2 = 0x02,
|
||||
sensitivity_3 = 0x03,
|
||||
sensitivity_4 = 0x04,
|
||||
|
||||
sensitivity_default = sensitivity_3,
|
||||
};
|
||||
|
||||
// CC Message:
|
||||
// 0b0LMMVVVV
|
||||
// L: toggle underneath LED
|
||||
// MM: mode as led_ring_mode
|
||||
// VVVV: value
|
||||
enum class led_ring_mode : uint8_t
|
||||
{
|
||||
mode_0 = 0b00, // one led only
|
||||
mode_1 = 0b01, // pan pot
|
||||
mode_2 = 0b10, // fill leds from left
|
||||
mode_3 = 0b11, // fill leds from middle
|
||||
};
|
||||
|
||||
enum class pot : uint8_t
|
||||
{
|
||||
pot_0 = 0x00,
|
||||
pot_1 = 0x01,
|
||||
pot_2 = 0x02,
|
||||
pot_3 = 0x03,
|
||||
pot_4 = 0x04,
|
||||
pot_5 = 0x05,
|
||||
pot_6 = 0x06,
|
||||
pot_7 = 0x07,
|
||||
};
|
||||
|
||||
enum class fader : uint8_t
|
||||
{
|
||||
fader_0 = 0x00,
|
||||
fader_1 = 0x01,
|
||||
fader_2 = 0x02,
|
||||
fader_3 = 0x03,
|
||||
fader_4 = 0x04,
|
||||
fader_5 = 0x05,
|
||||
fader_6 = 0x06,
|
||||
fader_7 = 0x07,
|
||||
fader_master = 0x08,
|
||||
};
|
||||
|
||||
// control changes
|
||||
enum class mixer_control : uint8_t
|
||||
{
|
||||
// rotation: CC
|
||||
// 0b00000001 : clockwise
|
||||
// 0b01000001 : counter-clockwise
|
||||
vpot_rotation_0 = 0x10 + 0x00,
|
||||
vpot_rotation_1 = 0x10 + 0x01,
|
||||
vpot_rotation_2 = 0x10 + 0x02,
|
||||
vpot_rotation_3 = 0x10 + 0x03,
|
||||
vpot_rotation_4 = 0x10 + 0x04,
|
||||
vpot_rotation_5 = 0x10 + 0x05,
|
||||
vpot_rotation_6 = 0x10 + 0x06,
|
||||
vpot_rotation_7 = 0x10 + 0x07,
|
||||
|
||||
external_control = 0x2E,
|
||||
|
||||
// led ring: CC
|
||||
vpot_led_0 = 0x30 + 0x00,
|
||||
vpot_led_1 = 0x30 + 0x01,
|
||||
vpot_led_2 = 0x30 + 0x02,
|
||||
vpot_led_3 = 0x30 + 0x03,
|
||||
vpot_led_4 = 0x30 + 0x04,
|
||||
vpot_led_5 = 0x30 + 0x05,
|
||||
vpot_led_6 = 0x30 + 0x06,
|
||||
vpot_led_7 = 0x30 + 0x07,
|
||||
|
||||
jog_wheel = 0x3C,
|
||||
|
||||
timecode_digit_0 = 0x40 + 0x00,
|
||||
timecode_digit_1 = 0x40 + 0x01,
|
||||
timecode_digit_2 = 0x40 + 0x02,
|
||||
timecode_digit_3 = 0x40 + 0x03,
|
||||
timecode_digit_4 = 0x40 + 0x04,
|
||||
timecode_digit_5 = 0x40 + 0x05,
|
||||
timecode_digit_6 = 0x40 + 0x06,
|
||||
timecode_digit_7 = 0x40 + 0x07,
|
||||
timecode_digit_8 = 0x40 + 0x08,
|
||||
timecode_digit_9 = 0x40 + 0x09,
|
||||
|
||||
assignment_digit_0 = 0x4A,
|
||||
assignment_digit_1 = 0x4B,
|
||||
};
|
||||
|
||||
// note events
|
||||
enum class mixer_command : uint8_t
|
||||
{
|
||||
vpot_click_0 = 0x20 + 0x00,
|
||||
vpot_click_1 = 0x20 + 0x01,
|
||||
vpot_click_2 = 0x20 + 0x02,
|
||||
vpot_click_3 = 0x20 + 0x03,
|
||||
vpot_click_4 = 0x20 + 0x04,
|
||||
vpot_click_5 = 0x20 + 0x05,
|
||||
vpot_click_6 = 0x20 + 0x06,
|
||||
vpot_click_7 = 0x20 + 0x07,
|
||||
|
||||
rec_0 = 0x00 + 0x00,
|
||||
rec_1 = 0x00 + 0x01,
|
||||
rec_2 = 0x00 + 0x02,
|
||||
rec_3 = 0x00 + 0x03,
|
||||
rec_4 = 0x00 + 0x04,
|
||||
rec_5 = 0x00 + 0x05,
|
||||
rec_6 = 0x00 + 0x06,
|
||||
rec_7 = 0x00 + 0x07,
|
||||
|
||||
solo_0 = 0x08 + 0x00,
|
||||
solo_1 = 0x08 + 0x01,
|
||||
solo_2 = 0x08 + 0x02,
|
||||
solo_3 = 0x08 + 0x03,
|
||||
solo_4 = 0x08 + 0x04,
|
||||
solo_5 = 0x08 + 0x05,
|
||||
solo_6 = 0x08 + 0x06,
|
||||
solo_7 = 0x08 + 0x07,
|
||||
|
||||
mute_0 = 0x10 + 0x00,
|
||||
mute_1 = 0x10 + 0x01,
|
||||
mute_2 = 0x10 + 0x02,
|
||||
mute_3 = 0x10 + 0x03,
|
||||
mute_4 = 0x10 + 0x04,
|
||||
mute_5 = 0x10 + 0x05,
|
||||
mute_6 = 0x10 + 0x06,
|
||||
mute_7 = 0x10 + 0x07,
|
||||
|
||||
sel_0 = 0x18 + 0x00,
|
||||
sel_1 = 0x18 + 0x01,
|
||||
sel_2 = 0x18 + 0x02,
|
||||
sel_3 = 0x18 + 0x03,
|
||||
sel_4 = 0x18 + 0x04,
|
||||
sel_5 = 0x18 + 0x05,
|
||||
sel_6 = 0x18 + 0x06,
|
||||
sel_7 = 0x18 + 0x07,
|
||||
|
||||
// TODO metering
|
||||
assign_track = 0x28,
|
||||
assign_send = 0x29,
|
||||
assign_pan = 0x2A,
|
||||
assign_plugin = 0x2B,
|
||||
assign_eq = 0x2C,
|
||||
assign_instrument = 0x2D,
|
||||
|
||||
bank_left = 0x2E,
|
||||
bank_right = 0x2F,
|
||||
channel_left = 0x30,
|
||||
channel_right = 0x31,
|
||||
flip = 0x32,
|
||||
global = 0x33,
|
||||
|
||||
name_value_button = 0x34,
|
||||
smpte_beats_button = 0x35,
|
||||
|
||||
f1 = 0x36 + 0x00,
|
||||
f2 = 0x36 + 0x01,
|
||||
f3 = 0x36 + 0x02,
|
||||
f4 = 0x36 + 0x03,
|
||||
f5 = 0x36 + 0x04,
|
||||
f6 = 0x36 + 0x05,
|
||||
f7 = 0x36 + 0x06,
|
||||
f8 = 0x36 + 0x07,
|
||||
|
||||
midi_tracks = 0x3E,
|
||||
inputs = 0x3F,
|
||||
audio_tracks = 0x40,
|
||||
audio_instruments = 0x41,
|
||||
aux = 0x42,
|
||||
busses = 0x43,
|
||||
outputs = 0x44,
|
||||
user = 0x45,
|
||||
|
||||
shift = 0x46,
|
||||
option = 0x47,
|
||||
control = 0x48,
|
||||
alt = 0x49,
|
||||
|
||||
save = 0x50,
|
||||
undo = 0x51,
|
||||
cancel = 0x52,
|
||||
enter = 0x53,
|
||||
|
||||
markers = 0x54,
|
||||
nudge = 0x55,
|
||||
cycle = 0x56,
|
||||
drop = 0x57,
|
||||
replace = 0x58,
|
||||
click = 0x59,
|
||||
solo = 0x5a,
|
||||
|
||||
rewind = 0x5b,
|
||||
forward = 0x5c,
|
||||
stop = 0x5d,
|
||||
play = 0x5e,
|
||||
record = 0x5f,
|
||||
|
||||
up = 0x60,
|
||||
down = 0x61,
|
||||
left = 0x62,
|
||||
right = 0x63,
|
||||
zoom = 0x64,
|
||||
scrub = 0x65,
|
||||
|
||||
user_switch_1 = 0x66,
|
||||
user_switch_2 = 0x67,
|
||||
|
||||
fader_touched_0 = 0x68,
|
||||
fader_touched_1 = 0x69,
|
||||
fader_touched_2 = 0x6a,
|
||||
fader_touched_3 = 0x6b,
|
||||
fader_touched_4 = 0x6c,
|
||||
fader_touched_5 = 0x6d,
|
||||
fader_touched_6 = 0x6e,
|
||||
fader_touched_7 = 0x6f,
|
||||
fader_touched_master = 0x70,
|
||||
|
||||
smpte_led = 0x71,
|
||||
beats_led = 0x72,
|
||||
rude_solo_led = 0x73,
|
||||
|
||||
relay_click = 0x76,
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
using arr = std::array<uint8_t, N>;
|
||||
|
||||
device_type type = device_type::mackie_control;
|
||||
|
||||
static libremidi::message make_command_impl(auto&&... data)
|
||||
{
|
||||
using namespace std;
|
||||
libremidi::message m;
|
||||
m.bytes.reserve((std::ssize(data) + ...));
|
||||
(m.bytes.insert(m.bytes.end(), begin(data), end(data)), ...);
|
||||
return m;
|
||||
}
|
||||
|
||||
libremidi::message make_command(command_to_device c, auto&&... data)
|
||||
{
|
||||
using namespace std;
|
||||
const auto type = to_underlying(this->type);
|
||||
const auto cmd = to_underlying(c);
|
||||
const uint8_t header[6]{0xF0, 0x00, 0x00, 0x66, type, cmd};
|
||||
const uint8_t footer[1]{0xF7};
|
||||
return make_command_impl(header, data..., footer);
|
||||
}
|
||||
|
||||
auto device_query() { return make_command(command_to_device::device_query); }
|
||||
|
||||
auto response_to_challenge(arr<4> c)
|
||||
{
|
||||
arr<4> r;
|
||||
|
||||
r[0] = 0x7F & (c[0] + (c[1] ^ 0x0A) - c[3]);
|
||||
r[1] = 0x7F & ((c[2] >> 4) ^ (c[0] + c[3]));
|
||||
r[2] = 0x7F & (c[3] - (c[2] << 2) ^ (c[0] | c[1]));
|
||||
r[3] = 0x7F & (c[1] - c[2] + (0xF0 ^ (c[3] << 4)));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
auto host_connection_reply(arr<7> serial, arr<4> challenge_code)
|
||||
{
|
||||
const auto res = response_to_challenge(challenge_code);
|
||||
return make_command(command_to_device::host_connection_reply, serial, res);
|
||||
}
|
||||
|
||||
auto transport_click(bool enabled)
|
||||
{
|
||||
return make_command(
|
||||
command_to_device::transport_click, arr<1>{uint8_t(enabled ? 0x01 : 0x00)});
|
||||
}
|
||||
|
||||
auto lcd_backlight_save(uint8_t timeout)
|
||||
{
|
||||
// 0: instant off otherwise timeout in minutes
|
||||
return make_command(command_to_device::lcd_backlight_save, arr<1>{timeout});
|
||||
}
|
||||
|
||||
auto touchless_movable_fader(bool enabled)
|
||||
{
|
||||
return make_command(
|
||||
command_to_device::touchless_movable_fader, arr<1>{uint8_t(enabled ? 0x01 : 0x00)});
|
||||
}
|
||||
|
||||
auto faders_touch_sensitivity(uint8_t fader_id, fader_sensitivity sens)
|
||||
{
|
||||
return make_command(
|
||||
command_to_device::faders_touch_sensitivity, arr<2>{fader_id, to_underlying(sens)});
|
||||
}
|
||||
|
||||
auto go_offline() { return make_command(command_to_device::go_offline, arr<1>{0x7F}); }
|
||||
|
||||
auto update_tc_display()
|
||||
{
|
||||
// FIXME 1 .. 10
|
||||
return make_command(command_to_device::update_tc_display, arr<10>{});
|
||||
}
|
||||
|
||||
auto update_assignment_display()
|
||||
{
|
||||
// FIXME 1 .. 2
|
||||
return make_command(command_to_device::update_assignment_display, arr<2>{});
|
||||
}
|
||||
|
||||
auto update_lcd(std::string_view txt, int pos)
|
||||
{
|
||||
// FIXME
|
||||
if (pos < 0 || pos >= 112)
|
||||
return libremidi::message{};
|
||||
|
||||
int len = int(std::ssize(txt));
|
||||
|
||||
if (len > (112 - pos))
|
||||
{
|
||||
txt = txt.substr(0, 112 - pos);
|
||||
len = 112 - pos;
|
||||
}
|
||||
|
||||
uint8_t buf[128];
|
||||
const int N = std::min(len, 112 - pos);
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
buf[i + pos] = charmap_lcd(txt[i]);
|
||||
}
|
||||
buf[55] = '\n';
|
||||
buf[111] = '\n';
|
||||
|
||||
uint8_t cmd_pos = pos;
|
||||
|
||||
return make_command(command_to_device::update_lcd, arr<1>{cmd_pos}, std::span(buf + pos, len));
|
||||
}
|
||||
|
||||
auto update_lcd(std::string_view txt)
|
||||
{
|
||||
uint8_t buf[112] = {};
|
||||
for (int i = 0; i < std::min(int(std::ssize(txt)), 112); i++)
|
||||
{
|
||||
buf[i] = charmap_lcd(txt[i]);
|
||||
}
|
||||
buf[55] = '\n';
|
||||
buf[111] = '\n';
|
||||
return make_command(command_to_device::update_lcd, arr<1>{0}, std::span(buf, 112));
|
||||
}
|
||||
|
||||
auto firmware_version_request()
|
||||
{
|
||||
return make_command(command_to_device::firmware_version_request, arr<1>{0});
|
||||
}
|
||||
|
||||
auto firmware_update(std::span<uint8_t> firmware)
|
||||
{
|
||||
return make_command(command_to_device::firmware_update, firmware);
|
||||
}
|
||||
|
||||
auto channel_meter_mode(uint8_t fader_id, bool level_meter, bool peak_hold, bool signal_led)
|
||||
{
|
||||
uint8_t mode = 0;
|
||||
|
||||
if (signal_led)
|
||||
mode |= 0b1;
|
||||
if (peak_hold)
|
||||
mode |= 0b10;
|
||||
if (level_meter)
|
||||
mode |= 0b100;
|
||||
|
||||
return make_command(command_to_device::channel_meter_mode, arr<2>{fader_id, mode});
|
||||
}
|
||||
|
||||
auto global_lcd_meter_mode(lcd_meter_mode mode)
|
||||
{
|
||||
return make_command(command_to_device::global_lcd_meter_mode, arr<1>{to_underlying(mode)});
|
||||
}
|
||||
|
||||
auto faders_to_minimum() { return make_command(command_to_device::faders_to_minimum); }
|
||||
|
||||
auto all_leds_off() { return make_command(command_to_device::all_leds_off); }
|
||||
|
||||
auto reset() { return make_command(command_to_device::reset); }
|
||||
|
||||
static auto timecode(int hi, int mi, int si, int framei)
|
||||
{
|
||||
std::vector<libremidi::message> msg;
|
||||
auto h = std::to_string(hi);
|
||||
while (h.size() < 3)
|
||||
h.insert(h.begin(), '0');
|
||||
auto m = std::to_string(mi);
|
||||
while (m.size() < 2)
|
||||
m.insert(m.begin(), '0');
|
||||
auto s = std::to_string(si);
|
||||
while (s.size() < 2)
|
||||
s.insert(s.begin(), '0');
|
||||
auto f = std::to_string(framei);
|
||||
while (f.size() < 3)
|
||||
f.insert(f.begin(), '0');
|
||||
|
||||
using ce = libremidi::channel_events;
|
||||
msg.push_back(ce::control_change(1, 0x49, charmap_7segment(h[0])));
|
||||
msg.push_back(ce::control_change(1, 0x48, charmap_7segment(h[1])));
|
||||
msg.push_back(ce::control_change(1, 0x47, charmap_7segment(h[2])));
|
||||
|
||||
msg.push_back(ce::control_change(1, 0x46, charmap_7segment(m[0])));
|
||||
msg.push_back(ce::control_change(1, 0x45, charmap_7segment(m[1])));
|
||||
|
||||
msg.push_back(ce::control_change(1, 0x44, charmap_7segment(s[0])));
|
||||
msg.push_back(ce::control_change(1, 0x43, charmap_7segment(s[1])));
|
||||
|
||||
msg.push_back(ce::control_change(1, 0x42, charmap_7segment(f[0])));
|
||||
msg.push_back(ce::control_change(1, 0x41, charmap_7segment(f[1])));
|
||||
msg.push_back(ce::control_change(1, 0x40, charmap_7segment(f[2])));
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
static uint8_t charmap_7segment(char c, bool dot)
|
||||
{
|
||||
uint8_t res = charmap_7segment(c);
|
||||
if (dot)
|
||||
res |= 0b00100000;
|
||||
return res;
|
||||
};
|
||||
|
||||
static uint8_t charmap_7segment(char c)
|
||||
{
|
||||
// FIXME there are some more characters but what to map them to ? :)
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 1;
|
||||
else if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A' + 1;
|
||||
else if (c >= '0' && c <= '9')
|
||||
return c - '0' + 0x30;
|
||||
else
|
||||
switch (c)
|
||||
{
|
||||
case '[':
|
||||
case '{':
|
||||
return 0x1B;
|
||||
case '\\':
|
||||
case '~': // yen too ?
|
||||
return 0x1C;
|
||||
case ']':
|
||||
case '}':
|
||||
return 0x1D;
|
||||
case '^':
|
||||
return 0x1E;
|
||||
case '_':
|
||||
return 0x1F;
|
||||
|
||||
case '!':
|
||||
return 0x21;
|
||||
case '"':
|
||||
return 0x22;
|
||||
case '#':
|
||||
return 0x23;
|
||||
case '$':
|
||||
return 0x24;
|
||||
case '%':
|
||||
return 0x25;
|
||||
case '&':
|
||||
return 0x26;
|
||||
case '\'':
|
||||
return 0x27;
|
||||
case '(':
|
||||
return 0x28;
|
||||
case ')':
|
||||
return 0x29;
|
||||
case '*':
|
||||
return 0x2A;
|
||||
case '+':
|
||||
return 0x2B;
|
||||
case ',':
|
||||
return 0x2C;
|
||||
case '-':
|
||||
return 0x2D;
|
||||
case '.':
|
||||
return 0x2E;
|
||||
case '/':
|
||||
return 0x2F;
|
||||
|
||||
case ':':
|
||||
return 0x3A;
|
||||
case ';':
|
||||
return 0x3B;
|
||||
case '<':
|
||||
return 0x3C;
|
||||
case '=':
|
||||
return 0x3D;
|
||||
case '>':
|
||||
return 0x3E;
|
||||
case '?':
|
||||
return 0x3F;
|
||||
|
||||
default:
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
static uint8_t charmap_lcd(char c)
|
||||
{
|
||||
// FIXME there are some more characters but what to map them to ? :)
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 0x61;
|
||||
else if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A' + 0x41;
|
||||
else if (c >= '0' && c <= '9')
|
||||
return c - '0' + 0x30;
|
||||
else
|
||||
switch (c)
|
||||
{
|
||||
case '!':
|
||||
return 0x21;
|
||||
case '"':
|
||||
return 0x22;
|
||||
case '#':
|
||||
return 0x23;
|
||||
case '$':
|
||||
return 0x24;
|
||||
case '%':
|
||||
return 0x25;
|
||||
case '&':
|
||||
return 0x26;
|
||||
case '\'':
|
||||
return 0x27;
|
||||
case '(':
|
||||
return 0x28;
|
||||
case ')':
|
||||
return 0x29;
|
||||
case '*':
|
||||
return 0x2A;
|
||||
case '+':
|
||||
return 0x2B;
|
||||
case ',':
|
||||
return 0x2C;
|
||||
case '-':
|
||||
return 0x2D;
|
||||
case '.':
|
||||
return 0x2E;
|
||||
case '/':
|
||||
return 0x2F;
|
||||
|
||||
case ':':
|
||||
return 0x3A;
|
||||
case ';':
|
||||
return 0x3B;
|
||||
case '<':
|
||||
return 0x3C;
|
||||
case '=':
|
||||
return 0x3D;
|
||||
case '>':
|
||||
return 0x3E;
|
||||
case '?':
|
||||
return 0x3F;
|
||||
|
||||
case '@':
|
||||
return 0x40;
|
||||
case '[':
|
||||
return 0x5B;
|
||||
case '~': // Yen symbol... builtin mojibake?
|
||||
return 0x5C;
|
||||
case ']':
|
||||
return 0x5D;
|
||||
case '^':
|
||||
return 0x5E;
|
||||
case '_':
|
||||
return 0x5F;
|
||||
case '`':
|
||||
return 0x60;
|
||||
case '{':
|
||||
return 0x7B;
|
||||
case '|':
|
||||
return 0x7C;
|
||||
case '}':
|
||||
return 0x7D;
|
||||
case '\u000E':
|
||||
return 0x7E;
|
||||
case '\u000F':
|
||||
return 0x7F;
|
||||
default:
|
||||
return c; // gives access to the bubble first row 0x00 > 0x0F
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct rcp_configuration
|
||||
{
|
||||
//! How to send MIDI messages to the device.
|
||||
//! Note: this function *will* be called from different thread,
|
||||
//! thus it has to be thread-safe, for instance
|
||||
//! by storing the message in an event queue.
|
||||
std::function<void(libremidi::message&&)> midi_out;
|
||||
|
||||
std::function<void(libremidi::remote_control_protocol::device_type)> on_connected;
|
||||
std::function<void(libremidi::remote_control_protocol::mixer_command, bool)> on_command;
|
||||
std::function<void(libremidi::remote_control_protocol::mixer_control, int)> on_control;
|
||||
std::function<void(libremidi::remote_control_protocol::fader, uint16_t)> on_fader;
|
||||
|
||||
libremidi::midi_error_callback on_error{};
|
||||
};
|
||||
|
||||
struct remote_control_processor : libremidi::error_handler
|
||||
{
|
||||
using rcp = libremidi::remote_control_protocol;
|
||||
rcp_configuration configuration;
|
||||
rcp impl;
|
||||
|
||||
explicit remote_control_processor(rcp_configuration conf)
|
||||
: configuration{std::move(conf)}
|
||||
{
|
||||
assert(configuration.midi_out);
|
||||
|
||||
if (!configuration.on_error)
|
||||
configuration.on_error = [](std::string_view s, auto&&...) {
|
||||
std::fprintf(stderr, "libremidi: rcp error: %s\n", s.data());
|
||||
};
|
||||
|
||||
if (!configuration.on_connected)
|
||||
configuration.on_connected
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_connected"); };
|
||||
|
||||
if (!configuration.on_command)
|
||||
configuration.on_command
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_command"); };
|
||||
|
||||
if (!configuration.on_control)
|
||||
configuration.on_control
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_control"); };
|
||||
|
||||
if (!configuration.on_fader)
|
||||
configuration.on_fader
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_fader"); };
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
current_state = waiting_for_query;
|
||||
configuration.midi_out(impl.device_query());
|
||||
}
|
||||
|
||||
void on_midi(const libremidi::message& message)
|
||||
{
|
||||
switch (message.get_message_type())
|
||||
{
|
||||
case libremidi::message_type::SYSTEM_EXCLUSIVE:
|
||||
if (auto N = message.size(); N >= 7)
|
||||
{
|
||||
const uint8_t* bytes = message.bytes.data();
|
||||
|
||||
// strip 0xF0 & 0xF7
|
||||
bytes += 1;
|
||||
N -= 2;
|
||||
|
||||
// Mackie manufacturer ID check
|
||||
if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x66)
|
||||
{
|
||||
impl.type = static_cast<rcp::device_type>(bytes[3]);
|
||||
|
||||
// strip header
|
||||
bytes += 4;
|
||||
N -= 4;
|
||||
on_rcp_command(std::span(bytes, N));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
libremidi_handle_error(configuration, "Invalid sysex");
|
||||
}
|
||||
break;
|
||||
case libremidi::message_type::NOTE_ON:
|
||||
configuration.on_command(static_cast<rcp::mixer_command>(message[1]), message[2] > 0);
|
||||
break;
|
||||
case libremidi::message_type::NOTE_OFF:
|
||||
break;
|
||||
case libremidi::message_type::CONTROL_CHANGE:
|
||||
configuration.on_control(static_cast<rcp::mixer_control>(message[1]), message[2]);
|
||||
break;
|
||||
case libremidi::message_type::PITCH_BEND: {
|
||||
uint16_t value = message.bytes[2] * 128 + message.bytes[1];
|
||||
configuration.on_fader(static_cast<rcp::fader>(uint8_t(message.get_channel() - 1)), value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void on_rcp_command(std::span<const uint8_t> cmd)
|
||||
{
|
||||
if (cmd.empty())
|
||||
{
|
||||
libremidi_handle_error(configuration, "on_rcp_command: empty command");
|
||||
return;
|
||||
}
|
||||
|
||||
auto command = static_cast<rcp::command_from_device>(cmd[0]);
|
||||
cmd = cmd.subspan(1);
|
||||
switch (command)
|
||||
{
|
||||
case rcp::command_from_device::host_connection_query: {
|
||||
if (cmd.size() == 11)
|
||||
{
|
||||
current_state = got_query;
|
||||
|
||||
std::array<uint8_t, 7> serial;
|
||||
std::array<uint8_t, 4> challenge;
|
||||
std::copy_n(cmd.data(), 7, serial.begin());
|
||||
std::copy_n(cmd.data() + 7, 4, challenge.begin());
|
||||
|
||||
configuration.midi_out(impl.host_connection_reply(serial, challenge));
|
||||
}
|
||||
else
|
||||
libremidi_handle_error(configuration, "host_connection_query: invalid size");
|
||||
break;
|
||||
}
|
||||
case rcp::command_from_device::host_connection_confirmation:
|
||||
current_state = connected;
|
||||
configuration.on_connected(impl.type);
|
||||
break;
|
||||
case rcp::command_from_device::host_connection_error:
|
||||
current_state = errored;
|
||||
libremidi_handle_error(configuration, "host_connection_error");
|
||||
break;
|
||||
case rcp::command_from_device::version_reply: {
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void update_timecode(int h, int m, int s, int f)
|
||||
{
|
||||
for (auto&& m : rcp::timecode(h, m, s, f))
|
||||
configuration.midi_out(std::move(m));
|
||||
}
|
||||
|
||||
void update_lcd(std::string_view v)
|
||||
{
|
||||
auto res = impl.update_lcd(v);
|
||||
if (!res.empty())
|
||||
configuration.midi_out(std::move(res));
|
||||
}
|
||||
|
||||
void update_lcd(std::string_view v, int pos)
|
||||
{
|
||||
auto res = impl.update_lcd(v, pos);
|
||||
if (!res.empty())
|
||||
configuration.midi_out(std::move(res));
|
||||
}
|
||||
|
||||
void command(remote_control_protocol::mixer_command c, bool press)
|
||||
{
|
||||
using ce = libremidi::channel_events;
|
||||
configuration.midi_out(ce::note_on(1, to_underlying(c), press ? 127 : 0));
|
||||
configuration.midi_out(ce::note_off(1, to_underlying(c), press ? 127 : 0));
|
||||
}
|
||||
|
||||
void control(remote_control_protocol::mixer_control c, int value)
|
||||
{
|
||||
using ce = libremidi::channel_events;
|
||||
configuration.midi_out(ce::control_change(1, to_underlying(c), value));
|
||||
}
|
||||
|
||||
void fader(remote_control_protocol::fader c, uint16_t value)
|
||||
{
|
||||
int idx = to_underlying(c);
|
||||
|
||||
using ce = libremidi::channel_events;
|
||||
configuration.midi_out(ce::pitch_bend(idx + 1, value));
|
||||
}
|
||||
|
||||
// State machine
|
||||
enum
|
||||
{
|
||||
waiting_for_query,
|
||||
got_query,
|
||||
connected,
|
||||
errored
|
||||
} current_state{waiting_for_query};
|
||||
};
|
||||
}
|
||||
@@ -521,7 +521,7 @@ LIBREMIDI_INLINE
|
||||
reader::reader(bool useAbsolute)
|
||||
: ticksPerBeat(480)
|
||||
, startingTempo(120)
|
||||
, useAbsoluteTicks(useAbsolute)
|
||||
, m_useAbsoluteTicks(useAbsolute)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -549,12 +549,12 @@ try
|
||||
return parse_result::invalid;
|
||||
}
|
||||
|
||||
const uint8_t* const dataEnd = dataPtr + size;
|
||||
const uint8_t* const data_end = dataPtr + size;
|
||||
|
||||
uint32_t headerId = read_checked::read_uint32_be(dataPtr, dataEnd);
|
||||
uint32_t headerLength = read_checked::read_uint32_be(dataPtr, dataEnd);
|
||||
uint32_t header_id = read_checked::read_uint32_be(dataPtr, data_end);
|
||||
uint32_t header_length = read_checked::read_uint32_be(dataPtr, data_end);
|
||||
|
||||
if (static_cast<int>(headerId) != str_to_headerid("MThd") || headerLength != 6)
|
||||
if (static_cast<int>(header_id) != str_to_headerid("MThd") || header_length != 6)
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "couldn't parse header" << std::endl;
|
||||
@@ -563,7 +563,7 @@ try
|
||||
}
|
||||
|
||||
format = read_checked::read_uint16_be(
|
||||
dataPtr, dataEnd); //@tofix format type -> save for later eventually
|
||||
dataPtr, data_end); //@tofix format type -> save for later eventually
|
||||
if (format > 2)
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
@@ -571,12 +571,12 @@ try
|
||||
#endif
|
||||
return parse_result::invalid;
|
||||
}
|
||||
int trackCount = read_checked::read_uint16_be(dataPtr, dataEnd);
|
||||
uint16_t timeDivision = read_checked::read_uint16_be(dataPtr, dataEnd);
|
||||
int track_count = read_checked::read_uint16_be(dataPtr, data_end);
|
||||
uint16_t time_division = read_checked::read_uint16_be(dataPtr, data_end);
|
||||
|
||||
// CBB: deal with the SMPTE style time coding
|
||||
// timeDivision is described here http://www.sonicspot.com/guide/midifiles.html
|
||||
if (timeDivision & 0x8000)
|
||||
if (time_division & 0x8000)
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "found SMPTE time frames (unsupported)" << std::endl;
|
||||
@@ -590,18 +590,18 @@ try
|
||||
}
|
||||
|
||||
startingTempo = 120.0f; // midi default
|
||||
ticksPerBeat = float(timeDivision); // ticks per beat (a beat is defined as a quarter note)
|
||||
ticksPerBeat = float(time_division); // ticks per beat (a beat is defined as a quarter note)
|
||||
|
||||
parse_result result = parse_result::validated;
|
||||
|
||||
for (int i = 0; i < trackCount; ++i)
|
||||
for (int i = 0; i < track_count; ++i)
|
||||
{
|
||||
midi_track track;
|
||||
|
||||
headerId = read_checked::read_uint32_be(dataPtr, dataEnd);
|
||||
headerLength = read_checked::read_uint32_be(dataPtr, dataEnd);
|
||||
header_id = read_checked::read_uint32_be(dataPtr, data_end);
|
||||
header_length = read_checked::read_uint32_be(dataPtr, data_end);
|
||||
|
||||
if (headerId != str_to_headerid("MTrk"))
|
||||
if (header_id != str_to_headerid("MTrk"))
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "couldn't find track header" << std::endl;
|
||||
@@ -609,8 +609,8 @@ try
|
||||
return parse_result::incomplete;
|
||||
}
|
||||
|
||||
int64_t available = dataEnd - dataPtr;
|
||||
if (available < headerLength)
|
||||
int64_t available = data_end - dataPtr;
|
||||
if (available < header_length)
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "not enough data available" << std::endl;
|
||||
@@ -618,35 +618,35 @@ try
|
||||
return parse_result::incomplete;
|
||||
}
|
||||
|
||||
track.reserve(headerLength / 3);
|
||||
track.reserve(header_length / 3);
|
||||
|
||||
const uint8_t* const trackEnd = dataPtr + headerLength;
|
||||
const uint8_t* const track_end = dataPtr + header_length;
|
||||
|
||||
auto runningEvent = message_type::INVALID;
|
||||
auto running_event = message_type::INVALID;
|
||||
|
||||
std::size_t tickCount = 0;
|
||||
std::size_t tick_count = 0;
|
||||
|
||||
while (dataPtr < trackEnd)
|
||||
while (dataPtr < track_end)
|
||||
{
|
||||
const auto tick = read_checked::read_variable_length(dataPtr, trackEnd);
|
||||
if (useAbsoluteTicks)
|
||||
const auto tick = read_checked::read_variable_length(dataPtr, track_end);
|
||||
if (m_useAbsoluteTicks)
|
||||
{
|
||||
tickCount += tick;
|
||||
tick_count += tick;
|
||||
}
|
||||
else
|
||||
{
|
||||
tickCount = tick;
|
||||
tick_count = tick;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
track_event ev
|
||||
= parse_event(static_cast<int>(tickCount), i, dataPtr, trackEnd, runningEvent);
|
||||
= parse_event(static_cast<int>(tick_count), i, dataPtr, track_end, running_event);
|
||||
if (!ev.m.empty())
|
||||
{
|
||||
if (!ev.m.is_meta_event())
|
||||
{
|
||||
runningEvent = static_cast<message_type>(ev.m.bytes[0]);
|
||||
running_event = static_cast<message_type>(ev.m.bytes[0]);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -654,7 +654,7 @@ try
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "could not read event" << std::endl;
|
||||
#endif
|
||||
dataPtr = trackEnd;
|
||||
dataPtr = track_end;
|
||||
result = parse_result::incomplete;
|
||||
continue;
|
||||
}
|
||||
@@ -666,7 +666,7 @@ try
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "" << e.what() << std::endl;
|
||||
#endif
|
||||
dataPtr = trackEnd;
|
||||
dataPtr = track_end;
|
||||
result = parse_result::incomplete;
|
||||
continue;
|
||||
}
|
||||
@@ -684,7 +684,7 @@ try
|
||||
|
||||
if (result == parse_result::validated)
|
||||
{
|
||||
if (dataPtr != dataEnd)
|
||||
if (dataPtr != data_end)
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "midifile has junk at end: " << std::intptr_t(dataEnd - dataPtr) << std::endl;
|
||||
@@ -706,33 +706,33 @@ catch (const std::exception& e)
|
||||
LIBREMIDI_INLINE
|
||||
double reader::get_end_time() const noexcept
|
||||
{
|
||||
if (useAbsoluteTicks)
|
||||
if (m_useAbsoluteTicks)
|
||||
{
|
||||
double totalLength = 0.;
|
||||
double total_length = 0.;
|
||||
for (const auto& t : tracks)
|
||||
{
|
||||
if (!t.empty())
|
||||
{
|
||||
const auto& last_event = t.back();
|
||||
if (last_event.tick > totalLength)
|
||||
totalLength = last_event.tick;
|
||||
if (last_event.tick > total_length)
|
||||
total_length = last_event.tick;
|
||||
}
|
||||
}
|
||||
return totalLength;
|
||||
return total_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
double totalLength = 0.;
|
||||
double total_length = 0.;
|
||||
for (const auto& t : tracks)
|
||||
{
|
||||
double trackLength = 0.;
|
||||
double track_length = 0.;
|
||||
for (const auto& e : t)
|
||||
trackLength += e.tick;
|
||||
track_length += e.tick;
|
||||
|
||||
if (trackLength > totalLength)
|
||||
totalLength = trackLength;
|
||||
if (track_length > total_length)
|
||||
total_length = track_length;
|
||||
}
|
||||
return totalLength;
|
||||
return total_length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
std::vector<midi_track> tracks;
|
||||
|
||||
private:
|
||||
bool useAbsoluteTicks{};
|
||||
bool m_useAbsoluteTicks{};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
#include <libremidi/api.hpp>
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <any>
|
||||
#include <libremidi/configurations.hpp>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -24,7 +22,9 @@ public:
|
||||
struct shared_configurations
|
||||
{
|
||||
std::shared_ptr<shared_context> context;
|
||||
std::any observer, in, out;
|
||||
observer_api_configuration observer;
|
||||
input_api_configuration in;
|
||||
output_api_configuration out;
|
||||
};
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
|
||||
+31
-21
@@ -1,14 +1,27 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <span>
|
||||
|
||||
#if LIBREMIDI_NI_MIDI2_COMPAT
|
||||
#include <midi/universal_packet.h>
|
||||
#endif
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
namespace midi2
|
||||
{
|
||||
// Imported from cmidi2
|
||||
enum class message_type
|
||||
{
|
||||
// MIDI 2.0 UMP Section 3.
|
||||
UTILITY = 0,
|
||||
SYSTEM = 1,
|
||||
MIDI_1_CHANNEL = 2,
|
||||
SYSEX7 = 3,
|
||||
MIDI_2_CHANNEL = 4,
|
||||
SYSEX8_MDS = 5,
|
||||
};
|
||||
}
|
||||
|
||||
struct ump
|
||||
{
|
||||
alignas(4) uint32_t data[4] = {};
|
||||
@@ -62,28 +75,16 @@ struct ump
|
||||
|
||||
constexpr std::size_t size() const noexcept
|
||||
{
|
||||
// Imported from cmidi2
|
||||
enum midi2_message_type
|
||||
switch (midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF))
|
||||
{
|
||||
// MIDI 2.0 UMP Section 3.
|
||||
UTILITY = 0,
|
||||
SYSTEM = 1,
|
||||
MIDI_1_CHANNEL = 2,
|
||||
SYSEX7 = 3,
|
||||
MIDI_2_CHANNEL = 4,
|
||||
SYSEX8_MDS = 5,
|
||||
};
|
||||
|
||||
switch (((data[0] & 0xF0000000) >> 28) & 0xF)
|
||||
{
|
||||
case UTILITY:
|
||||
case SYSTEM:
|
||||
case MIDI_1_CHANNEL:
|
||||
case midi2::message_type::UTILITY:
|
||||
case midi2::message_type::SYSTEM:
|
||||
case midi2::message_type::MIDI_1_CHANNEL:
|
||||
return 1;
|
||||
case MIDI_2_CHANNEL:
|
||||
case SYSEX7:
|
||||
case midi2::message_type::MIDI_2_CHANNEL:
|
||||
case midi2::message_type::SYSEX7:
|
||||
return 2;
|
||||
case SYSEX8_MDS:
|
||||
case midi2::message_type::SYSEX8_MDS:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
@@ -109,5 +110,14 @@ struct ump
|
||||
constexpr auto cend() const noexcept { return data + size(); }
|
||||
constexpr auto cbegin() noexcept { return data; }
|
||||
constexpr auto cend() noexcept { return data + size(); }
|
||||
|
||||
constexpr midi2::message_type get_type() const noexcept
|
||||
{
|
||||
return midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF);
|
||||
}
|
||||
constexpr uint8_t get_group() const noexcept { return (data[0] >> 24) & 0x0F; }
|
||||
constexpr uint8_t get_channel_0_15() const noexcept { return (data[0] >> 16) & 0x0F; }
|
||||
constexpr uint8_t get_channel() const noexcept { return get_channel_0_15() + 1; }
|
||||
constexpr uint8_t get_status_code() const noexcept { return (data[0] >> 16) & 0xF0; }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
#pragma once
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <libremidi/detail/conversion.hpp>
|
||||
#include <libremidi/ump.hpp>
|
||||
|
||||
// FIXME! we need to review them all to check the channels
|
||||
namespace libremidi::ump_events
|
||||
{
|
||||
inline libremidi::ump note_on(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, pitch, 0, vel, 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump note_off(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, pitch, 0, vel, 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump poly_pressure(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_paf(group, channel, pitch, vel);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump control_change(uint8_t group, uint8_t channel, uint8_t index, uint32_t value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_cc(group, channel, index, value);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump program_change(uint8_t group, uint8_t channel, uint8_t index)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, index, 0, 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump aftertouch(uint8_t group, uint8_t channel, uint32_t value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_caf(group, channel, value);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump pitch_bend(uint8_t group, uint8_t channel, uint32_t value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, value);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
namespace libremidi::from_midi1
|
||||
{
|
||||
inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, uint8_t vel)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_note_on(0, channel, pitch, 0, u7_to_u16(vel), 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, uint8_t vel)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_note_off(0, channel, pitch, 0, u7_to_u16(vel), 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, uint8_t value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_paf(0, channel, pitch, u7_to_u32(value));
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump control_change(uint8_t channel, uint8_t index, uint8_t value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_cc(0, channel, index, u7_to_u32(value));
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump program_change(uint8_t channel, uint8_t index)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_program(0, channel, 0, index, 0, 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump aftertouch(uint8_t channel, uint8_t value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_caf(0, channel, u7_to_u32(value));
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump pitch_bend(uint8_t channel, uint16_t value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(0, channel, u16_to_u32(value));
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
namespace libremidi::from_01
|
||||
{
|
||||
inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, double vel)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_note_on(
|
||||
0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits<uint16_t>::max(), 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, double vel)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_note_off(
|
||||
0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits<uint16_t>::max(), 0);
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, double value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_paf(
|
||||
0, channel, pitch, std::clamp(value, 0., 1.) * std::numeric_limits<uint32_t>::max());
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump control_change(uint8_t channel, uint8_t index, double value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_cc(
|
||||
0, channel, index, std::clamp(value, 0., 1.) * std::numeric_limits<uint32_t>::max());
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump aftertouch(uint8_t channel, double value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_caf(
|
||||
0, channel, std::clamp(value, 0., 1.) * std::numeric_limits<uint32_t>::max());
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
|
||||
inline libremidi::ump pitch_bend(uint8_t channel, double value)
|
||||
{
|
||||
libremidi::ump u;
|
||||
const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(
|
||||
0, channel, std::clamp(value, 0., 1.) * std::numeric_limits<uint32_t>::max());
|
||||
cmidi2_reverse(msg, u.data);
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
namespace libremidi::as_midi1
|
||||
{
|
||||
inline auto note_off(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t note;
|
||||
uint8_t velocity;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.note = cmidi2_ump_get_midi2_note_note(ump);
|
||||
res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200;
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto note_on(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t note;
|
||||
uint8_t velocity;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.note = cmidi2_ump_get_midi2_note_note(ump);
|
||||
res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200;
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto poly_pressure(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t note;
|
||||
uint8_t value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.note = cmidi2_ump_get_midi2_paf_note(ump);
|
||||
res.value = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000;
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto control_change(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t control;
|
||||
uint8_t value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.control = cmidi2_ump_get_midi2_cc_index(ump);
|
||||
res.value = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000;
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto program_change(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t program;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.program = cmidi2_ump_get_midi2_program_program(ump);
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto aftertouch(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.value = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000;
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto pitch_bend(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint16_t value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.value = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
namespace libremidi::as_01
|
||||
{
|
||||
inline auto note_off(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t note;
|
||||
double velocity;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.note = cmidi2_ump_get_midi2_note_note(ump);
|
||||
res.velocity
|
||||
= double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits<uint16_t>::max();
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto note_on(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t note;
|
||||
double velocity;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.note = cmidi2_ump_get_midi2_note_note(ump);
|
||||
res.velocity
|
||||
= double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits<uint16_t>::max();
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto poly_pressure(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t note;
|
||||
double value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.note = cmidi2_ump_get_midi2_paf_note(ump);
|
||||
res.value = double(cmidi2_ump_get_midi2_paf_data(ump)) / std::numeric_limits<uint32_t>::max();
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto control_change(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t control;
|
||||
double value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.control = cmidi2_ump_get_midi2_cc_index(ump);
|
||||
res.value = double(cmidi2_ump_get_midi2_cc_data(ump)) / std::numeric_limits<uint32_t>::max();
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto aftertouch(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
double value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.value = double(cmidi2_ump_get_midi2_caf_data(ump)) / std::numeric_limits<uint32_t>::max();
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto pitch_bend(const libremidi::ump& mess)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t channel;
|
||||
double value;
|
||||
} res;
|
||||
auto& ump = mess.data;
|
||||
res.channel = mess.get_channel_0_15();
|
||||
res.value
|
||||
= double(cmidi2_ump_get_midi2_pitch_bend_data(ump)) / std::numeric_limits<uint32_t>::max();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -62,14 +62,14 @@ static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value)
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr auto N = sizeof(value);
|
||||
static constexpr auto n = sizeof(value);
|
||||
struct storage
|
||||
{
|
||||
uint8_t bytes[N];
|
||||
uint8_t bytes[n];
|
||||
};
|
||||
auto data = std::bit_cast<storage>(value);
|
||||
std::reverse(data.bytes, data.bytes + N);
|
||||
out.write(reinterpret_cast<const char*>(data.bytes), static_cast<std::streamsize>(N));
|
||||
std::reverse(data.bytes, data.bytes + n);
|
||||
out.write(reinterpret_cast<const char*>(data.bytes), static_cast<std::streamsize>(n));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -146,12 +146,12 @@ void writer::write(std::ostream& out) const
|
||||
util::write_be<uint16_t>(out, static_cast<uint16_t>(tracks.size()));
|
||||
util::write_be<uint16_t>(out, ticksPerQuarterNote);
|
||||
|
||||
std::vector<uint8_t> trackRawData;
|
||||
std::vector<uint8_t> track_raw_data;
|
||||
for (const auto& event_list : tracks)
|
||||
{
|
||||
trackRawData.clear();
|
||||
track_raw_data.clear();
|
||||
// Rough estimation of the memory to allocate
|
||||
trackRawData.reserve(event_list.size() * 3);
|
||||
track_raw_data.reserve(event_list.size() * 3);
|
||||
|
||||
for (const auto& event : event_list)
|
||||
{
|
||||
@@ -164,7 +164,7 @@ void writer::write(std::ostream& out) const
|
||||
if (msg.get_meta_event_type() == meta_event_type::END_OF_TRACK)
|
||||
continue;
|
||||
|
||||
util::write_variable_length(static_cast<uint32_t>(event.tick), trackRawData);
|
||||
util::write_variable_length(static_cast<uint32_t>(event.tick), track_raw_data);
|
||||
|
||||
if ((msg.get_message_type() == message_type::SYSTEM_EXCLUSIVE)
|
||||
|| (event.m.get_message_type() == message_type::EOX))
|
||||
@@ -176,38 +176,38 @@ void writer::write(std::ostream& out) const
|
||||
// In other words, when creating a 0xf0 or 0xf7 MIDI message,
|
||||
// do not insert the VLV byte length yourself, as this code will
|
||||
// do it for you automatically.
|
||||
trackRawData.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7;
|
||||
track_raw_data.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7;
|
||||
|
||||
util::write_variable_length(static_cast<uint32_t>(msg.size()) - 1, trackRawData);
|
||||
util::write_variable_length(static_cast<uint32_t>(msg.size()) - 1, track_raw_data);
|
||||
|
||||
trackRawData.insert(
|
||||
trackRawData.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size());
|
||||
track_raw_data.insert(
|
||||
track_raw_data.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-sysex type of message, so just output the bytes of the message:
|
||||
trackRawData.insert(
|
||||
trackRawData.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size());
|
||||
track_raw_data.insert(
|
||||
track_raw_data.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
auto size = trackRawData.size();
|
||||
auto size = track_raw_data.size();
|
||||
const auto eot = meta_events::end_of_track();
|
||||
|
||||
if ((size < 3) || !((trackRawData[size - 3] == 0xFF) && (trackRawData[size - 2] == 0x2F)))
|
||||
if ((size < 3) || !((track_raw_data[size - 3] == 0xFF) && (track_raw_data[size - 2] == 0x2F)))
|
||||
{
|
||||
trackRawData.emplace_back(0x0); // tick
|
||||
trackRawData.emplace_back(eot[0]);
|
||||
trackRawData.emplace_back(eot[1]);
|
||||
trackRawData.emplace_back(eot[2]);
|
||||
track_raw_data.emplace_back(0x0); // tick
|
||||
track_raw_data.emplace_back(eot[0]);
|
||||
track_raw_data.emplace_back(eot[1]);
|
||||
track_raw_data.emplace_back(eot[2]);
|
||||
}
|
||||
|
||||
// Write the track ID marker "MTrk":
|
||||
out.write("MTrk", 4);
|
||||
util::write_be<uint32_t>(out, static_cast<uint32_t>(trackRawData.size()));
|
||||
util::write_be<uint32_t>(out, static_cast<uint32_t>(track_raw_data.size()));
|
||||
out.write(
|
||||
reinterpret_cast<char*>(trackRawData.data()),
|
||||
static_cast<std::streamsize>(trackRawData.size()));
|
||||
reinterpret_cast<char*>(track_raw_data.data()),
|
||||
static_cast<std::streamsize>(track_raw_data.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user