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
@@ -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