upgrade to libremidi 5.1, raylib and raygui latest
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/config.hpp>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
struct input_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
jack_client_t* context{};
|
||||
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
};
|
||||
|
||||
struct output_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
jack_client_t* context{};
|
||||
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
|
||||
int32_t ringbuffer_size = 16384;
|
||||
bool direct = false;
|
||||
};
|
||||
|
||||
struct observer_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
jack_client_t* context{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/helpers.hpp>
|
||||
#include <libremidi/backends/jack_ump/config.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
class midi_in_jack final
|
||||
: public midi2::in_api
|
||||
, public jack_helpers
|
||||
, private jack_midi1
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: libremidi::ump_input_configuration
|
||||
, jack_ump::input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit midi_in_jack(
|
||||
libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
auto status = connect(*this);
|
||||
if (!this->client)
|
||||
{
|
||||
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||
client_open_ = from_jack_status(status);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_jack() override
|
||||
{
|
||||
midi_in_jack::close_port();
|
||||
|
||||
disconnect(*this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||
|
||||
stdx::error open_port(const input_port& port, std::string_view portName) override
|
||||
{
|
||||
if (auto err
|
||||
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
||||
err != 0 && err != EEXIST)
|
||||
{
|
||||
libremidi_handle_error(
|
||||
configuration,
|
||||
"could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port));
|
||||
return from_errc(err);
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||
}
|
||||
|
||||
stdx::error close_port() override { return do_close_port(); }
|
||||
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
{
|
||||
return 1000 * jack_frames_to_time(client, jack_frame_time(client));
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
{
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = true,
|
||||
.has_samples = true,
|
||||
};
|
||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||
|
||||
// Timing
|
||||
jack_nframes_t current_frames{};
|
||||
jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC
|
||||
jack_time_t next_usecs{};
|
||||
float period_usecs{};
|
||||
jack_get_cycle_times(
|
||||
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
||||
|
||||
// We have midi events in buffer
|
||||
uint32_t ev_count = jack_midi_get_event_count(buff);
|
||||
for (uint32_t j = 0; j < ev_count; j++)
|
||||
{
|
||||
jack_midi_event_t event{};
|
||||
jack_midi_event_get(&event, buff, j);
|
||||
const auto to_ns
|
||||
= [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); };
|
||||
|
||||
m_processing.on_bytes(
|
||||
{(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)},
|
||||
m_processing.timestamp<timestamp_info>(to_ns, event.time));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/helpers.hpp>
|
||||
#include <libremidi/backends/jack_ump/config.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
class midi_out_jack
|
||||
: public midi2::out_api
|
||||
, public jack_helpers
|
||||
, private jack_midi1
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: libremidi::output_configuration
|
||||
, jack_ump::output_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
}
|
||||
|
||||
~midi_out_jack() override { }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||
|
||||
stdx::error open_port(const output_port& port, std::string_view portName) override
|
||||
{
|
||||
if (auto err
|
||||
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
// Connecting to the output
|
||||
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
|
||||
err != 0 && err != EEXIST)
|
||||
{
|
||||
libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||
}
|
||||
|
||||
stdx::error close_port() override { return do_close_port(); }
|
||||
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||
return from_errc(ret);
|
||||
}
|
||||
};
|
||||
|
||||
class midi_out_jack_queued final : public midi_out_jack
|
||||
{
|
||||
public:
|
||||
midi_out_jack_queued(
|
||||
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||
, m_queue{configuration.ringbuffer_size}
|
||||
{
|
||||
auto status = connect(*this);
|
||||
if (!this->client)
|
||||
{
|
||||
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||
client_open_ = from_jack_status(status);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_jack_queued() override
|
||||
{
|
||||
midi_out_jack::close_port();
|
||||
|
||||
disconnect(*this);
|
||||
}
|
||||
|
||||
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||
{
|
||||
return m_queue.write((unsigned char*)message, size * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||
jack_midi_clear_buffer(buff);
|
||||
|
||||
this->m_queue.read(buff);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
libremidi::jack_queue m_queue;
|
||||
};
|
||||
|
||||
class midi_out_jack_direct final : public midi_out_jack
|
||||
{
|
||||
public:
|
||||
midi_out_jack_direct(
|
||||
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
auto status = connect(*this);
|
||||
if (!this->client)
|
||||
{
|
||||
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||
client_open_ = from_jack_status(status);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer_size = jack_get_buffer_size(this->client);
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_jack_direct() override
|
||||
{
|
||||
midi_out_jack::close_port();
|
||||
|
||||
disconnect(*this);
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||
jack_midi_clear_buffer(buff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||
int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t));
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
int convert_timestamp(int64_t user) const noexcept
|
||||
{
|
||||
switch (configuration.timestamps)
|
||||
{
|
||||
case timestamp_mode::AudioFrame:
|
||||
return static_cast<int>(user);
|
||||
|
||||
default:
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||
int ret = jack_midi_event_write(
|
||||
buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t));
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
int buffer_size{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
template <>
|
||||
inline std::unique_ptr<midi_out_api>
|
||||
make<jack_ump::midi_out_jack>(output_configuration&& conf, jack_ump::output_configuration&& api)
|
||||
{
|
||||
if (api.direct)
|
||||
return std::make_unique<jack_ump::midi_out_jack_direct>(std::move(conf), std::move(api));
|
||||
else
|
||||
return std::make_unique<jack_ump::midi_out_jack_queued>(std::move(conf), std::move(api));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/helpers.hpp>
|
||||
#include <libremidi/backends/jack_ump/config.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
class observer_jack final
|
||||
: public observer_api
|
||||
, private jack_client
|
||||
, private jack_midi1 // seems like a bug in PipeWire? MIDI 2 flag is set but the MIDI 1 type string is used
|
||||
, private error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: libremidi::observer_configuration
|
||||
, jack_ump::observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer_jack(
|
||||
libremidi::observer_configuration&& conf, jack_ump::observer_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
// Initialize JACK client
|
||||
if (configuration.context)
|
||||
{
|
||||
this->client = configuration.context;
|
||||
set_callbacks();
|
||||
}
|
||||
else
|
||||
{
|
||||
jack_status_t status{};
|
||||
this->client
|
||||
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
|
||||
if (status != jack_status_t{})
|
||||
libremidi_handle_error(configuration, std::to_string((int)status));
|
||||
|
||||
if (this->client != nullptr)
|
||||
{
|
||||
set_callbacks();
|
||||
|
||||
jack_activate(this->client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initial_callback()
|
||||
{
|
||||
{
|
||||
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
|
||||
|
||||
if (ports != nullptr)
|
||||
{
|
||||
int i = 0;
|
||||
while (ports[i] != nullptr)
|
||||
{
|
||||
const auto port = jack_port_by_name(client, ports[i]);
|
||||
const auto flags = jack_port_flags(port);
|
||||
|
||||
if (!(flags & 0x20)) // midi 2 check
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool physical = flags & JackPortIsPhysical;
|
||||
bool ok = configuration.track_any;
|
||||
if (configuration.track_hardware)
|
||||
ok |= physical;
|
||||
if (configuration.track_virtual)
|
||||
ok |= !physical;
|
||||
|
||||
if (ok)
|
||||
{
|
||||
seen_input_ports.insert(ports[i]);
|
||||
if (this->configuration.input_added && configuration.notify_in_constructor)
|
||||
this->configuration.input_added(to_port_info<true>(client, port));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
jack_free(ports);
|
||||
}
|
||||
|
||||
{
|
||||
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
|
||||
|
||||
if (ports != nullptr)
|
||||
{
|
||||
int i = 0;
|
||||
while (ports[i] != nullptr)
|
||||
{
|
||||
const auto port = jack_port_by_name(client, ports[i]);
|
||||
const auto flags = jack_port_flags(port);
|
||||
|
||||
if (!(flags & 0x20)) // midi 2 check
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool physical = flags & JackPortIsPhysical;
|
||||
bool ok = configuration.track_any;
|
||||
if (configuration.track_hardware)
|
||||
ok |= physical;
|
||||
if (configuration.track_virtual)
|
||||
ok |= !physical;
|
||||
|
||||
if (ok)
|
||||
{
|
||||
seen_output_ports.insert(ports[i]);
|
||||
if (this->configuration.output_added && configuration.notify_in_constructor)
|
||||
this->configuration.output_added(to_port_info<false>(client, port));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
jack_free(ports);
|
||||
}
|
||||
}
|
||||
|
||||
void on_port_callback(jack_port_t* port, bool reg)
|
||||
{
|
||||
auto flags = jack_port_flags(port);
|
||||
std::string name = jack_port_name(port);
|
||||
if (reg)
|
||||
{
|
||||
std::string_view type = jack_port_type(port);
|
||||
if (type != port_type)
|
||||
return;
|
||||
|
||||
if (!(flags & 0x20)) // midi 2 check
|
||||
return;
|
||||
|
||||
bool physical = flags & JackPortIsPhysical;
|
||||
bool ok = configuration.track_any;
|
||||
if (configuration.track_hardware)
|
||||
ok |= physical;
|
||||
if (configuration.track_virtual)
|
||||
ok |= !physical;
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
// Note: we keep track of the ports as
|
||||
// when disconnecting, jack_port_type and jack_port_flags aren't correctly
|
||||
// set anymore.
|
||||
|
||||
if (flags & JackPortIsOutput)
|
||||
{
|
||||
seen_input_ports.insert(name);
|
||||
if (this->configuration.input_added)
|
||||
this->configuration.input_added(to_port_info<true>(client, port));
|
||||
}
|
||||
else if (flags & JackPortIsInput)
|
||||
{
|
||||
seen_output_ports.insert(name);
|
||||
if (this->configuration.output_added)
|
||||
this->configuration.output_added(to_port_info<false>(client, port));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())
|
||||
{
|
||||
if (this->configuration.input_removed)
|
||||
this->configuration.input_removed(to_port_info<true>(client, port));
|
||||
seen_input_ports.erase(it);
|
||||
}
|
||||
if (auto it = seen_output_ports.find(name); it != seen_output_ports.end())
|
||||
{
|
||||
if (this->configuration.output_removed)
|
||||
this->configuration.output_removed(to_port_info<false>(client, port));
|
||||
seen_output_ports.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_callbacks()
|
||||
{
|
||||
initial_callback();
|
||||
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
|
||||
jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) {
|
||||
auto& self = *(observer_jack*)arg;
|
||||
if (auto port = jack_port_by_id(self.client, p))
|
||||
{
|
||||
self.on_port_callback(port, r != 0);
|
||||
}
|
||||
}, this);
|
||||
|
||||
jack_set_port_rename_callback(
|
||||
this->client,
|
||||
+[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) {
|
||||
const auto& self = *static_cast<observer_jack*>(arg);
|
||||
|
||||
auto port = jack_port_by_id(self.client, p);
|
||||
if (!port)
|
||||
return;
|
||||
}, this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, true);
|
||||
}
|
||||
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, true);
|
||||
}
|
||||
|
||||
~observer_jack()
|
||||
{
|
||||
if (client && !configuration.context)
|
||||
{
|
||||
// If we own the client, deactivate it
|
||||
jack_deactivate(this->client);
|
||||
jack_client_close(this->client);
|
||||
this->client = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seen_input_ports;
|
||||
std::unordered_set<std::string> seen_output_ports;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user