very, very simple WP with midi CC out
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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_{};
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user