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
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <memory>
#include <mutex>
namespace libremidi
{
template <auto func>
struct deleter
{
template <typename U>
void operator()(U* x) noexcept(noexcept(func(x)))
{
func(x);
}
};
template <typename T, auto func>
using unique_handle = std::unique_ptr<T, deleter<func>>;
template <typename T>
std::shared_ptr<T> instance()
{
static std::mutex mut;
static std::weak_ptr<T> cache;
std::lock_guard _{mut};
if (auto ptr = cache.lock())
{
return ptr;
}
else
{
auto shared = std::make_shared<T>();
cache = shared;
return shared;
}
}
}
+87
View File
@@ -0,0 +1,87 @@
#pragma once
#include <libremidi/api.hpp>
#include <libremidi/config.hpp>
#include <libremidi/error.hpp>
#include <libremidi/observer_configuration.hpp>
#include <iostream>
#include <string_view>
namespace libremidi
{
struct error_handler
{
//! Error reporting function for libremidi classes. Throws.
template <typename Error_T>
void error(auto& configuration, std::string_view errorString) const
{
if (configuration.on_error)
{
if (first_error)
return;
first_error = true;
configuration.on_error(Error_T::code, errorString);
first_error = false;
}
else
{
#if defined(__LIBREMIDI_DEBUG__)
std::cerr << '\n' << errorString << "\n\n";
#endif
throw Error_T{errorString.data()};
}
}
//! Warning reporting function for libremidi classes.
void warning(auto& configuration, std::string_view errorString) const
{
if (configuration.on_warning)
{
if (first_warning)
return;
first_warning = true;
configuration.on_warning(midi_error::WARNING, errorString);
first_warning = false;
return;
}
#if defined(__LIBREMIDI_DEBUG__)
std::cerr << '\n' << errorString << "\n\n";
#endif
}
// To prevent infinite error loops
mutable bool first_error{};
mutable bool first_warning{};
};
class midi_api
{
public:
midi_api() = default;
virtual ~midi_api() = default;
midi_api(const midi_api&) = delete;
midi_api(midi_api&&) = delete;
midi_api& operator=(const midi_api&) = delete;
midi_api& operator=(midi_api&&) = delete;
[[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0;
[[nodiscard]] virtual bool open_virtual_port(std::string_view) = 0;
virtual void close_port() = 0;
virtual void set_client_name(std::string_view) = 0;
virtual void set_port_name(std::string_view) = 0;
bool is_port_open() const noexcept { return bool(port_open_); }
bool is_port_connected() const noexcept { return bool(connected_); }
protected:
friend class midi_in;
friend class midi_out;
bool port_open_{};
bool connected_{};
};
}
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/input_configuration.hpp>
namespace libremidi
{
class midi_in_api : public midi_api
{
public:
midi_in_api() = default;
~midi_in_api() override = default;
midi_in_api(const midi_in_api&) = delete;
midi_in_api(midi_in_api&&) = delete;
midi_in_api& operator=(const midi_in_api&) = delete;
midi_in_api& operator=(midi_in_api&&) = delete;
[[nodiscard]] virtual bool open_port(const input_port& pt, std::string_view local_port_name) = 0;
[[nodiscard]] virtual timestamp absolute_timestamp() const noexcept = 0;
};
namespace midi1
{
class in_api : public midi_in_api
{
public:
using midi_in_api::midi_in_api;
};
}
namespace midi2
{
class in_api : public midi_in_api
{
public:
using midi_in_api::midi_in_api;
bool firstMessage{true};
};
}
template <typename T, typename Arg>
std::unique_ptr<midi_in_api> make(libremidi::input_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::move(arg));
}
template <typename T, typename Arg>
std::unique_ptr<midi_in_api> make(libremidi::ump_input_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::move(arg));
}
}
+95
View File
@@ -0,0 +1,95 @@
#pragma once
#include <libremidi/cmidi2.hpp>
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/output_configuration.hpp>
#include <string_view>
namespace libremidi
{
class midi_out_api : public midi_api
{
public:
midi_out_api() = default;
~midi_out_api() override = default;
midi_out_api(const midi_out_api&) = delete;
midi_out_api(midi_out_api&&) = delete;
midi_out_api& operator=(const midi_out_api&) = delete;
midi_out_api& operator=(midi_out_api&&) = delete;
[[nodiscard]] virtual bool open_port(const output_port& pt, std::string_view local_port_name)
= 0;
[[nodiscard]] virtual int64_t current_time() const noexcept { return 0; }
virtual void send_message(const unsigned char* message, std::size_t size) = 0;
virtual void schedule_message(int64_t /*ts*/, const unsigned char* message, std::size_t size)
{
return send_message(message, size);
}
virtual void send_ump(const uint32_t* message, std::size_t size) = 0;
virtual void schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t size)
{
return send_ump(ump, size);
}
};
namespace midi1
{
class out_api : public midi_out_api
{
friend struct midi_stream_decoder;
public:
using midi_out_api::midi_out_api;
void send_ump(const uint32_t* message, std::size_t /*size*/)
{
uint8_t midi[65536];
const auto n
= cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));
if (n > 0)
send_message(midi, n);
}
};
}
namespace midi2
{
class out_api : public midi_out_api
{
friend struct midi_stream_decoder;
public:
using midi_out_api::midi_out_api;
void send_message(const unsigned char* message, std::size_t size)
{
cmidi2_midi_conversion_context context{};
cmidi2_midi_conversion_context_initialize(&context);
uint32_t ump[65536 / 4];
context.midi1 = const_cast<unsigned char*>(message);
context.midi1_num_bytes = size;
context.midi1_proceeded_bytes = 0;
context.ump = ump;
context.ump_num_bytes = sizeof(ump);
context.ump_proceeded_bytes = 0;
if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK)
return;
send_ump(context.ump, context.ump_proceeded_bytes / 4);
}
};
}
template <typename T, typename Arg>
std::unique_ptr<midi_out_api> make(libremidi::output_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::forward<Arg>(arg));
}
}
@@ -0,0 +1,314 @@
#pragma once
#include <libremidi/detail/midi_in.hpp>
#include <chrono>
#include <cstdint>
#include <cinttypes>
#include <span>
namespace libremidi
{
static inline int64_t system_ns() noexcept
{
namespace clk = std::chrono;
return clk::duration_cast<clk::nanoseconds>(clk::steady_clock::now().time_since_epoch()).count();
}
struct timestamp_backend_info
{
// The API provides some kind of timestamping
bool has_absolute_timestamps{};
// The provided timestamping is equivalent or more precise than
// e.g. clock_gettime(CLOCK_MONOTONIC)
bool absolute_is_monotonic{};
// The API can provide samples in a buffer cycle (only PipeWire and JACK so far)
bool has_samples{};
};
namespace midi1
{
struct input_state_machine
{
const input_configuration& configuration;
explicit input_state_machine(const input_configuration& conf)
: configuration{conf}
{
}
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
{
return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));
}
// Function to process a byte stream which may contain multiple successive
// MIDI events (CoreMIDI, ALSA Sequencer can work like this)
void on_bytes_multi(std::span<const uint8_t> bytes, int64_t timestamp)
{
int64_t nBytes = bytes.size();
int64_t iByte = 0;
const bool finished_sysex = has_finished_sysex(bytes);
switch (state)
{
case in_sysex: {
return on_continue_sysex(bytes, finished_sysex);
}
case main: {
while (iByte < nBytes)
{
int64_t size = 1;
// We are expecting that the next byte in the packet is a status
// byte.
const auto status = bytes[iByte];
if (!(status & 0x80))
break;
// Determine the number of bytes in the MIDI message.
if (status < 0xC0)
size = 3;
else if (status < 0xE0)
size = 2;
else if (status < 0xF0)
size = 3;
else if (status == 0xF0)
{
if (configuration.ignore_sysex)
{
size = 0;
iByte = nBytes;
}
else
{
size = nBytes - iByte;
}
if (bytes[nBytes - 1] != 0xF7)
{
// We know per CoreMIDI API there can't be anything else in this packet
state = in_sysex;
message.assign(bytes.begin(), bytes.begin() + size);
message.timestamp = timestamp;
return;
}
}
else if (status == 0xF1)
{
// A MIDI time code message
if (configuration.ignore_timing)
{
size = 0;
iByte += 2;
}
else
{
size = 2;
}
}
else if (status == 0xF2)
size = 3;
else if (status == 0xF3)
size = 2;
else if (status == 0xF8)
{
// A MIDI timing tick message
if (configuration.ignore_timing)
{
size = 0;
iByte += 1;
}
else
{
size = 1;
}
}
else if (status == 0xFE)
{
// A MIDI active sensing message
if (configuration.ignore_sensing)
{
size = 0;
iByte += 1;
}
else
{
size = 1;
}
}
else
{
// Remaining real-time messages
size = 1;
}
// Now process the actual bytes of the message
if (size > 0)
{
auto begin = bytes.begin() + iByte;
message.assign(begin, begin + size);
message.timestamp = timestamp;
this->configuration.on_message(std::move(message));
message.clear();
iByte += size;
}
}
}
}
}
void on_continue_sysex(std::span<const uint8_t> bytes, bool finished_sysex)
{
if (finished_sysex)
state = main;
if (configuration.ignore_sysex)
{
return;
}
else
{
message.insert(message.end(), bytes.begin(), bytes.end());
if (finished_sysex)
{
this->configuration.on_message(std::move(message));
message.clear();
}
}
return;
}
void on_main(std::span<const uint8_t> bytes, int64_t timestamp, bool finished_sysex)
{
switch (bytes[0])
{
// SYSEX start
case 0xF0: {
if (!finished_sysex)
state = in_sysex;
if (!this->configuration.ignore_sysex)
{
message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp;
if (finished_sysex)
{
this->configuration.on_message(std::move(message));
message.clear();
}
}
return;
}
case 0xF1:
case 0xF8:
if (this->configuration.ignore_timing)
return;
break;
case 0xFE:
if (this->configuration.ignore_sensing)
return;
break;
default:
break;
}
message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp;
this->configuration.on_message(std::move(message));
message.clear();
}
// Function to process bytes corresponding to at most one midi event
// e.g. a midi channel event or a single sysex
void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)
{
if (bytes.empty())
return;
const bool finished_sysex = has_finished_sysex(bytes);
switch (state)
{
case in_sysex:
return on_continue_sysex(bytes, finished_sysex);
case main:
return on_main(bytes, timestamp, finished_sysex);
}
}
template <timestamp_backend_info info>
int64_t timestamp(auto to_ns, int64_t samples)
{
switch (configuration.timestamps)
{
default:
case timestamp_mode::NoTimestamp:
return 0;
case timestamp_mode::Relative: {
int64_t time_ns;
if constexpr (info.has_absolute_timestamps)
time_ns = to_ns();
else
time_ns = system_ns();
int64_t res;
if (first_message)
{
first_message = false;
res = 0;
}
else
{
res = time_ns - last_time_ns;
}
last_time_ns = time_ns;
return res;
}
case timestamp_mode::Absolute:
if constexpr (info.has_absolute_timestamps)
return to_ns();
else
return system_ns();
case timestamp_mode::SystemMonotonic:
if constexpr (info.absolute_is_monotonic)
return to_ns();
else
return system_ns();
case timestamp_mode::AudioFrame:
if constexpr (info.has_samples)
return samples;
else
return 0;
case timestamp_mode::Custom:
return configuration.get_timestamp(to_ns());
}
}
libremidi::message message;
int64_t last_time_ns = 0;
enum
{
main,
in_sysex
} state{main};
bool first_message = true;
};
}
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <libremidi/api.hpp>
#include <libremidi/config.hpp>
#include <libremidi/observer_configuration.hpp>
#include <memory>
#include <vector>
namespace libremidi
{
class observer_api
{
public:
virtual ~observer_api() = default;
virtual libremidi::API get_current_api() const noexcept = 0;
virtual std::vector<libremidi::input_port> get_input_ports() const noexcept = 0;
virtual std::vector<libremidi::output_port> get_output_ports() const noexcept = 0;
};
template <typename T, typename Arg>
std::unique_ptr<observer_api> make(libremidi::observer_configuration&& conf, Arg&& arg)
{
return std::make_unique<T>(std::move(conf), std::move(arg));
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <chrono>
#include <semaphore>
struct semaphore_pair_lock
{
std::binary_semaphore sem_cleanup{0};
std::binary_semaphore sem_needpost{0};
void prepare_release_client()
{
using namespace std::literals;
// FIXME if jack is not running we can skip this
this->sem_needpost.release();
this->sem_cleanup.try_acquire_for(1s);
}
void check_client_released()
{
if (!this->sem_needpost.try_acquire())
this->sem_cleanup.release();
}
};
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <libremidi/cmidi2.hpp>
#include <cinttypes>
#include <cstdint>
namespace libremidi
{
enum class segmentation_error
{
no_error,
need_space,
other
};
/**
* Utility function to segment an ump stream into individual messages.
* Used to send a stream to APIs that work message-by-message.
*/
inline void
segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, auto realloc_func)
{
while (count > 0)
{
// Handle NOOP (or padding)
while (count > 0 && ump_stream[0] == 0)
{
count--;
ump_stream++;
}
if (count == 0)
break;
const auto ump_bytes = cmidi2_ump_get_num_bytes(ump_stream[0]);
// FIXME std::expected, propagate the error back to caller?
switch (write_func(ump_stream, ump_bytes))
{
case segmentation_error::no_error:
break;
case segmentation_error::need_space:
// Try again if we didn't have enough space in the OS queue
realloc_func();
if (write_func(ump_stream, ump_bytes) != segmentation_error::no_error)
return;
break;
case segmentation_error::other:
return;
}
const auto ump_uints = ump_bytes / 4;
ump_stream += ump_uints;
count -= ump_uints;
}
}
}