very, very simple WP with midi CC out

This commit is contained in:
Sebastian
2024-10-09 02:01:23 +02:00
commit 3d6fc151f1
126 changed files with 19539 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
#pragma once
#include <libremidi/config.hpp>
#include <string_view>
#include <vector>
namespace libremidi
{
//! MIDI API specifier arguments.
//! To get information on which feature is supported by each back-end, check their backend file
//! in e.g. backends/winmm.hpp, etc.
enum class API
{
UNSPECIFIED, /*!< Search for a working compiled API. */
// MIDI 1.0 APIs
COREMIDI, /*!< macOS CoreMidi API. */
ALSA_SEQ, /*!< Linux ALSA Sequencer API. */
ALSA_RAW, /*!< Linux Raw ALSA API. */
JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */
WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */
WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */
WEBMIDI, /*!< Web MIDI API through Emscripten */
PIPEWIRE, /*!< PipeWire */
// MIDI 2.0 APIs
ALSA_RAW_UMP, /*!< Raw ALSA API for MIDI 2.0 */
ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */
COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
DUMMY /*!< A compilable but non-functional API. */
};
/**
* \brief A function to determine the available compiled MIDI 1.0 APIs.
The values returned in the std::vector can be compared against
the enumerated list values. Note that there can be more than one
API compiled for certain operating systems.
*/
LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;
/**
* \brief A function to determine the available compiled MIDI 2.0 APIs.
The values returned in the std::vector can be compared against
the enumerated list values. Note that there can be more than one
API compiled for certain operating systems.
*/
LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;
//! A static function to determine the current version.
LIBREMIDI_EXPORT std::string_view get_version() noexcept;
//! Map from and to API names
LIBREMIDI_EXPORT std::string_view get_api_name(libremidi::API api);
//! Map from and to API names
LIBREMIDI_EXPORT std::string_view get_api_display_name(libremidi::API api);
//! Look-up an API through its name
LIBREMIDI_EXPORT libremidi::API get_compiled_api_by_name(std::string_view api);
namespace midi1
{
//! Returns the default MIDI 1.0 backend to use for the target OS.
inline constexpr libremidi::API default_api() noexcept
{
#if defined(__APPLE__)
return API::COREMIDI;
#elif defined(_WIN32)
return API::WINDOWS_MM;
#elif defined(__linux__)
return API::ALSA_SEQ;
#elif defined(__emscripten__)
return API::EMSCRIPTEN_WEBMIDI;
#else
return API::DUMMY;
#endif
}
}
namespace midi2
{
//! Returns the default MIDI 2.0 backend to use for the target OS.
inline constexpr libremidi::API default_api() noexcept
{
#if defined(__APPLE__)
return API::COREMIDI_UMP;
#elif defined(_WIN32)
return API::WINDOWS_MIDI_SERVICES;
#elif defined(__linux__)
return API::ALSA_SEQ_UMP;
#elif defined(__emscripten__)
return API::DUMMY;
#else
return API::DUMMY;
#endif
}
}
}
+187
View File
@@ -0,0 +1,187 @@
#pragma once
#include <tuple>
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
#if defined(LIBREMIDI_JACK)
#undef LIBREMIDI_JACK
#endif
#endif
#if !defined(LIBREMIDI_ALSA) && !defined(LIBREMIDI_JACK) && !defined(LIBREMIDI_COREMIDI) \
&& !defined(LIBREMIDI_WINMM)
#define LIBREMIDI_DUMMY
#endif
#if defined(LIBREMIDI_ALSA)
#include <libremidi/backends/alsa_seq.hpp>
#include <libremidi/backends/linux/alsa.hpp>
#if LIBREMIDI_ALSA_HAS_RAMWIDI
#include <libremidi/backends/alsa_raw.hpp>
#endif
#if LIBREMIDI_ALSA_HAS_UMP
#include <libremidi/backends/alsa_raw_ump.hpp>
#include <libremidi/backends/alsa_seq_ump.hpp>
#endif
#endif
#if defined(LIBREMIDI_JACK)
#include <libremidi/backends/jack.hpp>
#endif
#if defined(LIBREMIDI_PIPEWIRE)
#include <libremidi/backends/pipewire.hpp>
#endif
#if defined(LIBREMIDI_COREMIDI)
#include <libremidi/backends/coremidi.hpp>
#include <libremidi/backends/coremidi_ump.hpp>
#endif
#if defined(LIBREMIDI_WINMM)
#include <libremidi/backends/winmm.hpp>
#endif
#if defined(LIBREMIDI_WINUWP)
#include <libremidi/backends/winuwp.hpp>
#if __has_include(<winrt/Microsoft.Devices.Midi2.h>)
#include <libremidi/backends/winmidi.hpp>
#endif
#endif
#if defined(LIBREMIDI_EMSCRIPTEN)
#include <libremidi/backends/emscripten.hpp>
#endif
#include <libremidi/backends/dummy.hpp>
namespace libremidi
{
// The order here will control the order of the API search in
// the constructor.
template <typename unused, typename... Args>
constexpr auto make_tl(unused, Args...)
{
return std::tuple<Args...>{};
}
namespace midi1
{
static constexpr auto available_backends = make_tl(
0
#if defined(LIBREMIDI_ALSA)
,
alsa_seq::backend{}
#if LIBREMIDI_ALSA_HAS_RAMWIDI
,
alsa_raw::backend{}
#endif
#endif
#if defined(LIBREMIDI_COREMIDI)
,
core_backend{}
#endif
#if defined(LIBREMIDI_WINMM)
,
winmm_backend{}
#endif
#if defined(LIBREMIDI_WINUWP)
,
winuwp_backend{}
#endif
#if defined(LIBREMIDI_EMSCRIPTEN)
,
emscripten_backend{}
#endif
#if defined(LIBREMIDI_JACK)
,
jack_backend{}
#endif
#if defined(LIBREMIDI_PIPEWIRE)
,
pipewire::backend{}
#endif
,
dummy_backend{});
// There should always be at least one back-end.
static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);
template <typename F>
auto for_all_backends(F&& f)
{
std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends);
}
template <typename F>
auto for_backend(libremidi::API api, F&& f)
{
static constexpr auto is_api = [](auto& backend, libremidi::API api) {
return backend.available() && backend.API == api;
};
std::apply([&](auto&&... b) { ((is_api(b, api) && (f(b), true)) || ...); }, available_backends);
}
}
namespace midi2
{
static constexpr auto available_backends = make_tl(
0
#if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP
,
alsa_seq_ump::backend{}, alsa_raw_ump::backend{}
#endif
#if defined(LIBREMIDI_COREMIDI)
,
coremidi_ump::backend{}
#endif
#if defined(LIBREMIDI_JACK)
#endif
#if defined(LIBREMIDI_WINUWP)
#if __has_include(<winrt/Microsoft.Devices.Midi2.h>)
,
winmidi::backend{}
#endif
#endif
#if defined(LIBREMIDI_EMSCRIPTEN)
#endif
,
dummy_backend{});
// There should always be at least one back-end.
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);
}
template <typename F>
auto for_backend(libremidi::API api, F&& f)
{
static constexpr auto is_api
= [](auto& backend, libremidi::API api) { return backend.API == api; };
std::apply([&](auto&&... b) { ((is_api(b, api) && (f(b), true)) || ...); }, available_backends);
}
}
namespace midi_any
{
template <typename F>
auto for_all_backends(F&& f)
{
midi1::for_all_backends(f);
midi2::for_all_backends(f);
}
template <typename F>
auto for_backend(libremidi::API api, F&& f)
{
midi1::for_backend(api, f);
midi2::for_backend(api, f);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <libremidi/backends/alsa_raw/midi_in.hpp>
#include <libremidi/backends/alsa_raw/midi_out.hpp>
#include <libremidi/backends/alsa_raw/observer.hpp>
// 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
// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>
namespace libremidi::alsa_raw
{
struct backend
{
using midi_in = midi_in_impl;
using midi_out = midi_out_impl;
using midi_observer = observer_impl;
using midi_in_configuration = alsa_raw_input_configuration;
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 inline bool available() noexcept
{
static const libasound& snd = libasound::instance();
return snd.available && snd.rawmidi.available;
}
};
}
@@ -0,0 +1,79 @@
#pragma once
#include <libremidi/config.hpp>
#include <chrono>
#include <functional>
#include <optional>
#include <span>
#include <thread>
#if __has_include(<poll.h>)
#include <poll.h>
namespace libremidi
{
using poll_descriptors = pollfd;
}
#else
namespace libremidi
{
struct poll_descriptors
{
int fd;
short int events;
short int revents;
};
}
#endif
namespace libremidi
{
/**
* Used to determine how large sent messages will be chunked.
*/
struct LIBREMIDI_EXPORT chunking_parameters
{
std::chrono::milliseconds interval{};
int32_t size{};
/**
* @brief Will be called by the chunking code to allow the API user to wait.
*
* By default just calls sleep.
* Arguments are: the time that must be waited, the bytes currently written.
* Return false if you want to abort the transfer, and true otherwise.
*/
std::function<bool(std::chrono::microseconds, int64_t)> wait = chunking_parameters::default_wait;
static bool default_wait(std::chrono::microseconds time_to_wait, int64_t /*written_bytes*/)
{
std::this_thread::sleep_for(time_to_wait);
return true;
}
};
struct manual_poll_parameters
{
std::span<poll_descriptors> fds;
std::function<int64_t(std::span<poll_descriptors> fds)> callback;
};
struct alsa_raw_input_configuration
{
std::function<bool(const manual_poll_parameters&)> manual_poll;
};
struct alsa_raw_output_configuration
{
/**
* For large messages, chunk their content and wait.
* Setting a null optional will disable chunking.
*/
std::optional<chunking_parameters> chunking;
};
struct alsa_raw_observer_configuration
{
std::chrono::milliseconds poll_period{100};
};
}
@@ -0,0 +1,328 @@
#pragma once
#include <libremidi/backends/linux/alsa.hpp>
#include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp>
#include <chrono>
#include <functional>
#include <iostream>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
// 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
// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>
namespace libremidi
{
namespace
{
struct alsa_raw_port_id
{
int card{}, dev{}, port{};
std::string to_string() const noexcept
{
return "hw:" + std::to_string(card) + "," + std::to_string(dev) + "," + std::to_string(port);
}
};
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;
}
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.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16;
ret.port = (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);
static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7);
static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3);
}
namespace alsa_raw
{
struct alsa_raw_port_info
{
std::string device;
std::string card_name;
std::string device_name;
std::string subdevice_name;
int card{}, dev{}, sub{};
std::string pretty_name() const
{
return device + ": " + card_name + " : " + device_name + " : " + subdevice_name;
}
bool operator==(const alsa_raw_port_info& other) const noexcept = default;
};
struct enumerator;
struct snd_ctl_wrapper
{
const libasound& snd;
snd_ctl_t* ctl{};
inline snd_ctl_wrapper(enumerator& self, const char* name);
~snd_ctl_wrapper()
{
if (ctl)
{
snd.ctl.close(ctl);
}
}
snd_ctl_t& operator*() const noexcept { return *ctl; }
snd_ctl_t* operator->() const noexcept { return ctl; }
operator snd_ctl_t*() const noexcept { return ctl; }
};
struct enumerator
{
const libasound& snd = libasound::instance();
std::vector<alsa_raw_port_info> inputs;
std::vector<alsa_raw_port_info> outputs;
std::function<void(std::string_view)> error_callback;
std::function<void(std::string_view)> warn_callback;
template <typename... Args>
void warning(Args&&... args)
{
std::string s;
((s += args), ...);
if (warn_callback)
{
warn_callback(std::move(s));
}
else
{
std::cerr << s << std::endl;
}
}
template <typename... Args>
void error(Args&&... args)
{
std::string s;
((s += args), ...);
if (error_callback)
{
error_callback(std::move(s));
}
else
{
throw std::runtime_error(s.c_str());
}
}
// 1: is an input / output
// 0: isn't an input / output
// < 0: error
int is(snd_rawmidi_stream_t stream, snd_ctl_t* ctl, int card, int device, int sub)
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd.rawmidi.info_set_device(info, device);
snd.rawmidi.info_set_subdevice(info, sub);
snd.rawmidi.info_set_stream(info, stream);
const int status = snd.ctl.rawmidi.info(ctl, info);
if (status == 0)
{
return 1;
}
else if (status < 0 && status != -ENXIO)
{
error(
"alsa_raw_helpers::enumerator::is: cannot get rawmidi information:", card, device, sub,
snd.strerror(status));
return status;
}
else
{
return 0;
}
}
int is_input(snd_ctl_t* ctl, int card, int device, int sub)
{
return is(SND_RAWMIDI_STREAM_INPUT, ctl, card, device, sub);
}
int is_output(snd_ctl_t* ctl, int card, int device, int sub)
{
return is(SND_RAWMIDI_STREAM_OUTPUT, ctl, card, device, sub);
}
std::string get_card_name(int card)
{
char* card_name{};
snd.card.get_name(card, &card_name);
std::string str = card_name;
free(card_name);
return str;
}
static std::string device_identifier(int card, int device, int sub)
{
std::string s;
s.reserve(12);
s += "hw:";
s += std::to_string(card);
s += ",";
s += std::to_string(device);
s += ",";
s += std::to_string(sub);
return s;
}
void enumerate_cards()
{
int card = -1;
int status = snd.card.next(&card);
if (status < 0)
{
error(
"alsa_raw_helpers::enumerator::enumerate_cards: "
"cannot determine card number: ",
snd.strerror(status));
return;
}
if (card < 0)
{
error(
"alsa_raw_helpers::enumerator::enumerate_cards: "
"no sound cards found");
return;
}
while (card >= 0)
{
enumerate_devices(card);
if ((status = snd.card.next(&card)) < 0)
{
error(
"alsa_raw_helpers::enumerator::enumerate_cards: "
"cannot determine card number: ",
snd.strerror(status));
break;
}
}
}
virtual void enumerate_devices(int card) = 0;
};
inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)
: snd{self.snd}
{
int status = snd.ctl.open(&ctl, name, 0);
if (status < 0)
{
self.error(
"alsa_raw_helpers::enumerator::snd_ctl_wrapper: "
"cannot open control for card",
name, snd.strerror(status));
}
}
struct midi1_enumerator : enumerator
{
void enumerate_devices(int card) override
{
char name[128];
sprintf(name, "hw:%d", card);
// Open card.
snd_ctl_wrapper ctl{*this, name};
if (!ctl)
return;
// Enumerate devices.
int device = -1;
do
{
const int status = snd.ctl.rawmidi.next_device(ctl, &device);
if (device == -1)
return;
if (status < 0)
{
error(
"alsa_raw::midi1_enumerator::enumerate_devices: "
"cannot determine device number: ",
snd.strerror(status));
break;
}
if (device >= 0)
enumerate_subdevices(ctl, card, device);
} while (device >= 0);
}
void enumerate_subdevices(snd_ctl_t* ctl, int card, int device)
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd.rawmidi.info_set_device(info, device);
snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
snd.ctl.rawmidi.info(ctl, info);
const int subs_in = snd.rawmidi.info_get_subdevices_count(info);
snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);
snd.ctl.rawmidi.info(ctl, info);
const int subs_out = snd.rawmidi.info_get_subdevices_count(info);
alsa_raw_port_info d;
d.card = card;
d.dev = device;
d.card_name = get_card_name(card);
d.device_name = snd.rawmidi.info_get_name(info);
auto read_subdevice_info = [&](int sub) {
snd.rawmidi.info_set_subdevice(info, sub);
snd.ctl.rawmidi.info(ctl, info);
d.device = device_identifier(card, device, sub);
d.subdevice_name = snd.rawmidi.info_get_subdevice_name(info);
d.sub = sub;
};
if (subs_in > 0)
{
snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
for (int sub = 0; sub < subs_in; sub++)
{
read_subdevice_info(sub);
inputs.push_back(d);
}
}
if (subs_out > 0)
{
snd.rawmidi.info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);
for (int sub = 0; sub < subs_out; sub++)
{
read_subdevice_info(sub);
outputs.push_back(d);
}
}
}
};
}
}
@@ -0,0 +1,350 @@
#pragma once
#include <libremidi/backends/alsa_raw/config.hpp>
#include <libremidi/backends/alsa_raw/helpers.hpp>
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <alsa/asoundlib.h>
#include <chrono>
#include <thread>
namespace libremidi::alsa_raw
{
class midi_in_impl
: public midi1::in_api
, public error_handler
{
public:
struct
: input_configuration
, alsa_raw_input_configuration
{
} configuration;
const libasound& snd = libasound::instance();
explicit midi_in_impl(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
fds_.reserve(4);
}
~midi_in_impl() override { }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_in_alsa_raw: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_alsa_raw: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_alsa_raw: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
// Must be a string such as: "hw:2,4,1"
[[nodiscard]] int do_init_port(const char* portname)
{
constexpr int mode = SND_RAWMIDI_NONBLOCK;
if (const int err = snd.rawmidi.open(&midiport_, nullptr, portname, mode); err < 0)
{
error<driver_error>(this->configuration, "midi_in_alsa_raw::open_port: cannot open device.");
return err;
}
snd_rawmidi_params_t* params{};
snd_rawmidi_params_alloca(&params);
if (const int err = snd.rawmidi.params_current(midiport_, params); err < 0)
return err;
if (const int err = snd.rawmidi.params_set_no_active_sensing(midiport_, params, 1); err < 0)
return err;
#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
if (const int err
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_STANDARD);
err < 0)
return err;
if (const int err
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_NONE);
err < 0)
return err;
}
else
{
if (const int err
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_TSTAMP);
err < 0)
return err;
if (const int err
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_MONOTONIC);
err < 0)
return err;
}
#endif
if (const int err = snd.rawmidi.params(midiport_, params); err < 0)
return err;
return init_pollfd();
}
[[nodiscard]] int init_port(const port_information& p)
{
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
}
[[nodiscard]] int init_pollfd()
{
const int num_fds = snd.rawmidi.poll_descriptors_count(this->midiport_);
this->fds_.clear();
this->fds_.resize(num_fds);
return snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), num_fds);
}
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
{
// Read events
if (fds.empty())
{
return (this->*parse_func)();
}
else
{
unsigned short res{};
const int err = snd.rawmidi.poll_descriptors_revents(
this->midiport_, fds.data(), static_cast<unsigned int>(fds.size()), &res);
if (err < 0)
return err;
// Did we encounter an error during polling
if (res & (POLLERR | POLLHUP))
return -EIO;
// Is there data to read
if (res & POLLIN)
return (this->*parse_func)();
}
return 0;
}
ssize_t read_input_buffer()
{
static const constexpr int nbytes = 1024;
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = false,
.absolute_is_monotonic = false,
.has_samples = false,
};
unsigned char bytes[nbytes];
ssize_t err = 0;
// err is the amount of bytes read
while ((err = snd.rawmidi.read(this->midiport_, bytes, nbytes)) > 0)
{
const auto to_ns = [this] { return absolute_timestamp(); };
decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp<timestamp_info>(to_ns, 0));
}
return err;
}
#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD
ssize_t read_input_buffer_with_timestamps()
{
static constexpr int nbytes = 1024;
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = true,
.has_samples = false,
};
unsigned char bytes[nbytes];
struct timespec ts;
ssize_t err = 0;
// err is the amount of bytes read
while ((err = snd.rawmidi.tread(this->midiport_, &ts, bytes, nbytes)) > 0)
{
const auto to_ns = [ts] {
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
};
decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp<timestamp_info>(to_ns, 0));
}
return err;
}
#else
ssize_t read_input_buffer_with_timestamps() { return read_input_buffer(); }
#endif
void close_port() override
{
if (midiport_)
snd.rawmidi.close(midiport_);
midiport_ = nullptr;
}
timestamp absolute_timestamp() const noexcept final override { return system_ns(); }
snd_rawmidi_t* midiport_{};
std::vector<pollfd> fds_;
midi1::input_state_machine decoder_{this->configuration};
};
class midi_in_alsa_raw_threaded : public midi_in_impl
{
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)
{
error<driver_error>(
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
}
}
~midi_in_alsa_raw_threaded() override
{
// Close a connection if it exists.
this->midi_in_alsa_raw_threaded::close_port();
}
private:
void run_thread(auto parse_func)
{
fds_.push_back(this->termination_event);
for (;;)
{
// Poll
ssize_t err = poll(fds_.data(), fds_.size(), -1);
if (err == -EAGAIN)
continue;
else if (err < 0)
return;
else if (termination_event.ready(fds_.back()))
break;
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
if (err == -EAGAIN)
continue;
else if (err < 0)
return;
}
}
[[nodiscard]] ssize_t start_thread()
{
try
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
}
else
{
this->thread_ = std::thread{
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
}
}
catch (const std::system_error& e)
{
using namespace std::literals;
error<thread_error>(
this->configuration,
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
return false;
}
return true;
}
bool open_port(const input_port& port, std::string_view /*name*/) override
{
if (const int err = midi_in_impl::init_port(port); err < 0)
return false;
if (!start_thread())
return false;
return true;
}
void close_port() override
{
termination_event.notify();
if (thread_.joinable())
thread_.join();
termination_event.consume(); // Reset to zero
midi_in_impl::close_port();
}
std::thread thread_;
eventfd_notifier termination_event{};
};
class midi_in_alsa_raw_manual : public midi_in_impl
{
public:
using midi_in_impl::midi_in_impl;
~midi_in_alsa_raw_manual()
{
// Close a connection if it exists.
this->close_port();
}
private:
void send_poll_callback()
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
}
else
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
}
}
bool open_port(const input_port& p, std::string_view /*name*/) override
{
if (midi_in_impl::init_port(p) < 0)
return false;
send_poll_callback();
return true;
}
};
}
namespace libremidi
{
template <>
inline std::unique_ptr<midi_in_api> make<alsa_raw::midi_in_impl>(
libremidi::input_configuration&& conf, libremidi::alsa_raw_input_configuration&& api)
{
if (api.manual_poll)
return std::make_unique<alsa_raw::midi_in_alsa_raw_manual>(std::move(conf), std::move(api));
else
return std::make_unique<alsa_raw::midi_in_alsa_raw_threaded>(std::move(conf), std::move(api));
}
}
@@ -0,0 +1,177 @@
#pragma once
#include <libremidi/backends/alsa_raw/config.hpp>
#include <libremidi/backends/alsa_raw/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <alsa/asoundlib.h>
#include <atomic>
#include <thread>
namespace libremidi::alsa_raw
{
class midi_out_impl final
: public midi1::out_api
, public error_handler
{
public:
struct
: output_configuration
, alsa_raw_output_configuration
{
} configuration;
const libasound& snd = libasound::instance();
midi_out_impl(output_configuration&& conf, alsa_raw_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
~midi_out_impl() override
{
// Close a connection if it exists.
midi_out_impl::close_port();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_alsa_raw: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_alsa_raw: set_port_name unsupported");
}
int connect_port(const char* portname)
{
constexpr int mode = SND_RAWMIDI_SYNC;
int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);
if (status < 0)
{
error<driver_error>(
this->configuration, "midi_out_alsa_raw::open_port: cannot open device.");
return status;
}
return status;
}
bool open_port(const output_port& p, std::string_view) override
{
return connect_port(raw_from_port_handle(p.port).to_string().c_str()) == 0;
}
void close_port() override
{
if (midiport_)
snd.rawmidi.close(midiport_);
midiport_ = nullptr;
}
void send_message(const unsigned char* message, size_t size) override
{
if (!midiport_)
error<invalid_use_error>(
this->configuration,
"midi_out_alsa_raw::send_message: trying to send a message without an open "
"port.");
if (!this->configuration.chunking)
{
write(message, size);
}
else
{
write_chunked(message, size);
}
}
bool write(const unsigned char* message, size_t size)
{
if (snd.rawmidi.write(midiport_, message, size) < 0)
{
error<driver_error>(
this->configuration, "midi_out_alsa_raw::send_message: cannot write message.");
return false;
}
return true;
}
std::size_t get_chunk_size() const noexcept
{
snd_rawmidi_params_t* param;
snd_rawmidi_params_alloca(&param);
snd.rawmidi.params_current(midiport_, param);
std::size_t buffer_size = snd.rawmidi.params_get_buffer_size(param);
return std::min(buffer_size, (std::size_t)configuration.chunking->size);
}
std::size_t get_available_bytes_to_write() const noexcept
{
snd_rawmidi_status_t* st{};
snd_rawmidi_status_alloca(&st);
snd.rawmidi.status(midiport_, st);
return snd.rawmidi.status_get_avail(st);
}
// inspired from ALSA amidi.c source code
void write_chunked(const unsigned char* const begin, size_t size)
{
const unsigned char* data = begin;
const unsigned char* end = begin + size;
const std::size_t chunk_size = std::min(get_chunk_size(), size);
// Send the first buffer
std::size_t len = chunk_size;
if (!write(data, len))
return;
data += len;
while (data < end)
{
// Wait for the buffer to have some space available
const std::size_t written_bytes = data - begin;
std::size_t available{};
while ((available = get_available_bytes_to_write()) < chunk_size)
{
if (!configuration.chunking->wait(
std::chrono::microseconds((chunk_size - available) * 320), written_bytes))
return;
};
if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes))
return;
// Write more data
len = end - data;
// Maybe until the end of the sysex
if (const auto sysex_end = static_cast<const unsigned char*>(memchr(data, 0xf7, len)))
len = sysex_end - data + 1;
if (len > chunk_size)
len = chunk_size;
if (!write(data, len))
return;
data += len;
}
}
snd_rawmidi_t* midiport_{};
};
}
@@ -0,0 +1,240 @@
#pragma once
#include <libremidi/backends/alsa_raw/config.hpp>
#include <libremidi/backends/alsa_raw/helpers.hpp>
#include <libremidi/backends/dummy.hpp>
#if LIBREMIDI_HAS_UDEV
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/backends/linux/udev.hpp>
#include <libremidi/detail/observer.hpp>
#include <stdexcept>
namespace libremidi::alsa_raw
{
template <typename Enumerator>
class observer_impl_base : public observer_api
{
public:
struct
: observer_configuration
, alsa_raw_observer_configuration
{
} configuration;
const libasound& snd = libasound::instance();
explicit observer_impl_base(
observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (!configuration.has_callbacks())
return;
fds[0] = this->udev;
fds[1] = termination_event;
fds[2] = timer_fd;
// Set-up initial state
if (configuration.notify_in_constructor)
this->check_devices();
// Start thread
thread = std::thread{[this] { this->run(); }};
}
~observer_impl_base()
{
termination_event.notify();
if (thread.joinable())
thread.join();
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
std::vector<libremidi::input_port> ret;
Enumerator new_devs;
new_devs.enumerate_cards();
for (auto& d : new_devs.inputs)
{
ret.push_back(to_port_info<true>(d));
}
return ret;
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
std::vector<libremidi::output_port> ret;
Enumerator new_devs;
new_devs.enumerate_cards();
for (auto& d : new_devs.outputs)
{
ret.push_back(to_port_info<false>(d));
}
return ret;
}
private:
void run()
{
for (;;)
{
if (int err = poll(fds, 3, -1); err < 0)
{
if (err == -EAGAIN)
continue;
else
return;
}
// Check udev
if (fds[0].revents & POLLIN)
{
udev_device* dev = udev.udev.monitor_receive_device(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);
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;
}
}
udev.udev.device_unref(dev);
fds[0].revents = 0;
}
// Check eventfd
if (fds[1].revents & POLLIN)
{
break;
}
// Check timer
if (fds[2].revents & POLLIN)
{
if (this->timer_check_counts-- <= 0)
this->timer_fd.cancel();
fds[2].revents = 0;
check_devices();
}
}
}
template <bool Input>
auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept
-> std::conditional_t<Input, input_port, output_port>
{
return {
{.client = 0,
.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}};
}
void check_devices()
{
Enumerator new_devs;
new_devs.enumerate_cards();
for (auto& in_prev : current_inputs)
{
if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);
it == new_devs.inputs.end())
{
if (auto& cb = this->configuration.input_removed)
{
cb(to_port_info<true>(in_prev));
}
}
}
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& cb = this->configuration.input_added)
{
cb(to_port_info<true>(in_next));
}
}
}
for (auto& out_prev : current_outputs)
{
if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);
it == new_devs.outputs.end())
{
if (auto& cb = this->configuration.output_removed)
{
cb(to_port_info<false>(out_prev));
}
}
}
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& cb = this->configuration.output_added)
{
cb(to_port_info<false>(out_next));
}
}
}
current_inputs = std::move(new_devs.inputs);
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{}}
{
}
};
}
#endif
namespace libremidi::alsa_raw
{
struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>
{
using alsa_raw::observer_impl_base<midi1_enumerator>::observer_impl_base;
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
};
}
@@ -0,0 +1,30 @@
#pragma once
#include <libremidi/backends/alsa_raw/observer.hpp>
#include <libremidi/backends/alsa_raw_ump/midi_in.hpp>
#include <libremidi/backends/alsa_raw_ump/midi_out.hpp>
#include <libremidi/backends/alsa_raw_ump/observer.hpp>
#include <libremidi/backends/dummy.hpp>
namespace libremidi::alsa_raw_ump
{
struct backend
{
using midi_in = midi_in_impl;
using midi_out = midi_out_impl;
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
{
};
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 inline bool available() noexcept
{
static const libasound& snd = libasound::instance();
return snd.available && snd.rawmidi.available && snd.ump.available;
}
};
}
@@ -0,0 +1,24 @@
#pragma once
#include <libremidi/backends/alsa_raw/config.hpp>
namespace libremidi::alsa_raw_ump
{
struct input_configuration
{
std::function<bool(const manual_poll_parameters&)> manual_poll;
};
struct output_configuration
{
/**
* For large messages, chunk their content and wait.
* Setting a null optional will disable chunking.
*/
std::optional<chunking_parameters> chunking;
};
struct observer_configuration
{
std::chrono::milliseconds poll_period{100};
};
}
@@ -0,0 +1,66 @@
#pragma once
#include <libremidi/backends/alsa_raw/helpers.hpp>
#include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp>
namespace libremidi::alsa_raw_ump
{
struct midi2_enumerator : alsa_raw::enumerator
{
void enumerate_devices(int card) override
{
char name[128];
sprintf(name, "hw:%d", card);
// Open card.
alsa_raw::snd_ctl_wrapper ctl{*this, name};
if (!ctl)
return;
// Enumerate devices.
int device = -1;
do
{
const int status = snd.ctl.ump.next_device(ctl, &device);
if (device == -1)
return;
if (status < 0)
{
error(
"alsa_raw_ump::midi2_enumerator::enumerate_devices: "
"cannot determine device number: ",
snd.strerror(status));
break;
}
if (device >= 0)
{
enumerate_blocks(ctl, card, device);
enumerate_endpoints(ctl, card, device);
}
} while (device >= 0);
}
void enumerate_endpoints(snd_ctl_t* ctl, [[maybe_unused]] int card, [[maybe_unused]] int device)
{
snd_ump_endpoint_info_t* info{};
snd_ump_endpoint_info_alloca(&info);
snd.ctl.ump.endpoint_info(ctl, info);
fprintf(stderr, "UMP endpoint: %s", snd.ump.endpoint_info_get_name(info));
}
void enumerate_blocks(snd_ctl_t* ctl, [[maybe_unused]] int card, [[maybe_unused]] int device)
{
snd_ump_block_info_t* info{};
snd_ump_block_info_alloca(&info);
snd.ctl.ump.block_info(ctl, info);
fprintf(stderr, "UMP block: %s", snd.ump.block_info_get_name(info));
}
};
}
@@ -0,0 +1,383 @@
#pragma once
#include <libremidi/backends/alsa_raw_ump/config.hpp>
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <alsa/asoundlib.h>
#include <atomic>
#include <thread>
namespace libremidi::alsa_raw_ump
{
class midi_in_impl
: public midi2::in_api
, public error_handler
{
public:
struct
: libremidi::ump_input_configuration
, alsa_raw_ump::input_configuration
{
} configuration;
const libasound& snd = libasound::instance();
explicit midi_in_impl(
libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
fds_.reserve(4);
assert(snd.ump.available);
}
~midi_in_impl() override { }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "alsa_raw_ump::ump: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "alsa_raw_ump::ump: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "alsa_raw_ump::ump: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
// Must be a string such as: "hw:2,4,1"
[[nodiscard]] int do_init_port(const char* portname)
{
constexpr int mode = 0;
SND_RAWMIDI_NONBLOCK;
if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0)
{
error<driver_error>(
this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
return err;
}
snd_rawmidi_params_t* params{};
snd_rawmidi_params_alloca(&params);
auto rawmidi = snd.ump.rawmidi(midiport_);
if (int err = snd.ump.rawmidi_params_current(midiport_, params); err < 0)
return err;
if (int err = snd.rawmidi.params_set_no_active_sensing(rawmidi, params, 1); err < 0)
return err;
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_STANDARD);
err < 0)
return err;
if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_NONE);
err < 0)
return err;
}
else
{
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_TSTAMP);
err < 0)
return err;
if (int err
= snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_MONOTONIC);
err < 0)
return err;
}
if (int err = snd.ump.rawmidi_params(midiport_, params); err < 0)
return err;
return init_pollfd();
}
[[nodiscard]] int init_port(const port_information& p)
{
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
}
[[nodiscard]] int init_pollfd()
{
int num_fds = snd.ump.poll_descriptors_count(this->midiport_);
this->fds_.clear();
this->fds_.resize(num_fds);
return snd.ump.poll_descriptors(this->midiport_, fds_.data(), num_fds);
}
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
{
// Read events
if (fds.empty())
{
return (this->*parse_func)();
}
else
{
unsigned short res{};
ssize_t err = snd.ump.poll_descriptors_revents(
this->midiport_, fds.data(), static_cast<unsigned int>(fds.size()), &res);
if (err < 0)
return err;
// Did we encounter an error during polling
if (res & (POLLERR | POLLHUP))
return -EIO;
// Is there data to read
if (res & POLLIN)
return (this->*parse_func)();
}
return 0;
}
ssize_t read_input_buffer()
{
static const constexpr int nbytes = 1024;
unsigned char bytes[nbytes];
ssize_t err = 0;
while ((err = snd.ump.read(this->midiport_, bytes, nbytes)) > 0)
{
std::cerr << "We read: " << nbytes << "bytes !!!!";
return 1;
// err is the amount of bytes read
// decoder_.add_bytes(bytes, err);
}
return err;
}
void set_timestamp(struct timespec ts, timestamp& res)
{
static constexpr int64_t nanos = 1e9;
switch (configuration.timestamps)
{
// Unneeded here
case timestamp_mode::NoTimestamp:
break;
case timestamp_mode::Relative: {
const auto t = static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec);
if (firstMessage == true)
{
firstMessage = false;
res = 0;
}
else
{
res = t - last_time;
}
last_time = t;
break;
}
case timestamp_mode::Absolute:
case timestamp_mode::SystemMonotonic:
res = static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec);
break;
case timestamp_mode::Custom:
res = configuration.get_timestamp(
static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec));
break;
}
}
ssize_t read_input_buffer_with_timestamps()
{
static const constexpr int nbytes = 1024;
unsigned char bytes[nbytes];
struct timespec ts;
ssize_t err = 0;
while ((err = snd.ump.tread(this->midiport_, &ts, bytes, nbytes)) > 0)
{
// err is the amount of bytes read
int64_t ns{};
set_timestamp(ts, ns);
std::cerr << "We read: " << nbytes << "bytes !!!!";
return 1;
// decoder_.add_bytes(bytes, err, ns);
}
return err;
}
void close_port() override
{
if (midiport_)
snd.ump.close(midiport_);
midiport_ = nullptr;
}
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
midi2_enumerator get_device_enumerator() const noexcept
{
midi2_enumerator device_list;
device_list.error_callback
= [this](std::string_view text) { this->error<driver_error>(this->configuration, text); };
return device_list;
}
snd_ump_t* midiport_{};
std::vector<pollfd> fds_;
// midi_stream_decoder decoder_{this->configuration.on_message};
int64_t last_time{};
};
class midi_in_impl_threaded : public midi_in_impl
{
public:
midi_in_impl_threaded(
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)
{
error<driver_error>(
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
}
}
~midi_in_impl_threaded()
{
// Close a connection if it exists.
this->close_port();
}
private:
void run_thread(auto parse_func)
{
fds_.push_back(this->termination_event);
for (;;)
{
// Poll
ssize_t err = poll(fds_.data(), fds_.size(), -1);
if (err == -EAGAIN)
continue;
else if (err < 0)
return;
else if (termination_event.ready(fds_.back()))
break;
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
if (err == -EAGAIN)
continue;
else if (err < 0)
return;
}
}
[[nodiscard]] int start_thread()
{
try
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
}
else
{
this->thread_ = std::thread{
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
}
}
catch (const std::system_error& e)
{
using namespace std::literals;
error<thread_error>(
this->configuration,
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
return false;
}
return true;
}
bool open_port(const input_port& port, [[maybe_unused]] std::string_view name) override
{
if (int err = midi_in_impl::init_port(port); err < 0)
return false;
if (!start_thread())
return false;
return true;
}
void close_port() override
{
termination_event.notify();
if (thread_.joinable())
thread_.join();
termination_event.consume(); // Reset to zero
midi_in_impl::close_port();
}
std::thread thread_;
eventfd_notifier termination_event{};
};
class midi_in_impl_manual : public midi_in_impl
{
public:
using midi_in_impl::midi_in_impl;
~midi_in_impl_manual()
{
// Close a connection if it exists.
this->close_port();
}
private:
void send_poll_callback()
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
}
else
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
}
}
bool open_port(const input_port& p, [[maybe_unused]] std::string_view name) override
{
if (midi_in_impl::init_port(p) < 0)
return false;
send_poll_callback();
return true;
}
};
}
namespace libremidi
{
template <>
inline std::unique_ptr<midi_in_api> make<alsa_raw_ump::midi_in_impl>(
libremidi::ump_input_configuration&& conf, libremidi::alsa_raw_ump::input_configuration&& api)
{
if (api.manual_poll)
return std::make_unique<alsa_raw_ump::midi_in_impl_manual>(std::move(conf), std::move(api));
else
return std::make_unique<alsa_raw_ump::midi_in_impl_threaded>(std::move(conf), std::move(api));
}
}
@@ -0,0 +1,107 @@
#pragma once
#include <libremidi/backends/alsa_raw_ump/config.hpp>
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <alsa/asoundlib.h>
#include <atomic>
#include <thread>
namespace libremidi::alsa_raw_ump
{
class midi_out_impl final
: public midi2::out_api
, public error_handler
{
public:
struct
: libremidi::output_configuration
, libremidi::alsa_raw_ump::output_configuration
{
} configuration;
const libasound& snd = libasound::instance();
midi_out_impl(
libremidi::output_configuration&& conf,
libremidi::alsa_raw_ump::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
assert(snd.ump.available);
}
~midi_out_impl() override
{
// Close a connection if it exists.
midi_out_impl::close_port();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_alsa_raw: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_alsa_raw: set_port_name unsupported");
}
int connect_port(const char* portname)
{
constexpr int mode = SND_RAWMIDI_SYNC;
int status = snd.ump.open(NULL, &midiport_, portname, mode);
if (status < 0)
{
error<driver_error>(
this->configuration, "midi_out_alsa_raw::open_port: cannot open device.");
return status;
}
return status;
}
bool open_port(const output_port& p, std::string_view) override
{
return connect_port(raw_from_port_handle(p.port).to_string().c_str()) == 0;
}
void close_port() override
{
if (midiport_)
snd.ump.close(midiport_);
midiport_ = nullptr;
}
void send_ump(const uint32_t* ump_stream, std::size_t count) override
{
if (!midiport_)
error<invalid_use_error>(
this->configuration,
"midi_out_alsa_raw::send_message: trying to send a message without an open "
"port.");
write(ump_stream, count * sizeof(uint32_t));
}
bool write(const uint32_t* ump_stream, size_t bytes)
{
if (snd.ump.write(midiport_, ump_stream, bytes) < 0)
{
error<driver_error>(
this->configuration, "midi_out_alsa_raw::send_message: cannot write message.");
return false;
}
return true;
}
snd_ump_t* midiport_{};
};
}
@@ -0,0 +1,14 @@
#pragma once
#include <libremidi/backends/alsa_raw/observer.hpp>
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
namespace libremidi::alsa_raw_ump
{
class observer_impl : public alsa_raw::observer_impl_base<midi2_enumerator>
{
using alsa_raw::observer_impl_base<midi2_enumerator>::observer_impl_base;
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
};
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
//*********************************************************************//
// API: LINUX ALSA SEQUENCER
//*********************************************************************//
// API information found at:
// - http://www.alsa-project.org/documentation.php#Library
// The ALSA Sequencer API is based on the use of a callback function for
// MIDI input.
//
// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
// time stamps and other assorted fixes!!!
#include <libremidi/backends/alsa_seq/midi_in.hpp>
#include <libremidi/backends/alsa_seq/midi_out.hpp>
#include <libremidi/backends/alsa_seq/observer.hpp>
namespace libremidi::alsa_seq
{
struct backend
{
using midi_in
= alsa_seq::midi_in_impl<libremidi::input_configuration, alsa_seq::input_configuration>;
using midi_out = alsa_seq::midi_out_impl;
using midi_observer = alsa_seq::observer_impl<alsa_seq::observer_configuration>;
using midi_in_configuration = alsa_seq::input_configuration;
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 inline bool available() noexcept
{
static const libasound& snd = libasound::instance();
return snd.available && snd.seq.available;
}
};
}
@@ -0,0 +1,63 @@
#pragma once
#include <libremidi/backends/alsa_raw/config.hpp>
#if __has_include(<alsa/asoundlib.h>)
#include <alsa/asoundlib.h>
#else
extern "C" {
typedef struct _snd_seq snd_seq_t;
typedef struct snd_seq_event snd_seq_event_t;
typedef struct snd_seq_addr
{
unsigned char client;
unsigned char port;
} snd_seq_addr_t;
}
#endif
namespace libremidi::alsa_seq
{
// Possible parameters:
// - Direct / non-direct output
// - Timer source used (high resolution, etc)
// - Timestamping
// - Tempo, ppq?
struct poll_parameters
{
snd_seq_addr_t addr{};
std::function<int(const snd_seq_event_t&)> callback;
};
struct input_configuration
{
using poll_parameters_type = poll_parameters;
std::string client_name = "libremidi client";
snd_seq_t* context{};
std::function<bool(const poll_parameters&)> manual_poll;
std::function<bool(snd_seq_addr_t)> stop_poll;
static constexpr int midi_version = 1;
};
struct output_configuration
{
std::string client_name = "libremidi client";
snd_seq_t* context{};
static constexpr int midi_version = 1;
};
struct observer_configuration
{
std::string client_name = "libremidi client";
snd_seq_t* context{};
std::function<bool(const poll_parameters&)> manual_poll;
std::function<bool(snd_seq_addr_t)> stop_poll;
static constexpr int midi_version = 1;
};
}
@@ -0,0 +1,306 @@
#pragma once
#include <libremidi/backends/linux/alsa.hpp>
#include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp>
#include <sys/time.h>
#include <cstring>
#include <optional>
#include <string>
namespace libremidi::alsa_seq
{
struct event_handle
{
const libasound& snd;
snd_seq_event_t* ev{};
explicit event_handle(const libasound& snd) noexcept
: snd{snd}
{
}
explicit event_handle(const libasound& snd, snd_seq_event_t* ev) noexcept
: snd{snd}
, ev{ev}
{
}
event_handle(const event_handle&) noexcept = delete;
event_handle& operator=(const event_handle&) noexcept = delete;
event_handle(event_handle&&) noexcept = delete;
event_handle& operator=(event_handle&&) noexcept = delete;
void reset(snd_seq_event_t* new_ev) noexcept
{
if (ev)
snd.seq.free_event(ev);
ev = new_ev;
}
~event_handle() { snd.seq.free_event(ev); }
};
namespace
{
inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept
{
return (client << 32) + port;
}
inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept
{
int client = p >> 32;
int port = 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)
{
snd_seq_client_info_t* cinfo{};
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_t* pinfo{};
snd_seq_port_info_alloca(&pinfo);
snd.seq.client_info_set_client(cinfo, -1);
while (snd.seq.query_next_client(seq, cinfo) >= 0)
{
int client = snd.seq.client_info_get_client(cinfo);
if (client == 0)
continue;
// Reset query info
snd.seq.port_info_set_client(pinfo, client);
snd.seq.port_info_set_port(pinfo, -1);
while (snd.seq.query_next_port(seq, pinfo) >= 0)
{
func(*cinfo, *pinfo);
}
}
}
// This function is used to count or get the pinfo structure for a given port
// number.
inline unsigned int iterate_port_info(
const libasound& snd, snd_seq_t* seq, snd_seq_port_info_t* pinfo, unsigned int type,
int portNumber)
{
snd_seq_client_info_t* cinfo{};
int count = 0;
snd_seq_client_info_alloca(&cinfo);
snd.seq.client_info_set_client(cinfo, -1);
while (snd.seq.query_next_client(seq, cinfo) >= 0)
{
const int client = snd.seq.client_info_get_client(cinfo);
if (client == 0)
continue;
// Reset query info
snd.seq.port_info_set_client(pinfo, client);
snd.seq.port_info_set_port(pinfo, -1);
while (snd.seq.query_next_port(seq, pinfo) >= 0)
{
const unsigned int atyp = snd.seq.port_info_get_type(pinfo);
if (((atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC) == 0) && ((atyp & SND_SEQ_PORT_TYPE_SYNTH) == 0)
&& ((atyp & SND_SEQ_PORT_TYPE_APPLICATION) == 0))
continue;
const unsigned int caps = snd.seq.port_info_get_capability(pinfo);
if ((caps & type) != type)
continue;
if ((caps & SND_SEQ_PORT_CAP_NO_EXPORT) != 0)
continue;
if (count == portNumber)
return 1;
++count;
}
}
// If a negative portNumber was used, return the port count.
if (portNumber < 0)
return count;
return 0;
}
}
// A structure to hold variables related to the ALSA API
// implementation.
struct alsa_data
{
const libasound& snd = libasound::instance();
snd_seq_t* seq{};
int vport{-1};
snd_seq_addr_t vaddr{};
snd_seq_port_subscribe_t* subscription{};
snd_midi_event_t* coder{};
[[nodiscard]] int init_client(auto& configuration)
{
// Initialize or use the snd_seq client
if (configuration.context)
{
seq = configuration.context;
return 0;
}
else
{
// Set up the ALSA sequencer client.
int ret = snd.seq.open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
if (ret < 0)
return ret;
// Set client name.
if (!configuration.client_name.empty())
snd.seq.set_client_name(seq, configuration.client_name.data());
#if __has_include(<alsa/ump.h>)
if (snd.seq.ump.set_client_midi_version)
{
switch (configuration.midi_version)
{
case 1:
snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI);
break;
case 2:
snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0);
break;
}
}
#endif
return 0;
}
}
void set_client_name(std::string_view clientName)
{
snd.seq.set_client_name(seq, clientName.data());
}
void set_port_name(std::string_view portName)
{
snd_seq_port_info_t* pinfo;
snd_seq_port_info_alloca(&pinfo);
snd.seq.get_port_info(seq, vport, pinfo);
snd.seq.port_info_set_name(pinfo, portName.data());
snd.seq.set_port_info(seq, vport, pinfo);
}
unsigned int get_port_count(int caps) const
{
snd_seq_port_info_t* pinfo;
snd_seq_port_info_alloca(&pinfo);
return alsa_seq::iterate_port_info(snd, seq, pinfo, caps, -1);
}
std::optional<snd_seq_addr_t> get_port_info(const port_information& portNumber)
{
auto [client, port] = alsa_seq::seq_from_port_handle(portNumber.port);
// FIXME check that the {client, port} pair actually exists
// snd_seq_port_info_t* src_pinfo{};
// snd_seq_port_info_alloca(&src_pinfo);
// snd.seq.port_info_set_client(src_pinfo, client);
// snd.seq.port_info_set_port(src_pinfo, port);
// {
// self.template error<invalid_parameter_error>(
// self.configuration,
// "alsa::get_port_info: invalid 'portNumber' argument: " + std::to_string(portNumber));
// return {};
// }
snd_seq_addr_t addr;
addr.client = client;
addr.port = port;
return addr;
}
[[nodiscard]] int create_port(
auto& /*self*/, std::string_view portName, unsigned int caps, unsigned int type,
std::optional<int> queue)
{
if (this->vport < 0)
{
snd_seq_port_info_t* pinfo{};
snd_seq_port_info_alloca(&pinfo);
snd.seq.port_info_set_name(pinfo, portName.data());
snd.seq.port_info_set_client(pinfo, 0);
snd.seq.port_info_set_port(pinfo, 0);
snd.seq.port_info_set_capability(pinfo, caps);
snd.seq.port_info_set_type(pinfo, type);
if (type & SND_SEQ_PORT_TYPE_MIDI_GENERIC)
{
snd.seq.port_info_set_midi_channels(pinfo, 16);
}
if (queue)
{
snd.seq.port_info_set_timestamping(pinfo, 1);
snd.seq.port_info_set_timestamp_real(pinfo, 1);
snd.seq.port_info_set_timestamp_queue(pinfo, *queue);
}
if (int err = snd.seq.create_port(this->seq, pinfo); err < 0)
return err;
this->vport = snd.seq.port_info_get_port(pinfo);
if (int err = snd.seq.get_port_info(this->seq, this->vport, pinfo); err < 0)
return err;
if (auto addr = snd.seq.port_info_get_addr(pinfo))
this->vaddr = *addr;
else
return -1;
return this->vport;
}
return 0;
}
int create_connection(auto& self, snd_seq_addr_t sender, snd_seq_addr_t receiver, bool realtime)
{
// Create the connection between ports
// Make subscription
if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0)
{
self.template error<driver_error>(
self.configuration, "create_connection: ALSA error allocation port subscription.");
return err;
}
snd.seq.port_subscribe_set_sender(this->subscription, &sender);
snd.seq.port_subscribe_set_dest(this->subscription, &receiver);
if (realtime)
{
snd.seq.port_subscribe_set_time_update(this->subscription, 1);
snd.seq.port_subscribe_set_time_real(this->subscription, 1);
}
if (int err = snd.seq.subscribe_port(this->seq, this->subscription); err != 0)
{
snd.seq.port_subscribe_free(this->subscription);
this->subscription = nullptr;
return err;
}
return 0;
}
void unsubscribe()
{
if (this->subscription)
{
snd.seq.unsubscribe_port(this->seq, this->subscription);
snd.seq.port_subscribe_free(this->subscription);
this->subscription = nullptr;
}
}
};
}
@@ -0,0 +1,575 @@
#pragma once
#include <libremidi/backends/alsa_seq/config.hpp>
#include <libremidi/backends/alsa_seq/helpers.hpp>
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi::alsa_seq
{
struct dummy_processing
{
explicit dummy_processing(auto&&...) { }
};
template <typename ConfigurationImpl>
using midi_in_base
= std::conditional_t<ConfigurationImpl::midi_version == 1, midi1::in_api, midi2::in_api>;
template <typename ConfigurationImpl>
using midi_in_processing = std::conditional_t<
ConfigurationImpl::midi_version == 1, midi1::input_state_machine, dummy_processing>;
template <typename ConfigurationBase, typename ConfigurationImpl>
class midi_in_impl
: public midi_in_base<ConfigurationImpl>
, protected alsa_data
, public error_handler
{
public:
struct
: ConfigurationBase
, ConfigurationImpl
{
} configuration;
midi_in_processing<ConfigurationImpl> m_processing{this->configuration};
bool require_timestamps() const noexcept
{
switch (configuration.timestamps)
{
case timestamp_mode::NoTimestamp:
case timestamp_mode::SystemMonotonic:
case timestamp_mode::AudioFrame:
return false;
case timestamp_mode::Absolute:
case timestamp_mode::Relative:
case timestamp_mode::Custom:
return true;
}
return true;
}
explicit midi_in_impl(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)
: midi_in_base<ConfigurationImpl>{}
, configuration{std::move(conf), std::move(apiconf)}
{
if (init_client(configuration) < 0)
{
error<driver_error>(
this->configuration,
"midi_in_alsa::initialize: error creating ALSA sequencer client "
"object.");
return;
}
// Create the input queue
if (require_timestamps())
{
this->queue_id = snd.seq.alloc_queue(seq);
// Set arbitrary tempo (mm=100) and resolution (240)
snd_seq_queue_tempo_t* qtempo{};
snd_seq_queue_tempo_alloca(&qtempo);
snd.seq.queue_tempo_set_tempo(qtempo, 600000);
snd.seq.queue_tempo_set_ppq(qtempo, 240);
snd.seq.set_queue_tempo(this->seq, this->queue_id, qtempo);
snd.seq.drain_output(this->seq);
}
// Create the event -> midi encoder
{
int result = snd.midi.event_new(0, &coder);
if (result < 0)
{
error<driver_error>(
this->configuration, "midi_in_alsa::initialize: error during snd_midi_event_new.");
return;
}
snd.midi.event_init(coder);
snd.midi.event_no_status(coder, 1);
}
}
~midi_in_impl() override
{
// Cleanup.
if (this->vport >= 0)
snd.seq.delete_port(this->seq, this->vport);
if (require_timestamps())
snd.seq.free_queue(this->seq, this->queue_id);
snd.midi.event_free(coder);
// Close if we do not have an user-provided client object
if (!configuration.context)
snd.seq.close(this->seq);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
[[nodiscard]] int create_port(std::string_view portName)
{
return alsa_data::create_port(
*this, portName, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION,
require_timestamps() ? std::optional<int>{this->queue_id} : std::nullopt);
}
void start_queue()
{
if (require_timestamps())
{
snd.seq.control_queue(this->seq, this->queue_id, SND_SEQ_EVENT_START, 0, nullptr);
this->queue_creation_time = std::chrono::steady_clock::now();
snd.seq.drain_output(this->seq);
}
}
void stop_queue()
{
if (require_timestamps())
{
snd.seq.control_queue(this->seq, this->queue_id, SND_SEQ_EVENT_STOP, 0, nullptr);
snd.seq.drain_output(this->seq);
}
}
int connect_port(snd_seq_addr_t sender)
{
snd_seq_addr_t receiver{};
receiver.client = snd.seq.client_id(this->seq);
receiver.port = this->vport;
return create_connection(*this, sender, receiver, false);
}
std::optional<snd_seq_addr_t> to_address(const port_information& p)
{
return alsa_data::get_port_info(p);
}
int init_port(std::optional<snd_seq_addr_t> source, std::string_view portName)
{
this->close_port();
if (!source)
return -1;
if (int ret = create_port(portName); ret < 0)
{
error<driver_error>(configuration, "midi_in_alsa::create_port: ALSA error creating port.");
return ret;
}
if (int ret = connect_port(*source); ret < 0)
{
error<driver_error>(
configuration, "midi_in_alsa::create_port: ALSA error making port connection.");
return ret;
}
start_queue();
return 0;
}
int init_virtual_port(std::string_view portName)
{
this->close_port();
if (int ret = create_port(portName); ret < 0)
return ret;
start_queue();
return 0;
}
void close_port() override
{
unsubscribe();
stop_queue();
}
void set_client_name(std::string_view clientName) override
{
alsa_data::set_client_name(clientName);
}
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
protected:
void set_timestamp(const auto& ev, auto& msg) noexcept
{
static constexpr int64_t nanos = 1e9;
switch (configuration.timestamps)
{
case timestamp_mode::NoTimestamp:
msg.timestamp = 0;
return;
case timestamp_mode::Relative: {
const auto t1 = static_cast<int64_t>(ev.time.time.tv_sec) * nanos
+ static_cast<int64_t>(ev.time.time.tv_nsec);
const auto t0 = static_cast<int64_t>(last_time.tv_sec) * nanos
+ static_cast<int64_t>(last_time.tv_nsec);
const auto time = t1 - t0;
last_time = ev.time.time;
if (this->firstMessage == true)
{
this->firstMessage = false;
msg.timestamp = 0;
}
else
{
msg.timestamp = time;
}
return;
}
case timestamp_mode::Absolute: {
msg.timestamp = ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec;
break;
}
case timestamp_mode::Custom: {
msg.timestamp
= configuration.get_timestamp(ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec);
break;
}
case timestamp_mode::SystemMonotonic: {
namespace clk = std::chrono;
msg.timestamp = system_ns();
break;
}
}
}
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - this->queue_creation_time)
.count();
}
int process_event(const snd_seq_event_t& ev)
{
if constexpr (ConfigurationImpl::midi_version == 1)
{
switch (ev.type)
{
case SND_SEQ_EVENT_PORT_SUBSCRIBED:
case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
return 0;
case SND_SEQ_EVENT_SYSEX: {
if (configuration.ignore_sysex)
return 0;
else if (ev.data.ext.len > decoding_buffer.size())
decoding_buffer.resize(ev.data.ext.len);
break;
}
}
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = false,
.has_samples = false,
};
const auto to_ns = [ts = ev.time.time] {
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
};
auto buf = decoding_buffer.data();
auto buf_space = decoding_buffer.size();
// FIXME according to the doc snd_midi_event_decode can apparently return multiple events????
const auto avail = snd.midi.event_decode(coder, buf, buf_space, &ev);
if (avail > 0)
{
m_processing.on_bytes(
{buf, buf + avail}, m_processing.template timestamp<timestamp_info>(to_ns, 0));
return 0;
}
else
{
return avail;
}
}
return 0;
}
int process_events()
{
if constexpr (ConfigurationImpl::midi_version == 1)
{
snd_seq_event_t* ev{};
event_handle handle{snd};
int result = 0;
while ((result = snd.seq.event_input(seq, &ev)) > 0)
{
handle.reset(ev);
if (int err = process_event(*ev); err < 0)
return err;
}
return result;
}
else
{
return 0;
}
}
#if __has_include(<alsa/ump.h>)
int process_ump_event(const snd_seq_ump_event_t& ev)
{
// Filter the message types before any decoding
switch (ev.type)
{
case SND_SEQ_EVENT_PORT_SUBSCRIBED:
case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
return 0;
case SND_SEQ_EVENT_QFRAME: // MIDI time code
case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick
case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick
if (configuration.ignore_timing)
return 0;
break;
case SND_SEQ_EVENT_SENSING: // Active sensing
if (configuration.ignore_sensing)
return 0;
break;
case SND_SEQ_EVENT_SYSEX: {
if (configuration.ignore_sysex)
return 0;
break;
}
}
// MIDI 2 : no decoder, we can just send the UMP data directly, yay
libremidi::ump ump;
std::memcpy(ump.data, ev.ump, sizeof(ev.ump));
set_timestamp(ev, ump);
configuration.on_message(std::move(ump));
return 0;
}
int process_ump_events()
{
snd_seq_ump_event_t* ev{};
event_handle handle{snd};
int result = 0;
while ((result = snd.seq.ump.event_input(seq, &ev)) > 0)
{
handle.reset((snd_seq_event_t*)ev);
if (int err = process_ump_event(*ev); err < 0)
return err;
}
return result;
}
#endif
int queue_id{}; // an input queue is needed to get timestamped events
snd_seq_real_time_t last_time{};
// Only needed for midi 1
std::vector<unsigned char> decoding_buffer = std::vector<unsigned char>(4096);
std::chrono::steady_clock::time_point queue_creation_time;
};
template <typename ConfigurationBase, typename ConfigurationImpl>
class midi_in_alsa_threaded : public midi_in_impl<ConfigurationBase, ConfigurationImpl>
{
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)
{
this->template error<driver_error>(
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
}
}
~midi_in_alsa_threaded() { this->close_port(); }
private:
bool open_port(const input_port& pt, std::string_view local_port_name) override
{
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
return false;
if (!start_thread())
return false;
return true;
}
bool open_virtual_port(std::string_view portName) override
{
if (int err = this->init_virtual_port(portName); err < 0)
return false;
if (!this->start_thread())
return false;
return true;
}
void close_port() override
{
midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
stop_thread();
}
[[nodiscard]] int start_thread()
{
try
{
this->thread = std::thread([this] { thread_handler(); });
}
catch (const std::system_error& e)
{
using namespace std::literals;
this->unsubscribe();
this->template error<thread_error>(
this->configuration,
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
return false;
}
return true;
}
void stop_thread()
{
termination_event.notify();
if (this->thread.joinable())
this->thread.join();
termination_event.consume();
}
void thread_handler()
{
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;
alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);
for (;;)
{
if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)
{
// No data pending
if (poll(poll_fds, poll_fd_count, -1) >= 0)
{
// We got our stop-thread signal
if (termination_event.ready(poll_fds[0]))
{
break;
}
}
continue;
}
int res{};
if constexpr (ConfigurationImpl::midi_version == 1)
{
res = this->process_events();
}
#if __has_include(<alsa/ump.h>)
else if constexpr (ConfigurationImpl::midi_version == 2)
{
res = this->process_ump_events();
}
#endif
(void)res;
#if defined(__LIBREMIDI_DEBUG__)
if (res < 0)
std::cerr << "midi_in_alsa::thread_handler: MIDI input error: " << snd.strerror(res)
<< "\n";
#endif
}
}
std::thread thread{};
eventfd_notifier termination_event{};
};
template <typename ConfigurationBase, typename ConfigurationImpl>
class midi_in_alsa_manual : public midi_in_impl<ConfigurationBase, ConfigurationImpl>
{
public:
midi_in_alsa_manual(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)
: midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}
{
assert(this->configuration.manual_poll);
assert(this->configuration.stop_poll);
}
[[nodiscard]] int init_callback()
{
using poll_params = typename ConfigurationImpl::poll_parameters_type;
this->configuration.manual_poll(
poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) {
if constexpr (ConfigurationImpl::midi_version == 1)
return this->process_event(ev);
#if __has_include(<alsa/ump.h>)
else
return this->process_ump_event(ev);
#endif
}});
return 0;
}
~midi_in_alsa_manual() { this->close_port(); }
bool open_port(const input_port& pt, std::string_view local_port_name) override
{
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
return false;
if (int err = init_callback(); err < 0)
return false;
return true;
}
bool open_virtual_port(std::string_view name) override
{
if (int err = this->init_virtual_port(name); err < 0)
return false;
if (int err = init_callback(); err < 0)
return false;
return true;
}
void close_port() override
{
this->configuration.stop_poll(this->vaddr);
midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
}
};
}
namespace libremidi
{
template <>
inline std::unique_ptr<midi_in_api>
make<alsa_seq::midi_in_impl<libremidi::input_configuration, alsa_seq::input_configuration>>(
libremidi::input_configuration&& conf, libremidi::alsa_seq::input_configuration&& api)
{
if (api.manual_poll)
return std::make_unique<alsa_seq::midi_in_alsa_manual<
libremidi::input_configuration, alsa_seq::input_configuration>>(
std::move(conf), std::move(api));
else
return std::make_unique<alsa_seq::midi_in_alsa_threaded<
libremidi::input_configuration, alsa_seq::input_configuration>>(
std::move(conf), std::move(api));
}
}
@@ -0,0 +1,174 @@
#pragma once
#include <libremidi/backends/alsa_seq/config.hpp>
#include <libremidi/backends/alsa_seq/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi::alsa_seq
{
class midi_out_impl final
: public midi1::out_api
, private alsa_data
, public error_handler
{
public:
struct
: libremidi::output_configuration
, alsa_seq::output_configuration
{
} configuration;
midi_out_impl(libremidi::output_configuration&& conf, alsa_seq::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (init_client(configuration) < 0)
{
error<driver_error>(
this->configuration,
"midi_in_alsa::initialize: error creating ALSA sequencer client "
"object.");
return;
}
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
{
error<driver_error>(
this->configuration,
"midi_out_alsa::initialize: error initializing MIDI event "
"parser.");
return;
}
snd.midi.event_init(this->coder);
}
~midi_out_impl() override
{
// Close a connection if it exists.
midi_out_impl::close_port();
// Cleanup.
if (this->vport >= 0)
snd.seq.delete_port(this->seq, this->vport);
if (this->coder)
snd.midi.event_free(this->coder);
if (!configuration.context)
snd.seq.close(this->seq);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
[[nodiscard]] int create_port(std::string_view portName)
{
return alsa_data::create_port(
*this, portName, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
}
bool 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)
{
error<no_devices_found_error>(
this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!");
return false;
}
auto sink = get_port_info(p);
if (!sink)
return false;
if (int err = create_port(portName); err < 0)
{
error<driver_error>(configuration, "midi_out_alsa::create_port: ALSA error creating port.");
return false;
}
snd_seq_addr_t source{
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
if (int err = create_connection(*this, source, *sink, true); err < 0)
{
error<driver_error>(
configuration, "midi_out_alsa::create_port: ALSA error making port connection.");
return false;
}
return true;
}
bool open_virtual_port(std::string_view portName) override
{
if (int err = create_port(portName); err < 0)
return false;
return true;
}
void close_port() override { unsubscribe(); }
void set_client_name(std::string_view clientName) override
{
alsa_data::set_client_name(clientName);
}
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
void send_message(const unsigned char* message, std::size_t size) override
{
int64_t result{};
if (size > this->bufferSize)
{
this->bufferSize = size;
result = snd.midi.event_resize_buffer(this->coder, size);
if (result != 0)
{
error<driver_error>(
this->configuration,
"midi_out_alsa::send_message: ALSA error resizing MIDI event "
"buffer.");
return;
}
}
std::size_t offset = 0;
while (offset < size)
{
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
snd_seq_ev_set_source(&ev, this->vport);
snd_seq_ev_set_subs(&ev);
// 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);
if (result < 0)
{
warning(this->configuration, "midi_out_alsa::send_message: event parsing error!");
return;
}
if (ev.type == SND_SEQ_EVENT_NONE)
{
warning(this->configuration, "midi_out_alsa::send_message: incomplete message!");
return;
}
offset += result;
result = snd.seq.event_output(this->seq, &ev);
if (result < 0)
{
warning(
this->configuration,
"midi_out_alsa::send_message: error sending MIDI message to port.");
return;
}
}
snd.seq.drain_output(this->seq);
}
private:
uint64_t bufferSize{32};
};
}
@@ -0,0 +1,340 @@
#pragma once
#include <libremidi/backends/alsa_seq/config.hpp>
#include <libremidi/backends/alsa_seq/helpers.hpp>
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <alsa/asoundlib.h>
#include <bitset>
#include <map>
namespace libremidi::alsa_seq
{
struct port_info
{
std::string client_name;
std::string port_name;
int client{};
int port{};
bool isInput{};
bool isOutput{};
};
template <typename ConfigurationImpl>
class observer_impl
: public observer_api
, public alsa_data
{
public:
struct
: libremidi::observer_configuration
, ConfigurationImpl
{
} configuration;
explicit observer_impl(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
using namespace std::literals;
if (int err = init_client(configuration); err < 0)
{
throw driver_error("observer_alsa: snd_seq_open failed");
}
if (!configuration.has_callbacks())
return;
// Init with the existing ports
if (configuration.notify_in_constructor)
init_all_ports();
// Create the port to listen on the server events
{
#if __has_include(<alsa/ump.h>)
constexpr int midi2_cap
= ConfigurationImpl::midi_version == 2 ? SND_SEQ_PORT_CAP_UMP_ENDPOINT : 0;
#else
constexpr int midi2_cap = 0;
#endif
constexpr int caps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE
| SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE | midi2_cap;
int err = alsa_data::create_port(
*this, "libremidi-observe", caps, SND_SEQ_PORT_TYPE_APPLICATION, false);
if (err < 0)
{
throw driver_error("observer: ALSA error creating port.");
}
}
// Connect the ALSA server events to our port
{
int err
= snd.seq.connect_from(seq, vport, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
if (err < 0)
{
throw driver_error("observer_alsa: snd_seq_connect_from failed");
}
}
}
std::optional<port_info> get_info(int client, int port) const noexcept
{
port_info p;
p.client = client;
p.port = port;
snd_seq_client_info_t* cinfo;
snd_seq_client_info_alloca(&cinfo);
if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0)
return std::nullopt;
snd_seq_port_info_t* pinfo;
snd_seq_port_info_alloca(&pinfo);
if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0)
return std::nullopt;
const auto tp = snd.seq.port_info_get_type(pinfo);
bool ok = this->configuration.track_any;
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
ok = true;
else if ((tp & SND_SEQ_PORT_TYPE_SOFTWARE) && this->configuration.track_virtual)
ok = true;
if (!ok)
return {};
if (auto name = snd.seq.client_info_get_name(cinfo))
p.client_name = name;
if (auto name = snd.seq.port_info_get_name(pinfo))
p.port_name = name;
auto cap = snd.seq.port_info_get_capability(pinfo);
p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ);
p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE);
return p;
}
template <bool Input>
auto to_port_info(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));
return {
{.client = std::uintptr_t(this->seq),
.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}};
}
void init_all_ports()
{
alsa_seq::for_all_ports(
snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
register_port(clt, pt);
});
}
libremidi::API get_current_api() const noexcept override
{
if constexpr (ConfigurationImpl::midi_version == 1)
return libremidi::API::ALSA_SEQ;
else
return libremidi::API::ALSA_SEQ_UMP;
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
std::vector<libremidi::input_port> ret;
alsa_seq::for_all_ports(
snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt))
if (p->isInput)
ret.push_back(to_port_info<true>(*p));
});
return ret;
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
std::vector<libremidi::output_port> ret;
alsa_seq::for_all_ports(
snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt))
if (p->isOutput)
ret.push_back(to_port_info<false>(*p));
});
return ret;
}
void register_port(int client, int port)
{
auto pp = get_info(client, port);
if (!pp)
return;
auto& p = *pp;
if (p.client == snd.seq.client_id(seq))
return;
knownClients_[{p.client, p.port}] = p;
if (p.isInput && configuration.input_added)
{
configuration.input_added(to_port_info<true>(p));
}
if (p.isOutput && configuration.output_added)
{
configuration.output_added(to_port_info<false>(p));
}
}
void unregister_port(int client, int port)
{
auto it = knownClients_.find({client, port});
if (it != knownClients_.end())
{
auto p = it->second;
knownClients_.erase(it);
if (p.isInput && configuration.input_removed)
{
configuration.input_removed(to_port_info<true>(p));
}
if (p.isOutput && configuration.output_removed)
{
configuration.output_removed(to_port_info<false>(p));
}
}
}
void handle_event(const snd_seq_event_t& ev)
{
switch (ev.type)
{
case SND_SEQ_EVENT_PORT_START: {
register_port(ev.data.addr.client, ev.data.addr.port);
break;
}
case SND_SEQ_EVENT_PORT_EXIT: {
unregister_port(ev.data.addr.client, ev.data.addr.port);
break;
}
case SND_SEQ_EVENT_PORT_CHANGE:
default:
break;
}
}
~observer_impl()
{
if (seq)
{
if (vport)
snd.seq.delete_port(seq, vport);
if (!configuration.context)
snd.seq.close(seq);
}
}
private:
std::map<std::pair<int, int>, port_info> knownClients_;
};
template <typename ConfigurationImpl>
class observer_threaded : public observer_impl<ConfigurationImpl>
{
public:
observer_threaded(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)
: observer_impl<ConfigurationImpl>{std::move(conf), std::move(apiconf)}
{
// 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);
descriptors_.back() = this->termination_event;
// Start the listening thread
thread = std::thread{[this] {
auto& snd = alsa_data::snd;
for (;;)
{
int err = poll(descriptors_.data(), descriptors_.size(), -1);
if (err >= 0)
{
// We got our stop-thread signal
if (descriptors_.back().revents & POLLIN)
break;
snd_seq_event_t* ev{};
event_handle handle{snd};
while (snd.seq.event_input(this->seq, &ev) >= 0)
{
handle.reset(ev);
this->handle_event(*ev);
}
}
}
}};
}
~observer_threaded()
{
termination_event.notify();
if (thread.joinable())
thread.join();
}
eventfd_notifier termination_event{};
std::thread thread;
std::vector<pollfd> descriptors_;
};
template <typename ConfigurationImpl>
class observer_manual : public observer_impl<ConfigurationImpl>
{
public:
observer_manual(libremidi::observer_configuration&& conf, ConfigurationImpl&& apiconf)
: observer_impl<ConfigurationImpl>{std::move(conf), std::move(apiconf)}
{
this->configuration.manual_poll(
poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) {
this->handle_event(v);
return 0;
}});
}
~observer_manual() { this->configuration.stop_poll(this->vaddr); }
};
}
namespace libremidi
{
template <>
inline std::unique_ptr<observer_api>
make<alsa_seq::observer_impl<alsa_seq::observer_configuration>>(
libremidi::observer_configuration&& conf, libremidi::alsa_seq::observer_configuration&& api)
{
if (api.manual_poll)
return std::make_unique<alsa_seq::observer_manual<alsa_seq::observer_configuration>>(
std::move(conf), std::move(api));
else
return std::make_unique<alsa_seq::observer_threaded<alsa_seq::observer_configuration>>(
std::move(conf), std::move(api));
}
}
@@ -0,0 +1,197 @@
#pragma once
#if __has_include(<boost/lockfree/spsc_queue.hpp>)
#include <libremidi/backends/alsa_seq/config.hpp>
#include <libremidi/backends/alsa_seq/helpers.hpp>
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/shared_context.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <variant>
namespace libremidi::alsa_seq
{
struct shared_handler : public libremidi::shared_context
{
const libasound& snd = libasound::instance();
struct equals_addr
{
constexpr bool operator()(const snd_seq_addr_t& lhs, const snd_seq_addr_t& rhs) noexcept
{
return lhs.client == rhs.client && lhs.port == rhs.port;
}
};
explicit shared_handler(std::string_view v)
{
if (int err = snd.seq.open(&client, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); err < 0)
{
client = nullptr;
// fixme throw?
return;
}
if (!v.empty())
snd.seq.set_client_name(client, v.data());
// Last descriptor is the eventfd one
int fds_size = snd.seq.poll_descriptors_count(client, POLLIN);
fds.reserve(fds_size + 2);
fds.resize(fds_size);
snd.seq.poll_descriptors(client, fds.data(), fds_size, POLLIN);
fds.push_back(termination_event);
fds.push_back(queue_event);
}
void start_processing() override
{
thread = std::thread{[this] { process(); }};
}
void stop_processing() override
{
termination_event.notify();
if (thread.joinable())
thread.join();
termination_event.consume();
}
static shared_configurations make(std::string_view client_name)
{
auto clt = std::make_shared<shared_handler>(client_name);
auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) {
if (auto clt = client.lock())
{
clt->events.push(
{.type = shared_handler::event_type::callback_added, .payload = std::move(params)});
clt->queue_event.notify();
}
return true;
};
auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) {
if (auto clt = client.lock())
{
clt->events.push(
{.type = shared_handler::event_type::callback_removed, .payload = std::move(id)});
clt->queue_event.notify();
}
return true;
};
return {
.context = clt,
.observer = alsa_seq::
observer_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb},
.in = alsa_seq::
input_configuration{.context = clt->client, .manual_poll = cb, .stop_poll = stop_cb},
.out = alsa_seq::output_configuration{.context = clt->client},
};
}
int64_t index_of_address(snd_seq_addr_t addr)
{
auto it = std::find_if(addresses.begin(), addresses.end(), [=](snd_seq_addr_t other) {
return equals_addr{}(addr, other);
});
if (it != addresses.end())
{
return std::distance(addresses.begin(), it);
}
else
{
return -1;
}
}
void process()
{
for (;;)
{
int err = poll(fds.data(), fds.size(), -1);
if (err < 0)
return;
// Check for termination signal
if (termination_event.ready(fds[fds.size() - 2]))
return;
// Check for queue processing signal
if (queue_event.ready(fds[fds.size() - 1]))
{
this->queue_event.consume();
event ev;
while (this->events.pop(ev))
{
switch (ev.type)
{
case callback_added: {
auto [addr, cb]
= std::move(*std::get_if<libremidi::alsa_seq::poll_parameters>(&ev.payload));
addresses.push_back(addr);
callbacks.push_back(std::move(cb));
break;
}
case callback_removed:
auto addr = std::move(*std::get_if<snd_seq_addr_t>(&ev.payload));
if (auto index = index_of_address(addr); index >= 0)
{
addresses.erase(addresses.begin() + index);
callbacks.erase(callbacks.begin() + index);
}
break;
}
}
}
// Look for who's ready
for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++)
{
if (fds[i].revents & POLLIN)
{
// Read alsa event
snd_seq_event_t* ev{};
event_handle handle{snd};
int result = 0;
while ((result = snd.seq.event_input(client, &ev)) > 0)
{
handle.reset(ev);
if (auto index = index_of_address(ev->dest); index >= 0)
{
// Dispatch the event to the correct observer or midi_in object
int err = callbacks[index](*ev);
if (err < 0 && err != -EAGAIN)
return;
}
}
}
}
}
}
~shared_handler() { snd.seq.close(client); }
enum event_type
{
callback_added,
callback_removed,
};
struct event
{
event_type type;
std::variant<libremidi::alsa_seq::poll_parameters, snd_seq_addr_t> payload;
};
snd_seq_t* client{};
boost::lockfree::spsc_queue<event> events{16};
std::vector<snd_seq_addr_t> addresses;
std::vector<std::function<int(const snd_seq_event_t&)>> callbacks;
std::vector<pollfd> fds;
eventfd_notifier termination_event, queue_event{false};
std::thread thread;
};
}
#endif
@@ -0,0 +1,62 @@
#pragma once
#include <libremidi/backends/alsa_seq/midi_in.hpp>
#include <libremidi/backends/alsa_seq/observer.hpp>
#include <libremidi/backends/alsa_seq_ump/config.hpp>
#include <libremidi/backends/alsa_seq_ump/midi_out.hpp>
namespace libremidi
{
template <>
inline std::unique_ptr<observer_api>
make<alsa_seq::observer_impl<alsa_seq_ump::observer_configuration>>(
libremidi::observer_configuration&& conf,
libremidi::alsa_seq_ump::observer_configuration&& api)
{
if (api.manual_poll)
return std::make_unique<alsa_seq::observer_manual<alsa_seq_ump::observer_configuration>>(
std::move(conf), std::move(api));
else
return std::make_unique<alsa_seq::observer_threaded<alsa_seq_ump::observer_configuration>>(
std::move(conf), std::move(api));
}
template <>
inline std::unique_ptr<midi_in_api> make<
alsa_seq::midi_in_impl<libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(
libremidi::ump_input_configuration&& conf, libremidi::alsa_seq_ump::input_configuration&& api)
{
if (api.manual_poll)
return std::make_unique<alsa_seq::midi_in_alsa_manual<
libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(
std::move(conf), std::move(api));
else
return std::make_unique<alsa_seq::midi_in_alsa_threaded<
libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>>(
std::move(conf), std::move(api));
}
}
namespace libremidi::alsa_seq_ump
{
struct backend
{
using midi_in = alsa_seq::midi_in_impl<
libremidi::ump_input_configuration, alsa_seq_ump::input_configuration>;
using midi_out = alsa_seq_ump::midi_out_impl;
using midi_observer = alsa_seq::observer_impl<alsa_seq_ump::observer_configuration>;
using midi_in_configuration = alsa_seq_ump::input_configuration;
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 inline bool available() noexcept
{
static const libasound& snd = libasound::instance();
return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available;
}
};
}
@@ -0,0 +1,53 @@
#pragma once
#include <libremidi/backends/alsa_seq/config.hpp>
#if __has_include(<alsa/asoundlib.h>)
#include <alsa/asoundlib.h>
#endif
// Necessary for the versions of ALSA that did have UMP
// through snd_seq_event, before snd_seq_ump_event existed.
extern "C" {
typedef struct snd_seq_ump_event snd_seq_ump_event_t;
}
namespace libremidi::alsa_seq_ump
{
struct poll_parameters
{
snd_seq_addr_t addr{};
std::function<int(const snd_seq_ump_event_t&)> callback;
};
struct input_configuration
{
using poll_parameters_type = poll_parameters;
std::string client_name = "libremidi client";
snd_seq_t* context{};
std::function<bool(const poll_parameters&)> manual_poll;
std::function<bool(snd_seq_addr_t)> stop_poll;
static constexpr int midi_version = 2;
};
struct output_configuration
{
std::string client_name = "libremidi client";
snd_seq_t* context{};
static constexpr int midi_version = 2;
};
struct observer_configuration
{
std::string client_name = "libremidi client";
snd_seq_t* context{};
std::function<bool(const libremidi::alsa_seq::poll_parameters&)> manual_poll;
std::function<bool(snd_seq_addr_t)> stop_poll;
static constexpr int midi_version = 2;
};
}
@@ -0,0 +1,151 @@
#pragma once
#include <libremidi/backends/alsa_seq/helpers.hpp>
#include <libremidi/backends/alsa_seq_ump/config.hpp>
#include <libremidi/backends/alsa_seq_ump/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/ump_stream.hpp>
namespace libremidi::alsa_seq_ump
{
class midi_out_impl final
: public midi2::out_api
, private alsa_seq::alsa_data
, public error_handler
{
public:
struct
: libremidi::output_configuration
, alsa_seq_ump::output_configuration
{
} configuration;
midi_out_impl(
libremidi::output_configuration&& conf, alsa_seq_ump::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
assert(snd.seq.ump.available);
if (init_client(configuration) < 0)
{
error<driver_error>(
this->configuration,
"midi_in_alsa::initialize: error creating ALSA sequencer client "
"object.");
return;
}
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
{
error<driver_error>(
this->configuration,
"midi_out_alsa::initialize: error initializing MIDI event "
"parser.");
return;
}
snd.midi.event_init(this->coder);
}
~midi_out_impl() override
{
// Close a connection if it exists.
midi_out_impl::close_port();
// Cleanup.
if (this->vport >= 0)
snd.seq.delete_port(this->seq, this->vport);
if (this->coder)
snd.midi.event_free(this->coder);
if (!configuration.context)
snd.seq.close(this->seq);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
[[nodiscard]] int create_port(std::string_view portName)
{
return alsa_data::create_port(
*this, portName,
SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_UMP_ENDPOINT,
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
}
bool 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)
{
error<no_devices_found_error>(
this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!");
return false;
}
auto sink = get_port_info(p);
if (!sink)
return false;
if (int err = create_port(portName); err < 0)
{
error<driver_error>(configuration, "midi_out_alsa::create_port: ALSA error creating port.");
return false;
}
snd_seq_addr_t source{
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
if (int err = create_connection(*this, source, *sink, true); err < 0)
{
error<driver_error>(
configuration, "midi_out_alsa::create_port: ALSA error making port connection.");
return false;
}
return true;
}
bool open_virtual_port(std::string_view portName) override
{
if (int err = create_port(portName); err < 0)
return false;
return true;
}
void close_port() override { unsubscribe(); }
void set_client_name(std::string_view clientName) override
{
alsa_data::set_client_name(clientName);
}
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
void send_ump(const uint32_t* ump_stream, std::size_t count) override
{
snd_seq_ump_event_t ev;
memset(&ev, 0, sizeof(snd_seq_ump_event_t));
snd_seq_ev_set_ump(&ev);
snd_seq_ev_set_source(&ev, this->vport);
snd_seq_ev_set_subs(&ev);
snd_seq_ev_set_direct(&ev);
auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) {
std::memcpy(ev.ump, ump, bytes);
const int result = snd.seq.ump.event_output_direct(this->seq, &ev);
if (result < 0)
{
warning(
this->configuration,
"midi_out_alsa::send_message: error sending MIDI message to port.");
return libremidi::segmentation_error::other;
}
return libremidi::segmentation_error::no_error;
};
segment_ump_stream(ump_stream, count, write_func, []() {});
snd.seq.drain_output(this->seq);
}
private:
unsigned int bufferSize{32};
};
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <libremidi/backends/coremidi/midi_in.hpp>
#include <libremidi/backends/coremidi/midi_out.hpp>
#include <libremidi/backends/coremidi/observer.hpp>
namespace libremidi
{
struct core_backend
{
using midi_in = midi_in_core;
using midi_out = midi_out_core;
using midi_observer = observer_core;
using midi_in_configuration = coremidi_input_configuration;
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 constexpr inline bool available() noexcept { return true; }
};
}
@@ -0,0 +1,41 @@
#pragma once
#include <libremidi/config.hpp>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#if defined(__APPLE__)
#if __LP64__
typedef unsigned int UInt32;
#else
typedef unsigned long UInt32;
#endif
typedef UInt32 MIDIObjectRef;
typedef MIDIObjectRef MIDIClientRef;
#else
using MIDIClientRef = uint32_t;
#endif
namespace libremidi
{
struct coremidi_input_configuration
{
std::string client_name = "libremidi client";
std::optional<MIDIClientRef> context{};
};
struct coremidi_output_configuration
{
std::string client_name = "libremidi client";
std::optional<MIDIClientRef> context{};
};
struct coremidi_observer_configuration
{
std::string client_name = "libremidi client";
std::function<void(MIDIClientRef)> on_create_context{};
};
}
@@ -0,0 +1,324 @@
#pragma once
#include <libremidi/detail/memory.hpp>
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/input_configuration.hpp>
#include <CoreMIDI/CoreMIDI.h>
#include <CoreServices/CoreServices.h>
#include <cmath>
#include <bit>
#if TARGET_OS_IPHONE
#include <CoreAudio/CoreAudioTypes.h>
#include <mach/mach_time.h>
#define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME mach_absolute_time
#else
#include <CoreAudio/HostTime.h>
#define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime
#endif
namespace libremidi
{
using CFString_handle = unique_handle<const __CFString, CFRelease>;
using CFStringMutable_handle = unique_handle<__CFString, CFRelease>;
namespace
{
static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept
{
CFStringRef res;
MIDIObjectGetStringProperty(object, property, &res);
char name[256];
CFStringGetCString(res, name, sizeof(name), kCFStringEncodingUTF8);
CFRelease(res);
return name;
}
static inline int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept
{
SInt32 res;
MIDIObjectGetIntegerProperty(object, property, &res);
return res;
}
static inline CFString_handle toCFString(std::string_view str) noexcept
{
return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)};
}
#if TARGET_OS_IPHONE
inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)
{
static const struct mach_timebase_info timebase = [] {
struct mach_timebase_info theTimeBaseInfo;
mach_timebase_info(&theTimeBaseInfo);
return theTimeBaseInfo;
}();
const auto numer = timebase.numer;
const auto denom = timebase.denom;
__uint128_t res = hostTime;
if (numer != denom)
{
res *= numer;
res /= denom;
}
return static_cast<uint64_t>(res);
}
#endif
// 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))
{
CFStringAppend(result, endpoint_name.get());
}
// some MIDI devices have a leading space in endpoint name. trim
CFStringTrim(result, CFSTR(" "));
MIDIEntityRef entity = 0;
MIDIDeviceRef device = 0;
MIDIEndpointGetEntity(endpoint, &entity);
if (entity == 0)
goto finish;
if (CFStringGetLength(result) == 0)
{
// endpoint name has zero length -- try the entity
if (auto entity_name = getProp(entity))
{
CFStringAppend(result, entity_name.get());
}
}
// now consider the device's name
MIDIEntityGetDevice(entity, &device);
if (device == 0)
goto finish;
if (auto dev_name = getProp(device))
{
const auto dev_strlen = CFStringGetLength(dev_name.get());
// if an external device has only one entity, throw away
// the endpoint name and just use the device name
if (CFStringGetLength(result) == 0
|| (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2))
{
CFStringAppend(result, dev_name.get());
goto finish;
}
// does the entity name already start with the device name?
// (some drivers do this though they shouldn't)
// if so, do not prepend
if (CFStringCompareWithOptions(result, dev_name.get(), CFRangeMake(0, dev_strlen), 0)
!= kCFCompareEqualTo)
{
// prepend the device name to the entity name
if (CFStringGetLength(result) > 0)
CFStringInsert(result, 0, CFSTR(" "));
CFStringInsert(result, 0, dev_name.get());
}
}
finish:
if (CFStringGetLength(result) == 0)
return CFSTR("No name");
else
return result;
}
// This function was submitted by Douglas Casey Tucker and apparently
// derived largely from PortMidi.
inline CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)
{
CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);
CFStringRef str{};
// Does the endpoint have connections?
CFDataRef connections = nullptr;
std::size_t nConnected = 0;
bool anyStrings = false;
MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);
if (connections != nullptr)
{
// It has connections, follow them
// Concatenate the names of all connected devices
nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID);
if (nConnected)
{
const SInt32* pid = (const SInt32*)(CFDataGetBytePtr(connections));
for (std::size_t i = 0; i < nConnected; ++i, ++pid)
{
MIDIUniqueID id = CFSwapInt32BigToHost(*pid);
MIDIObjectRef connObject;
MIDIObjectType connObjectType;
auto err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);
if (err == noErr)
{
if (connObjectType == kMIDIObjectType_ExternalSource
|| connObjectType == kMIDIObjectType_ExternalDestination)
{
// Connected to an external device's endpoint (10.3 and later).
str = EndpointName((MIDIEndpointRef)(connObject), true);
}
else
{
// Connected to an external device (10.2) (or something else,
// catch-
str = nullptr;
MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);
}
if (str != nullptr)
{
if (anyStrings)
CFStringAppend(result, CFSTR(", "));
else
anyStrings = true;
CFStringAppend(result, str);
CFRelease(str);
}
}
}
}
CFRelease(connections);
}
if (anyStrings)
return result;
CFRelease(result);
// Here, either the endpoint had no connections, or we failed to obtain names
return EndpointName(endpoint, false);
}
inline MIDIObjectRef
locate_object(auto& self, const port_information& info, MIDIObjectType requested_type)
{
auto uid = std::bit_cast<std::int32_t>((uint32_t)info.port);
MIDIObjectRef object{};
MIDIObjectType type{};
auto ret = MIDIObjectFindByUniqueID(uid, &object, &type);
if (ret != noErr)
{
self.template error<invalid_parameter_error>(
self.configuration, "coremidi::locate_object: cannot find port: " + info.port_name);
return 0;
}
if (type != requested_type || object == 0)
{
self.template error<invalid_parameter_error>(
self.configuration, "coremidi::locate_object: invalid object: " + info.port_name + " : "
+ std::to_string(object));
return 0;
}
return object;
}
}
// A structure to hold variables related to the CoreMIDI API
// implementation.
struct coremidi_data
{
MIDIClientRef client{};
MIDIPortRef port{};
MIDIEndpointRef endpoint = 0;
[[nodiscard]] OSStatus init_client(auto& configuration)
{
if (configuration.context)
{
client = *configuration.context;
return noErr;
}
else
{
// Set up our client.
return MIDIClientCreate(
toCFString(configuration.client_name).get(), nullptr, nullptr, &client);
}
}
static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept
{
if (tp == 0)
{ // this happens when receiving asynchronous sysex messages
return clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
}
else
{
return AudioConvertHostTimeToNanos(tp);
}
}
static void set_timestamp(auto& self, MIDITimeStamp packet, timestamp& msg) noexcept
{
// packet.timeStamp is in mach_absolute_time units
// We want a timestamp in nanoseconds
switch (self.configuration.timestamps)
{
case timestamp_mode::NoTimestamp:
msg = 0;
return;
case timestamp_mode::Relative: {
if (self.firstMessage)
{
self.firstMessage = false;
msg = 0;
return;
}
else
{
if constexpr (requires { self.continueSysex; })
{
if (self.continueSysex)
return;
}
auto time = time_in_nanos(packet);
time -= self.last_time;
msg = time;
}
break;
}
case timestamp_mode::Absolute:
case timestamp_mode::SystemMonotonic:
if constexpr (requires { self.continueSysex; })
{
if (self.continueSysex)
return;
}
msg = time_in_nanos(packet);
break;
case timestamp_mode::Custom: {
if constexpr (requires { self.continueSysex; })
{
if (self.continueSysex)
return;
}
msg = self.configuration.get_timestamp(time_in_nanos(packet));
break;
}
}
}
};
}
@@ -0,0 +1,178 @@
#pragma once
#include <libremidi/backends/coremidi/config.hpp>
#include <libremidi/backends/coremidi/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
{
class midi_in_core final
: public midi1::in_api
, private coremidi_data
, public error_handler
{
public:
struct
: input_configuration
, coremidi_input_configuration
{
} configuration;
midi_in_core(input_configuration&& conf, coremidi_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (auto result = init_client(configuration); result != noErr)
{
error<driver_error>(
this->configuration,
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
return;
}
}
~midi_in_core() override
{
// Close a connection if it exists.
midi_in_core::close_port();
if (this->endpoint)
MIDIEndpointDispose(this->endpoint);
close_client();
}
void close_client()
{
if (!configuration.context)
MIDIClientDispose(this->client);
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_core: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_core: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
bool open_port(const input_port& info, std::string_view portName) override
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
auto source = locate_object(*this, info, kMIDIObjectType_Source);
if (source == 0)
return false;
// Create our local sink
MIDIPortRef port;
OSStatus result = MIDIInputPortCreate(
this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &port);
if (result != noErr)
{
close_client();
error<driver_error>(
this->configuration, "midi_in_core::open_port: error creating macOS MIDI input port: "
+ std::to_string(result));
return false;
}
// Make the connection.
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
{
MIDIPortDispose(port);
close_client();
error<driver_error>(
this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port.");
return false;
}
// Save our api-specific port information.
this->port = port;
return true;
}
bool open_virtual_port(std::string_view portName) override
{
// Create a virtual MIDI input destination.
MIDIEndpointRef endpoint;
OSStatus result = MIDIDestinationCreate(
this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &endpoint);
if (result != noErr)
{
error<driver_error>(
this->configuration,
"midi_in_core::open_virtual_port: error creating virtual macOS MIDI "
"destination.");
return false;
}
// Save our api-specific connection information.
this->endpoint = endpoint;
return true;
}
void close_port() override
{
if (this->endpoint)
{
MIDIEndpointDispose(this->endpoint);
this->endpoint = 0;
}
if (this->port)
{
MIDIPortDispose(this->port);
this->port = 0;
}
}
timestamp absolute_timestamp() const noexcept override
{
return coremidi_data::time_in_nanos(LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME());
}
static void midiInputCallback(const MIDIPacketList* list, void* procRef, void* /*srcRef*/)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = false,
.has_samples = false,
};
auto& self = *(midi_in_core*)procRef;
const MIDIPacket* packet = &list->packet[0];
for (unsigned int i = 0; i < list->numPackets; ++i)
{
// My interpretation of the CoreMIDI documentation: all message
// types, except sysex, are complete within a packet and there may
// be several of them in a single packet. Sysex messages can be
// broken across multiple packets and PacketLists but are bundled
// alone within each packet (these packets do not contain other
// message types). If sysex messages are split across multiple
// MIDIPacketLists, they must be handled by multiple calls to this
// function.
if (packet->length == 0)
{
packet = MIDIPacketNext(packet);
continue;
}
auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };
self.m_processing.on_bytes_multi(
{packet->data, packet->data + packet->length},
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
packet = MIDIPacketNext(packet);
}
}
midi1::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,203 @@
#pragma once
#include <libremidi/backends/coremidi/config.hpp>
#include <libremidi/backends/coremidi/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
{
class midi_out_core final
: public midi1::out_api
, private coremidi_data
, public error_handler
{
public:
struct
: output_configuration
, coremidi_output_configuration
{
} configuration;
midi_out_core(output_configuration&& conf, coremidi_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (auto result = init_client(configuration); result != noErr)
{
error<driver_error>(
this->configuration,
"midi_out_core: error creating MIDI client object: " + std::to_string(result));
return;
}
}
~midi_out_core()
{
midi_out_core::close_port();
if (this->endpoint)
MIDIEndpointDispose(this->endpoint);
close_client();
}
void close_client()
{
if (!configuration.context)
MIDIClientDispose(this->client);
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_core: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_core: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
bool open_port(const output_port& info, std::string_view portName) override
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
// Find where we want to send
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
if (destination == 0)
return false;
// Create our local source
MIDIPortRef port;
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
if (result != noErr)
{
close_client();
error<driver_error>(
this->configuration, "midi_out_core::open_port: error creating macOS MIDI output port.");
return false;
}
// Save our api-specific connection information.
this->port = port;
this->destinationId = destination;
return true;
}
bool open_virtual_port(std::string_view portName) override
{
if (this->endpoint)
{
warning(
configuration,
"midi_out_core::open_virtual_port: a virtual output port already "
"exists!");
return false;
}
// Create a virtual MIDI output source.
MIDIEndpointRef endpoint;
OSStatus result = MIDISourceCreate(this->client, toCFString(portName).get(), &endpoint);
if (result != noErr)
{
error<driver_error>(
this->configuration,
"midi_out_core::initialize: error creating macOS virtual MIDI source.");
return false;
}
// Save our api-specific connection information.
this->endpoint = endpoint;
return true;
}
void close_port() override
{
if (this->endpoint)
{
MIDIEndpointDispose(this->endpoint);
this->endpoint = 0;
}
if (this->port)
{
MIDIPortDispose(this->port);
this->port = 0;
}
}
void send_message(const unsigned char* message, size_t size) override
{
unsigned int nBytes = static_cast<unsigned int>(size);
if (nBytes == 0)
{
warning(configuration, "midi_out_core::send_message: no data in message argument!");
return;
}
if (message[0] != 0xF0 && nBytes > 3)
{
warning(
configuration,
"midi_out_core::send_message: message format problem ... not sysex but "
"> 3 bytes?");
return;
}
const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes;
Byte buffer[bufsize + 16]; // pad for other struct members
ByteCount listSize = sizeof(buffer);
MIDIPacketList* packetList = (MIDIPacketList*)buffer;
ByteCount remainingBytes = nBytes;
while (remainingBytes)
{
MIDIPacket* packet = MIDIPacketListInit(packetList);
// A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer,
// break it up into chunks of 64K or less and send out as a MIDIPacketList with only one
// MIDIPacket. Here, we reuse the memory allocated above on the stack for all.
ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes;
const Byte* dataStartPtr = (const Byte*)&message[nBytes - remainingBytes];
packet = MIDIPacketListAdd(
packetList, listSize, packet, timestamp, bytesForPacket, dataStartPtr);
remainingBytes -= bytesForPacket;
if (!packet)
{
error<driver_error>(
this->configuration, "midi_out_core::send_message: could not allocate packet list");
return;
}
// Send to any destinations that may have connected to us.
if (this->endpoint)
{
auto result = MIDIReceived(this->endpoint, packetList);
if (result != noErr)
{
warning(
this->configuration,
"midi_out_core::send_message: error sending MIDI to virtual "
"destinations.");
}
}
// And send to an explicit destination port if we're connected.
if (this->destinationId != 0)
{
auto result = MIDISend(this->port, this->destinationId, packetList);
if (result != noErr)
{
warning(
this->configuration,
"midi_out_core::send_message: error sending MIDI message to port.");
}
}
}
}
MIDIEndpointRef destinationId{};
};
}
@@ -0,0 +1,175 @@
#pragma once
#include <libremidi/backends/coremidi/config.hpp>
#include <libremidi/backends/coremidi/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <CoreMIDI/CoreMIDI.h>
#include <bit>
namespace libremidi
{
class observer_core
: public observer_api
, public error_handler
{
public:
struct
: observer_configuration
, coremidi_observer_configuration
{
} configuration;
explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (configuration.client_name.empty())
configuration.client_name = "libremidi observer";
if (!configuration.has_callbacks())
return;
auto result = MIDIClientCreate(
toCFString(configuration.client_name).get(),
+[](const MIDINotification* message, void* ctx) {
((observer_core*)ctx)->notify(message);
},
this, &client);
if (result != noErr)
{
error<driver_error>(
this->configuration,
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
return;
}
if (configuration.on_create_context)
configuration.on_create_context(client);
if (configuration.notify_in_constructor)
{
if (this->configuration.input_added)
for (auto& p : get_input_ports())
this->configuration.input_added(p);
if (this->configuration.output_added)
for (auto& p : get_output_ports())
this->configuration.output_added(p);
}
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
template <bool Input>
auto to_port_info(MIDIObjectRef obj) const noexcept
-> std::optional<std::conditional_t<Input, input_port, output_port>>
{
MIDIEntityRef e{};
MIDIEndpointGetEntity(obj, &e);
bool physical = bool(e);
bool ok = false;
if (physical && this->configuration.track_hardware)
ok = true;
else if ((!physical) && this->configuration.track_virtual)
ok = true;
if (!ok)
return {};
return std::conditional_t<Input, input_port, output_port>{
{.client = (std::uintptr_t)this->client,
.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)}};
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
std::vector<libremidi::input_port> ret;
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
for (ItemCount i = 0; i < MIDIGetNumberOfSources(); i++)
{
if (auto p = to_port_info<true>(MIDIGetSource(i)))
ret.push_back(std::move(*p));
}
return ret;
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
std::vector<libremidi::output_port> ret;
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
for (ItemCount i = 0; i < MIDIGetNumberOfDestinations(); i++)
{
if (auto p = to_port_info<false>(MIDIGetDestination(i)))
ret.push_back(std::move(*p));
}
return ret;
}
void notify(const MIDINotification* message)
{
switch (message->messageID)
{
case kMIDIMsgObjectAdded: {
auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
switch (obj->childType)
{
case kMIDIObjectType_Source:
if (auto& cb = configuration.input_added)
if (auto p = to_port_info<true>(obj->child))
cb(std::move(*p));
break;
case kMIDIObjectType_Destination:
if (auto& cb = configuration.output_added)
if (auto p = to_port_info<false>(obj->child))
cb(std::move(*p));
break;
default:
break;
}
break;
}
case kMIDIMsgObjectRemoved: {
auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
switch (obj->childType)
{
case kMIDIObjectType_Source:
if (auto& cb = configuration.input_removed)
if (auto p = to_port_info<true>(obj->child))
cb(std::move(*p));
break;
case kMIDIObjectType_Destination:
if (auto& cb = configuration.output_removed)
if (auto p = to_port_info<false>(obj->child))
cb(std::move(*p));
break;
default:
break;
}
break;
}
default:
break;
}
}
~observer_core() { MIDIClientDispose(this->client); }
private:
MIDIClientRef client{};
};
}
@@ -0,0 +1,22 @@
#pragma once
#include <libremidi/backends/coremidi_ump/midi_in.hpp>
#include <libremidi/backends/coremidi_ump/midi_out.hpp>
#include <libremidi/backends/coremidi_ump/observer.hpp>
namespace libremidi::coremidi_ump
{
struct backend
{
using midi_in = midi_in_impl;
using midi_out = midi_out_impl;
using midi_observer = observer_impl;
using midi_in_configuration = coremidi_input_configuration;
using midi_out_configuration = coremidi_output_configuration;
using midi_observer_configuration = coremidi_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 constexpr inline bool available() noexcept { return true; /* todo? */ }
};
}
@@ -0,0 +1,9 @@
#pragma once
#include <libremidi/backends/coremidi/config.hpp>
namespace libremidi::coremidi_ump
{
using input_configuration = coremidi_input_configuration;
using output_configuration = coremidi_output_configuration;
using observer_configuration = coremidi_observer_configuration;
}
@@ -0,0 +1,2 @@
#pragma once
#include <libremidi/backends/coremidi/helpers.hpp>
@@ -0,0 +1,183 @@
#pragma once
#include <libremidi/backends/coremidi_ump/config.hpp>
#include <libremidi/backends/coremidi_ump/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
namespace libremidi::coremidi_ump
{
class midi_in_impl final
: public midi2::in_api
, private coremidi_data
, public error_handler
{
public:
struct
: ump_input_configuration
, coremidi_ump::input_configuration
{
} configuration;
midi_in_impl(ump_input_configuration&& conf, coremidi_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (auto result = init_client(configuration); result != noErr)
{
error<driver_error>(
this->configuration,
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
return;
}
}
~midi_in_impl() override
{
// Close a connection if it exists.
close_port();
if (this->endpoint)
MIDIEndpointDispose(this->endpoint);
close_client();
}
void close_client()
{
if (!configuration.context)
MIDIClientDispose(this->client);
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_core: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_core: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
bool open_port(const input_port& info, std::string_view portName) override
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
auto source = locate_object(*this, info, kMIDIObjectType_Source);
if (source == 0)
return false;
// Create our local sink
MIDIPortRef port;
OSStatus result = MIDIInputPortCreateWithProtocol(
this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &port,
^(const MIDIEventList* evtlist, void* __nullable srcConnRefCon) {
this->midiInputCallback(evtlist, srcConnRefCon);
});
if (result != noErr)
{
close_client();
error<driver_error>(
this->configuration, "midi_in_core::open_port: error creating macOS MIDI input port: "
+ std::to_string(result));
return false;
}
// Make the connection.
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
{
MIDIPortDispose(port);
close_client();
error<driver_error>(
this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port.");
return false;
}
// Save our api-specific port information.
this->port = port;
return true;
}
bool open_virtual_port(std::string_view portName) override
{
// Create a virtual MIDI input destination.
MIDIEndpointRef endpoint;
OSStatus result = MIDIDestinationCreateWithProtocol(
this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &endpoint,
^(const MIDIEventList* evtlist, void* __nullable srcConnRefCon) {
this->midiInputCallback(evtlist, srcConnRefCon);
});
if (result != noErr)
{
error<driver_error>(
this->configuration,
"midi_in_core::open_virtual_port: error creating virtual macOS MIDI "
"destination.");
return false;
}
// Save our api-specific connection information.
this->endpoint = endpoint;
return true;
}
void close_port() override
{
if (this->endpoint)
{
MIDIEndpointDispose(this->endpoint);
this->endpoint = 0;
}
if (this->port)
{
MIDIPortDispose(this->port);
this->port = 0;
}
}
void set_timestamp(
[[maybe_unused]] const MIDIEventPacket& packet,
[[maybe_unused]] libremidi::ump& msg) noexcept
{
}
timestamp absolute_timestamp() const noexcept override
{
return coremidi_data::time_in_nanos(LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME());
}
void midiInputCallback(const MIDIEventList* list, void* /*srcRef*/)
{
unsigned short nBytes{};
const MIDIEventPacket* packet = &list->packet[0];
for (unsigned int i = 0; i < list->numPackets; ++i)
{
nBytes = packet->wordCount;
if (nBytes == 0)
{
packet = MIDIEventPacketNext(packet);
continue;
}
libremidi::ump msg;
coremidi_data::set_timestamp(*this, packet->timeStamp, msg.timestamp);
if (packet->wordCount <= 4)
{
std::copy_n(packet->words, packet->wordCount, msg.data);
configuration.on_message(std::move(msg));
}
last_time = time_in_nanos(packet->timeStamp);
packet = MIDIEventPacketNext(packet);
}
}
unsigned long long last_time{};
};
}
@@ -0,0 +1,185 @@
#pragma once
#include <libremidi/backends/coremidi_ump/config.hpp>
#include <libremidi/backends/coremidi_ump/helpers.hpp>
#include <libremidi/cmidi2.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/ump_stream.hpp>
namespace libremidi::coremidi_ump
{
class midi_out_impl final
: public midi2::out_api
, private coremidi_data
, public error_handler
{
public:
struct
: libremidi::output_configuration
, coremidi_ump::output_configuration
{
} configuration;
midi_out_impl(
libremidi::output_configuration&& conf, coremidi_ump::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (auto result = init_client(configuration); result != noErr)
{
error<driver_error>(
this->configuration,
"midi_out_impl: error creating MIDI client object: " + std::to_string(result));
return;
}
}
~midi_out_impl()
{
midi_out_impl::close_port();
if (this->endpoint)
MIDIEndpointDispose(this->endpoint);
close_client();
}
void close_client()
{
if (!configuration.context)
MIDIClientDispose(this->client);
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_impl: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_impl: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
bool open_port(const output_port& info, std::string_view portName) override
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
// Find where we want to send
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
if (destination == 0)
return false;
// Create our local source
MIDIPortRef port;
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
if (result != noErr)
{
close_client();
error<driver_error>(
this->configuration, "midi_out_impl::open_port: error creating macOS MIDI output port.");
return false;
}
// Save our api-specific connection information.
this->port = port;
this->destinationId = destination;
return true;
}
bool open_virtual_port(std::string_view portName) override
{
if (this->endpoint)
{
warning(
configuration,
"midi_out_impl::open_virtual_port: a virtual output port already "
"exists!");
return false;
}
// Create a virtual MIDI output source.
OSStatus result = MIDISourceCreateWithProtocol(
this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &this->endpoint);
if (result != noErr)
{
this->endpoint = 0;
error<driver_error>(
this->configuration,
"midi_out_impl::initialize: error creating macOS virtual MIDI source.");
return false;
}
return true;
}
void close_port() override
{
if (this->endpoint)
{
MIDIEndpointDispose(this->endpoint);
this->endpoint = 0;
}
if (this->port)
{
MIDIPortDispose(this->port);
this->port = 0;
}
}
void send_ump(const uint32_t* ump_stream, std::size_t count) override
{
MIDIEventList* eventList = reinterpret_cast<MIDIEventList*>(m_eventListBuffer);
MIDIEventPacket* packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);
const MIDITimeStamp ts = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
auto write_fun = [ts, &packet, &eventList](const uint32_t* ump, int bytes) {
packet = MIDIEventListAdd(eventList, event_list_max_size, packet, ts, bytes / 4, ump);
if (packet)
return segmentation_error::no_error;
else
return segmentation_error::need_space;
};
auto realloc_fun = [this, &packet, &eventList] {
push_event_list(eventList);
packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);
};
segment_ump_stream(ump_stream, count, write_fun, realloc_fun);
push_event_list(eventList);
}
void push_event_list(MIDIEventList* eventList)
{
if (this->endpoint)
{
auto result = MIDIReceivedEventList(this->endpoint, eventList);
if (result != noErr)
{
warning(
this->configuration,
"midi_out_core::send_message: error sending MIDI to virtual "
"destinations.");
}
}
if (this->destinationId != 0)
{
auto result = MIDISendEventList(this->port, this->destinationId, eventList);
if (result != noErr)
{
warning(
this->configuration,
"midi_out_core::send_message: error sending MIDI message to port.");
}
}
}
MIDIEndpointRef destinationId{};
static constexpr int event_list_max_size = 65535;
unsigned char m_eventListBuffer[sizeof(MIDIEventList) + event_list_max_size];
};
}
@@ -0,0 +1,13 @@
#pragma once
#include <libremidi/backends/coremidi/observer.hpp>
namespace libremidi::coremidi_ump
{
class observer_impl final : public libremidi::observer_core
{
using observer_core::observer_core;
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
};
}
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <libremidi/configurations.hpp>
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/observer.hpp>
namespace libremidi
{
class observer_dummy : public observer_api
{
public:
explicit observer_dummy(const auto& /*configuration*/, const auto&) { }
~observer_dummy() { }
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override { return {}; }
std::vector<libremidi::output_port> get_output_ports() const noexcept override { return {}; }
};
class midi_in_dummy final
: public midi1::in_api
, public error_handler
{
public:
explicit midi_in_dummy(const auto& configuration, const auto&)
{
warning(configuration, "midi_in_dummy: This class provides no functionality.");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
bool open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override
{
return true;
}
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
void close_port() override { }
void set_client_name(std::string_view /*clientName*/) override { }
void set_port_name(std::string_view /*portName*/) override { }
timestamp absolute_timestamp() const noexcept override { return 0; }
};
class midi_out_dummy final
: public midi1::out_api
, public error_handler
{
public:
explicit midi_out_dummy(const auto& configuration, const auto&)
{
warning(configuration, "midi_out_dummy: This class provides no functionality.");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
bool open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override
{
return true;
}
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
void close_port() override { }
void set_client_name(std::string_view /*clientName*/) override { }
void set_port_name(std::string_view /*portName*/) override { }
void send_message(const unsigned char* /*message*/, size_t /*size*/) override { }
};
struct dummy_backend
{
using midi_in = midi_in_dummy;
using midi_out = midi_out_dummy;
using midi_observer = observer_dummy;
using midi_in_configuration = dummy_configuration;
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 constexpr inline bool available() noexcept { return true; }
};
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/config.hpp>
#include <libremidi/backends/emscripten/midi_in.hpp>
#include <libremidi/backends/emscripten/midi_out.hpp>
#include <libremidi/backends/emscripten/observer.hpp>
namespace libremidi
{
struct emscripten_backend
{
using midi_in = midi_in_emscripten;
using midi_out = midi_out_emscripten;
using midi_observer = observer_emscripten;
using midi_in_configuration = emscripten_input_configuration;
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 constexpr inline bool available() noexcept { return true; }
};
}
#endif
@@ -0,0 +1,17 @@
#pragma once
#include <libremidi/config.hpp>
namespace libremidi
{
struct emscripten_input_configuration
{
};
struct emscripten_output_configuration
{
};
struct emscripten_observer_configuration
{
};
}
@@ -0,0 +1,17 @@
#pragma once
#include <libremidi/config.hpp>
#include <string>
namespace libremidi
{
namespace webmidi_helpers
{
struct device_information
{
std::string id;
std::string name;
bool connected{};
};
}
}
@@ -0,0 +1,22 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
extern "C" {
LIBREMIDI_EXPORT
LIBREMIDI_INLINE
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_poll()
{
libremidi::webmidi_helpers::midi_access_emscripten::instance().devices_poll();
}
LIBREMIDI_EXPORT
LIBREMIDI_INLINE
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_input(int port, double timestamp, int len, char* bytes)
{
libremidi::webmidi_helpers::midi_access_emscripten::instance().devices_input(
port, timestamp, len, bytes);
}
}
#endif
@@ -0,0 +1,303 @@
#pragma once
#include <libremidi/backends/emscripten/midi_in.hpp>
#include <libremidi/backends/emscripten/midi_out.hpp>
#include <libremidi/backends/emscripten/observer.hpp>
#include <libremidi/libremidi.hpp>
#include <emscripten.h>
#include <map>
extern "C" {
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_poll();
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_input(int port, double timestamp, int len, char* bytes);
}
namespace libremidi
{
namespace webmidi_helpers
{
class midi_access_emscripten
{
public:
static midi_access_emscripten& instance() noexcept
{
static midi_access_emscripten inst;
return inst;
}
bool available() const noexcept
{
return EM_ASM_INT(return typeof globalThis.__libreMidi_access !== undefined;);
}
int input_count() const noexcept
{
return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;);
}
int output_count() const noexcept
{
return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;);
}
void load_current_infos() noexcept
{
#define get_js_string(variable_to_read, ...) \
(char*)EM_ASM_INT( \
{ \
var jsstr = variable_to_read; \
var bytes = lengthBytesUTF8(jsstr) + 1; \
var str = _malloc(bytes); \
stringToUTF8(jsstr, str, bytes); \
return str; \
}, \
__VA_ARGS__);
EM_ASM_INT({
let inputs = [];
let outputs = [];
for (let inpt of globalThis.__libreMidi_access.inputs.values())
{
inputs.push(inpt);
}
for (let outpt of globalThis.__libreMidi_access.outputs.values())
{
outputs.push(outpt);
}
globalThis.__libreMidi_currentInputs = inputs;
globalThis.__libreMidi_currentOutputs = outputs;
});
const int inputs = input_count();
const int outputs = output_count();
m_current_inputs.resize(inputs);
m_current_outputs.resize(outputs);
for (int i = 0; i < inputs; i++)
{
int device_index = -1;
char* midi_id = get_js_string(globalThis.__libreMidi_currentInputs[$0].id, i);
auto it = m_input_indices.find(midi_id); // TODO transparent comparator, string_view...
if (it == m_input_indices.end())
{
device_index = m_input_indices.size();
m_input_indices[midi_id] = device_index;
}
else
{
device_index = it->second;
}
char* midi_name = get_js_string(globalThis.__libreMidi_currentInputs[$0].name, i);
const bool connected
= EM_ASM_INT(return globalThis.__libreMidi_currentInputs[$0].state === "connected", i);
m_current_inputs[device_index].id = midi_id;
m_current_inputs[device_index].name = midi_name;
m_current_inputs[device_index].connected = connected;
free(midi_id);
free(midi_name);
}
for (int i = 0; i < outputs; i++)
{
int device_index = -1;
char* midi_id = get_js_string(globalThis.__libreMidi_currentOutputs[$0].id, i);
auto it = m_output_indices.find(midi_id); // TODO transparent comparator, string_view...
if (it == m_output_indices.end())
{
device_index = m_output_indices.size();
m_output_indices[midi_id] = device_index;
}
else
{
device_index = it->second;
}
char* midi_name = get_js_string(globalThis.__libreMidi_currentOutputs[$0].name, i);
const bool connected
= EM_ASM_INT(return globalThis.__libreMidi_currentOutputs[$0].state === "connected", i);
m_current_outputs[device_index].id = midi_id;
m_current_outputs[device_index].name = midi_name;
m_current_outputs[device_index].connected = connected;
free(midi_id);
free(midi_name);
}
#undef get_js_string
}
void register_observer(observer_emscripten& obs)
{
m_observers.push_back(&obs);
if (m_observers.size() == 1)
{
start_observing();
}
}
void unregister_observer(observer_emscripten& obs)
{
if (m_observers.size() == 1)
{
stop_observing();
}
auto it = std::find(m_observers.begin(), m_observers.end(), &obs);
if (it != m_observers.end())
{
m_observers.erase(it);
}
}
void devices_poll()
{
load_current_infos();
for (auto& obs : m_observers)
{
obs->update(m_current_inputs, m_current_outputs);
}
}
void open_input(int port_index, midi_in_emscripten& input)
{
auto& vec = m_opened_inputs[port_index];
vec.push_back(&input);
if (vec.size() != 1)
return;
start_stream(port_index);
}
void close_input(int port_index, midi_in_emscripten& input)
{
auto& vec = m_opened_inputs[port_index];
auto it = std::find(vec.begin(), vec.end(), &input);
if (it != vec.end())
{
vec.erase(it);
}
if (vec.empty())
{
stop_stream(port_index);
}
}
void devices_input(int port, double timestamp, int len, char* data)
{
unsigned char* bytes = reinterpret_cast<unsigned char*>(data);
for (auto input : m_opened_inputs[port])
{
input->on_input(timestamp, bytes, bytes + len);
}
}
void send_message(int port_index, const char* bytes, int len)
{
const auto& id = m_current_outputs[port_index].id;
EM_ASM(
{
let data = HEAPU8.subarray($0, $0 + $1);
const id = UTF8ToString($2);
let output = globalThis.__libreMidi_access.outputs.get(id);
let bytes = HEAPU8.subarray($0, $0 + $1);
output.send(Array.from(bytes));
},
bytes, len, id.c_str());
}
const std::vector<device_information>& inputs() const noexcept { return m_current_inputs; }
const std::vector<device_information>& outputs() const noexcept { return m_current_outputs; }
private:
midi_access_emscripten() noexcept
{
EM_ASM(
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess().then(
(midiAccess) => { globalThis.__libreMidi_access = midiAccess; },
() => console.log('MIDI support rejected, MIDI will not be available;'));
} else { console.log('WebMIDI is not supported in this browser.'); });
}
~midi_access_emscripten() { stop_observing(); }
void start_observing()
{
EM_ASM(
let id = setInterval(Module._libremidi_devices_poll, 100);
globalThis.__libreMidi_timer = id;);
}
void stop_observing()
{
EM_ASM(clearInterval(globalThis.__libreMidi_timer); globalThis.__libreMidi_timer = undefined;);
}
void start_stream(int port_index)
{
// Isn't life great...
// https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays
const auto& id = m_current_inputs[port_index].id;
EM_ASM(
const port_index = $0;
const id = UTF8ToString($1);
let input = globalThis.__libreMidi_access.inputs.get(id);
function _arrayToHeap(typedArray){
const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;
const ptr = Module._malloc(numBytes);
const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes);
heapBytes.set(new Uint8Array(typedArray.buffer));
return heapBytes;
}
function _freeArray(heapBytes){
Module._free(heapBytes.byteOffset);
}
input.onmidimessage = (message) => {
let bytes = message.data;
var heapBytes = _arrayToHeap(bytes);
Module._libremidi_devices_input(port_index, message.timeStamp, bytes.length, heapBytes.byteOffset);
_freeArray(heapBytes);
};
, port_index
, id.c_str()
);
}
void stop_stream(int port_index)
{
const auto& id = m_current_inputs[port_index].id;
EM_ASM(const id = UTF8ToString($1);
let input = globalThis.__libreMidi_access.inputs.get(id);
input.onmidimessage = undefined;, id.c_str());
}
std::vector<observer_emscripten*> m_observers;
std::vector<device_information> m_current_inputs;
std::vector<device_information> m_current_outputs;
std::map<int, std::vector<midi_in_emscripten*>> m_opened_inputs;
std::map<std::string, int> m_input_indices;
std::map<std::string, int> m_output_indices;
};
}
}
@@ -0,0 +1,91 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <chrono>
namespace libremidi
{
LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten(
input_configuration&& conf, emscripten_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
LIBREMIDI_INLINE midi_in_emscripten::~midi_in_emscripten()
{
// Close a connection if it exists.
midi_in_emscripten::close_port();
}
LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noexcept
{
return libremidi::API::WEBMIDI;
}
LIBREMIDI_INLINE bool midi_in_emscripten::open_port(int portNumber, std::string_view)
{
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
if (portNumber < 0 || portNumber >= midi.input_count())
{
error<no_devices_found_error>(
this->configuration, "midi_in_emscripten::open_port: no MIDI output sources found.");
return false;
}
midi.open_input(portNumber, *this);
portNumber_ = portNumber;
return true;
}
LIBREMIDI_INLINE bool
midi_in_emscripten::open_port(const libremidi::input_port& p, std::string_view nm)
{
return open_port(p.port, nm);
}
LIBREMIDI_INLINE bool midi_in_emscripten::open_virtual_port(std::string_view)
{
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
return false;
}
LIBREMIDI_INLINE void midi_in_emscripten::close_port()
{
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
midi.close_input(portNumber_, *this);
}
LIBREMIDI_INLINE void midi_in_emscripten::set_client_name(std::string_view)
{
warning(configuration, "midi_in_emscripten::set_client_name: unsupported.");
}
LIBREMIDI_INLINE void midi_in_emscripten::set_port_name(std::string_view)
{
warning(configuration, "midi_in_emscripten::set_port_name: unsupported.");
}
LIBREMIDI_INLINE int64_t midi_in_emscripten::absolute_timestamp() const noexcept
{
return system_ns();
}
LIBREMIDI_INLINE void
midi_in_emscripten::on_input(double ts, unsigned char* begin, unsigned char* end)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = true,
.has_samples = false,
};
const auto to_ns = [=] { return 1e6 * ts; };
m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
}
#endif
@@ -0,0 +1,41 @@
#pragma once
#include <libremidi/backends/emscripten/config.hpp>
#include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
{
class midi_in_emscripten final
: public midi1::in_api
, public error_handler
{
public:
struct
: input_configuration
, emscripten_input_configuration
{
} configuration;
midi_in_emscripten(input_configuration&& conf, emscripten_input_configuration&& apiconf);
~midi_in_emscripten() override;
libremidi::API get_current_api() const noexcept override;
bool open_port(int portNumber, std::string_view);
bool open_port(const input_port& p, std::string_view) override;
bool open_virtual_port(std::string_view) override;
void close_port() override;
void set_client_name(std::string_view clientName) override;
void set_port_name(std::string_view portName) override;
timestamp absolute_timestamp() const noexcept override;
void on_input(double ts, unsigned char* begin, unsigned char* end);
private:
int portNumber_{};
midi1::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,74 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/midi_out.hpp>
namespace libremidi
{
LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten(
output_configuration&& conf, emscripten_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
LIBREMIDI_INLINE midi_out_emscripten::~midi_out_emscripten()
{
// Close a connection if it exists.
midi_out_emscripten::close_port();
}
LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noexcept
{
return libremidi::API::WEBMIDI;
}
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(unsigned int portNumber, std::string_view)
{
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
if (portNumber >= midi.output_count())
{
error<no_devices_found_error>(
this->configuration, "midi_out_emscripten::open_port: no MIDI output sources found.");
return false;
}
portNumber_ = portNumber;
return true;
}
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
{
return open_port(p.port, nm);
}
LIBREMIDI_INLINE void midi_out_emscripten::close_port() { }
LIBREMIDI_INLINE void midi_out_emscripten::set_client_name(std::string_view clientName)
{
warning(configuration, "midi_out_emscripten::set_client_name: unsupported.");
}
LIBREMIDI_INLINE void midi_out_emscripten::set_port_name(std::string_view portName)
{
warning(configuration, "midi_out_emscripten::set_port_name: unsupported.");
}
LIBREMIDI_INLINE bool midi_out_emscripten::open_virtual_port(std::string_view)
{
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
return false;
}
LIBREMIDI_INLINE void midi_out_emscripten::send_message(const unsigned char* message, size_t size)
{
if (portNumber_ < 0)
error<invalid_use_error>(
this->configuration,
"midi_out_emscripten::send_message: trying to send a message without an open "
"port.");
webmidi_helpers::midi_access_emscripten::instance().send_message(
portNumber_, reinterpret_cast<const char*>(message), size);
}
}
#endif
@@ -0,0 +1,37 @@
#pragma once
#include <libremidi/backends/emscripten/config.hpp>
#include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
{
class midi_out_emscripten final
: public midi1::out_api
, public error_handler
{
public:
struct
: output_configuration
, emscripten_output_configuration
{
} configuration;
midi_out_emscripten(output_configuration&& conf, emscripten_output_configuration&& apiconf);
~midi_out_emscripten() override;
libremidi::API get_current_api() const noexcept override;
bool open_port(unsigned int portNumber, std::string_view);
bool open_port(const output_port& p, std::string_view) override;
bool open_virtual_port(std::string_view) override;
void close_port() override;
void set_client_name(std::string_view clientName) override;
void set_port_name(std::string_view portName) override;
void send_message(const unsigned char* message, size_t size) override;
private:
int portNumber_{-1};
};
}
@@ -0,0 +1,105 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/observer.hpp>
#include <cassert>
namespace libremidi
{
LIBREMIDI_INLINE observer_emscripten::observer_emscripten(
observer_configuration&& conf, emscripten_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (!configuration.has_callbacks())
return;
auto& webmidi = webmidi_helpers::midi_access_emscripten::instance();
webmidi.register_observer(*this);
// Trigger an initial notification
webmidi.load_current_infos();
if (configuration.notify_in_constructor)
update(webmidi.inputs(), webmidi.outputs());
}
LIBREMIDI_INLINE observer_emscripten::~observer_emscripten()
{
if (!configuration.has_callbacks())
return;
webmidi_helpers::midi_access_emscripten::instance().unregister_observer(*this);
}
LIBREMIDI_INLINE
libremidi::API observer_emscripten::get_current_api() const noexcept
{
return libremidi::API::WEBMIDI;
}
template <bool Input>
static auto to_port_info(int index, const webmidi_helpers::device_information& dev)
-> std::conditional_t<Input, input_port, output_port>
{
return {
{.client = 0,
.port = (uint64_t)index,
.manufacturer = "",
.device_name = "",
.port_name = dev.name,
.display_name = dev.name}};
}
LIBREMIDI_INLINE
std::vector<libremidi::input_port> observer_emscripten::get_input_ports() const noexcept
{
std::vector<libremidi::input_port> ret;
auto& webmidi = webmidi_helpers::midi_access_emscripten::instance();
webmidi.load_current_infos();
for (std::size_t i = 0, n = webmidi.input_count(); i < n; i++)
{
ret.push_back(to_port_info<true>(i, webmidi.inputs()[i]));
}
return ret;
}
LIBREMIDI_INLINE
std::vector<libremidi::output_port> observer_emscripten::get_output_ports() const noexcept
{
std::vector<libremidi::output_port> ret;
auto& webmidi = webmidi_helpers::midi_access_emscripten::instance();
webmidi.load_current_infos();
for (std::size_t i = 0, n = webmidi.output_count(); i < n; i++)
{
ret.push_back(to_port_info<false>(i, webmidi.outputs()[i]));
}
return ret;
}
LIBREMIDI_INLINE void observer_emscripten::update(
const std::vector<observer_emscripten::device>& current_inputs,
const std::vector<observer_emscripten::device>& current_outputs)
{
// WebMIDI never remove inputs, it just marks them as disconnected.
// At least in known browsers...
assert(current_inputs.size() >= m_known_inputs.size());
assert(current_outputs.size() >= m_known_outputs.size());
for (std::size_t i = m_known_inputs.size(); i < current_inputs.size(); i++)
{
m_known_inputs.push_back(current_inputs[i]);
configuration.input_added(to_port_info<true>(i, m_known_inputs[i]));
}
for (std::size_t i = m_known_outputs.size(); i < current_outputs.size(); i++)
{
m_known_outputs.push_back(current_outputs[i]);
configuration.output_added(to_port_info<false>(i, m_known_outputs[i]));
}
}
}
#endif
@@ -0,0 +1,37 @@
#pragma once
#include <libremidi/backends/emscripten/config.hpp>
#include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <vector>
namespace libremidi
{
class observer_emscripten final : public observer_api
{
using device = webmidi_helpers::device_information;
public:
struct
: observer_configuration
, emscripten_observer_configuration
{
} configuration;
explicit observer_emscripten(
observer_configuration&& conf, emscripten_observer_configuration&& apiconf);
~observer_emscripten();
void
update(const std::vector<device>& current_inputs, const std::vector<device>& current_outputs);
libremidi::API get_current_api() const noexcept override;
std::vector<libremidi::input_port> get_input_ports() const noexcept override;
std::vector<libremidi::output_port> get_output_ports() const noexcept override;
private:
std::vector<device> m_known_inputs;
std::vector<device> m_known_outputs;
};
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <libremidi/backends/jack/midi_in.hpp>
#include <libremidi/backends/jack/midi_out.hpp>
#include <libremidi/backends/jack/observer.hpp>
//*********************************************************************//
// API: UNIX JACK
//
// Written primarily by Alexander Svetalkin, with updates for delta
// time by Gary Scavone, April 2011.
//
// *********************************************************************//
namespace libremidi
{
struct jack_backend
{
using midi_in = midi_in_jack;
using midi_out = midi_out_jack;
using midi_observer = observer_jack;
using midi_in_configuration = jack_input_configuration;
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 inline bool available() noexcept
{
#if LIBREMIDI_WEAKJACK
return WeakJack::instance().available() == 0;
#else
return true;
#endif
}
};
}
@@ -0,0 +1,50 @@
#pragma once
#include <libremidi/config.hpp>
#include <cinttypes>
#include <cstdint>
#include <functional>
#include <string>
extern "C" {
typedef struct _jack_client jack_client_t;
typedef uint32_t jack_nframes_t;
typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg);
}
namespace libremidi
{
using jack_callback_function = std::function<void(int nframes)>;
struct jack_callback
{
int64_t token;
std::function<void(int nframes)> callback;
};
struct jack_input_configuration
{
std::string client_name = "libremidi client";
jack_client_t* context{};
std::function<void(jack_callback)> set_process_func;
std::function<void(int64_t)> clear_process_func;
};
struct jack_output_configuration
{
std::string client_name = "libremidi client";
jack_client_t* context{};
std::function<void(jack_callback)> set_process_func;
std::function<void(int64_t)> clear_process_func;
int32_t ringbuffer_size = 16384;
bool direct = false;
};
struct jack_observer_configuration
{
std::string client_name = "libremidi client";
jack_client_t* context{};
};
}
+241
View File
@@ -0,0 +1,241 @@
#pragma once
#if __has_include(<weakjack/weak_libjack.h>)
#include <weakjack/weak_libjack.h>
#elif __has_include(<weak_libjack.h>)
#include <weak_libjack.h>
#elif __has_include(<jack/jack.h> )
#include <jack/jack.h>
#include <jack/midiport.h>
#include <jack/ringbuffer.h>
#endif
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/semaphore.hpp>
#include <atomic>
#include <semaphore>
namespace libremidi
{
struct jack_client
{
jack_client_t* client{};
static std::string get_port_display_name(jack_port_t* port)
{
auto p1 = std::make_unique<char[]>(jack_port_name_size());
auto p2 = std::make_unique<char[]>(jack_port_name_size());
char* aliases[3] = {p1.get(), p2.get(), nullptr};
int n = jack_port_get_aliases(port, aliases);
if (n > 1)
{
return aliases[1];
}
else if (n > 0)
{
std::string str = aliases[0];
if (str.starts_with("alsa_pcm:"))
str.erase(0, strlen("alsa_pcm:"));
return str;
}
else
{
const auto short_name = jack_port_short_name(port);
if (short_name && strlen(short_name) > 0)
return short_name;
return jack_port_name(port);
}
}
template <bool Input>
static auto to_port_info(jack_client_t* client, jack_port_t* port)
-> std::conditional_t<Input, input_port, output_port>
{
return {{
.client = reinterpret_cast<std::uintptr_t>(client),
.port = 0,
.manufacturer = "",
.device_name = "",
.port_name = jack_port_name(port),
.display_name = get_port_display_name(port),
}};
}
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>>
{
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);
if (ports == nullptr)
return {};
int i = 0;
while (ports[i] != nullptr)
{
// 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));
i++;
}
jack_free(ports);
return ret;
}
};
struct jack_helpers : jack_client
{
struct port_handle
{
port_handle& operator=(jack_port_t* p)
{
impl.get()->store(p);
return *this;
}
operator jack_port_t*() const noexcept
{
if (impl)
return impl.get()->load();
return {};
}
std::shared_ptr<std::atomic<jack_port_t*>> impl
= std::make_shared<std::atomic<jack_port_t*>>(nullptr);
} port;
int64_t this_instance{};
semaphore_pair_lock thread_lock;
jack_helpers()
{
static std::atomic_int64_t instance{};
this_instance = ++instance;
}
template <typename Self>
jack_status_t connect(Self& self)
{
auto& configuration = self.configuration;
if (this->client)
return jack_status_t{};
// Initialize JACK client
if (configuration.context)
{
if (!configuration.set_process_func)
return JackFailure;
configuration.set_process_func(
{.token = this_instance,
.callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {
if (auto pt = p.lock())
if (auto ppt = pt->load())
self.process(nf);
self.thread_lock.check_client_released();
return 0;
}});
this->client = configuration.context;
return jack_status_t{};
}
else
{
jack_status_t status{};
this->client
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
if (this->client != nullptr)
{
jack_set_process_callback(
this->client,
+[](jack_nframes_t nf, void* ctx) -> int {
auto& self = *static_cast<Self*>(ctx);
jack_port_t* port = self.port;
// Is port created?
if (port == nullptr)
return 0;
self.process(nf);
self.thread_lock.check_client_released();
return 0;
},
&self);
jack_activate(this->client);
}
return status;
}
}
template <typename Self>
void disconnect(Self& self)
{
if (self.configuration.context)
{
if (self.configuration.clear_process_func)
{
self.configuration.clear_process_func(this_instance);
}
}
if (this->client && !self.configuration.context)
jack_client_close(this->client);
}
bool create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)
{
// full name: "client_name:port_name\0"
if (portName.empty())
portName = flags & JackPortIsInput ? "i" : "o";
if (self.configuration.client_name.size() + portName.size() + 2u
>= static_cast<size_t>(jack_port_name_size()))
{
self.template error<invalid_use_error>(
self.configuration, "JACK: port name length limit exceeded");
return false;
}
if (!this->port)
{
this->port
= jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0);
}
if (!this->port)
{
self.template error<driver_error>(self.configuration, "JACK: error creating port");
return false;
}
return true;
}
void do_close_port()
{
if (this->port == nullptr)
return;
// 1. Ensure that the next time the cycle runs it sees the port as nullptr
jack_port_t* port_ptr = this->port.impl->load();
this->port = nullptr;
// 2. Signal through the semaphore and wait for the signal return
this->thread_lock.prepare_release_client();
// 3. Now we are sure that the client is not going to use the port anymore
jack_port_unregister(this->client, port_ptr);
}
};
}
+114
View File
@@ -0,0 +1,114 @@
#pragma once
#include <libremidi/backends/jack/config.hpp>
#include <libremidi/backends/jack/helpers.hpp>
#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 error_handler
{
public:
struct
: input_configuration
, jack_input_configuration
{
} configuration;
explicit midi_in_jack(input_configuration&& conf, jack_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
auto status = connect(*this);
if (status != jack_status_t{})
warning(configuration, "midi_in_jack: " + std::to_string((int)jack_status_t{}));
}
~midi_in_jack() override
{
midi_in_jack::close_port();
disconnect(*this);
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_jack: set_client_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
bool open_port(const input_port& port, std::string_view portName) override
{
if (!create_local_port(*this, portName, JackPortIsInput))
return false;
if (auto ret = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
ret != 0)
{
error<invalid_parameter_error>(
configuration, "JACK: could not connect to port: " + port.port_name + " -> "
+ jack_port_name(this->port));
return false;
}
return true;
}
bool open_virtual_port(std::string_view portName) override
{
return create_local_port(*this, portName, JackPortIsInput);
}
void close_port() override { return do_close_port(); }
void set_port_name(std::string_view portName) override
{
jack_port_rename(this->client, this->port, portName.data());
}
timestamp absolute_timestamp() const noexcept override
{
return 1000 * jack_frames_to_time(client, jack_frame_time(client));
}
int process(jack_nframes_t nframes)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = true,
.has_samples = true,
};
void* buff = jack_port_get_buffer(this->port, nframes);
// Timing
jack_nframes_t current_frames{};
jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC
jack_time_t next_usecs{};
float period_usecs{};
jack_get_cycle_times(
this->client, &current_frames, &current_usecs, &next_usecs, &period_usecs);
// We have midi events in buffer
uint32_t evCount = jack_midi_get_event_count(buff);
for (uint32_t j = 0; j < evCount; 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(
{event.buffer, event.buffer + event.size},
m_processing.timestamp<timestamp_info>(to_ns, event.time));
}
return 0;
}
midi1::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,231 @@
#pragma once
#include <libremidi/backends/jack/config.hpp>
#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);
}
void write(const unsigned char* data, int64_t sz) const noexcept
{
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
return;
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);
}
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 error_handler
{
public:
struct
: output_configuration
, jack_output_configuration
{
} configuration;
midi_out_jack(output_configuration&& conf, jack_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
~midi_out_jack() override { }
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_jack: set_client_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
bool open_port(const output_port& port, std::string_view portName) override
{
if (!create_local_port(*this, portName, JackPortIsOutput))
return false;
// Connecting to the output
if (jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()) != 0)
{
error<invalid_parameter_error>(
configuration, "JACK: could not connect to port" + port.port_name);
return false;
}
return true;
}
bool open_virtual_port(std::string_view portName) override
{
return create_local_port(*this, portName, JackPortIsOutput);
}
void close_port() override { return do_close_port(); }
void set_port_name(std::string_view portName) override
{
jack_port_rename(this->client, this->port, portName.data());
}
};
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}
{
auto status = connect(*this);
if (status != jack_status_t{})
warning(configuration, "midi_out_jack_queued: " + std::to_string((int)jack_status_t{}));
}
~midi_out_jack_queued() override
{
midi_out_jack::close_port();
disconnect(*this);
}
void send_message(const unsigned char* message, std::size_t size) override
{
queue.write(message, size);
}
int process(jack_nframes_t nframes)
{
void* buff = jack_port_get_buffer(this->port, nframes);
jack_midi_clear_buffer(buff);
this->queue.read(buff);
return 0;
}
private:
jack_queue queue;
};
class midi_out_jack_direct final : public midi_out_jack
{
public:
midi_out_jack_direct(output_configuration&& conf, jack_output_configuration&& apiconf)
: midi_out_jack{std::move(conf), std::move(apiconf)}
{
auto status = connect(*this);
if (status != jack_status_t{})
warning(configuration, "midi_out_jack_direct: " + std::to_string((int)jack_status_t{}));
buffer_size = jack_get_buffer_size(this->client);
}
~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;
}
void send_message(const unsigned char* message, size_t size) override
{
void* buff = jack_port_get_buffer(this->port, buffer_size);
jack_midi_event_write(buff, 0, message, size);
}
int convert_timestamp(int64_t user) const noexcept
{
switch (configuration.timestamps)
{
case timestamp_mode::AudioFrame:
return static_cast<int>(user);
default:
// TODO
return 0;
}
}
void schedule_message(int64_t ts, const unsigned char* message, size_t size) override
{
void* buff = jack_port_get_buffer(this->port, buffer_size);
jack_midi_event_write(buff, convert_timestamp(ts), message, size);
}
int buffer_size{};
};
}
namespace libremidi
{
template <>
inline std::unique_ptr<midi_out_api> make<midi_out_jack>(
libremidi::output_configuration&& conf, libremidi::jack_output_configuration&& api)
{
if (api.direct)
return std::make_unique<midi_out_jack_direct>(std::move(conf), std::move(api));
else
return std::make_unique<midi_out_jack_queued>(std::move(conf), std::move(api));
}
}
@@ -0,0 +1,224 @@
#pragma once
#include <libremidi/backends/jack/config.hpp>
#include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <unordered_set>
namespace libremidi
{
class observer_jack final
: public observer_api
, private jack_client
, private error_handler
{
public:
struct
: observer_configuration
, jack_observer_configuration
{
} configuration;
explicit observer_jack(observer_configuration&& conf, jack_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{})
warning(configuration, "observer_jack: " + std::to_string((int)jack_status_t{}));
if (this->client != nullptr)
{
set_callbacks();
jack_activate(this->client);
}
}
}
void initial_callback()
{
{
const char** ports
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);
if (ports != nullptr)
{
int i = 0;
while (ports[i] != nullptr)
{
auto port = jack_port_by_name(client, ports[i]);
auto flags = jack_port_flags(port);
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, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
if (ports != nullptr)
{
int i = 0;
while (ports[i] != nullptr)
{
auto port = jack_port_by_name(client, ports[i]);
auto flags = jack_port_flags(port);
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 != JACK_DEFAULT_MIDI_TYPE)
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_MIDI; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<true>(this->client, nullptr, JackPortIsOutput);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<false>(this->client, nullptr, JackPortIsInput);
}
~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;
};
}
@@ -0,0 +1,153 @@
#pragma once
#if __has_include(<boost/lockfree/spsc_queue.hpp>)
#include <libremidi/backends/jack/config.hpp>
#include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/shared_context.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <variant>
namespace libremidi::jack
{
// Create a JACK client which will be shared across objects
struct shared_handler : public libremidi::shared_context
{
explicit shared_handler(std::string_view v)
{
midiin_callbacks.reserve(64);
midiout_callbacks.reserve(64);
jack_status_t status{};
client = jack_client_open(v.data(), JackNoStartServer, &status);
assert(client);
assert(status == 0);
jack_set_process_callback(
client,
+[](jack_nframes_t cnt, void* ctx) -> int {
((shared_handler*)ctx)->jack_callback(cnt);
return 0;
},
this);
}
virtual void start_processing() override { jack_activate(client); }
virtual void stop_processing() override { jack_deactivate(client); }
static shared_configurations make(std::string_view client_name)
{
auto clt = std::make_shared<shared_handler>(client_name);
auto add_in_cb = [client = std::weak_ptr{clt}](libremidi::jack_callback cb) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::in_callback_added, std::move(cb)});
};
auto clear_in_cb = [client = std::weak_ptr{clt}](int64_t index) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::in_callback_removed, index});
};
auto add_out_cb = [client = std::weak_ptr{clt}](libremidi::jack_callback cb) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::out_callback_added, std::move(cb)});
};
auto clear_out_cb = [client = std::weak_ptr{clt}](int64_t index) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::out_callback_removed, index});
};
return {
.context = clt,
.observer = jack_observer_configuration{.context = clt->client},
.in
= jack_input_configuration{.context = clt->client, .set_process_func = add_in_cb, .clear_process_func = clear_in_cb},
.out
= jack_output_configuration{.context = clt->client, .set_process_func = add_out_cb, .clear_process_func = clear_out_cb},
};
}
int jack_callback(jack_nframes_t cnt)
{
// 1. Process the events that will change the callback list
event ev;
while (events.pop(ev))
{
switch (ev.type)
{
case in_callback_added:
midiin_callbacks.push_back(
std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));
break;
case in_callback_removed: {
auto idx = *std::get_if<int64_t>(&ev.payload);
for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)
{
if (it->token == idx)
{
midiin_callbacks.erase(it);
break;
}
else
{
++it;
}
}
break;
}
case out_callback_added:
midiout_callbacks.push_back(
std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));
break;
case out_callback_removed:
auto idx = *std::get_if<int64_t>(&ev.payload);
for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)
{
if (it->token == idx)
{
midiout_callbacks.erase(it);
break;
}
else
{
++it;
}
}
break;
}
}
for (auto& cb : midiin_callbacks)
cb.callback(cnt);
for (auto& cb : midiout_callbacks)
cb.callback(cnt);
return 0;
}
~shared_handler()
{
jack_deactivate(client);
jack_client_close(client);
}
jack_client_t* client{};
enum event_type
{
in_callback_added,
in_callback_removed,
out_callback_added,
out_callback_removed,
};
struct event
{
event_type type;
std::variant<libremidi::jack_callback, int64_t> payload;
};
boost::lockfree::spsc_queue<event> events{16};
std::vector<libremidi::jack_callback> midiin_callbacks;
std::vector<libremidi::jack_callback> midiout_callbacks;
};
}
#endif
+459
View File
@@ -0,0 +1,459 @@
#pragma once
#include <libremidi/backends/linux/dylib_loader.hpp>
#include <alsa/asoundlib.h>
#if defined(SND_LIB_VERSION)
#if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)
#define LIBREMIDI_ALSA_HAS_RAMWIDI 1
#define LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD 1
#endif
#if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10)
#define LIBREMIDI_ALSA_HAS_UMP 1
#endif
#endif
namespace libremidi
{
struct libasound
{
// Useful one-liner:
// nm -A * | grep ' snd_' | grep -v '@' | cut -f 2 -d 'U' | sort | uniq | sed 's/ snd_//' | sed 's/_/, /' | awk ' { print "LIBREMIDI_SYMBOL_DEF(snd_"$1 " " $2 ");" }'
explicit libasound()
: library{"libasound.so.2"}
{
if (!library)
{
available = false;
return;
}
strerror = library.symbol<decltype(&::snd_strerror)>("snd_strerror");
if (!strerror)
available = false;
}
static const libasound& instance()
{
static const libasound self;
return self;
}
dylib_loader library;
decltype(&::snd_strerror) strerror{};
bool available{true};
struct card_t
{
explicit card_t(const dylib_loader& library)
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_card, get_name);
LIBREMIDI_SYMBOL_INIT(snd_card, next);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_card, get_name);
LIBREMIDI_SYMBOL_DEF(snd_card, next);
} card{library};
struct ctl_t
{
explicit ctl_t(const dylib_loader& library)
: rawmidi{library}
#if LIBREMIDI_ALSA_HAS_UMP
, ump{library}
#endif
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_ctl, close);
LIBREMIDI_SYMBOL_INIT(snd_ctl, open);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_ctl, close);
LIBREMIDI_SYMBOL_DEF(snd_ctl, open);
struct rawmidi_t
{
explicit rawmidi_t(const dylib_loader& library)
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info);
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info);
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device);
} rawmidi;
#if LIBREMIDI_ALSA_HAS_UMP
struct ump_t
{
explicit ump_t(const dylib_loader& library)
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, block_info);
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, endpoint_info);
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, next_device);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, block_info);
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, endpoint_info);
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, next_device);
} ump;
#endif
} ctl{library};
struct midi_t
{
explicit midi_t(const dylib_loader& library)
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_midi, event_decode);
LIBREMIDI_SYMBOL_INIT(snd_midi, event_encode);
LIBREMIDI_SYMBOL_INIT(snd_midi, event_free);
LIBREMIDI_SYMBOL_INIT(snd_midi, event_init);
LIBREMIDI_SYMBOL_INIT(snd_midi, event_new);
LIBREMIDI_SYMBOL_INIT(snd_midi, event_no_status);
LIBREMIDI_SYMBOL_INIT(snd_midi, event_resize_buffer);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_midi, event_decode);
LIBREMIDI_SYMBOL_DEF(snd_midi, event_encode);
LIBREMIDI_SYMBOL_DEF(snd_midi, event_free);
LIBREMIDI_SYMBOL_DEF(snd_midi, event_init);
LIBREMIDI_SYMBOL_DEF(snd_midi, event_new);
LIBREMIDI_SYMBOL_DEF(snd_midi, event_no_status);
LIBREMIDI_SYMBOL_DEF(snd_midi, event_resize_buffer);
} midi{library};
#if LIBREMIDI_ALSA_HAS_RAMWIDI
struct rawmidi_t
{
explicit rawmidi_t(const dylib_loader& library)
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, close);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_name);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevice_name);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevices_count);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_device);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_stream);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_subdevice);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, open);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_current);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_get_buffer_size);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_clock_type);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_no_active_sensing);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_read_mode);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_count);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_revents);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, read);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_get_avail);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, tread);
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, write);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, close);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_name);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevice_name);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevices_count);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_device);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_stream);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_subdevice);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, open);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_current);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_get_buffer_size);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_clock_type);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_no_active_sensing);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_read_mode);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_count);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_revents);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, read);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_get_avail);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, tread);
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, write);
} rawmidi{library};
#endif
struct seq_t
{
explicit seq_t(const dylib_loader& library)
#if LIBREMIDI_ALSA_HAS_UMP
: ump{library}
#endif
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_seq, alloc_queue);
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_set_client);
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_seq, close);
LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from);
LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue);
LIBREMIDI_SYMBOL_INIT(snd_seq, create_port);
LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port);
LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output);
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input);
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending);
LIBREMIDI_SYMBOL_INIT(snd_seq, event_output);
LIBREMIDI_SYMBOL_INIT(snd_seq, free_event);
LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue);
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info);
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info);
LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info);
LIBREMIDI_SYMBOL_INIT(snd_seq, open);
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors);
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real);
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update);
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client);
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port);
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq);
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo);
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name);
LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info);
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo);
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port);
LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_seq, alloc_queue);
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_set_client);
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_seq, close);
LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from);
LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue);
LIBREMIDI_SYMBOL_DEF(snd_seq, create_port);
LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port);
LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output);
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input);
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending);
LIBREMIDI_SYMBOL_DEF(snd_seq, event_output);
LIBREMIDI_SYMBOL_DEF(snd_seq, free_event);
LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue);
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info);
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info);
LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info);
LIBREMIDI_SYMBOL_DEF(snd_seq, open);
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors);
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real);
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update);
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client);
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port);
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq);
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo);
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_name);
LIBREMIDI_SYMBOL_DEF(snd_seq, set_port_info);
LIBREMIDI_SYMBOL_DEF(snd_seq, set_queue_tempo);
LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port);
LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port);
#if LIBREMIDI_ALSA_HAS_UMP
struct ump_t
{
explicit ump_t(const dylib_loader& library)
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version);
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input);
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output);
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version);
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input);
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output);
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct);
} ump;
#endif
} seq{library};
#if LIBREMIDI_ALSA_HAS_UMP
struct ump_t
{
explicit ump_t(const dylib_loader& library)
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name);
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_ump, close);
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name);
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof);
LIBREMIDI_SYMBOL_INIT(snd_ump, open);
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors);
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count);
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents);
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi);
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params);
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current);
LIBREMIDI_SYMBOL_INIT(snd_ump, read);
LIBREMIDI_SYMBOL_INIT(snd_ump, tread);
LIBREMIDI_SYMBOL_INIT(snd_ump, write);
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name);
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_ump, close);
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name);
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof);
LIBREMIDI_SYMBOL_DEF(snd_ump, open);
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors);
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count);
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents);
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi);
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params);
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current);
LIBREMIDI_SYMBOL_DEF(snd_ump, read);
LIBREMIDI_SYMBOL_DEF(snd_ump, tread);
LIBREMIDI_SYMBOL_DEF(snd_ump, write);
} ump{library};
#endif
};
#undef snd_dylib_alloca
#define snd_dylib_alloca(ptr, access, type) \
{ \
*ptr = (snd_##access##_##type##_t*)alloca(snd.access.type##_sizeof()); \
memset(*ptr, 0, snd.access.type##_sizeof()); \
}
#define snd_dylib_alloca2(ptr, access1, access2, type) \
{ \
*ptr = (snd_##access1##_access2##_##type##_t*)alloca(snd.access1.access2.type##_sizeof()); \
memset(*ptr, 0, snd.access1.access2.type##_sizeof()); \
}
#undef snd_rawmidi_info_alloca
#define snd_rawmidi_info_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, info)
#undef snd_rawmidi_params_alloca
#define snd_rawmidi_params_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, params)
#undef snd_rawmidi_status_alloca
#define snd_rawmidi_status_alloca(ptr) snd_dylib_alloca(ptr, rawmidi, status)
#undef snd_seq_client_info_alloca
#define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info)
#undef snd_seq_port_info_alloca
#define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info)
#undef snd_seq_queue_tempo_alloca
#define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo)
#if LIBREMIDI_ALSA_HAS_UMP
#undef snd_ump_block_info_alloca
#define snd_ump_block_info_alloca(ptr) snd_dylib_alloca(ptr, ump, block_info)
#undef snd_ump_endpoint_info_alloca
#define snd_ump_endpoint_info_alloca(ptr) snd_dylib_alloca(ptr, ump, endpoint_info)
#endif
}
@@ -0,0 +1,85 @@
#pragma once
#if __has_include(<dlfcn.h>)
#include <dlfcn.h>
#include <cassert>
namespace libremidi
{
class dylib_loader
{
public:
explicit dylib_loader(const char* const so)
{
impl = dlopen(so, RTLD_LAZY | RTLD_LOCAL | RTLD_NODELETE);
}
dylib_loader(const dylib_loader&) noexcept = delete;
dylib_loader& operator=(const dylib_loader&) noexcept = delete;
dylib_loader(dylib_loader&& other) noexcept
{
impl = other.impl;
other.impl = nullptr;
}
dylib_loader& operator=(dylib_loader&& other) noexcept
{
impl = other.impl;
other.impl = nullptr;
return *this;
}
~dylib_loader()
{
if (impl)
{
dlclose(impl);
}
}
template <typename T>
T symbol(const char* const sym) const noexcept
{
assert(impl);
return reinterpret_cast<T>(dlsym(impl, sym));
}
operator bool() const noexcept { return bool(impl); }
private:
void* impl{};
};
}
#define LIBREMIDI_SYMBOL_NAME_S(prefix, name) #prefix "_" #name
#define LIBREMIDI_SYMBOL_NAME(prefix, name) prefix##_##name
#define LIBREMIDI_SYMBOL_DEF(prefix, name) \
decltype(&::LIBREMIDI_SYMBOL_NAME(prefix, name)) name{};
#define LIBREMIDI_SYMBOL_INIT(prefix, name) \
{ \
name = library.symbol<decltype(&::LIBREMIDI_SYMBOL_NAME(prefix, name))>( \
LIBREMIDI_SYMBOL_NAME_S(prefix, name)); \
if (!name) \
{ \
available = false; \
return; \
} \
}
// Because some libs have names that are C++ keywords, e.g. udev_new:
#define LIBREMIDI_SYMBOL_NAME2_S(prefix, name, varname) #prefix "_" #name
#define LIBREMIDI_SYMBOL_NAME2(prefix, name, varname) prefix##_##name
#define LIBREMIDI_SYMBOL_DEF2(prefix, name, varname) \
decltype(&::LIBREMIDI_SYMBOL_NAME2(prefix, name, varname)) varname{};
#define LIBREMIDI_SYMBOL_INIT2(prefix, name, varname) \
{ \
varname = library.symbol<decltype(&::LIBREMIDI_SYMBOL_NAME2(prefix, name, varname))>( \
LIBREMIDI_SYMBOL_NAME2_S(prefix, name, varname)); \
if (!varname) \
{ \
available = false; \
return; \
} \
}
#endif
@@ -0,0 +1,77 @@
#pragma once
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <poll.h>
#include <unistd.h>
#include <iostream>
namespace libremidi
{
struct eventfd_notifier
{
eventfd_notifier(bool semaphore = true)
{
if (semaphore)
this->fd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
else
this->fd = eventfd(0, EFD_NONBLOCK);
}
~eventfd_notifier() { close(this->fd); }
eventfd_notifier(const eventfd_notifier&) = delete;
eventfd_notifier(eventfd_notifier&&) = delete;
eventfd_notifier& operator=(const eventfd_notifier&) = delete;
eventfd_notifier& operator=(eventfd_notifier&&) = delete;
void notify() noexcept { eventfd_write(fd, 1); }
static bool ready(pollfd res) noexcept { return res.revents & POLLIN; }
eventfd_t consume() noexcept
{
eventfd_t val;
eventfd_read(fd, &val);
return val;
}
operator int() const noexcept { return fd; }
operator pollfd() const noexcept { return {.fd = fd, .events = POLLIN, .revents = 0}; }
int fd{-1};
};
struct timerfd_timer
{
timerfd_timer() { this->fd = timerfd_create(CLOCK_MONOTONIC, 0); }
~timerfd_timer() { close(this->fd); }
timerfd_timer(const timerfd_timer&) = delete;
timerfd_timer(timerfd_timer&&) = delete;
timerfd_timer& operator=(const timerfd_timer&) = delete;
timerfd_timer& operator=(timerfd_timer&&) = delete;
void oneshot(int64_t nsec)
{
itimerspec t{};
t.it_value.tv_nsec = nsec;
timerfd_settime(this->fd, 0, &t, nullptr);
}
void restart(int64_t nsec)
{
itimerspec t{};
t.it_value.tv_nsec = nsec;
t.it_interval.tv_nsec = nsec;
timerfd_settime(this->fd, 0, &t, nullptr);
}
void cancel()
{
itimerspec t{};
timerfd_settime(this->fd, 0, &t, nullptr);
}
operator int() const noexcept { return fd; }
operator pollfd() const noexcept { return {.fd = fd, .events = POLLIN, .revents = 0}; }
int fd{-1};
};
}
@@ -0,0 +1,169 @@
#pragma once
#include <libremidi/backends/linux/dylib_loader.hpp>
#include <pipewire/pipewire.h>
namespace libremidi
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
class libpipewire
{
public:
decltype(&::pw_init) init{};
decltype(&::pw_deinit) deinit{};
decltype(&::pw_context_new) context_new{};
decltype(&::pw_context_connect) context_connect{};
decltype(&::pw_context_destroy) context_destroy{};
decltype(&::pw_core_disconnect) core_disconnect{};
decltype(&::pw_proxy_add_listener) proxy_add_listener{};
decltype(&::pw_proxy_destroy) proxy_destroy{};
decltype(&::pw_main_loop_new) main_loop_new{};
decltype(&::pw_main_loop_destroy) main_loop_destroy{};
decltype(&::pw_main_loop_quit) main_loop_quit{};
decltype(&::pw_main_loop_run) main_loop_run{};
decltype(&::pw_main_loop_get_loop) main_loop_get_loop{};
/*
decltype(&::pw_thread_loop_new) thread_loop_new{};
decltype(&::pw_thread_loop_destroy) thread_loop_destroy{};
decltype(&::pw_thread_loop_wait) thread_loop_wait{};
decltype(&::pw_thread_loop_start) thread_loop_start{};
decltype(&::pw_thread_loop_stop) thread_loop_stop;
decltype(&::pw_thread_loop_lock) thread_loop_lock{};
decltype(&::pw_thread_loop_unlock) thread_loop_unlock;
decltype(&::pw_thread_loop_get_loop) thread_loop_get_loop{};
*/
decltype(&::pw_properties_new) properties_new{};
decltype(&::pw_properties_free) properties_free{};
decltype(&::pw_properties_get) properties_get{};
decltype(&::pw_filter_new_simple) filter_new_simple{};
decltype(&::pw_filter_get_node_id) filter_get_node_id{};
decltype(&::pw_filter_get_properties) filter_get_properties{};
decltype(&::pw_filter_add_port) filter_add_port{};
decltype(&::pw_filter_remove_port) filter_remove_port{};
decltype(&::pw_filter_update_properties) filter_update_properties{};
decltype(&::pw_filter_update_params) filter_update_params{};
decltype(&::pw_filter_get_time) filter_get_time{};
decltype(&::pw_filter_destroy) filter_destroy{};
decltype(&::pw_filter_connect) filter_connect{};
decltype(&::pw_filter_get_dsp_buffer) filter_get_dsp_buffer{};
decltype(&::pw_filter_queue_buffer) filter_queue_buffer{};
decltype(&::pw_filter_dequeue_buffer) filter_dequeue_buffer{};
decltype(&::pw_filter_flush) filter_flush{};
static const libpipewire& instance()
{
static const libpipewire self;
return self;
}
bool available{true};
private:
dylib_loader library;
libpipewire()
: library("libpipewire-0.3.so.0")
{
if (!library)
{
available = false;
return;
}
// 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");
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");
core_disconnect = 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");
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_get_loop
= 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");
filter_new_simple = 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");
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");
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");
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");
filter_get_dsp_buffer
= 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");
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");
assert(init);
assert(deinit);
assert(context_new);
assert(context_connect);
assert(context_destroy);
assert(core_disconnect);
assert(proxy_destroy);
assert(main_loop_new);
assert(main_loop_destroy);
assert(main_loop_quit);
assert(main_loop_run);
assert(main_loop_get_loop);
assert(properties_new);
assert(properties_free);
assert(properties_get);
assert(filter_new_simple);
assert(filter_get_node_id);
assert(filter_get_properties);
assert(filter_add_port);
assert(filter_remove_port);
assert(filter_update_properties);
assert(filter_update_params);
assert(filter_get_time);
assert(filter_destroy);
assert(filter_connect);
assert(filter_get_dsp_buffer);
assert(filter_dequeue_buffer);
assert(filter_queue_buffer);
assert(filter_flush);
}
};
#pragma GCC diagnostic pop
}
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include <libremidi/backends/linux/dylib_loader.hpp>
#include <libremidi/backends/linux/helpers.hpp>
#include <libudev.h>
#include <cassert>
namespace libremidi
{
struct libudev
{
// Useful one-liner:
// nm -A * | grep ' udev_' | grep -v '@' | cut -f 2 -d 'U' | sort | uniq | sed 's/ udev_//'| awk ' { print "LIBREMIDI_SYMBOL_DEF(udev, "$1");" }'
explicit libudev()
: library{"libudev.so.1"}
{
if (!library)
{
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(udev, device_get_action);
LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem);
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_INIT2(udev, new, create);
LIBREMIDI_SYMBOL_INIT(udev, unref);
}
static const libudev& instance()
{
static const libudev self;
return self;
}
dylib_loader library;
bool available{true};
LIBREMIDI_SYMBOL_DEF(udev, device_get_action);
LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem);
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_DEF2(udev, new, create);
LIBREMIDI_SYMBOL_DEF(udev, unref);
};
struct udev_helper
{
udev_helper()
{
instance = udev.create();
assert(instance);
monitor = udev.monitor_new_from_netlink(instance, "udev");
assert(monitor);
udev.monitor_enable_receiving(monitor);
}
~udev_helper()
{
udev.monitor_unref(monitor);
udev.unref(instance);
}
udev_helper(const udev_helper&) = delete;
udev_helper(udev_helper&&) = delete;
udev_helper& operator=(const udev_helper&) = delete;
udev_helper& operator=(udev_helper&&) = delete;
operator pollfd() const noexcept
{
return {.fd = udev.monitor_get_fd(monitor), .events = POLLIN, .revents = 0};
}
const libudev& udev = libudev::instance();
struct udev* instance{};
udev_monitor* monitor{};
};
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <libremidi/backends/dummy.hpp>
#include <libremidi/backends/linux/pipewire.hpp>
#include <libremidi/backends/pipewire/config.hpp>
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/backends/pipewire/midi_in.hpp>
#include <libremidi/backends/pipewire/midi_out.hpp>
#include <libremidi/backends/pipewire/observer.hpp>
namespace libremidi::pipewire
{
struct backend
{
using midi_in = midi_in_pipewire;
using midi_out = midi_out_pipewire;
using midi_observer = observer_pipewire;
using midi_in_configuration = pipewire_input_configuration;
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 inline bool available() noexcept
{
static const libpipewire& pw = libpipewire::instance();
return pw.available;
}
};
}
@@ -0,0 +1,53 @@
#pragma once
#include <libremidi/config.hpp>
#include <cinttypes>
#include <cstdint>
#include <functional>
#include <string>
extern "C" {
struct pw_main_loop;
struct pw_filter;
struct spa_io_position;
}
namespace libremidi
{
using pipewire_callback_function = std::function<void(spa_io_position*)>;
struct pipewire_callback
{
int64_t token;
pipewire_callback_function callback;
};
struct pipewire_input_configuration
{
std::string client_name = "libremidi client";
pw_main_loop* context{};
pw_filter* filter{};
std::function<void(pipewire_callback)> set_process_func;
std::function<void(int64_t)> clear_process_func;
};
struct pipewire_output_configuration
{
std::string client_name = "libremidi client";
pw_main_loop* context{};
pw_filter* filter{};
std::function<void(pipewire_callback)> set_process_func;
std::function<void(int64_t)> clear_process_func;
int64_t output_buffer_size{65536};
};
struct pipewire_observer_configuration
{
std::string client_name = "libremidi client";
pw_main_loop* context{};
};
}
@@ -0,0 +1,600 @@
#pragma once
#include <libremidi/backends/linux/pipewire.hpp>
#include <libremidi/detail/memory.hpp>
#include <pipewire/filter.h>
#include <pipewire/pipewire.h>
#include <spa/control/control.h>
#include <spa/param/props.h>
#include <spa/utils/defs.h>
#include <spa/utils/result.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <spa/param/audio/format-utils.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
namespace libremidi
{
template <typename K, typename V>
using hash_map = std::unordered_map<K, V>;
struct pipewire_instance
{
const libpipewire& pw = libpipewire::instance();
pipewire_instance()
{
/// Initialize the PipeWire main loop, context, etc.
int argc = 0;
char* argv[] = {NULL};
char** aa = argv;
pw.init(&argc, &aa);
}
~pipewire_instance() { pw.deinit(); }
};
struct pipewire_context
{
struct listened_port
{
uint32_t id{};
pw_port* port{};
std::unique_ptr<spa_hook> listener;
};
struct port_info
{
uint32_t id{};
std::string format;
std::string port_name;
std::string port_alias;
std::string object_path;
std::string node_id;
std::string port_id;
bool physical{};
bool terminal{};
bool monitor{};
pw_direction direction{};
};
struct node
{
std::vector<port_info> inputs;
std::vector<port_info> outputs;
};
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;
void for_each_port(auto func)
{
for (auto& map : {physical_audio, physical_midi, software_audio, software_midi})
{
for (auto& [id, node] : map)
{
for (auto& port : node.inputs)
func(port);
for (auto& port : node.outputs)
func(port);
}
}
}
void remove_port(uint32_t id)
{
port_cache.erase(id);
for (auto map : {&physical_audio, &physical_midi, &software_audio, &software_midi})
{
for (auto& [_, node] : *map)
{
std::erase_if(node.inputs, [id](const port_info& p) { return p.id == id; });
std::erase_if(node.outputs, [id](const port_info& p) { return p.id == id; });
}
}
}
} current_graph;
explicit pipewire_context(pw_main_loop* inst)
: main_loop{inst}
, owns_main_loop{false}
{
assert(main_loop);
initialize();
}
explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)
: global_instance{inst}
, owns_main_loop{true}
{
this->main_loop = pw.main_loop_new(nullptr);
if (!this->main_loop)
{
// libremidi::logger().error("PipeWire: main_loop_new failed!");
return;
}
initialize();
}
void initialize()
{
this->lp = pw.main_loop_get_loop(this->main_loop);
if (!lp)
{
// libremidi::logger().error("PipeWire: main_loop_get_loop failed!");
return;
}
this->context = pw.context_new(lp, nullptr, 0);
if (!this->context)
{
// libremidi::logger().error("PipeWire: context_new failed!");
return;
}
this->core = pw.context_connect(this->context, nullptr, 0);
if (!this->core)
{
// libremidi::logger().error("PipeWire: context_connect failed!");
return;
}
this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);
if (!this->registry)
{
// libremidi::logger().error("PipeWire: core_get_registry failed!");
return;
}
initialize_observation();
synchronize();
// Add a manual 1ms event loop iteration at the end of
// ctor to ensure synchronous clients will still see the ports
pw_loop_iterate(this->lp, 1);
}
void initialize_observation()
{
// Register a listener which will listen on when ports are added / removed
spa_zero(registry_listener);
static constexpr const struct pw_registry_events registry_events = {
.version = PW_VERSION_REGISTRY_EVENTS,
.global =
[](void* object, uint32_t id, uint32_t /*permissions*/, const char* type,
uint32_t /*version*/, const struct spa_dict* /*props*/) {
pipewire_context& self = *(pipewire_context*)object;
if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
self.register_port(id, type);
},
.global_remove =
[](void* object, uint32_t id) {
pipewire_context& self = *(pipewire_context*)object;
self.unregister_port(id);
},
};
// Start listening
pw_registry_add_listener(this->registry, &this->registry_listener, &registry_events, this);
}
void register_port(uint32_t id, const char* type)
{
auto port = (pw_port*)pw_registry_bind(registry, id, type, PW_VERSION_PORT, 0);
port_listener.push_back({id, port, std::make_unique<spa_hook>()});
auto& l = port_listener.back();
static constexpr const struct pw_port_events port_events = {
.version = PW_VERSION_PORT_EVENTS,
.info
= [](void* object,
const pw_port_info* info) { ((pipewire_context*)object)->update_port_info(info); },
};
pw_port_add_listener(l.port, l.listener.get(), &port_events, this);
}
void unregister_port(uint32_t id)
{
// When a port is removed:
// Notify
std::unique_lock _{current_graph.mtx, std::defer_lock};
if (on_port_removed)
{
_.lock();
if (auto it = current_graph.port_cache.find(id); it != current_graph.port_cache.end())
{
auto copy = it->second;
_.unlock();
on_port_removed(copy);
}
else
{
_.unlock();
}
}
// Remove from the graph
{
_.lock();
current_graph.remove_port(id);
_.unlock();
}
// Remove from the listeners
auto it
= std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) {
return l.id == id;
});
if (it != port_listener.end())
{
pw.proxy_destroy((pw_proxy*)it->port);
port_listener.erase(it);
}
}
void synchronize()
{
pending = 0;
done = 0;
if (!core)
return;
spa_hook core_listener;
static constexpr struct pw_core_events core_events = {
.version = PW_VERSION_CORE_EVENTS,
.done =
[](void* object, uint32_t id, int seq) {
auto& self = *(pipewire_context*)object;
if(id == PW_ID_CORE && seq == self.pending)
{
self.done = 1;
libpipewire::instance().main_loop_quit(self.main_loop);
}
},
};
spa_zero(core_listener);
pw_core_add_listener(core, &core_listener, &core_events, this);
pending = pw_core_sync(core, PW_ID_CORE, 0);
while (!done)
{
pw.main_loop_run(this->main_loop);
}
spa_hook_remove(&core_listener);
}
[[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_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,
std::to_string(in_port).c_str(), nullptr);
auto proxy = (pw_proxy*)pw_core_create_object(
this->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);
if (!proxy)
{
std::cerr << "PipeWire: could not allocate link\n";
pw.properties_free(props);
return nullptr;
}
synchronize();
pw.properties_free(props);
return proxy;
}
void unlink_ports(pw_proxy* link) { pw.proxy_destroy(link); }
void update_port_info(const pw_port_info* info)
{
const spa_dict_item* item{};
port_info p;
p.id = info->id;
spa_dict_for_each(item, info->props)
{
std::string_view k{item->key}, v{item->value};
if (k == "format.dsp")
p.format = v;
else if (k == "port.name")
p.port_name = v;
else if (k == "port.alias")
p.port_alias = v;
else if (k == "object.path")
p.object_path = v;
else if (k == "port.id")
p.port_id = v;
else if (k == "node.id")
p.node_id = v;
else if (k == "port.physical" && v == "true")
p.physical = true;
else if (k == "port.terminal" && v == "true")
p.terminal = true;
else if (k == "port.monitor" && v == "true")
p.monitor = true;
else if (k == "port.direction")
{
if (v == "out")
{
p.direction = pw_direction::SPA_DIRECTION_OUTPUT;
}
else
{
p.direction = pw_direction::SPA_DIRECTION_INPUT;
}
}
}
if (p.node_id.empty())
return;
const auto nid = std::stoul(p.node_id);
auto get_node = [&]() -> node* {
if (p.physical)
{
if (p.format.find("audio") != p.format.npos)
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("audio") != p.format.npos)
return &this->current_graph.software_audio[nid];
else if (p.format.find("midi") != p.format.npos)
return &this->current_graph.software_midi[nid];
}
return nullptr;
};
{
std::lock_guard _{current_graph.mtx};
current_graph.port_cache[p.id] = p;
if (auto node = get_node())
{
if (p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
node->outputs.push_back(p);
else
node->inputs.push_back(p);
}
}
if (on_port_added)
on_port_added(p);
}
int get_fd() const noexcept
{
if (!this->lp)
return -1;
auto spa_callbacks = this->lp->control->iface.cb;
auto spa_loop_methods = (const spa_loop_control_methods*)spa_callbacks.funcs;
if (spa_loop_methods->get_fd)
return spa_loop_methods->get_fd(spa_callbacks.data);
else
return -1;
}
~pipewire_context()
{
if (this->registry)
pw.proxy_destroy((pw_proxy*)this->registry);
for (auto& [id, p, l] : this->port_listener)
if (l)
pw.proxy_destroy((pw_proxy*)p);
if (this->core)
pw.core_disconnect(this->core);
if (this->context)
pw.context_destroy(this->context);
if (owns_main_loop && this->main_loop)
pw.main_loop_destroy(this->main_loop);
}
friend struct pipewire_filter;
const libpipewire& pw = libpipewire::instance();
std::shared_ptr<pipewire_instance> global_instance;
pw_main_loop* main_loop{};
pw_loop* lp{};
pw_context* context{};
pw_core* core{};
pw_registry* registry{};
spa_hook registry_listener{};
std::function<void(const port_info&)> on_port_added;
std::function<void(const port_info&)> on_port_removed;
std::vector<listened_port> port_listener{};
std::atomic<int> pending{};
std::atomic<int> done{};
bool owns_main_loop{true};
int sync{};
};
struct pipewire_filter
{
const libpipewire& pw = libpipewire::instance();
std::shared_ptr<pipewire_context> loop{};
pw_filter* filter{};
std::vector<pw_proxy*> links{};
struct port
{
void* data;
}* port{};
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop)
: loop{loop}
{
}
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)
: loop{loop}
, filter{filter}
{
}
void create_filter(std::string_view filter_name, const pw_filter_events& events, void* context)
{
assert(!filter);
auto& pw = libpipewire::instance();
// clang-format off
this->filter = pw.filter_new_simple(
loop->lp,
filter_name.data(),
pw.properties_new(
PW_KEY_MEDIA_TYPE, "Midi",
PW_KEY_MEDIA_CATEGORY, "Filter",
PW_KEY_MEDIA_ROLE, "DSP",
PW_KEY_MEDIA_NAME, "libremidi",
#if defined(PW_KEY_NODE_LOCK_RATE)
PW_KEY_NODE_LOCK_RATE, "true",
#endif
PW_KEY_NODE_ALWAYS_PROCESS, "true",
PW_KEY_NODE_PAUSE_ON_IDLE, "false",
#if defined(PW_KEY_NODE_SUSPEND_ON_IDLE)
PW_KEY_NODE_SUSPEND_ON_IDLE, "false",
#endif
nullptr),
&events,
context);
// clang-format on
assert(filter);
}
void destroy()
{
if (this->filter)
pw.filter_destroy(this->filter);
}
void create_local_port(std::string_view port_name, spa_direction direction)
{
// clang-format off
this->port = (struct port*)pw.filter_add_port(
this->filter,
direction,
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(struct port),
pw.properties_new(
PW_KEY_FORMAT_DSP, "8 bit raw midi",
PW_KEY_PORT_NAME, port_name.data(),
nullptr),
nullptr, 0);
// clang-format on
assert(port);
}
void set_port_buffer(int bytes)
{
uint8_t buffer[1024];
struct spa_pod_builder builder;
spa_pod_builder_init(&builder, buffer, sizeof(buffer));
// clang-format off
const struct spa_pod* params[1] = {
(spa_pod*) spa_pod_builder_add_object(
&builder,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 32),
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(bytes, 4096, INT32_MAX),
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)
)
};
// clang-format on
pw.filter_update_params(this->filter, this->port, params, 1);
}
void remove_port()
{
assert(this->port);
pw.filter_remove_port(this->port);
this->port = nullptr;
}
void rename_port(std::string_view port_name)
{
assert(this->port);
spa_dict_item items[1] = {
SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name.data()),
};
auto properties = SPA_DICT_INIT(items, 1);
pw.filter_update_properties(this->filter, this->port, &properties);
this->port = nullptr;
}
void start_filter()
{
if (pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0)
{
std::cerr << "can't connect\n";
return;
}
}
uint32_t filter_node_id() { return this->loop->pw.filter_get_node_id(this->filter); }
void synchronize_node()
{
this->loop->synchronize();
int k = 0;
auto node_id = filter_node_id();
while (node_id == 4294967295)
{
this->loop->synchronize();
node_id = filter_node_id();
if (k++; k > 100)
return;
}
}
void synchronize_ports(const pipewire_context::node& this_node)
{
// Leave some time to resolve the ports
int k = 0;
const auto num_local_ins = 1;
const auto num_local_outs = 0;
while (this_node.inputs.size() < num_local_ins || this_node.outputs.size() < num_local_outs)
{
this->loop->synchronize();
if (k++; k > 100)
return;
}
}
};
}
#pragma GCC diagnostic pop
@@ -0,0 +1,464 @@
#pragma once
#include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/backends/linux/pipewire.hpp>
#include <libremidi/backends/pipewire/context.hpp>
#include <libremidi/detail/memory.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/semaphore.hpp>
#include <atomic>
#include <semaphore>
#include <stop_token>
#include <thread>
namespace libremidi
{
struct pipewire_helpers
{
struct port
{
void* data{};
};
// All pipewire operations have to happen in the same thread
// - and pipewire checks that internally.
std::jthread main_loop_thread;
const libpipewire& pw = libpipewire::instance();
std::shared_ptr<pipewire_instance> global_instance;
std::shared_ptr<pipewire_context> global_context;
std::unique_ptr<pipewire_filter> filter;
pw_proxy* link{};
int64_t this_instance{};
eventfd_notifier termination_event{};
pollfd fds[2]{};
semaphore_pair_lock thread_lock;
std::shared_ptr<void> canary = std::make_shared<int>();
enum poll_state
{
start_poll,
in_poll,
not_in_poll
};
std::atomic<poll_state> current_state{not_in_poll};
pipewire_helpers()
{
static std::atomic_int64_t instance{};
this_instance = ++instance;
fds[1] = termination_event;
}
template <typename Self>
void create_filter(Self& self)
{
if (this->filter)
return;
auto& configuration = self.configuration;
if (configuration.context && configuration.filter && configuration.set_process_func)
{
this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);
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);
self.thread_lock.check_client_released();
}};
configuration.set_process_func(cbs);
}
else
{
this->filter = std::make_unique<pipewire_filter>(this->global_context);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
static constexpr struct pw_filter_events filter_events
= {.version = PW_VERSION_FILTER_EVENTS,
.process = +[](void* _data, struct spa_io_position* position) -> void {
// FIXME likely we need the thread_lock check here too
Self& self = *static_cast<Self*>(_data);
self.process(position);
}};
#pragma GCC diagnostic pop
this->filter->create_filter(self.configuration.client_name, filter_events, &self);
this->filter->start_filter();
}
}
template <typename Self>
void destroy_filter(Self& self)
{
assert(global_context);
if (!global_context->owns_main_loop)
{
if (self.configuration.clear_process_func)
{
self.configuration.clear_process_func(this_instance);
}
}
else
{
if (this->filter)
{
this->filter->destroy();
}
}
this->filter.reset();
}
template <typename Self>
int create_context(Self& self)
{
if (this->global_context)
return 0;
// Initialize PipeWire client
auto& configuration = self.configuration;
if (configuration.context)
{
this->global_context = std::make_shared<pipewire_context>(configuration.context);
}
else
{
this->global_instance = std::make_shared<pipewire_instance>();
this->global_context = std::make_shared<pipewire_context>(this->global_instance);
}
return 0;
}
void destroy_context()
{
assert(this->global_context);
this->global_context.reset();
this->global_instance.reset();
}
void run_poll_loop()
try
{
// Note: called from a std::jthread.
assert(this->global_context);
if (int fd = this->global_context->get_fd(); fd != -1)
{
fds[0] = {.fd = fd, .events = POLLIN, .revents = 0};
current_state = poll_state::in_poll;
for (;;)
{
if (int err = poll(fds, 2, -1); err < 0)
{
if (err == -EAGAIN)
continue;
else
break;
}
// Check pipewire fd:
if (fds[0].revents & POLLIN)
{
if (auto lp = this->global_context->lp)
{
int result = pw_loop_iterate(lp, 0);
if (result < 0)
std::cerr << "pw_loop_iterate: " << spa_strerror(result) << "\n";
}
fds[0].revents = 0;
}
// Check exit fd:
if (fds[1].revents & POLLIN)
{
break;
}
}
}
current_state = poll_state::not_in_poll;
}
catch (...)
{
current_state = poll_state::not_in_poll;
}
template <typename Self>
bool create_local_port(Self& self, std::string_view portName, spa_direction direction)
{
assert(this->global_context);
assert(this->filter);
if (portName.empty())
portName = direction == SPA_DIRECTION_INPUT ? "i" : "o";
if (!this->filter->port)
{
this->filter->create_local_port(portName.data(), direction);
}
if (!this->filter->port)
{
self.template error<driver_error>(self.configuration, "PipeWire: error creating port");
return false;
}
return true;
}
void add_callbacks(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)
return;
bool unfiltered = conf.track_any;
unfiltered |= (port.physical && conf.track_hardware);
unfiltered |= (!port.physical && conf.track_virtual);
if (unfiltered)
{
if (port.direction == SPA_DIRECTION_INPUT)
{
if (conf.output_added)
conf.output_added(to_port_info<SPA_DIRECTION_INPUT>(port));
}
else
{
if (conf.input_added)
conf.input_added(to_port_info<SPA_DIRECTION_OUTPUT>(port));
}
}
};
global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) {
if (port.format.find("midi") == std::string::npos)
return;
bool unfiltered = conf.track_any;
unfiltered |= (port.physical && conf.track_hardware);
unfiltered |= (!port.physical && conf.track_virtual);
if (unfiltered)
{
if (port.direction == SPA_DIRECTION_INPUT)
{
if (conf.output_removed)
conf.output_removed(to_port_info<SPA_DIRECTION_INPUT>(port));
}
else
{
if (conf.input_removed)
conf.input_removed(to_port_info<SPA_DIRECTION_OUTPUT>(port));
}
}
};
}
void start_thread()
{
if (!this->global_context->owns_main_loop)
return;
current_state = poll_state::start_poll;
main_loop_thread = std::jthread{[this]() { run_poll_loop(); }};
}
void stop_thread()
{
assert(this->global_context);
if (!this->global_context->owns_main_loop)
return;
if (main_loop_thread.joinable() || current_state != poll_state::not_in_poll)
{
termination_event.notify();
main_loop_thread.request_stop();
termination_event.notify();
for (int i = 0; i < 100; i++)
{
if (current_state == poll_state::not_in_poll)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
termination_event.notify();
}
if (main_loop_thread.joinable())
main_loop_thread.join();
}
}
void do_close_port()
{
if (!this->filter)
return;
if (!this->filter->port)
return;
if (!this->global_context->owns_main_loop)
{
this->canary.reset();
this->thread_lock.prepare_release_client();
}
unlink_ports();
this->filter->remove_port();
}
void rename_port(std::string_view port_name)
{
if (this->filter)
this->filter->rename_port(port_name);
}
void unlink_ports()
{
if (link)
{
this->global_context->unlink_ports(link);
link = nullptr;
}
}
bool link_ports(auto& self, const input_port& in_port)
{
// Wait for the pipewire server to send us back our node's info
for (int i = 0; i < 1000; i++)
this->filter->synchronize_node();
auto this_node = this->filter->filter_node_id();
auto& midi = this->global_context->current_graph.software_midi;
auto node_it = midi.find(this_node);
if (node_it == midi.end())
{
std::cerr << "Node " << this_node << " not found! \n";
return false;
}
// Wait for the pipewire server to send us back our node's ports
this->filter->synchronize_ports(node_it->second);
if (node_it->second.inputs.empty())
{
std::cerr << "Node " << this_node << " has no ports! \n";
return false;
}
// Link ports
const auto& p = node_it->second.inputs.front();
link = this->global_context->link_ports(in_port.port, p.id);
pw_loop_iterate(this->global_context->lp, 1);
if (!link)
{
self.template error<invalid_parameter_error>(
self.configuration,
"PipeWire: could not connect to port: " + in_port.port_name + " -> " + p.port_name);
return false;
}
return true;
}
bool link_ports(auto& self, const output_port& out_port)
{
// Wait for the pipewire server to send us back our node's info
for (int i = 0; i < 1000; i++)
this->filter->synchronize_node();
auto this_node = this->filter->filter_node_id();
auto& midi = this->global_context->current_graph.software_midi;
auto node_it = midi.find(this_node);
if (node_it == midi.end())
{
std::cerr << "Node " << this_node << " not found! \n";
return false;
}
// Wait for the pipewire server to send us back our node's ports
this->filter->synchronize_ports(node_it->second);
if (node_it->second.outputs.empty())
{
std::cerr << "Node " << this_node << " has no ports! \n";
return false;
}
// Link ports
const auto& p = node_it->second.outputs.front();
link = this->global_context->link_ports(p.id, out_port.port);
pw_loop_iterate(this->global_context->lp, 1);
if (!link)
{
self.template error<invalid_parameter_error>(
self.configuration,
"PipeWire: could not connect to port: " + p.port_name + " -> " + out_port.port_name);
return false;
}
return true;
}
template <spa_direction Direction>
static auto to_port_info(const pipewire_context::port_info& port)
-> std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>
{
std::string device_name, port_name;
auto name_colon = port.port_alias.find(':');
if (name_colon != std::string::npos)
{
device_name = port.port_alias.substr(0, name_colon);
port_name = port.port_alias.substr(name_colon + 1);
}
else
{
port_name = port.port_alias;
}
return {{
.client = 0,
.port = port.id,
.manufacturer = "",
.device_name = device_name,
.port_name = port.port_name,
.display_name = port_name,
}};
}
// 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 pipewire_context& ctx) noexcept -> std::vector<
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
{
std::vector<std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
ret;
{
std::lock_guard _{ctx.current_graph.mtx};
for (auto& node : ctx.current_graph.physical_midi)
{
for (auto& p :
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{
ret.push_back(to_port_info<Direction>(p));
}
}
for (auto& node : ctx.current_graph.software_midi)
{
for (auto& p :
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{
ret.push_back(to_port_info<Direction>(p));
}
}
}
return ret;
}
};
}
@@ -0,0 +1,125 @@
#pragma once
#include <libremidi/backends/pipewire/config.hpp>
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <chrono>
namespace libremidi
{
class midi_in_pipewire final
: public midi1::in_api
, public pipewire_helpers
, public error_handler
{
public:
struct
: input_configuration
, pipewire_input_configuration
{
} configuration;
explicit midi_in_pipewire(input_configuration&& conf, pipewire_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
create_context(*this);
create_filter(*this);
}
~midi_in_pipewire() override
{
stop_thread();
do_close_port();
destroy_filter(*this);
destroy_context();
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_pipewire: set_client_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
bool open_port(const input_port& in_port, std::string_view name) override
{
if (!create_local_port(*this, name, SPA_DIRECTION_INPUT))
return false;
if (!link_ports(*this, in_port))
return false;
start_thread();
return true;
}
bool open_virtual_port(std::string_view name) override
{
if (!create_local_port(*this, name, SPA_DIRECTION_INPUT))
return false;
start_thread();
return true;
}
void close_port() override
{
stop_thread();
do_close_port();
}
void set_port_name(std::string_view port_name) override { 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_Midi)
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(
{data, data + size}, m_processing.timestamp<timestamp_info>(to_ns, c->offset));
}
pw.filter_queue_buffer(this->filter->port, b);
}
midi1::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,171 @@
#pragma once
#include <libremidi/backends/pipewire/config.hpp>
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <readerwriterqueue.h>
#include <semaphore>
namespace libremidi
{
class midi_out_pipewire
: public midi1::out_api
, public pipewire_helpers
, public error_handler
{
public:
struct
: output_configuration
, pipewire_output_configuration
{
} configuration;
midi_out_pipewire(output_configuration&& conf, pipewire_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
create_context(*this);
create_filter(*this);
}
~midi_out_pipewire() override
{
stop_thread();
do_close_port();
destroy_filter(*this);
destroy_context();
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_pipewire: set_client_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
bool open_port(const output_port& out_port, std::string_view name) override
{
if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT))
return false;
this->filter->set_port_buffer(configuration.output_buffer_size);
if (!link_ports(*this, out_port))
return false;
start_thread();
return true;
}
bool open_virtual_port(std::string_view name) override
{
if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT))
return false;
this->filter->set_port_buffer(configuration.output_buffer_size);
start_thread();
return true;
}
void close_port() override
{
stop_thread();
do_close_port();
}
void set_port_name(std::string_view port_name) override { 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;
if (m.empty())
{
m_queue.pop();
continue;
}
// TODO why
if (m.bytes[0] == 0xff)
{
m_queue.pop();
continue;
}
spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi);
int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size());
// Try again next buffer
if (res == -ENOSPC)
break;
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;
}
void send_message(const unsigned char* message, size_t size) override
{
m_queue.enqueue(libremidi::message(midi_bytes{message, message + size}, 0));
}
int convert_timestamp(int64_t user) const noexcept
{
switch (configuration.timestamps)
{
case timestamp_mode::AudioFrame:
return static_cast<int>(user);
default:
// TODO
return 0;
}
}
void schedule_message(int64_t ts, const unsigned char* message, size_t size) override
{
m_queue.enqueue(
libremidi::message(midi_bytes{message, message + size}, convert_timestamp(ts)));
}
moodycamel::ReaderWriterQueue<libremidi::message> m_queue;
std::atomic_int64_t m_process_clock = 0;
};
}
@@ -0,0 +1,74 @@
#pragma once
#include <libremidi/backends/pipewire/config.hpp>
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <unordered_set>
namespace libremidi
{
class observer_pipewire final
: public observer_api
, private pipewire_helpers
, private error_handler
{
public:
struct
: observer_configuration
, pipewire_observer_configuration
{
} configuration;
explicit observer_pipewire(observer_configuration&& conf, pipewire_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
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->start_thread();
}
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_OUTPUT>(*this->global_context);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_INPUT>(*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;
std::unordered_set<std::string> seen_output_ports;
};
}
@@ -0,0 +1,153 @@
#pragma once
#if __has_include(<boost/lockfree/spsc_queue.hpp>)
#include <libremidi/backends/pipewire/config.hpp>
#include <libremidi/backends/pipewire/helpers.hpp>
#include <libremidi/shared_context.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <variant>
namespace libremidi::pipewire
{
// Create a PipeWire client which will be shared across objects
struct shared_handler : public libremidi::shared_context
{
explicit shared_handler(std::string_view v)
{
midiin_callbacks.reserve(64);
midiout_callbacks.reserve(64);
pipewire_status_t status{};
client = pipewire_client_open(v.data(), PipewireNoStartServer, &status);
assert(client);
assert(status == 0);
pipewire_set_process_callback(
client,
+[](pipewire_nframes_t cnt, void* ctx) -> int {
((shared_handler*)ctx)->pipewire_callback(cnt);
return 0;
},
this);
}
virtual void start_processing() override { pipewire_activate(client); }
virtual void stop_processing() override { pipewire_deactivate(client); }
static shared_configurations make(std::string_view client_name)
{
auto clt = std::make_shared<shared_handler>(client_name);
auto add_in_cb = [client = std::weak_ptr{clt}](libremidi::pipewire_callback cb) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::in_callback_added, std::move(cb)});
};
auto clear_in_cb = [client = std::weak_ptr{clt}](int64_t index) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::in_callback_removed, index});
};
auto add_out_cb = [client = std::weak_ptr{clt}](libremidi::pipewire_callback cb) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::out_callback_added, std::move(cb)});
};
auto clear_out_cb = [client = std::weak_ptr{clt}](int64_t index) {
if (auto clt = client.lock())
clt->events.push({shared_handler::event_type::out_callback_removed, index});
};
return {
.context = clt,
.observer = pipewire_observer_configuration{.context = clt->client},
.in
= pipewire_input_configuration{.context = clt->client, .set_process_func = add_in_cb, .clear_process_func = clear_in_cb},
.out
= pipewire_output_configuration{.context = clt->client, .set_process_func = add_out_cb, .clear_process_func = clear_out_cb},
};
}
int pipewire_callback(pipewire_nframes_t cnt)
{
// 1. Process the events that will change the callback list
event ev;
while (events.pop(ev))
{
switch (ev.type)
{
case in_callback_added:
midiin_callbacks.push_back(
std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));
break;
case in_callback_removed: {
auto idx = *std::get_if<int64_t>(&ev.payload);
for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)
{
if (it->token == idx)
{
midiin_callbacks.erase(it);
break;
}
else
{
++it;
}
}
break;
}
case out_callback_added:
midiout_callbacks.push_back(
std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));
break;
case out_callback_removed:
auto idx = *std::get_if<int64_t>(&ev.payload);
for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)
{
if (it->token == idx)
{
midiout_callbacks.erase(it);
break;
}
else
{
++it;
}
}
break;
}
}
for (auto& cb : midiin_callbacks)
cb.callback(cnt);
for (auto& cb : midiout_callbacks)
cb.callback(cnt);
return 0;
}
~shared_handler()
{
pipewire_deactivate(client);
pipewire_client_close(client);
}
pipewire_client_t* client{};
enum event_type
{
in_callback_added,
in_callback_removed,
out_callback_added,
out_callback_removed,
};
struct event
{
event_type type;
std::variant<libremidi::pipewire_callback, int64_t> payload;
};
boost::lockfree::spsc_queue<event> events{16};
std::vector<libremidi::pipewire_callback> midiin_callbacks;
std::vector<libremidi::pipewire_callback> midiout_callbacks;
};
}
#endif
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <libremidi/backends/winmidi/midi_in.hpp>
#include <libremidi/backends/winmidi/midi_out.hpp>
#include <libremidi/backends/winmidi/observer.hpp>
namespace libremidi::winmidi
{
struct backend
{
using midi_in = midi_in_impl;
using midi_out = midi_out_impl;
using midi_observer = observer_impl;
using midi_in_configuration = winmidi::input_configuration;
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 constexpr inline bool available() noexcept { return true; }
};
} // namespace libremidi
@@ -0,0 +1,19 @@
#pragma once
#include <libremidi/config.hpp>
namespace libremidi::winmidi
{
struct input_configuration
{
};
struct output_configuration
{
};
struct observer_configuration
{
};
}
@@ -0,0 +1,27 @@
#pragma once
// clang-format off
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN 1
#include <libremidi/detail/midi_api.hpp>
#include <mutex>
#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/Microsoft.Devices.Midi2.h>
// clang-format on
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 Microsoft::Devices::Midi2;
using namespace Windows::Devices::Enumeration;
}
@@ -0,0 +1,94 @@
#pragma once
#include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmidi/helpers.hpp>
#include <libremidi/backends/winmidi/observer.hpp>
#include <libremidi/detail/midi_in.hpp>
namespace libremidi::winmidi
{
class midi_in_impl final
: public midi2::in_api
, public error_handler
{
public:
struct
: libremidi::ump_input_configuration
, winmidi::input_configuration
{
} configuration;
explicit midi_in_impl(
libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
~midi_in_impl() override { close_port(); }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_in_winmidi: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_winmidi: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_winmidi: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::WINDOWS_MIDI_SERVICES;
}
bool open_port(const input_port& port, std::string_view) override
{
#if 0
const auto id = winrt::to_hstring(port.port_name);
if (id.empty())
return false;
port_ = get(MidiInPort::FromIdAsync(id));
if (!port_)
return false;
port_.MessageReceived(
[=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort,
const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) {
this->process_message(args.Message());
});
#endif
return true;
}
#if 0
void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)
{
auto reader = DataReader::FromBuffer(msg.RawData());
auto begin = msg.RawData().data();
auto end = begin + msg.RawData().Length();
auto t = msg.Timestamp().count();
this->configuration.on_message(libremidi::message{{begin, end}, t});
}
#endif
void close_port() override
{
#if 0
if (port_)
{
port_.Close();
port_ = nullptr;
}
#endif
}
private:
// winrt::Microsoft::Devices::Midi2::IMidiInPort port_{nullptr};
};
}
@@ -0,0 +1,91 @@
#pragma once
#include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmidi/helpers.hpp>
#include <libremidi/backends/winmidi/observer.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi::winmidi
{
class midi_out_impl final
: public midi2::out_api
, public error_handler
{
public:
struct
: libremidi::output_configuration
, winmidi::output_configuration
{
} configuration;
midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
~midi_out_impl() override { close_port(); }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_out_winmidi: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_winmidi: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_winmidi: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::WINDOWS_MIDI_SERVICES;
}
bool open_port(const output_port& port, std::string_view) override
{
#if 0
const auto id = winrt::to_hstring(port.port_name);
if (id.empty())
return false;
port_ = get(MidiOutPort::FromIdAsync(id));
return bool(port_);
#endif
return true;
}
void close_port() override
{
#if 0
if (port_)
{
port_.Close();
port_ = {};
}
#endif
}
void send_ump(const uint32_t* message, size_t size) override
{
#if 0
if (!port_)
return;
InMemoryRandomAccessStream str;
DataWriter rb(str);
rb.WriteBytes(
winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size});
port_.SendBuffer(rb.DetachBuffer());
#endif
}
private:
#if 0
winrt::Windows::Devices::Midi2::IMidiOutPort port_{nullptr};
#endif
};
}
@@ -0,0 +1,151 @@
#pragma once
#include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmidi/helpers.hpp>
#include <libremidi/detail/observer.hpp>
;
namespace libremidi::winmidi
{
struct port_info
{
hstring id;
hstring name;
};
class observer_impl final : public observer_api
{
public:
struct
: libremidi::observer_configuration
, winmidi::observer_configuration
{
} configuration;
MidiSession session;
explicit observer_impl(
libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, session{
MidiSession::CreateNewSession(L"libremidi session", MidiSessionSettings::Default())}
{
if (!configuration.has_callbacks())
return;
if (configuration.notify_in_constructor)
{
if (configuration.input_added)
for (const auto& p : get_input_ports())
configuration.input_added(p);
if (configuration.output_added)
for (const auto& p : get_output_ports())
configuration.output_added(p);
}
/*
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); });
*/
}
~observer_impl()
{
if (!configuration.has_callbacks())
return;
// internalInPortObserver_.PortAdded(evTokenOnInputAdded_);
// internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);
// internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);
// internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);
}
libremidi::API get_current_api() const noexcept override
{
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>
{
return {
{.client = 0,
.port = 0,
.manufacturer = "",
.device_name = "",
.port_name = to_string(p.Id()),
.display_name = to_string(p.Name())}};
}
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& selectedEndpointInformation : endpointDevices)
{
// FIXME if(has input...)
ret.emplace_back(to_port_info(selectedEndpointInformation));
}
return ret;
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
std::vector<libremidi::output_port> ret;
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
for (const auto& selectedEndpointInformation : endpointDevices)
{
// FIXME if(has output...)
ret.emplace_back(to_port_info(selectedEndpointInformation));
}
return ret;
}
void on_input_added(const DeviceInformation& name)
{
if (configuration.input_added)
configuration.input_added(to_port_info(name));
}
void on_input_removed(const DeviceInformation& name)
{
if (configuration.input_removed)
configuration.input_removed(to_port_info(name));
}
void on_output_added(const DeviceInformation& name)
{
if (configuration.output_added)
configuration.output_added(to_port_info(name));
}
void on_output_removed(const DeviceInformation& name)
{
if (configuration.output_removed)
configuration.output_removed(to_port_info(name));
}
private:
#if 0
static inline observer_winmidi_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};
static inline observer_winmidi_internal internalOutPortObserver_{
MidiOutPort::GetDeviceSelector()};
int evTokenOnInputAdded_{-1};
int evTokenOnInputRemoved_{-1};
int evTokenOnOutputAdded_{-1};
int evTokenOnOutputRemoved_{-1};
#endif
};
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <libremidi/backends/winmm/midi_in.hpp>
#include <libremidi/backends/winmm/midi_out.hpp>
#include <libremidi/backends/winmm/observer.hpp>
// 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
//*********************************************************************//
// API: Windows Multimedia Library (MM)
//*********************************************************************//
// API information deciphered from:
// -
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp
// Thanks to Jean-Baptiste Berruchon for the sysex code.
namespace libremidi
{
struct winmm_backend
{
using midi_in = midi_in_winmm;
using midi_out = midi_out_winmm;
using midi_observer = observer_winmm;
using midi_in_configuration = winmm_input_configuration;
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 constexpr inline bool available() noexcept { return true; }
};
}
@@ -0,0 +1,31 @@
#pragma once
#include <libremidi/config.hpp>
#include <chrono>
#include <functional>
namespace libremidi
{
struct winmm_input_configuration
{
int sysex_buffer_size = 1024;
int sysex_buffer_count = 4;
};
struct winmm_output_configuration
{
};
struct poll_parameters
{
std::function<void()> callback;
};
struct winmm_observer_configuration
{
std::chrono::milliseconds poll_period{100};
std::function<void(const poll_parameters&)> manual_poll;
};
}
@@ -0,0 +1,81 @@
#pragma once
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN 1
#include <libremidi/detail/midi_api.hpp>
#include <algorithm>
// clang-format off
#include <windows.h>
#include <mmsystem.h>
// clang-format on
namespace libremidi
{
// Convert a nullptr-terminated wide string or ANSI-encoded string to UTF-8.
inline std::string ConvertToUTF8(const TCHAR* str)
{
std::string u8str;
const WCHAR* wstr = L"";
#if defined(UNICODE) || defined(_UNICODE)
wstr = str;
#else
// Convert from ANSI encoding to wide string
int wlength = MultiByteToWideChar(CP_ACP, 0, str, -1, nullptr, 0);
std::wstring wstrtemp;
if (wlength)
{
wstrtemp.assign(wlength - 1, 0);
MultiByteToWideChar(CP_ACP, 0, str, -1, &wstrtemp[0], wlength);
wstr = &wstrtemp[0];
}
#endif
// Convert from wide string to UTF-8
int length = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
if (length)
{
u8str.assign(static_cast<std::string::size_type>(length - 1), 0);
/*length =*/WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &u8str[0], length, nullptr, nullptr);
}
return u8str;
}
// Next functions add the portNumber to the name so that
// the device's names are sure to be listed with individual names
// even when they have the same brand name
inline void MakeUniqueInPortName(std::string& deviceName, unsigned int portNumber)
{
int x = 1;
for (unsigned int i = 0; i < portNumber; i++)
{
MIDIINCAPS deviceCaps;
midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS));
auto stringName = ConvertToUTF8(deviceCaps.szPname);
if (deviceName == stringName)
{
x++;
}
}
deviceName += " ";
deviceName += std::to_string(x);
}
inline void MakeUniqueOutPortName(std::string& deviceName, unsigned int portNumber)
{
int x = 1;
for (unsigned int i = 0; i < portNumber; i++)
{
MIDIOUTCAPS deviceCaps;
midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS));
auto stringName = ConvertToUTF8(deviceCaps.szPname);
if (deviceName == stringName)
{
x++;
}
}
deviceName += " ";
deviceName += std::to_string(x);
}
}
@@ -0,0 +1,309 @@
#pragma once
#include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winmm/helpers.hpp>
#include <libremidi/backends/winmm/observer.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
{
class midi_in_winmm final
: public midi1::in_api
, public error_handler
{
public:
struct
: input_configuration
, winmm_input_configuration
{
} configuration;
explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
// We'll issue a warning here if no devices are available but not
// throw an error since the user can plugin something later.
if (midiInGetNumDevs() == 0)
{
warning(
configuration, "midi_in_winmm::initialize: no MIDI input devices currently available.");
}
if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400))
{
warning(
configuration,
"midi_in_winmm::initialize: InitializeCriticalSectionAndSpinCount failed.");
}
}
~midi_in_winmm() override
{
// Close a connection if it exists.
midi_in_winmm::close_port();
DeleteCriticalSection(&(this->_mutex));
}
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_in_winmm: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_winmm: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_winmm: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
bool do_open(unsigned int portNumber)
{
MMRESULT result = midiInOpen(
&this->inHandle, portNumber, std::bit_cast<DWORD_PTR>(&midiInputCallback),
std::bit_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
if (result != MMSYSERR_NOERROR)
{
error<driver_error>(
configuration, "midi_in_winmm::open_port: error creating Windows MM MIDI input port.");
return false;
}
// Allocate and init the sysex buffers.
const auto bufferCount = static_cast<std::size_t>(configuration.sysex_buffer_count);
this->sysexBuffer.resize(bufferCount);
for (std::size_t i = 0; i < bufferCount; ++i)
{
this->sysexBuffer[i] = new MIDIHDR;
this->sysexBuffer[i]->lpData = new char[configuration.sysex_buffer_size];
this->sysexBuffer[i]->dwBufferLength = static_cast<DWORD>(configuration.sysex_buffer_size);
this->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator
this->sysexBuffer[i]->dwFlags = 0;
result = midiInPrepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
midiInClose(this->inHandle);
this->inHandle = nullptr;
error<driver_error>(
configuration,
"midi_in_winmm::open_port: error starting Windows MM MIDI input port "
"(PrepareHeader).");
return false;
}
// Register the buffer.
result = midiInAddBuffer(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
midiInClose(this->inHandle);
this->inHandle = nullptr;
error<driver_error>(
configuration,
"midi_in_winmm::open_port: error starting Windows MM MIDI input port "
"(AddBuffer).");
return false;
}
}
result = midiInStart(this->inHandle);
midi_start_timestamp = std::chrono::steady_clock::now();
if (result != MMSYSERR_NOERROR)
{
midiInClose(this->inHandle);
this->inHandle = nullptr;
error<driver_error>(
configuration, "midi_in_winmm::open_port: error starting Windows MM MIDI input port.");
return false;
}
return true;
}
bool open_port(const input_port& p, std::string_view) override
{
observer_winmm obs{{}, winmm_observer_configuration{}};
auto ports = obs.get_input_ports();
// First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2
for (auto& port : ports)
{
if (p.display_name == port.display_name)
return do_open(port.port);
}
// If nothing is found, try to check with the raw name
for (auto& port : ports)
{
if (p.port_name == port.port_name)
return do_open(port.port);
}
error<invalid_parameter_error>(
configuration, "midi_in_winmm::open_port: port not found: " + p.port_name);
return false;
}
void close_port() override
{
if (connected_)
{
EnterCriticalSection(&(this->_mutex));
midiInReset(this->inHandle);
midiInStop(this->inHandle);
for (std::size_t i = 0; i < static_cast<std::size_t>(configuration.sysex_buffer_count); ++i)
{
MMRESULT res{};
int wait_count = 5;
while (
((res = midiInUnprepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR)))
== MIDIERR_STILLPLAYING)
&& wait_count-- >= 0)
{
Sleep(1);
}
if (res != MMSYSERR_NOERROR)
{
warning(
configuration,
"midi_in_winmm::open_port: error closing Windows MM MIDI input "
"port (midiInUnprepareHeader).");
continue;
}
else
{
delete[] this->sysexBuffer[i]->lpData;
delete[] this->sysexBuffer[i];
}
}
midiInClose(this->inHandle);
this->inHandle = nullptr;
LeaveCriticalSection(&(this->_mutex));
}
}
private:
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - midi_start_timestamp)
.count();
}
static constexpr int bytes_for_message(uint8_t status)
{
if (status < 0xC0)
return 3;
else if (status < 0xE0)
return 2;
else if (status < 0xF0)
return 3;
else if (status == 0xF1)
return 2;
else if (status == 0xF2)
return 3;
else if (status == 0xF3)
return 2;
else if (status == 0xF8)
return 1;
else if (status == 0xFE)
return 1;
else
return 0;
}
static void CALLBACK midiInputCallback(
HMIDIIN /*hmin*/, UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage,
DWORD_PTR timestamp)
{
if (inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR)
return;
auto& self = *reinterpret_cast<midi_in_winmm*>(instancePtr);
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = false,
.has_samples = false,
};
const auto to_ns = [timestamp] { return timestamp * 1'000'000; };
if (inputStatus == MIM_DATA)
{
// Channel or system message
uint8_t message[sizeof(DWORD_PTR)];
memcpy(message, &midiMessage, sizeof(DWORD_PTR));
// Make sure the first byte is a status byte.
if (message[0] & 0x80)
{
self.m_processing.on_bytes(
{message, message + bytes_for_message(message[0])},
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
}
}
else
{
// Sysex message ( MIM_LONGDATA or MIM_LONGERROR )
const auto* sysex = reinterpret_cast<MIDIHDR*>(midiMessage);
if(inputStatus == MIM_LONGERROR)
{
self.m_processing.message.bytes.clear();
self.m_processing.state = self.m_processing.main;
}
else if (!self.configuration.ignore_sysex)
{
if(sysex->dwBytesRecorded > 0)
{
const auto sysex_bytes = reinterpret_cast<uint8_t*>(sysex->lpData);
self.m_processing.on_bytes(
{sysex_bytes, sysex_bytes + sysex->dwBytesRecorded},
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
}
}
// The WinMM API requires that the sysex buffer be requeued after
// input of each sysex message. Even if we are ignoring sysex
// messages, we still need to requeue the buffer in case the user
// decides to not ignore sysex messages in the future. However,
// it seems that WinMM calls this function with an empty sysex
// buffer when an application closes and in this case, we should
// avoid requeueing it, else the computer suddenly reboots after
// one or two minutes.
if (self.sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0)
{
EnterCriticalSection(&(self._mutex));
MMRESULT result
= midiInAddBuffer(self.inHandle, self.sysexBuffer[sysex->dwUser], sizeof(MIDIHDR));
LeaveCriticalSection(&(self._mutex));
if (result != MMSYSERR_NOERROR)
{
#if defined(__LIBREMIDI_DEBUG__)
std::cerr << "\nmidi_in::midiInputCallback: error sending sysex to "
"Midi device!!\n\n";
#endif
}
}
}
}
HMIDIIN inHandle; // Handle to Midi Input Device
std::vector<LPMIDIHDR> sysexBuffer;
// [Patrice] see
// https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
CRITICAL_SECTION _mutex;
std::chrono::steady_clock::time_point midi_start_timestamp;
midi1::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,181 @@
#pragma once
#include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winmm/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
{
class midi_out_winmm final
: public midi1::out_api
, public error_handler
{
public:
struct
: output_configuration
, winmm_output_configuration
{
} configuration;
midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
// We'll issue a warning here if no devices are available but not
// throw an error since the user can plug something in later.
if (midiOutGetNumDevs() == 0)
{
warning(
configuration,
"midi_out_winmm::initialize: no MIDI output devices currently "
"available.");
}
}
~midi_out_winmm() override
{
// Close a connection if it exists.
midi_out_winmm::close_port();
}
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_out_winmm: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_winmm: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_winmm: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
[[nodiscard]] bool do_open(unsigned int portNumber)
{
MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL);
if (result != MMSYSERR_NOERROR)
{
error<driver_error>(
configuration,
"midi_out_winmm::open_port: error creating Windows MM MIDI output "
"port.");
return false;
}
return true;
}
bool open_port(const output_port& p, std::string_view) override
{
observer_winmm obs{{}, winmm_observer_configuration{}};
auto ports = obs.get_output_ports();
// First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2
for (auto& port : ports)
{
if (p.display_name == port.display_name)
return do_open(port.port);
}
// If nothing is found, try to check with the raw name
for (auto& port : ports)
{
if (p.port_name == port.port_name)
return do_open(port.port);
}
error<invalid_parameter_error>(
configuration, "midi_out_winmm::open_port: port not found: " + p.port_name);
return false;
}
void close_port() override
{
if (this->outHandle)
midiOutClose(this->outHandle);
this->outHandle = nullptr;
connected_ = false;
}
void send_message(const unsigned char* message, size_t size) override
{
if (!connected_)
return;
if (size == 0)
{
warning(configuration, "midi_out_winmm::send_message: message argument is empty!");
return;
}
if (message[0] == 0xF0)
{ // Sysex message
buffer.assign(message, message + size);
// FIXME this can be made asynchronous... see Chrome source.
// But need to know whe buffers are freed.
// Create and prepare MIDIHDR structure.
MIDIHDR sysex{};
sysex.lpData = (LPSTR)buffer.data();
sysex.dwBufferLength = size;
sysex.dwFlags = 0;
auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
error<driver_error>(
configuration, "midi_out_winmm::send_message: error preparing sysex header.");
return;
}
// Send the message.
result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
error<driver_error>(
configuration, "midi_out_winmm::send_message: error sending sysex message.");
return;
}
// Unprepare the buffer and MIDIHDR.
// FIXME yuck
while (MIDIERR_STILLPLAYING
== midiOutUnprepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)))
Sleep(1);
}
else
{ // Channel or system message.
// Make sure the message size isn't too big.
if (size > 3)
{
warning(
configuration,
"midi_out_winmm::send_message: message size is greater than 3 bytes "
"(and not sysex)!");
return;
}
// Pack MIDI bytes into double word.
DWORD packet;
std::copy_n(message, size, (unsigned char*)&packet);
// Send the message immediately.
auto result = midiOutShortMsg(this->outHandle, packet);
if (result != MMSYSERR_NOERROR)
{
error<driver_error>(
configuration, "midi_out_winmm::send_message: error sending MIDI message.");
}
}
}
private:
HMIDIOUT outHandle; // Handle to Midi Output Device
std::vector<char> buffer;
};
}
@@ -0,0 +1,271 @@
#pragma once
#include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winmm/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <condition_variable>
#include <mutex>
#include <ranges>
#include <thread>
namespace libremidi
{
class observer_winmm : public observer_api
{
public:
struct
: observer_configuration
, winmm_observer_configuration
{
} configuration;
explicit observer_winmm(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (!configuration.has_callbacks())
return;
if (configuration.notify_in_constructor)
check_new_ports<true>();
else
check_new_ports<false>();
}
~observer_winmm() { }
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
std::vector<input_port> get_input_ports() const noexcept override
{
return get_port_list<true>();
}
std::vector<output_port> get_output_ports() const noexcept override
{
return get_port_list<false>();
}
protected:
template <bool Notify>
void check_new_ports()
{
auto currInputPortList = get_port_list<true>();
if constexpr (Notify)
{
compare_port_lists_and_notify_clients(
inputPortList, currInputPortList, configuration.input_added,
configuration.input_removed);
}
inputPortList = std::move(currInputPortList);
auto currOutputPortList = get_port_list<false>();
if constexpr (Notify)
{
compare_port_lists_and_notify_clients(
outputPortList, currOutputPortList, configuration.output_added,
configuration.output_removed);
}
outputPortList = std::move(currOutputPortList);
}
void compare_port_lists_and_notify_clients(
const auto& prevList, const auto& currList, const auto& portAddedFunc,
const auto& portRemovedFunc)
{
if (portAddedFunc)
{
for (const auto& port : currList)
{
auto iter
= std::ranges::find(prevList, port.display_name, &port_information::display_name);
if (iter == prevList.end())
portAddedFunc(port);
}
}
if (portRemovedFunc)
{
for (const auto& port : prevList)
{
auto iter
= std::ranges::find(currList, port.display_name, &port_information::display_name);
if (iter == currList.end())
portRemovedFunc(port);
}
}
}
input_port to_in_port_info(std::size_t index) const noexcept
{
MIDIINCAPS deviceCaps;
midiInGetDevCaps(index, &deviceCaps, sizeof(MIDIINCAPS));
auto rawName = ConvertToUTF8(deviceCaps.szPname);
auto portName = rawName;
MakeUniqueInPortName(portName, index);
return {
{.client = 0,
.port = index,
.manufacturer = "",
.device_name = "",
.port_name = rawName,
.display_name = portName}};
}
output_port to_out_port_info(std::size_t index) const noexcept
{
MIDIOUTCAPS deviceCaps;
midiOutGetDevCaps(index, &deviceCaps, sizeof(MIDIOUTCAPS));
auto rawName = ConvertToUTF8(deviceCaps.szPname);
auto portName = rawName;
MakeUniqueOutPortName(portName, index);
return {
{.client = 0,
.port = index,
.manufacturer = "",
.device_name = "",
.port_name = rawName,
.display_name = portName}};
}
template <bool Input>
auto get_port_list() const noexcept
-> std::vector<std::conditional_t<Input, input_port, output_port>>
{
std::vector<std::conditional_t<Input, input_port, output_port>> portList;
if constexpr (Input)
{
std::size_t nDevices = midiInGetNumDevs();
for (std::size_t i = 0; i < nDevices; ++i)
{
portList.push_back(to_in_port_info(i));
}
}
else
{
std::size_t nDevices = midiOutGetNumDevs();
for (std::size_t i = 0; i < nDevices; ++i)
{
portList.push_back(to_out_port_info(i));
}
}
return portList;
}
static constexpr bool INPUT = true;
static constexpr bool OUTPUT = false;
std::vector<input_port> inputPortList;
std::vector<output_port> outputPortList;
};
}
#if __has_include(<stop_token>) && __cpp_lib_jthread >= 201911L
#include <stop_token>
namespace libremidi::winmm
{
class observer_threaded final : public observer_winmm
{
public:
struct
: observer_configuration
, winmm_observer_configuration
{
} configuration;
explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
: observer_winmm{std::move(conf), std::move(apiconf)}
{
thread = std::jthread([this](std::stop_token tk) {
while (!tk.stop_requested())
{
check_new_ports<true>();
std::this_thread::sleep_for(this->configuration.poll_period);
}
});
}
private:
std::jthread thread;
};
}
#else
#include <atomic>
#include <semaphore>
namespace libremidi::winmm
{
class observer_threaded final : public observer_winmm
{
public:
struct
: observer_configuration
, winmm_observer_configuration
{
} configuration;
explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
: observer_winmm{std::move(conf), std::move(apiconf)}
, sema{0}
{
thread = std::thread([this] {
while (!stop_flag.test(std::memory_order_acquire))
{
check_new_ports<true>();
std::this_thread::sleep_for(this->configuration.poll_period);
}
sema.release();
});
}
~observer_threaded()
{
stop_flag.test_and_set();
sema.acquire();
thread.join();
}
private:
std::thread thread;
std::atomic_flag stop_flag = ATOMIC_FLAG_INIT;
std::binary_semaphore sema;
};
}
#endif
namespace libremidi::winmm
{
class observer_manual final : public observer_winmm
{
public:
struct
: observer_configuration
, winmm_observer_configuration
{
} configuration;
explicit observer_manual(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
: observer_winmm{std::move(conf), std::move(apiconf)}
{
this->configuration.manual_poll({.callback = [this] { this->check_new_ports<true>(); }});
}
~observer_manual() { }
};
}
namespace libremidi
{
template <>
inline std::unique_ptr<observer_api> make<observer_winmm>(
libremidi::observer_configuration&& conf, libremidi::winmm_observer_configuration&& api)
{
if (api.manual_poll)
return std::make_unique<winmm::observer_manual>(std::move(conf), std::move(api));
else
return std::make_unique<winmm::observer_threaded>(std::move(conf), std::move(api));
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <libremidi/backends/winuwp/midi_in.hpp>
#include <libremidi/backends/winuwp/midi_out.hpp>
#include <libremidi/backends/winuwp/observer.hpp>
namespace libremidi
{
struct winuwp_backend
{
using midi_in = midi_in_winuwp;
using midi_out = midi_out_winuwp;
using midi_observer = observer_winuwp;
using midi_in_configuration = winuwp_input_configuration;
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 constexpr inline bool available() noexcept { return true; }
};
} // namespace libremidi
@@ -0,0 +1,19 @@
#pragma once
#include <libremidi/config.hpp>
namespace libremidi
{
struct winuwp_input_configuration
{
};
struct winuwp_output_configuration
{
};
struct winuwp_observer_configuration
{
};
}
@@ -0,0 +1,63 @@
#pragma once
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN 1
#include <libremidi/detail/midi_api.hpp>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Midi.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Storage.Streams.h>
namespace libremidi
{
inline void winrt_init()
{
// init_apartment should only be called on the threads we own.
// Since we're the library we don't own the threads we are called from,
// so we should not perform this initialization ourselves.
// winrt::init_apartment();
}
namespace
{
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Devices::Midi;
using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Storage::Streams;
// Helper function to allow waiting for aynchronous operation completion
// from the thread in STA. The only benefit from it compared to the
// get() function from winrt is that we avoid an assertion if waiting
// from the STA thread.
template <typename T>
auto get(T const& async)
{
if (async.Status() != AsyncStatus::Completed)
{
slim_mutex m;
slim_condition_variable cv;
bool completed = false;
async.Completed([&](auto&&, auto&&) {
{
slim_lock_guard const guard(m);
completed = true;
}
cv.notify_one();
});
slim_lock_guard guard(m);
cv.wait(m, [&] { return completed; });
}
return async.GetResults();
}
}
}
@@ -0,0 +1,106 @@
#pragma once
#include <libremidi/backends/winuwp/config.hpp>
#include <libremidi/backends/winuwp/helpers.hpp>
#include <libremidi/backends/winuwp/observer.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
{
class midi_in_winuwp final
: public midi1::in_api
, public error_handler
{
public:
struct
: input_configuration
, winuwp_input_configuration
{
} configuration;
explicit midi_in_winuwp(input_configuration&& conf, winuwp_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
winrt_init();
}
~midi_in_winuwp() override { close_port(); }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_in_winuwp: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_winuwp: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_winuwp: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
bool open_port(const input_port& port, std::string_view) override
{
const auto id = winrt::to_hstring(port.port_name);
if (id.empty())
return false;
port_ = get(MidiInPort::FromIdAsync(id));
if (!port_)
return false;
midi_start_timestamp = std::chrono::steady_clock::now();
port_.MessageReceived(
[=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort,
const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) {
this->process_message(args.Message());
});
return true;
}
void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = false,
.has_samples = false,
};
auto reader = DataReader::FromBuffer(msg.RawData());
auto begin = msg.RawData().data();
auto end = begin + msg.RawData().Length();
const auto to_ns = [&msg] { return msg.Timestamp().count() * 1'000'000; };
m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
void close_port() override
{
if (port_)
{
port_.Close();
port_ = nullptr;
}
}
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - midi_start_timestamp)
.count();
}
private:
winrt::Windows::Devices::Midi::IMidiInPort port_{nullptr};
std::chrono::steady_clock::time_point midi_start_timestamp;
midi1::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,80 @@
#pragma once
#include <libremidi/backends/winuwp/config.hpp>
#include <libremidi/backends/winuwp/helpers.hpp>
#include <libremidi/backends/winuwp/observer.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
{
class midi_out_winuwp final
: public midi1::out_api
, public error_handler
{
public:
struct
: output_configuration
, winuwp_output_configuration
{
} configuration;
midi_out_winuwp(output_configuration&& conf, winuwp_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
winrt_init();
}
~midi_out_winuwp() override { close_port(); }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_out_winuwp: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_winuwp: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_winuwp: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
bool open_port(const output_port& port, std::string_view) override
{
const auto id = winrt::to_hstring(port.port_name);
if (id.empty())
return false;
port_ = get(MidiOutPort::FromIdAsync(id));
return bool(port_);
}
void close_port() override
{
if (port_)
{
port_.Close();
port_ = {};
}
}
void send_message(const unsigned char* message, size_t size) override
{
if (!port_)
return;
InMemoryRandomAccessStream str;
DataWriter rb(str);
rb.WriteBytes(
winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size});
port_.SendBuffer(rb.DetachBuffer());
}
private:
winrt::Windows::Devices::Midi::IMidiOutPort port_{nullptr};
};
}
@@ -0,0 +1,296 @@
#pragma once
#include <libremidi/backends/winuwp/config.hpp>
#include <libremidi/detail/observer.hpp>
namespace libremidi
{
class observer_winuwp_internal
{
public:
struct port_info
{
hstring id;
hstring name;
};
struct callback
{
int token{};
std::function<void(const port_info&)> function;
};
struct callbacks
{
std::vector<callback> cbs;
int current_token{};
void operator()(const port_info& p)
{
for (auto& cb : cbs)
cb.function(p);
}
int add(std::function<void(const port_info&)> f)
{
int tk = current_token++;
cbs.emplace_back(tk, f);
return tk;
}
void remove(int tk)
{
auto it = std::remove_if(
cbs.begin(), cbs.end(), [tk](const callback& c) { return c.token == tk; });
auto r = std::distance(it, cbs.end());
cbs.erase(it, cbs.end());
}
};
explicit observer_winuwp_internal(hstring deviceSelector) { initialize(deviceSelector); }
~observer_winuwp_internal() { terminate(); }
std::vector<port_info> get_ports() const
{
std::lock_guard<std::mutex> lock(portListMutex_);
return portList_;
}
unsigned int get_port_count() const
{
std::lock_guard<std::mutex> lock(portListMutex_);
return static_cast<unsigned int>(portList_.size());
}
bool get_port_info(unsigned int portNumber, port_info& portInfo) const
{
std::lock_guard<std::mutex> lock(portListMutex_);
if (portNumber >= portList_.size())
return false;
portInfo = portList_[portNumber];
return true;
}
hstring get_port_id(unsigned int portNumber) const
{
std::lock_guard<std::mutex> lock(portListMutex_);
return portNumber < portList_.size() ? portList_[portNumber].id : hstring{};
}
std::string get_port_name(unsigned int portNumber) const
{
std::lock_guard<std::mutex> lock(portListMutex_);
return portNumber < portList_.size() ? to_string(portList_[portNumber].name) : std::string{};
}
int PortAdded(const std::function<void(port_info)>& handler)
{
return portAddedEvent_.add(handler);
}
void PortAdded(int token) noexcept { portAddedEvent_.remove(token); }
int PortRemoved(const std::function<void(port_info)>& handler)
{
return portRemovedEvent_.add(handler);
}
void PortRemoved(int token) noexcept { portRemovedEvent_.remove(token); }
private:
observer_winuwp_internal(const observer_winuwp_internal&) = delete;
observer_winuwp_internal& operator=(const observer_winuwp_internal&) = delete;
private:
void initialize(hstring deviceSelector)
{
deviceWatcher_ = DeviceInformation::CreateWatcher(deviceSelector);
evTokenOnDeviceAdded_
= deviceWatcher_.Added({this, &observer_winuwp_internal::on_device_added});
evTokenOnDeviceRemoved_
= deviceWatcher_.Removed({this, &observer_winuwp_internal::on_device_removed});
evTokenOnDeviceUpdated_
= deviceWatcher_.Updated({this, &observer_winuwp_internal::on_device_updated});
evTokenOnDeviceEnumerationCompleted_ = deviceWatcher_.EnumerationCompleted(
{this, &observer_winuwp_internal::on_device_enumeration_completed});
deviceWatcher_.Start();
}
void terminate()
{
deviceWatcher_.Stop();
deviceWatcher_.EnumerationCompleted(evTokenOnDeviceEnumerationCompleted_);
deviceWatcher_.Updated(evTokenOnDeviceUpdated_);
deviceWatcher_.Removed(evTokenOnDeviceRemoved_);
deviceWatcher_.Added(evTokenOnDeviceAdded_);
}
void on_device_added(DeviceWatcher sender, DeviceInformation deviceInfo)
{
port_info p;
{
std::lock_guard<std::mutex> lock(portListMutex_);
p = port_info{deviceInfo.Id(), deviceInfo.Name()};
portList_.push_back(p);
}
portAddedEvent_(p);
}
void on_device_removed(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate)
{
const auto id = deviceUpdate.Id();
auto pred = [&id](const port_info& portInfo) { return portInfo.id == id; };
std::optional<port_info> p;
hstring name;
{
std::lock_guard<std::mutex> lock(portListMutex_);
auto iter = std::find_if(portList_.begin(), portList_.end(), pred);
if (iter != portList_.end())
{
p = *iter;
portList_.erase(iter);
}
}
if (p)
portRemovedEvent_(*p);
}
void on_device_updated(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) { }
void on_device_enumeration_completed(DeviceWatcher sender, IInspectable const&) { }
private:
std::vector<port_info> portList_;
mutable std::mutex portListMutex_;
DeviceWatcher deviceWatcher_{nullptr};
event_token evTokenOnDeviceAdded_;
event_token evTokenOnDeviceRemoved_;
event_token evTokenOnDeviceUpdated_;
event_token evTokenOnDeviceEnumerationCompleted_;
callbacks portAddedEvent_;
callbacks portRemovedEvent_;
};
class observer_winuwp final : public observer_api
{
public:
struct
: observer_configuration
, winuwp_observer_configuration
{
} configuration;
using port_info = observer_winuwp_internal::port_info;
explicit observer_winuwp(observer_configuration&& conf, winuwp_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (!configuration.has_callbacks())
return;
if (configuration.notify_in_constructor)
{
if (configuration.input_added)
for (const auto& p : get_input_ports())
configuration.input_added(p);
if (configuration.output_added)
for (const auto& p : get_output_ports())
configuration.output_added(p);
}
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); });
}
~observer_winuwp()
{
if (!configuration.has_callbacks())
return;
internalInPortObserver_.PortAdded(evTokenOnInputAdded_);
internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);
internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);
internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
template <bool Input>
auto to_port_info(const observer_winuwp_internal::port_info& p) const noexcept
-> std::conditional_t<Input, input_port, output_port>
{
return {
{.client = 0,
.port = 0,
.manufacturer = "",
.device_name = "",
.port_name = to_string(p.id),
.display_name = to_string(p.name)}};
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
std::vector<libremidi::input_port> ret;
for (auto& port : internalInPortObserver_.get_ports())
ret.push_back(to_port_info<true>(port));
return ret;
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
std::vector<libremidi::output_port> ret;
for (auto& port : internalOutPortObserver_.get_ports())
ret.push_back(to_port_info<false>(port));
return ret;
}
static observer_winuwp_internal& get_internal_in_port_observer()
{
return internalInPortObserver_;
}
static observer_winuwp_internal& get_internal_out_port_observer()
{
return internalOutPortObserver_;
}
void on_input_added(const observer_winuwp_internal::port_info& name)
{
if (configuration.input_added)
configuration.input_added(to_port_info<true>(name));
}
void on_input_removed(const observer_winuwp_internal::port_info& name)
{
if (configuration.input_removed)
configuration.input_removed(to_port_info<true>(name));
}
void on_output_added(const observer_winuwp_internal::port_info& name)
{
if (configuration.output_added)
configuration.output_added(to_port_info<false>(name));
}
void on_output_removed(const observer_winuwp_internal::port_info& name)
{
if (configuration.output_removed)
configuration.output_removed(to_port_info<false>(name));
}
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};
int evTokenOnOutputRemoved_{-1};
};
}
+47
View File
@@ -0,0 +1,47 @@
#if !defined(LIBREMIDI_HEADER_ONLY)
#include "client.hpp"
#endif
#include <libremidi/backends.hpp>
#include <libremidi/shared_context.hpp>
#ifdef LIBREMIDI_ALSA
#include <libremidi/backends/alsa_seq/shared_handler.hpp>
#endif
#ifdef LIBREMIDI_JACK
#include <libremidi/backends/jack/shared_handler.hpp>
#endif
namespace libremidi
{
LIBREMIDI_INLINE
shared_configurations
create_shared_context(const libremidi::API api, [[maybe_unused]] std::string_view client_name)
{
switch (api)
{
#if __has_include(<boost/lockfree/spsc_queue.hpp>)
#if defined(LIBREMIDI_ALSA)
case libremidi::API::ALSA_SEQ:
return alsa_seq::shared_handler::make(client_name);
#endif
#if defined(LIBREMIDI_JACK)
case libremidi::API::JACK_MIDI:
return jack::shared_handler::make(client_name);
#endif
#endif
case libremidi::API::COREMIDI:
case libremidi::API::COREMIDI_UMP:
// TODO
case libremidi::API::WINDOWS_MIDI_SERVICES:
// TODO
default:
return {
.context = {},
.observer = observer_configuration_for(api),
.in = midi_in_configuration_for(api),
.out = midi_out_configuration_for(api)};
}
}
}
+196
View File
@@ -0,0 +1,196 @@
#pragma once
#include <libremidi/configurations.hpp>
#include <libremidi/libremidi.hpp>
#include <libremidi/shared_context.hpp>
#include <map>
namespace libremidi::midi1
{
struct client_configuration
{
libremidi::API api = libremidi::midi1::default_api();
//! A client name, if the backend allows it (e.g. JACK, ALSA SEQ...)
std::string_view client_name = "libremidi client";
//! Set a callback function to be invoked for incoming MIDI messages.
//! Mandatory!
std::function<void(const libremidi::input_port&, message&&)> on_message
= [](const libremidi::input_port& /*port*/, libremidi::message&&) {};
//! Observation callbacks for when ports are added or removed
input_port_callback input_added;
input_port_callback input_removed;
output_port_callback output_added;
output_port_callback output_removed;
//! Error callback function to be invoked when an error has occured.
/*!
The callback function will be called whenever an error has occured. It is
best to set the error callback function before opening a port.
*/
midi_error_callback on_error{};
midi_error_callback on_warning{};
//! Poll period for observation polling operations, if relevant to the backend
std::chrono::milliseconds poll_period{100};
//! Specify whether certain MIDI message types should be queued or ignored
//! during input.
/*!
By default, MIDI timing and active sensing messages are ignored
during message input because of their relative high data rates.
MIDI sysex messages are ignored by default as well. Variable
values of "true" imply that the respective message type will be
ignored.
*/
uint32_t ignore_sysex : 1 = true;
uint32_t ignore_timing : 1 = true;
uint32_t ignore_sensing : 1 = true;
uint32_t timestamps : 3 = timestamp_mode::Absolute;
//! Observe hardware ports
uint32_t track_hardware : 1 = true;
//! Observe software (virtual) ports if the API provides it
uint32_t track_virtual : 1 = false;
};
class client
{
public:
explicit client(const client_configuration& conf)
: client{conf, create_shared_context(conf.api, conf.client_name)}
{
}
explicit client(const client_configuration& conf, shared_configurations ctx)
: configuration{conf}
, context{ctx}
, m_observer{
observer_configuration{
.on_error = conf.on_error,
.on_warning = conf.on_warning,
.input_added = conf.input_added,
.input_removed = conf.input_removed,
.output_added = conf.output_added,
.output_removed = conf.output_removed,
.track_hardware = conf.track_hardware,
.track_virtual = conf.track_virtual,
.notify_in_constructor = false},
context.observer}
{
if (context.context)
context.context->start_processing();
}
~client()
{
m_inputs.clear();
m_outputs.clear();
if (context.context)
context.context->stop_processing();
}
std::vector<libremidi::input_port> get_input_ports() const noexcept
{
return m_observer.get_input_ports();
}
std::vector<libremidi::output_port> get_output_ports() const noexcept
{
return m_observer.get_output_ports();
}
void add_input(const input_port& port, std::string_view name)
{
if (m_inputs.find(port) != m_inputs.end())
return;
auto res = m_inputs.try_emplace(
port,
input_configuration{
.on_message
= [this, port](libremidi::message&& m) {
configuration.on_message(port, std::move(m));
},
.get_timestamp = {},
.on_error = configuration.on_error,
.on_warning = configuration.on_warning,
.ignore_sysex = configuration.ignore_sysex,
.ignore_timing = configuration.ignore_timing,
.ignore_sensing = configuration.ignore_sensing,
.timestamps = configuration.timestamps},
context.in);
res.first->second.open_port(port, name);
}
void add_output(const output_port& port, std::string_view name)
{
if (m_outputs.find(port) != m_outputs.end())
return;
auto res = m_outputs.try_emplace(
port,
output_configuration{
.on_error = configuration.on_error,
.on_warning = configuration.on_warning,
.timestamps = configuration.timestamps},
context.out);
res.first->second.open_port(port, name);
}
void remove_input(const input_port& port) { m_inputs.erase(port); }
void remove_output(const output_port& port) { m_outputs.erase(port); }
void send_message(const unsigned char* message, size_t size)
{
for (auto& [_, out] : m_outputs)
{
out.send_message(message, size);
}
}
void send_ump(const uint32_t* message, size_t size)
{
for (auto& [_, out] : m_outputs)
{
out.send_ump(message, size);
}
}
void send_message(const output_port& port, const unsigned char* message, size_t size)
{
if (auto it = m_outputs.find(port); it != m_outputs.end())
it->second.send_message(message, size);
}
void send_ump(const output_port& port, const uint32_t* message, size_t size)
{
if (auto it = m_outputs.find(port); it != m_outputs.end())
it->second.send_ump(message, size);
}
private:
client_configuration configuration;
shared_configurations context;
std::map<input_port, midi_in> m_inputs;
std::map<output_port, midi_out> m_outputs;
observer m_observer;
};
}
#if defined(LIBREMIDI_HEADER_ONLY)
#include "client.cpp"
#endif
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#if defined(MSC_VER)
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN
#endif
#include <algorithm>
#include <cinttypes>
#include <memory>
#include <stdexcept>
#include <vector>
#if defined(LIBREMIDI_EXPORTS)
#if defined(_MSC_VER)
#define LIBREMIDI_EXPORT __declspec(dllexport)
#elif defined(__GNUC__) || defined(__clang__)
#define LIBREMIDI_EXPORT __attribute__((visibility("default")))
#endif
#else
#define LIBREMIDI_EXPORT
#endif
#define LIBREMIDI_VERSION "4.5.0"
#if defined(LIBREMIDI_USE_BOOST)
#if !__has_include(<boost/container/small_vector.hpp>)
#error \
"Boost was used for building libremidi but is not found when using it. Add Boost to your include paths."
#endif
#if defined(LIBREMIDI_NO_BOOST)
#error "Boost was used for building libremidi but LIBREMIDI_NO_BOOST is defined."
#endif
#endif
#if __has_include(<boost/container/small_vector.hpp>) && !defined(LIBREMIDI_NO_BOOST)
#if LIBREMIDI_SLIM_MESSAGE > 0
#include <boost/container/static_vector.hpp>
namespace libremidi
{
using midi_bytes = boost::container::static_vector<unsigned char, LIBREMIDI_SLIM_MESSAGE>;
}
#else
#include <boost/container/small_vector.hpp>
namespace libremidi
{
static constexpr int small_vector_minimum_size
= sizeof(boost::container::small_vector<unsigned char, 1>);
using midi_bytes = boost::container::small_vector<unsigned char, small_vector_minimum_size>;
}
#endif
#else
namespace libremidi
{
using midi_bytes = std::vector<unsigned char>;
}
#endif
#if __has_include(<midi/universal_packet.h>) && defined(LIBREMIDI_USE_NI_MIDI2)
#define LIBREMIDI_NI_MIDI2_COMPAT 1
#endif
#if defined(LIBREMIDI_HEADER_ONLY)
#define LIBREMIDI_INLINE inline
#else
#define LIBREMIDI_INLINE
#endif
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#if defined(__linux__)
#include <libremidi/backends/alsa_raw/config.hpp>
#include <libremidi/backends/alsa_raw_ump/config.hpp>
#include <libremidi/backends/alsa_seq/config.hpp>
#include <libremidi/backends/alsa_seq_ump/config.hpp>
#include <libremidi/backends/pipewire/config.hpp>
#endif
#if defined(__APPLE__)
#include <libremidi/backends/coremidi/config.hpp>
#include <libremidi/backends/coremidi_ump/config.hpp>
#endif
#if defined(_WIN32)
#include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winuwp/config.hpp>
#endif
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/config.hpp>
#endif
#include <libremidi/backends/jack/config.hpp>
namespace libremidi
{
struct dummy_configuration
{
};
}
+98
View File
@@ -0,0 +1,98 @@
#pragma once
#include <libremidi/api.hpp>
#include <libremidi/config.hpp>
#include <libremidi/input_configuration.hpp>
#include <libremidi/observer_configuration.hpp>
#include <libremidi/output_configuration.hpp>
#include <any>
#include <optional>
namespace libremidi
{
class midi_in;
class midi_out;
class observer;
//! Get the default midi in configuration object for a given API
LIBREMIDI_EXPORT
std::any 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);
//! Get the default observer configuration object for a given API
LIBREMIDI_EXPORT
std::any 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&);
//! Get a matching midi out configuration object for a given observer instance.
LIBREMIDI_EXPORT
std::any 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.
LIBREMIDI_EXPORT
std::optional<input_port> in_default_port(libremidi::API api) noexcept;
//! Get the default port for midi output (if any) in a given API.
//! Note that this will only return a hardware / physical port.
LIBREMIDI_EXPORT
std::optional<output_port> out_default_port(libremidi::API api) noexcept;
namespace midi1
{
//! Get a default configuration for creating a MIDI 1 input
LIBREMIDI_EXPORT
std::any in_default_configuration();
//! Get a default configuration for creating a MIDI 1 output
LIBREMIDI_EXPORT
std::any out_default_configuration();
//! Get a default configuration for creating a MIDI 1 observer
LIBREMIDI_EXPORT
std::any 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
{
return libremidi::in_default_port(default_api());
}
//! Get a default MIDI 1 output port for the default API of the system
inline std::optional<output_port> out_default_port() noexcept
{
return libremidi::out_default_port(default_api());
}
}
namespace midi2
{
//! Get a default configuration for creating a MIDI 2 input
LIBREMIDI_EXPORT
std::any in_default_configuration();
//! Get a default configuration for creating a MIDI 2 output
LIBREMIDI_EXPORT
std::any out_default_configuration();
//! Get a default configuration for creating a MIDI 2 observer
LIBREMIDI_EXPORT
std::any 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
{
return libremidi::in_default_port(default_api());
}
//! Get a default MIDI 2 output port for the default API of the system
inline std::optional<output_port> out_default_port() noexcept
{
return libremidi::out_default_port(default_api());
}
}
}
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <memory>
#include <mutex>
namespace libremidi
{
template <auto func>
struct deleter
{
template <typename U>
void operator()(U* x) noexcept(noexcept(func(x)))
{
func(x);
}
};
template <typename T, auto func>
using unique_handle = std::unique_ptr<T, deleter<func>>;
template <typename T>
std::shared_ptr<T> instance()
{
static std::mutex mut;
static std::weak_ptr<T> cache;
std::lock_guard _{mut};
if (auto ptr = cache.lock())
{
return ptr;
}
else
{
auto shared = std::make_shared<T>();
cache = shared;
return shared;
}
}
}
+87
View File
@@ -0,0 +1,87 @@
#pragma once
#include <libremidi/api.hpp>
#include <libremidi/config.hpp>
#include <libremidi/error.hpp>
#include <libremidi/observer_configuration.hpp>
#include <iostream>
#include <string_view>
namespace libremidi
{
struct error_handler
{
//! Error reporting function for libremidi classes. Throws.
template <typename Error_T>
void error(auto& configuration, std::string_view errorString) const
{
if (configuration.on_error)
{
if (first_error)
return;
first_error = true;
configuration.on_error(Error_T::code, errorString);
first_error = false;
}
else
{
#if defined(__LIBREMIDI_DEBUG__)
std::cerr << '\n' << errorString << "\n\n";
#endif
throw Error_T{errorString.data()};
}
}
//! Warning reporting function for libremidi classes.
void warning(auto& configuration, std::string_view errorString) const
{
if (configuration.on_warning)
{
if (first_warning)
return;
first_warning = true;
configuration.on_warning(midi_error::WARNING, errorString);
first_warning = false;
return;
}
#if defined(__LIBREMIDI_DEBUG__)
std::cerr << '\n' << errorString << "\n\n";
#endif
}
// To prevent infinite error loops
mutable bool first_error{};
mutable bool first_warning{};
};
class midi_api
{
public:
midi_api() = default;
virtual ~midi_api() = default;
midi_api(const midi_api&) = delete;
midi_api(midi_api&&) = delete;
midi_api& operator=(const midi_api&) = delete;
midi_api& operator=(midi_api&&) = delete;
[[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0;
[[nodiscard]] virtual bool open_virtual_port(std::string_view) = 0;
virtual void close_port() = 0;
virtual void set_client_name(std::string_view) = 0;
virtual void set_port_name(std::string_view) = 0;
bool is_port_open() const noexcept { return bool(port_open_); }
bool is_port_connected() const noexcept { return bool(connected_); }
protected:
friend class midi_in;
friend class midi_out;
bool port_open_{};
bool connected_{};
};
}
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/input_configuration.hpp>
namespace libremidi
{
class midi_in_api : public midi_api
{
public:
midi_in_api() = default;
~midi_in_api() override = default;
midi_in_api(const midi_in_api&) = delete;
midi_in_api(midi_in_api&&) = delete;
midi_in_api& operator=(const midi_in_api&) = delete;
midi_in_api& operator=(midi_in_api&&) = delete;
[[nodiscard]] virtual bool open_port(const input_port& pt, std::string_view local_port_name) = 0;
[[nodiscard]] virtual timestamp absolute_timestamp() const noexcept = 0;
};
namespace midi1
{
class in_api : public midi_in_api
{
public:
using midi_in_api::midi_in_api;
};
}
namespace midi2
{
class in_api : public midi_in_api
{
public:
using midi_in_api::midi_in_api;
bool firstMessage{true};
};
}
template <typename T, typename Arg>
std::unique_ptr<midi_in_api> make(libremidi::input_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::move(arg));
}
template <typename T, typename Arg>
std::unique_ptr<midi_in_api> make(libremidi::ump_input_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::move(arg));
}
}
+95
View File
@@ -0,0 +1,95 @@
#pragma once
#include <libremidi/cmidi2.hpp>
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/output_configuration.hpp>
#include <string_view>
namespace libremidi
{
class midi_out_api : public midi_api
{
public:
midi_out_api() = default;
~midi_out_api() override = default;
midi_out_api(const midi_out_api&) = delete;
midi_out_api(midi_out_api&&) = delete;
midi_out_api& operator=(const midi_out_api&) = delete;
midi_out_api& operator=(midi_out_api&&) = delete;
[[nodiscard]] virtual bool open_port(const output_port& pt, std::string_view local_port_name)
= 0;
[[nodiscard]] virtual int64_t current_time() const noexcept { return 0; }
virtual void send_message(const unsigned char* message, std::size_t size) = 0;
virtual void schedule_message(int64_t /*ts*/, const unsigned char* message, std::size_t size)
{
return send_message(message, size);
}
virtual void send_ump(const uint32_t* message, std::size_t size) = 0;
virtual void schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t size)
{
return send_ump(ump, size);
}
};
namespace midi1
{
class out_api : public midi_out_api
{
friend struct midi_stream_decoder;
public:
using midi_out_api::midi_out_api;
void send_ump(const uint32_t* message, std::size_t /*size*/)
{
uint8_t midi[65536];
const auto n
= cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));
if (n > 0)
send_message(midi, n);
}
};
}
namespace midi2
{
class out_api : public midi_out_api
{
friend struct midi_stream_decoder;
public:
using midi_out_api::midi_out_api;
void send_message(const unsigned char* message, std::size_t size)
{
cmidi2_midi_conversion_context context{};
cmidi2_midi_conversion_context_initialize(&context);
uint32_t ump[65536 / 4];
context.midi1 = const_cast<unsigned char*>(message);
context.midi1_num_bytes = size;
context.midi1_proceeded_bytes = 0;
context.ump = ump;
context.ump_num_bytes = sizeof(ump);
context.ump_proceeded_bytes = 0;
if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK)
return;
send_ump(context.ump, context.ump_proceeded_bytes / 4);
}
};
}
template <typename T, typename Arg>
std::unique_ptr<midi_out_api> make(libremidi::output_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::forward<Arg>(arg));
}
}
@@ -0,0 +1,314 @@
#pragma once
#include <libremidi/detail/midi_in.hpp>
#include <chrono>
#include <cstdint>
#include <cinttypes>
#include <span>
namespace libremidi
{
static inline int64_t system_ns() noexcept
{
namespace clk = std::chrono;
return clk::duration_cast<clk::nanoseconds>(clk::steady_clock::now().time_since_epoch()).count();
}
struct timestamp_backend_info
{
// The API provides some kind of timestamping
bool has_absolute_timestamps{};
// The provided timestamping is equivalent or more precise than
// e.g. clock_gettime(CLOCK_MONOTONIC)
bool absolute_is_monotonic{};
// The API can provide samples in a buffer cycle (only PipeWire and JACK so far)
bool has_samples{};
};
namespace midi1
{
struct input_state_machine
{
const input_configuration& configuration;
explicit input_state_machine(const input_configuration& conf)
: configuration{conf}
{
}
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
{
return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));
}
// Function to process a byte stream which may contain multiple successive
// MIDI events (CoreMIDI, ALSA Sequencer can work like this)
void on_bytes_multi(std::span<const uint8_t> bytes, int64_t timestamp)
{
int64_t nBytes = bytes.size();
int64_t iByte = 0;
const bool finished_sysex = has_finished_sysex(bytes);
switch (state)
{
case in_sysex: {
return on_continue_sysex(bytes, finished_sysex);
}
case main: {
while (iByte < nBytes)
{
int64_t size = 1;
// We are expecting that the next byte in the packet is a status
// byte.
const auto status = bytes[iByte];
if (!(status & 0x80))
break;
// Determine the number of bytes in the MIDI message.
if (status < 0xC0)
size = 3;
else if (status < 0xE0)
size = 2;
else if (status < 0xF0)
size = 3;
else if (status == 0xF0)
{
if (configuration.ignore_sysex)
{
size = 0;
iByte = nBytes;
}
else
{
size = nBytes - iByte;
}
if (bytes[nBytes - 1] != 0xF7)
{
// We know per CoreMIDI API there can't be anything else in this packet
state = in_sysex;
message.assign(bytes.begin(), bytes.begin() + size);
message.timestamp = timestamp;
return;
}
}
else if (status == 0xF1)
{
// A MIDI time code message
if (configuration.ignore_timing)
{
size = 0;
iByte += 2;
}
else
{
size = 2;
}
}
else if (status == 0xF2)
size = 3;
else if (status == 0xF3)
size = 2;
else if (status == 0xF8)
{
// A MIDI timing tick message
if (configuration.ignore_timing)
{
size = 0;
iByte += 1;
}
else
{
size = 1;
}
}
else if (status == 0xFE)
{
// A MIDI active sensing message
if (configuration.ignore_sensing)
{
size = 0;
iByte += 1;
}
else
{
size = 1;
}
}
else
{
// Remaining real-time messages
size = 1;
}
// Now process the actual bytes of the message
if (size > 0)
{
auto begin = bytes.begin() + iByte;
message.assign(begin, begin + size);
message.timestamp = timestamp;
this->configuration.on_message(std::move(message));
message.clear();
iByte += size;
}
}
}
}
}
void on_continue_sysex(std::span<const uint8_t> bytes, bool finished_sysex)
{
if (finished_sysex)
state = main;
if (configuration.ignore_sysex)
{
return;
}
else
{
message.insert(message.end(), bytes.begin(), bytes.end());
if (finished_sysex)
{
this->configuration.on_message(std::move(message));
message.clear();
}
}
return;
}
void on_main(std::span<const uint8_t> bytes, int64_t timestamp, bool finished_sysex)
{
switch (bytes[0])
{
// SYSEX start
case 0xF0: {
if (!finished_sysex)
state = in_sysex;
if (!this->configuration.ignore_sysex)
{
message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp;
if (finished_sysex)
{
this->configuration.on_message(std::move(message));
message.clear();
}
}
return;
}
case 0xF1:
case 0xF8:
if (this->configuration.ignore_timing)
return;
break;
case 0xFE:
if (this->configuration.ignore_sensing)
return;
break;
default:
break;
}
message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp;
this->configuration.on_message(std::move(message));
message.clear();
}
// Function to process bytes corresponding to at most one midi event
// e.g. a midi channel event or a single sysex
void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)
{
if (bytes.empty())
return;
const bool finished_sysex = has_finished_sysex(bytes);
switch (state)
{
case in_sysex:
return on_continue_sysex(bytes, finished_sysex);
case main:
return on_main(bytes, timestamp, finished_sysex);
}
}
template <timestamp_backend_info info>
int64_t timestamp(auto to_ns, int64_t samples)
{
switch (configuration.timestamps)
{
default:
case timestamp_mode::NoTimestamp:
return 0;
case timestamp_mode::Relative: {
int64_t time_ns;
if constexpr (info.has_absolute_timestamps)
time_ns = to_ns();
else
time_ns = system_ns();
int64_t res;
if (first_message)
{
first_message = false;
res = 0;
}
else
{
res = time_ns - last_time_ns;
}
last_time_ns = time_ns;
return res;
}
case timestamp_mode::Absolute:
if constexpr (info.has_absolute_timestamps)
return to_ns();
else
return system_ns();
case timestamp_mode::SystemMonotonic:
if constexpr (info.absolute_is_monotonic)
return to_ns();
else
return system_ns();
case timestamp_mode::AudioFrame:
if constexpr (info.has_samples)
return samples;
else
return 0;
case timestamp_mode::Custom:
return configuration.get_timestamp(to_ns());
}
}
libremidi::message message;
int64_t last_time_ns = 0;
enum
{
main,
in_sysex
} state{main};
bool first_message = true;
};
}
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <libremidi/api.hpp>
#include <libremidi/config.hpp>
#include <libremidi/observer_configuration.hpp>
#include <memory>
#include <vector>
namespace libremidi
{
class observer_api
{
public:
virtual ~observer_api() = default;
virtual libremidi::API get_current_api() const noexcept = 0;
virtual std::vector<libremidi::input_port> get_input_ports() const noexcept = 0;
virtual std::vector<libremidi::output_port> get_output_ports() const noexcept = 0;
};
template <typename T, typename Arg>
std::unique_ptr<observer_api> make(libremidi::observer_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::move(arg));
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <chrono>
#include <semaphore>
struct semaphore_pair_lock
{
std::binary_semaphore sem_cleanup{0};
std::binary_semaphore sem_needpost{0};
void prepare_release_client()
{
using namespace std::literals;
// FIXME if jack is not running we can skip this
this->sem_needpost.release();
this->sem_cleanup.try_acquire_for(1s);
}
void check_client_released()
{
if (!this->sem_needpost.try_acquire())
this->sem_cleanup.release();
}
};

Some files were not shown because too many files have changed in this diff Show More