Files
viseq/include/libremidi/backends/alsa_raw/midi_out.hpp
T

170 lines
4.2 KiB
C++
Raw Normal View History

2024-10-09 02:01:23 +02:00
#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)}
{
2024-10-28 19:24:18 +01:00
client_open_ = stdx::error{};
2024-10-09 02:01:23 +02:00
}
~midi_out_impl() override
{
// Close a connection if it exists.
midi_out_impl::close_port();
2024-10-28 19:24:18 +01:00
client_open_ = std::errc::not_connected;
2024-10-09 02:01:23 +02:00
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
2024-10-28 19:24:18 +01:00
stdx::error connect_port(const char* portname)
2024-10-09 02:01:23 +02:00
{
constexpr int mode = SND_RAWMIDI_SYNC;
int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);
if (status < 0)
{
2024-10-28 19:24:18 +01:00
libremidi_handle_error(
this->configuration, "cannot open device.");
return from_errc(status);
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 open_port(const output_port& p, std::string_view) override
2024-10-09 02:01:23 +02:00
{
2024-10-28 19:24:18 +01:00
return connect_port(raw_from_port_handle(p.port).to_string().c_str());
2024-10-09 02:01:23 +02:00
}
2024-10-28 19:24:18 +01:00
stdx::error close_port() override
2024-10-09 02:01:23 +02:00
{
if (midiport_)
snd.rawmidi.close(midiport_);
midiport_ = nullptr;
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 unsigned char* message, size_t size) override
2024-10-09 02:01:23 +02:00
{
if (!midiport_)
2024-10-28 19:24:18 +01:00
libremidi_handle_error(
2024-10-09 02:01:23 +02:00
this->configuration,
2024-10-28 19:24:18 +01:00
"trying to send a message without an open "
2024-10-09 02:01:23 +02:00
"port.");
if (!this->configuration.chunking)
{
2024-10-28 19:24:18 +01:00
return write(message, size);
2024-10-09 02:01:23 +02:00
}
else
{
2024-10-28 19:24:18 +01:00
return write_chunked(message, size);
2024-10-09 02:01:23 +02:00
}
}
2024-10-28 19:24:18 +01:00
stdx::error write(const unsigned char* message, size_t size)
2024-10-09 02:01:23 +02:00
{
2024-10-28 19:24:18 +01:00
if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0)
2024-10-09 02:01:23 +02:00
{
2024-10-28 19:24:18 +01:00
libremidi_handle_error(
this->configuration, "cannot write message.");
return from_errc(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
}
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
2024-10-28 19:24:18 +01:00
stdx::error write_chunked(const unsigned char* const begin, size_t size)
2024-10-09 02:01:23 +02:00
{
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;
2024-10-28 19:24:18 +01:00
if (auto err = write(data, len); err != stdx::error{})
return err;
2024-10-09 02:01:23 +02:00
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))
2024-10-28 19:24:18 +01:00
return std::errc::protocol_error;
2024-10-09 02:01:23 +02:00
};
if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes))
2024-10-28 19:24:18 +01:00
return std::errc::protocol_error;
2024-10-09 02:01:23 +02:00
// 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;
2024-10-28 19:24:18 +01:00
if (auto err = write(data, len); err != stdx::error{})
return err;
2024-10-09 02:01:23 +02:00
data += len;
}
2024-10-28 19:24:18 +01:00
return stdx::error{};
2024-10-09 02:01:23 +02:00
}
snd_rawmidi_t* midiport_{};
};
}