2024-10-09 02:01:23 +02:00
|
|
|
#pragma once
|
|
|
|
|
#include <libremidi/configurations.hpp>
|
|
|
|
|
#include <libremidi/libremidi.hpp>
|
|
|
|
|
#include <libremidi/shared_context.hpp>
|
|
|
|
|
|
|
|
|
|
#include <map>
|
2025-06-13 19:16:35 +02:00
|
|
|
#include <utility>
|
2024-10-09 02:01:23 +02:00
|
|
|
|
|
|
|
|
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{};
|
2024-10-28 19:24:18 +01:00
|
|
|
midi_warning_callback on_warning{};
|
2024-10-09 02:01:23 +02:00
|
|
|
|
|
|
|
|
//! 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)
|
2025-06-13 19:16:35 +02:00
|
|
|
: m_configuration{conf}
|
|
|
|
|
, m_context{std::move(ctx)}
|
2024-10-09 02:01:23 +02:00
|
|
|
, 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},
|
2025-06-13 19:16:35 +02:00
|
|
|
m_context.observer}
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
2025-06-13 19:16:35 +02:00
|
|
|
if (m_context.context)
|
|
|
|
|
m_context.context->start_processing();
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~client()
|
|
|
|
|
{
|
|
|
|
|
m_inputs.clear();
|
|
|
|
|
m_outputs.clear();
|
|
|
|
|
|
2025-06-13 19:16:35 +02:00
|
|
|
if (m_context.context)
|
|
|
|
|
m_context.context->stop_processing();
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2024-10-28 19:24:18 +01:00
|
|
|
= [this,
|
2025-06-13 19:16:35 +02:00
|
|
|
port](libremidi::message&& m) { m_configuration.on_message(port, std::move(m)); },
|
2024-10-28 19:24:18 +01:00
|
|
|
.on_raw_data = {},
|
2024-10-09 02:01:23 +02:00
|
|
|
.get_timestamp = {},
|
|
|
|
|
|
2025-06-13 19:16:35 +02:00
|
|
|
.on_error = m_configuration.on_error,
|
|
|
|
|
.on_warning = m_configuration.on_warning,
|
2024-10-09 02:01:23 +02:00
|
|
|
|
2025-06-13 19:16:35 +02:00
|
|
|
.ignore_sysex = m_configuration.ignore_sysex,
|
|
|
|
|
.ignore_timing = m_configuration.ignore_timing,
|
|
|
|
|
.ignore_sensing = m_configuration.ignore_sensing,
|
2024-10-09 02:01:23 +02:00
|
|
|
|
2025-06-13 19:16:35 +02:00
|
|
|
.timestamps = m_configuration.timestamps},
|
|
|
|
|
m_context.in);
|
2024-10-09 02:01:23 +02:00
|
|
|
|
|
|
|
|
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{
|
2025-06-13 19:16:35 +02:00
|
|
|
.on_error = m_configuration.on_error,
|
|
|
|
|
.on_warning = m_configuration.on_warning,
|
2024-10-09 02:01:23 +02:00
|
|
|
|
2025-06-13 19:16:35 +02:00
|
|
|
.timestamps = m_configuration.timestamps},
|
|
|
|
|
m_context.out);
|
2024-10-09 02:01:23 +02:00
|
|
|
|
|
|
|
|
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); }
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
stdx::error send_message(const unsigned char* message, size_t size)
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
|
|
|
|
for (auto& [_, out] : m_outputs)
|
|
|
|
|
{
|
2024-10-28 19:24:18 +01:00
|
|
|
if (auto err = out.send_message(message, size); err != stdx::error{})
|
|
|
|
|
return err;
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
2024-10-28 19:24:18 +01:00
|
|
|
return stdx::error{};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
stdx::error send_ump(const uint32_t* message, size_t size)
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
|
|
|
|
for (auto& [_, out] : m_outputs)
|
|
|
|
|
{
|
2024-10-28 19:24:18 +01:00
|
|
|
if (auto err = out.send_ump(message, size); err != stdx::error{})
|
|
|
|
|
return err;
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
2024-10-28 19:24:18 +01:00
|
|
|
return stdx::error{};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
stdx::error send_message(const output_port& port, const unsigned char* message, size_t size)
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
|
|
|
|
if (auto it = m_outputs.find(port); it != m_outputs.end())
|
2024-10-28 19:24:18 +01:00
|
|
|
return it->second.send_message(message, size);
|
|
|
|
|
|
|
|
|
|
return stdx::error{};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
stdx::error send_ump(const output_port& port, const uint32_t* message, size_t size)
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
|
|
|
|
if (auto it = m_outputs.find(port); it != m_outputs.end())
|
2024-10-28 19:24:18 +01:00
|
|
|
return it->second.send_ump(message, size);
|
|
|
|
|
|
|
|
|
|
return stdx::error{};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2025-06-13 19:16:35 +02:00
|
|
|
client_configuration m_configuration;
|
|
|
|
|
shared_configurations m_context;
|
2024-10-09 02:01:23 +02:00
|
|
|
|
|
|
|
|
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
|