working on win32 uwp! and libremidi update

This commit is contained in:
Sebastian @ HfG
2024-10-28 19:24:18 +01:00
parent 06cb6d8319
commit 1e3755148c
106 changed files with 8474 additions and 2198 deletions
+86
View File
@@ -0,0 +1,86 @@
#pragma once
#include <libremidi/config.hpp>
#include <string>
namespace boost::asio
{
struct io_context;
}
namespace libremidi::net
{
enum class protocol
{
OSC_MIDI,
};
struct dgram_input_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI;
std::string accept = "0.0.0.0";
int port{};
boost::asio::io_context* io_context{};
};
struct dgram_output_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI;
std::string host = "127.0.0.1";
int port{};
bool broadcast{};
boost::asio::io_context* io_context{};
};
struct dgram_observer_configuration
{
std::string client_name = "libremidi client";
boost::asio::io_context* io_context{};
};
}
namespace libremidi::net_ump
{
enum class protocol
{
OSC_MIDI2,
};
struct dgram_input_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI2;
std::string accept = "0.0.0.0";
int port{};
boost::asio::io_context* io_context{};
};
struct dgram_output_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI2;
std::string host = "127.0.0.1";
int port{};
bool broadcast{};
boost::asio::io_context* io_context{};
};
struct dgram_observer_configuration
{
std::string client_name = "libremidi client";
boost::asio::io_context* io_context{};
};
}
@@ -0,0 +1,91 @@
#pragma once
#include <libremidi/config.hpp>
#include <boost/asio/ip/udp.hpp>
#include <memory>
namespace stdx
{ /*
class boost_system_error_domain : public error_domain
{
public:
constexpr boost_system_error_domain() noexcept
: error_domain{
{1 - 0x3c223c0aa3cf45e5ULL, 1 - 0x80dac24345cfb9fcULL},
default_error_resource_management_t<detail::exception_ptr_wrapper>{}}
{
}
string_ref name() const noexcept override { return "boost::system_error domain"; }
bool equivalent(const error& lhs, const error& rhs) const noexcept override { return false; }
string_ref message(const error& e) const noexcept override {
auto err = e.m_value;
}
[[noreturn]] void throw_exception(const error& e) const override
{
assert(e.domain() == *this);
std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());
}
};
*/
}
namespace libremidi
{
template <typename T>
struct optionally_owned
{
public:
explicit optionally_owned(T* maybe_existing)
: storage{.ref = maybe_existing}
{
if (storage.ref)
{
ownership = unowned;
return;
}
else
{
std::destroy_at(&storage.ref);
std::construct_at<T>(reinterpret_cast<T*>(storage.object));
ownership = owned;
}
}
~optionally_owned()
{
if (is_owned())
std::destroy_at(reinterpret_cast<T*>(storage.object));
}
T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); }
const T& get() const noexcept
{
return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref);
}
optionally_owned(const optionally_owned&) = delete;
optionally_owned(optionally_owned&&) noexcept = delete;
optionally_owned& operator=(const optionally_owned&) = delete;
optionally_owned& operator=(optionally_owned&&) noexcept = delete;
bool is_owned() const noexcept { return ownership == owned; }
private:
union
{
alignas(T) unsigned char object[sizeof(T)];
T* ref;
} storage;
enum
{
owned,
unowned
} ownership{};
};
}
+440
View File
@@ -0,0 +1,440 @@
#pragma once
#include <libremidi/backends/net/config.hpp>
#include <libremidi/backends/net/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
{
template <typename Impl, typename F>
struct osc_parser
{
[[no_unique_address]]
Impl impl;
F on_message;
std::string_view port_name;
stdx::error parse_bundle(const char* data, std::size_t sz)
{
std::string_view txt(data, sz);
if (!txt.starts_with("#bundle"))
return std::errc::bad_message;
return std::errc::protocol_not_supported;
}
stdx::error parse_message(const char* data, std::size_t sz)
{
const auto begin = data;
const auto end = data + sz;
std::size_t pattern_len = strnlen(data, sz);
if (pattern_len == sz)
return std::errc::bad_message;
if (auto pat = std::string_view(data, pattern_len); pat != port_name)
return std::errc::bad_address;
data += pattern_len;
for (; data < end; ++data)
if (*data == 0)
continue;
else
break;
// Now we shold reach the typetag beginning, ","
if (data == end)
return std::errc::bad_message;
if ((data - begin) % 4 != 0)
return std::errc::bad_message;
if (*data++ != ',')
return std::errc::bad_message;
// Count the ,mmmmm arguments... yummy
int num_msgs = 0;
for (; data < end; ++data)
if (*data == Impl::typetag)
num_msgs++;
else if (*data == 0)
break;
else
return std::errc::bad_message;
if (num_msgs == 0)
return std::errc::no_message;
// By now there's at least 5 bytes left as the ,mmm has to be round-up to 4 and padded with zeros:
if (end - data < 4)
return std::errc::bad_message;
switch ((1 + num_msgs) % 4)
{
case 0:
data += 4;
break;
case 1:
data += 3;
break;
case 2:
data += 2;
break;
case 3:
data += 1;
break;
}
// Data starts
return impl.process_midi_bytes(on_message, num_msgs, data, end - data);
}
stdx::error parse_int_message(const char* /* data */, std::size_t /* sz */)
{
return std::errc::protocol_not_supported;
}
stdx::error parse_packet(const char* data, std::size_t sz)
{
if (sz == 0)
return std::errc::no_message;
switch (data[0])
{
case '#':
return parse_bundle(data, sz);
break;
case '/':
return parse_message(data, sz);
break;
case '\0':
return parse_int_message(data, sz);
break;
default:
return std::errc::bad_message;
}
}
};
}
namespace libremidi::net
{
struct osc_parser_midi1
{
static constexpr char typetag = 'm';
stdx::error
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t sz)
{
if (sz != num_msgs * 4)
return std::errc::bad_message;
auto end = data + sz;
for (; data < end; data += 4)
on_message(data + 1);
return {};
}
};
class midi_in final
: public midi1::in_api
, public error_handler
{
public:
using midi_api::client_open_;
struct
: input_configuration
, dgram_input_configuration
{
} configuration;
explicit midi_in(input_configuration&& conf, dgram_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, m_ctx{configuration.io_context}
, m_socket{m_ctx.get()}
{
m_socket.open(boost::asio::ip::udp::v4());
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
m_endpoint
= {boost::asio::ip::make_address(this->configuration.accept),
static_cast<unsigned short>(this->configuration.port)};
boost::system::error_code ec;
m_socket.bind(m_endpoint, ec);
if (ec != boost::system::error_code{})
{
client_open_ = std::errc::address_in_use;
return;
}
client_open_ = stdx::error{};
}
~midi_in() override { close_port(); }
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }
stdx::error open_port(const input_port&, std::string_view port) override
{
return open_virtual_port(port);
}
stdx::error open_virtual_port(std::string_view port) override
{
m_portname = std::string(port);
receive();
if (m_ctx.is_owned())
{
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
auto wg = boost::asio::make_work_guard(m_ctx);
ctx.run();
}};
}
return stdx::error{};
}
void receive()
{
m_socket.async_receive_from(
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
[this](auto ec, std::size_t sz) {
if (ec == boost::asio::error::operation_aborted)
return;
if (!ec && sz > 0)
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
this->receive();
});
}
stdx::error close_port() override
{
// FIXME async close
if (m_socket.is_open())
m_socket.close();
return {};
}
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
}
void on_bytes(const char* data, std::size_t size)
{
const auto on_msg = [this](const char* bytes) { this->on_message(bytes); };
osc_parser<osc_parser_midi1, decltype(on_msg)> parser{{}, on_msg, m_portname};
parser.parse_packet(data, size);
}
void on_message(const char* data)
{
// OSC enforces always 3 bytes
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = false,
.absolute_is_monotonic = false,
.has_samples = false,
};
static constexpr auto to_ns = []() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
};
auto ptr = reinterpret_cast<const uint8_t*>(data);
m_processing.on_bytes({ptr, ptr + 3}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
midi1::input_state_machine m_processing{this->configuration};
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
boost::asio::ip::udp::endpoint m_endpoint;
boost::asio::ip::udp::socket m_socket;
std::jthread m_thread;
std::string m_portname;
alignas(16) unsigned char m_data[65535];
};
}
namespace libremidi::net_ump
{
struct osc_parser_midi2
{
static constexpr char typetag = 'M';
stdx::error
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t byte_sz)
{
if (byte_sz % 4 != 0)
return std::errc::bad_message;
auto sz = byte_sz / 4;
auto begin = reinterpret_cast<const uint32_t*>(data);
auto end = begin + sz;
std::size_t accounted = 0;
for (auto it = begin; it < end && accounted < num_msgs;)
{
const auto N = cmidi2_ump_get_message_size_bytes(it) / 4;
switch (N)
{
case 1:
case 2:
case 4:
if (it + N <= end)
{
on_message(it, N);
accounted++;
}
else
return std::errc::bad_message;
break;
default:
return std::errc::bad_message;
}
it += N;
}
return {};
}
};
class midi_in final
: public midi2::in_api
, public error_handler
{
public:
using midi_api::client_open_;
struct
: ump_input_configuration
, dgram_input_configuration
{
} configuration;
explicit midi_in(ump_input_configuration&& conf, dgram_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, m_ctx{configuration.io_context}
, m_socket{m_ctx.get()}
{
m_socket.open(boost::asio::ip::udp::v4());
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
m_endpoint
= {boost::asio::ip::make_address(this->configuration.accept),
static_cast<unsigned short>(this->configuration.port)};
boost::system::error_code ec;
m_socket.bind(m_endpoint, ec);
if (ec != boost::system::error_code{})
{
client_open_ = std::errc::address_in_use;
return;
}
client_open_ = stdx::error{};
}
~midi_in() override { close_port(); }
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }
stdx::error open_port(const input_port&, std::string_view port) override
{
return open_virtual_port(port);
}
stdx::error open_virtual_port(std::string_view port) override
{
m_portname = std::string(port);
receive();
if (m_ctx.is_owned())
{
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
auto wg = boost::asio::make_work_guard(m_ctx);
ctx.run();
}};
}
return stdx::error{};
}
void receive()
{
m_socket.async_receive_from(
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
[this](auto ec, std::size_t sz) {
if (ec == boost::asio::error::operation_aborted)
return;
if (!ec && sz > 0)
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
this->receive();
});
}
stdx::error close_port() override
{
// FIXME async close
if (m_ctx.is_owned())
m_ctx.get().stop();
if (m_socket.is_open())
m_socket.close();
return {};
}
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
}
void on_bytes(const char* data, std::size_t size)
{
const auto on_msg
= [this](const uint32_t* bytes, std::size_t N) { this->on_message(bytes, N); };
osc_parser<osc_parser_midi2, decltype(on_msg)> parser{{}, on_msg, m_portname};
parser.parse_packet(data, size);
}
void on_message(const uint32_t* ump, std::size_t sz)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = false,
.absolute_is_monotonic = false,
.has_samples = false,
};
static constexpr auto to_ns = []() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
};
m_processing.on_bytes({ump, sz}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
midi2::input_state_machine m_processing{this->configuration};
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
boost::asio::ip::udp::endpoint m_endpoint;
boost::asio::ip::udp::socket m_socket;
std::jthread m_thread;
std::string m_portname;
alignas(16) unsigned char m_data[65535];
};
}
+288
View File
@@ -0,0 +1,288 @@
#pragma once
#include <libremidi/backends/net/config.hpp>
#include <libremidi/backends/net/helpers.hpp>
#include <libremidi/cmidi2.hpp>
#include <libremidi/detail/midi_out.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/endian.hpp>
namespace libremidi::net
{
struct osc_midi1_packet
{
stdx::error init_packet(std::string_view osc_pattern)
{
int i = 0;
for (; i < std::ssize(osc_pattern); i++)
if (osc_pattern.data()[i] != 0)
bytes[i] = osc_pattern.data()[i];
else
break;
bytes[i] = 0;
while (i % 4 != 3)
{
++i;
bytes[i] = 0;
}
bytes[++i] = ',';
bytes[++i] = 'm';
bytes[++i] = 0;
bytes[++i] = 0;
// MIDI message (m) spec: port n°, status byte, data 1, data 2
bytes[++i] = 0;
message_size = ++i + 3;
return stdx::error{}; // FIXME
}
stdx::error deinit()
{
message_size = 0;
return stdx::error{};
}
stdx::error set_packet_content(const unsigned char* message, size_t size)
{
if (message_size == 0)
return std::errc::not_connected;
if (size != 3)
return std::errc::message_size;
std::memcpy(this->bytes + message_size - 3, message, 3);
return stdx::error{};
}
std::span<const char> get_data() { return std::span(this->bytes, this->message_size); }
char bytes[512 + 8 + 8];
int message_size{};
};
class midi_out final
: public midi1::out_api
, public error_handler
{
public:
struct
: output_configuration
, dgram_output_configuration
{
} configuration;
midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, ctx{configuration.io_context}
, m_socket{ctx.get()}
{
m_socket.open(boost::asio::ip::udp::v4());
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
m_socket.set_option(boost::asio::socket_base::broadcast(true));
m_endpoint
= {boost::asio::ip::make_address(this->configuration.host),
static_cast<unsigned short>(this->configuration.port)};
this->client_open_ = stdx::error{};
}
~midi_out() override { close_port(); }
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }
stdx::error open_port(const output_port& /* port */, std::string_view portName) override
{
return open_virtual_port(portName);
}
stdx::error open_virtual_port(std::string_view portName) override
{
// Random arbitrary limit to avoid abuse
if (portName.size() >= 512)
return std::errc::invalid_argument;
pkt.init_packet(portName);
return stdx::error{};
}
stdx::error close_port() override
{
pkt.deinit();
// FIXME async close
if (m_socket.is_open())
m_socket.close();
return {};
}
stdx::error send_message(const unsigned char* message, size_t size) override
{
if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})
return err;
auto dat = pkt.get_data();
boost::system::error_code ec;
m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);
return stdx::error{}; // FIXME
}
stdx::error schedule_message(int64_t, const unsigned char*, size_t) override
{
int ret = 0;
return from_errc(ret);
}
libremidi::optionally_owned<boost::asio::io_context> ctx;
boost::asio::ip::udp::endpoint m_endpoint;
boost::asio::ip::udp::socket m_socket;
osc_midi1_packet pkt;
};
}
namespace libremidi::net_ump
{
struct osc_midi2_packet
{
stdx::error init_packet(std::string_view osc_pattern)
{
int i = 0;
for (; i < std::ssize(osc_pattern); i++)
if (osc_pattern.data()[i] != 0)
bytes[i] = osc_pattern.data()[i];
else
break;
bytes[i] = 0;
while (i % 4 != 3)
{
++i;
bytes[i] = 0;
}
bytes[++i] = ',';
bytes[++i] = 'M';
bytes[++i] = 0;
bytes[++i] = 0;
// MIDI 2 message (M) spec: an UMP
header_size = ++i;
return stdx::error{}; // FIXME
}
stdx::error deinit()
{
header_size = 0;
return stdx::error{};
}
stdx::error set_packet_content(const uint32_t* message, size_t size)
{
if (header_size == 0)
return std::errc::not_connected;
if (size > 4)
return std::errc::message_size;
message_size = 4 * size;
std::memcpy(this->bytes + header_size, message, message_size);
return stdx::error{};
}
std::span<const char> get_data()
{
return std::span(this->bytes, this->header_size + this->message_size);
}
char bytes[512 + 8 + 64];
int header_size{};
int message_size{};
};
class midi_out final
: public midi2::out_api
, public error_handler
{
public:
struct
: output_configuration
, dgram_output_configuration
{
} configuration;
midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, ctx{configuration.io_context}
, m_socket{ctx.get()}
{
m_socket.open(boost::asio::ip::udp::v4());
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
m_socket.set_option(boost::asio::socket_base::broadcast(true));
m_endpoint
= {boost::asio::ip::make_address(this->configuration.host),
static_cast<unsigned short>(this->configuration.port)};
this->client_open_ = stdx::error{};
}
~midi_out() override { close_port(); }
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }
stdx::error open_port(const output_port& /* port */, std::string_view portName) override
{
return open_virtual_port(portName);
}
stdx::error open_virtual_port(std::string_view portName) override
{
// Random arbitrary limit to avoid abuse
if (portName.size() >= 512)
return std::errc::invalid_argument;
pkt.init_packet(portName);
return stdx::error{};
}
stdx::error close_port() override
{
pkt.deinit();
if (m_socket.is_open())
m_socket.close();
return {};
}
stdx::error send_ump(const uint32_t* message, size_t size) override
{
if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})
return err;
auto dat = pkt.get_data();
boost::system::error_code ec;
m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);
return stdx::error{}; // FIXME
}
stdx::error schedule_ump(int64_t, const uint32_t*, size_t) override
{
int ret = 0;
return from_errc(ret);
}
libremidi::optionally_owned<boost::asio::io_context> ctx;
boost::asio::ip::udp::endpoint m_endpoint;
boost::asio::ip::udp::socket m_socket;
osc_midi2_packet pkt;
};
}
@@ -0,0 +1,14 @@
#pragma once
#include <libremidi/backends/dummy.hpp>
namespace libremidi::net
{
using net_observer_configuration = libremidi::dummy_configuration;
using observer = libremidi::observer_dummy;
}
namespace libremidi::net_ump
{
using net_observer_configuration = libremidi::dummy_configuration;
using observer = libremidi::observer_dummy;
}