Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f6cbdfad0 | |||
| 96a19d55ef | |||
| e519b7c758 | |||
| 95620a5f07 | |||
| 9878b7b2a4 | |||
| 4a346350f4 | |||
| dc7eae1931 | |||
| 0c797807dd | |||
| 374efbe47c | |||
| bfd8bb192e | |||
| 7bdd68bdcc | |||
| a4ea20b2b3 | |||
| 522e9d456a | |||
| 70d0a186a4 | |||
| 41cad27806 | |||
| a10335ed47 | |||
| 19b2c9549f | |||
| c0eb7c98ba | |||
| f7a13e48b7 | |||
| ba5874837c | |||
| c0f5138765 | |||
| ad7b0e4524 | |||
| c63eeed1b4 | |||
| fc48c2e7ed | |||
| 1e3755148c | |||
| 06cb6d8319 | |||
| 58766cc373 | |||
| bd17288ab0 | |||
| cb08f69e3b | |||
| 53289bb9e2 | |||
| d747f80d11 |
@@ -1,4 +1,7 @@
|
||||
build/**
|
||||
.cache
|
||||
viseq
|
||||
viseq.exe
|
||||
compile_commands.json
|
||||
*.json
|
||||
.DS_STORE
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
CXX = g++
|
||||
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing
|
||||
CXXINC = -Iinclude
|
||||
|
||||
LDLIBS = -lraylib -pthread -lobjc
|
||||
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib -framework CoreFoundation -framework Cocoa -framework IOKit
|
||||
|
||||
hdr_files = ${wildcard src/*.h}
|
||||
|
||||
src_files = ${wildcard src/*.cpp}
|
||||
src_files += ${wildcard include/libremidi/*.cpp}
|
||||
|
||||
all: viseq
|
||||
|
||||
viseq: $(src_files) $(hdr_files)
|
||||
mkdir -p build
|
||||
$(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
$(src_files): %.cpp : %_src
|
||||
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
|
||||
|
||||
$(hdr_files): %.h : %_hdr
|
||||
@echo "G++ >---------- $(*F).h -----------<"
|
||||
|
||||
%_src:
|
||||
@echo "G++ >---------- $(*F).cpp -----------<"
|
||||
|
||||
%_hdr:
|
||||
@echo "G++ >---------- $(*F).h -----------<"
|
||||
|
||||
clean:
|
||||
rm -f build/*
|
||||
rm -f viseq
|
||||
@@ -0,0 +1,33 @@
|
||||
CXX = g++
|
||||
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_WINUWP=1
|
||||
CXXINC = -Iinclude
|
||||
|
||||
LDLIBS = -lraylibdll -pthread -lwindowsapp
|
||||
LDFLAGS = -L./lib -L/usr/lib -L/usr/local/lib
|
||||
|
||||
hdr_files = ${wildcard src/*.h}
|
||||
|
||||
src_files = ${wildcard src/*.cpp}
|
||||
src_files += ${wildcard include/libremidi/*.cpp}
|
||||
|
||||
all: viseq
|
||||
|
||||
viseq: $(src_files) $(hdr_files)
|
||||
mkdir -p build
|
||||
$(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
$(src_files): %.cpp : %_src
|
||||
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
|
||||
|
||||
$(hdr_files): %.h : %_hdr
|
||||
@echo "G++ >---------- $(*F).h -----------<"
|
||||
|
||||
%_src:
|
||||
@echo "G++ >---------- $(*F).cpp -----------<"
|
||||
|
||||
%_hdr:
|
||||
@echo "G++ >---------- $(*F).h -----------<"
|
||||
|
||||
clean:
|
||||
rm -f build/*
|
||||
rm -f viseq
|
||||
@@ -0,0 +1,5 @@
|
||||
prerequisite for MacOS ARM64:
|
||||
local raylib install (e.g. brew install raylib)
|
||||
|
||||
Build:
|
||||
make -f MakefileMacOSarm64
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//! MIDI API specifier arguments.
|
||||
//! To get information on which feature is supported by each back-end, check their backend file
|
||||
//! in e.g. backends/winmm.hpp, etc.
|
||||
typedef enum libremidi_api
|
||||
{
|
||||
UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */
|
||||
|
||||
// MIDI 1.0 APIs
|
||||
COREMIDI = 0x1, /*!< macOS CoreMidi API. */
|
||||
ALSA_SEQ, /*!< Linux ALSA Sequencer API. */
|
||||
ALSA_RAW, /*!< Linux Raw ALSA API. */
|
||||
JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */
|
||||
WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */
|
||||
WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */
|
||||
WEBMIDI, /*!< Web MIDI API through Emscripten */
|
||||
PIPEWIRE, /*!< PipeWire */
|
||||
KEYBOARD, /*!< Computer keyboard input */
|
||||
NETWORK, /*!< MIDI over IP */
|
||||
|
||||
// MIDI 2.0 APIs
|
||||
ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */
|
||||
ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */
|
||||
COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */
|
||||
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
|
||||
KEYBOARD_UMP, /*!< Computer keyboard input */
|
||||
NETWORK_UMP, /*!< MIDI2 over IP */
|
||||
|
||||
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
|
||||
} libremidi_api;
|
||||
|
||||
#if __cplusplus
|
||||
}
|
||||
#endif
|
||||
+17
-22
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include <libremidi/api-c.h>
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <any>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
@@ -9,28 +11,7 @@ namespace libremidi
|
||||
//! MIDI API specifier arguments.
|
||||
//! To get information on which feature is supported by each back-end, check their backend file
|
||||
//! in e.g. backends/winmm.hpp, etc.
|
||||
enum class API
|
||||
{
|
||||
UNSPECIFIED, /*!< Search for a working compiled API. */
|
||||
|
||||
// MIDI 1.0 APIs
|
||||
COREMIDI, /*!< macOS CoreMidi API. */
|
||||
ALSA_SEQ, /*!< Linux ALSA Sequencer API. */
|
||||
ALSA_RAW, /*!< Linux Raw ALSA API. */
|
||||
JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */
|
||||
WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */
|
||||
WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */
|
||||
WEBMIDI, /*!< Web MIDI API through Emscripten */
|
||||
PIPEWIRE, /*!< PipeWire */
|
||||
|
||||
// MIDI 2.0 APIs
|
||||
ALSA_RAW_UMP, /*!< Raw ALSA API for MIDI 2.0 */
|
||||
ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */
|
||||
COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */
|
||||
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
|
||||
|
||||
DUMMY /*!< A compilable but non-functional API. */
|
||||
};
|
||||
using API = libremidi_api;
|
||||
|
||||
/**
|
||||
* \brief A function to determine the available compiled MIDI 1.0 APIs.
|
||||
@@ -50,6 +31,9 @@ LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;
|
||||
*/
|
||||
LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
libremidi::API midi_api(const std::any& conf);
|
||||
|
||||
//! A static function to determine the current version.
|
||||
LIBREMIDI_EXPORT std::string_view get_version() noexcept;
|
||||
|
||||
@@ -60,6 +44,17 @@ LIBREMIDI_EXPORT std::string_view get_api_display_name(libremidi::API api);
|
||||
//! Look-up an API through its name
|
||||
LIBREMIDI_EXPORT libremidi::API get_compiled_api_by_name(std::string_view api);
|
||||
|
||||
inline constexpr bool is_midi1(libremidi::API api)
|
||||
{
|
||||
return (static_cast<int>(api) >= 0x1 && static_cast<int>(api) < 0x1000)
|
||||
|| api == libremidi::API::DUMMY;
|
||||
}
|
||||
|
||||
inline constexpr bool is_midi2(libremidi::API api)
|
||||
{
|
||||
return static_cast<int>(api) >= 0x1000;
|
||||
}
|
||||
|
||||
namespace midi1
|
||||
{
|
||||
//! Returns the default MIDI 1.0 backend to use for the target OS.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <any>
|
||||
#include <tuple>
|
||||
|
||||
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
|
||||
@@ -44,9 +47,10 @@
|
||||
|
||||
#if defined(LIBREMIDI_WINUWP)
|
||||
#include <libremidi/backends/winuwp.hpp>
|
||||
#if __has_include(<winrt/Microsoft.Devices.Midi2.h>)
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_WINMIDI)
|
||||
#include <libremidi/backends/winmidi.hpp>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_EMSCRIPTEN)
|
||||
@@ -55,6 +59,15 @@
|
||||
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
|
||||
#if defined(LIBREMIDI_KEYBOARD)
|
||||
#include <libremidi/backends/keyboard.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_NETWORK)
|
||||
#include <libremidi/backends/network.hpp>
|
||||
#include <libremidi/backends/network_ump.hpp>
|
||||
#endif
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
// The order here will control the order of the API search in
|
||||
@@ -100,6 +113,14 @@ static constexpr auto available_backends = make_tl(
|
||||
#if defined(LIBREMIDI_PIPEWIRE)
|
||||
,
|
||||
pipewire::backend{}
|
||||
#endif
|
||||
#if defined(LIBREMIDI_KEYBOARD)
|
||||
,
|
||||
kbd_backend{}
|
||||
#endif
|
||||
#if defined(LIBREMIDI_NETWORK)
|
||||
,
|
||||
net::backend{}
|
||||
#endif
|
||||
,
|
||||
dummy_backend{});
|
||||
@@ -135,15 +156,13 @@ static constexpr auto available_backends = make_tl(
|
||||
,
|
||||
coremidi_ump::backend{}
|
||||
#endif
|
||||
#if defined(LIBREMIDI_JACK)
|
||||
#endif
|
||||
#if defined(LIBREMIDI_WINUWP)
|
||||
#if __has_include(<winrt/Microsoft.Devices.Midi2.h>)
|
||||
#if defined(LIBREMIDI_WINMIDI)
|
||||
,
|
||||
winmidi::backend{}
|
||||
#endif
|
||||
#endif
|
||||
#if defined(LIBREMIDI_EMSCRIPTEN)
|
||||
#if defined(LIBREMIDI_NETWORK)
|
||||
,
|
||||
net_ump::backend{}
|
||||
#endif
|
||||
,
|
||||
dummy_backend{});
|
||||
@@ -183,5 +202,49 @@ auto for_backend(libremidi::API api, F&& f)
|
||||
midi2::for_backend(api, f);
|
||||
}
|
||||
|
||||
void for_input_configuration(auto f, std::any& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
|
||||
{
|
||||
f(*conf);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
|
||||
return;
|
||||
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||
}
|
||||
|
||||
void for_output_configuration(auto f, std::any& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))
|
||||
{
|
||||
f(*conf);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
|
||||
return;
|
||||
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||
}
|
||||
|
||||
void for_observer_configuration(auto f, std::any& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))
|
||||
{
|
||||
f(*conf);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
|
||||
return;
|
||||
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ struct manual_poll_parameters
|
||||
struct alsa_raw_input_configuration
|
||||
{
|
||||
std::function<bool(const manual_poll_parameters&)> manual_poll;
|
||||
std::chrono::milliseconds poll_period{2};
|
||||
};
|
||||
|
||||
struct alsa_raw_output_configuration
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
#include <libremidi/config.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
// Credits: greatly inspired from
|
||||
@@ -87,39 +82,16 @@ struct snd_ctl_wrapper
|
||||
struct enumerator
|
||||
{
|
||||
const libasound& snd = libasound::instance();
|
||||
const error_handler& handler;
|
||||
const observer_configuration& configuration;
|
||||
std::vector<alsa_raw_port_info> inputs;
|
||||
std::vector<alsa_raw_port_info> outputs;
|
||||
|
||||
std::function<void(std::string_view)> error_callback;
|
||||
std::function<void(std::string_view)> warn_callback;
|
||||
explicit enumerator(const auto& self)
|
||||
: handler{self}
|
||||
, configuration{self.configuration}
|
||||
{
|
||||
|
||||
template <typename... Args>
|
||||
void warning(Args&&... args)
|
||||
{
|
||||
std::string s;
|
||||
((s += args), ...);
|
||||
if (warn_callback)
|
||||
{
|
||||
warn_callback(std::move(s));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << s << std::endl;
|
||||
}
|
||||
}
|
||||
template <typename... Args>
|
||||
void error(Args&&... args)
|
||||
{
|
||||
std::string s;
|
||||
((s += args), ...);
|
||||
if (error_callback)
|
||||
{
|
||||
error_callback(std::move(s));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(s.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// 1: is an input / output
|
||||
@@ -141,9 +113,9 @@ struct enumerator
|
||||
}
|
||||
else if (status < 0 && status != -ENXIO)
|
||||
{
|
||||
error(
|
||||
"alsa_raw_helpers::enumerator::is: cannot get rawmidi information:", card, device, sub,
|
||||
snd.strerror(status));
|
||||
handler.libremidi_handle_error(
|
||||
configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub)
|
||||
+ " : " + snd.strerror(status));
|
||||
return status;
|
||||
}
|
||||
else
|
||||
@@ -185,26 +157,23 @@ struct enumerator
|
||||
return s;
|
||||
}
|
||||
|
||||
void enumerate_cards()
|
||||
stdx::error enumerate_cards()
|
||||
{
|
||||
using namespace std::literals;
|
||||
int card = -1;
|
||||
|
||||
int status = snd.card.next(&card);
|
||||
if (status < 0)
|
||||
{
|
||||
error(
|
||||
"alsa_raw_helpers::enumerator::enumerate_cards: "
|
||||
"cannot determine card number: ",
|
||||
snd.strerror(status));
|
||||
return;
|
||||
handler.libremidi_handle_error(
|
||||
configuration, "Cannot determine card number: "s + snd.strerror(status));
|
||||
return from_errc(status);
|
||||
}
|
||||
|
||||
if (card < 0)
|
||||
{
|
||||
error(
|
||||
"alsa_raw_helpers::enumerator::enumerate_cards: "
|
||||
"no sound cards found");
|
||||
return;
|
||||
handler.libremidi_handle_error(configuration, "No sound cards found");
|
||||
return std::errc::no_such_device;
|
||||
}
|
||||
|
||||
while (card >= 0)
|
||||
@@ -213,13 +182,12 @@ struct enumerator
|
||||
|
||||
if ((status = snd.card.next(&card)) < 0)
|
||||
{
|
||||
error(
|
||||
"alsa_raw_helpers::enumerator::enumerate_cards: "
|
||||
"cannot determine card number: ",
|
||||
snd.strerror(status));
|
||||
break;
|
||||
handler.libremidi_handle_error(
|
||||
configuration, "cannot determine card number: "s + snd.strerror(status));
|
||||
return std::errc::no_such_device;
|
||||
}
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
virtual void enumerate_devices(int card) = 0;
|
||||
@@ -228,20 +196,22 @@ struct enumerator
|
||||
inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)
|
||||
: snd{self.snd}
|
||||
{
|
||||
using namespace std::literals;
|
||||
int status = snd.ctl.open(&ctl, name, 0);
|
||||
if (status < 0)
|
||||
{
|
||||
self.error(
|
||||
"alsa_raw_helpers::enumerator::snd_ctl_wrapper: "
|
||||
"cannot open control for card",
|
||||
name, snd.strerror(status));
|
||||
self.handler.libremidi_handle_error(
|
||||
self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status));
|
||||
}
|
||||
}
|
||||
|
||||
struct midi1_enumerator : enumerator
|
||||
{
|
||||
using alsa_raw::enumerator::enumerator;
|
||||
|
||||
void enumerate_devices(int card) override
|
||||
{
|
||||
using namespace std::literals;
|
||||
char name[128];
|
||||
|
||||
sprintf(name, "hw:%d", card);
|
||||
@@ -261,10 +231,8 @@ struct midi1_enumerator : enumerator
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
error(
|
||||
"alsa_raw::midi1_enumerator::enumerate_devices: "
|
||||
"cannot determine device number: ",
|
||||
snd.strerror(status));
|
||||
handler.libremidi_handle_error(
|
||||
configuration, "Cannot determine device number: "s + snd.strerror(status));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,83 +33,72 @@ public:
|
||||
|
||||
~midi_in_impl() override { }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_alsa_raw: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_alsa_raw: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_alsa_raw: set_port_name unsupported");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
||||
|
||||
// Must be a string such as: "hw:2,4,1"
|
||||
[[nodiscard]] int do_init_port(const char* portname)
|
||||
[[nodiscard]] stdx::error do_init_port(const char* portname)
|
||||
{
|
||||
constexpr int mode = SND_RAWMIDI_NONBLOCK;
|
||||
if (const int err = snd.rawmidi.open(&midiport_, nullptr, portname, mode); err < 0)
|
||||
{
|
||||
error<driver_error>(this->configuration, "midi_in_alsa_raw::open_port: cannot open device.");
|
||||
return err;
|
||||
libremidi_handle_error(this->configuration, "cannot open device.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
snd_rawmidi_params_t* params{};
|
||||
snd_rawmidi_params_alloca(¶ms);
|
||||
|
||||
if (const int err = snd.rawmidi.params_current(midiport_, params); err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
if (const int err = snd.rawmidi.params_set_no_active_sensing(midiport_, params, 1); err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD
|
||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||
{
|
||||
if (const int err
|
||||
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_STANDARD);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
if (const int err
|
||||
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_NONE);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const int err
|
||||
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_TSTAMP);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
if (const int err
|
||||
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_MONOTONIC);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (const int err = snd.rawmidi.params(midiport_, params); err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
|
||||
return init_pollfd();
|
||||
}
|
||||
|
||||
[[nodiscard]] int init_port(const port_information& p)
|
||||
[[nodiscard]] stdx::error init_port(const port_information& p)
|
||||
{
|
||||
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||
}
|
||||
|
||||
[[nodiscard]] int init_pollfd()
|
||||
[[nodiscard]] stdx::error init_pollfd()
|
||||
{
|
||||
const int num_fds = snd.rawmidi.poll_descriptors_count(this->midiport_);
|
||||
|
||||
this->fds_.clear();
|
||||
this->fds_.resize(num_fds);
|
||||
|
||||
return snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
||||
int ret = snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
||||
if (ret < 0)
|
||||
return from_errc(ret);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
|
||||
@@ -155,7 +144,8 @@ public:
|
||||
while ((err = snd.rawmidi.read(this->midiport_, bytes, nbytes)) > 0)
|
||||
{
|
||||
const auto to_ns = [this] { return absolute_timestamp(); };
|
||||
decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp<timestamp_info>(to_ns, 0));
|
||||
m_processing.on_bytes(
|
||||
{bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -180,7 +170,8 @@ public:
|
||||
const auto to_ns = [ts] {
|
||||
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
|
||||
};
|
||||
decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp<timestamp_info>(to_ns, 0));
|
||||
m_processing.on_bytes(
|
||||
{bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -188,18 +179,19 @@ public:
|
||||
ssize_t read_input_buffer_with_timestamps() { return read_input_buffer(); }
|
||||
#endif
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (midiport_)
|
||||
snd.rawmidi.close(midiport_);
|
||||
midiport_ = nullptr;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept final override { return system_ns(); }
|
||||
|
||||
snd_rawmidi_t* midiport_{};
|
||||
std::vector<pollfd> fds_;
|
||||
midi1::input_state_machine decoder_{this->configuration};
|
||||
midi1::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
|
||||
class midi_in_alsa_raw_threaded : public midi_in_impl
|
||||
@@ -210,26 +202,33 @@ public:
|
||||
{
|
||||
if (this->termination_event < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
|
||||
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_alsa_raw_threaded() override
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
this->midi_in_alsa_raw_threaded::close_port();
|
||||
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
private:
|
||||
void run_thread(auto parse_func)
|
||||
{
|
||||
fds_.push_back(this->termination_event);
|
||||
const auto period
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||
.count();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Poll
|
||||
ssize_t err = poll(fds_.data(), fds_.size(), -1);
|
||||
ssize_t err = poll(fds_.data(), fds_.size(), period);
|
||||
if (err == -EAGAIN)
|
||||
continue;
|
||||
else if (err < 0)
|
||||
@@ -245,7 +244,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] ssize_t start_thread()
|
||||
[[nodiscard]] stdx::error start_thread()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -258,36 +257,37 @@ private:
|
||||
this->thread_ = std::thread{
|
||||
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
catch (const std::system_error& e)
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
error<thread_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
|
||||
return false;
|
||||
"error starting MIDI input thread: "s + e.what());
|
||||
return e.code();
|
||||
}
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_port(const input_port& port, std::string_view /*name*/) override
|
||||
stdx::error open_port(const input_port& port, std::string_view /*name*/) override
|
||||
{
|
||||
if (const int err = midi_in_impl::init_port(port); err < 0)
|
||||
return false;
|
||||
if (!start_thread())
|
||||
return false;
|
||||
return true;
|
||||
if (auto err = midi_in_impl::init_port(port); err != stdx::error{})
|
||||
return err;
|
||||
if (auto err = start_thread(); err != stdx::error{})
|
||||
return err;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
termination_event.notify();
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
termination_event.consume(); // Reset to zero
|
||||
|
||||
midi_in_impl::close_port();
|
||||
return midi_in_impl::close_port();
|
||||
}
|
||||
|
||||
std::thread thread_;
|
||||
@@ -297,12 +297,18 @@ private:
|
||||
class midi_in_alsa_raw_manual : public midi_in_impl
|
||||
{
|
||||
public:
|
||||
using midi_in_impl::midi_in_impl;
|
||||
midi_in_alsa_raw_manual(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
|
||||
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_alsa_raw_manual()
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
this->close_port();
|
||||
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -326,12 +332,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool open_port(const input_port& p, std::string_view /*name*/) override
|
||||
stdx::error open_port(const input_port& p, std::string_view /*name*/) override
|
||||
{
|
||||
if (midi_in_impl::init_port(p) < 0)
|
||||
return false;
|
||||
if (auto err = midi_in_impl::init_port(p); err != stdx::error{})
|
||||
return err;
|
||||
send_poll_callback();
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,83 +26,73 @@ public:
|
||||
midi_out_impl(output_configuration&& conf, alsa_raw_output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_impl() override
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
midi_out_impl::close_port();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_alsa_raw: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_alsa_raw: set_port_name unsupported");
|
||||
}
|
||||
|
||||
int connect_port(const char* portname)
|
||||
stdx::error connect_port(const char* portname)
|
||||
{
|
||||
constexpr int mode = SND_RAWMIDI_SYNC;
|
||||
int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);
|
||||
if (status < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_out_alsa_raw::open_port: cannot open device.");
|
||||
return status;
|
||||
libremidi_handle_error(
|
||||
this->configuration, "cannot open device.");
|
||||
return from_errc(status);
|
||||
}
|
||||
return status;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_port(const output_port& p, std::string_view) override
|
||||
stdx::error open_port(const output_port& p, std::string_view) override
|
||||
{
|
||||
return connect_port(raw_from_port_handle(p.port).to_string().c_str()) == 0;
|
||||
return connect_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (midiport_)
|
||||
snd.rawmidi.close(midiport_);
|
||||
midiport_ = nullptr;
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
if (!midiport_)
|
||||
error<invalid_use_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_alsa_raw::send_message: trying to send a message without an open "
|
||||
"trying to send a message without an open "
|
||||
"port.");
|
||||
|
||||
if (!this->configuration.chunking)
|
||||
{
|
||||
write(message, size);
|
||||
return write(message, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_chunked(message, size);
|
||||
return write_chunked(message, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool write(const unsigned char* message, size_t size)
|
||||
stdx::error write(const unsigned char* message, size_t size)
|
||||
{
|
||||
if (snd.rawmidi.write(midiport_, message, size) < 0)
|
||||
if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_out_alsa_raw::send_message: cannot write message.");
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
this->configuration, "cannot write message.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
std::size_t get_chunk_size() const noexcept
|
||||
@@ -125,7 +115,7 @@ public:
|
||||
}
|
||||
|
||||
// inspired from ALSA amidi.c source code
|
||||
void write_chunked(const unsigned char* const begin, size_t size)
|
||||
stdx::error write_chunked(const unsigned char* const begin, size_t size)
|
||||
{
|
||||
const unsigned char* data = begin;
|
||||
const unsigned char* end = begin + size;
|
||||
@@ -135,8 +125,8 @@ public:
|
||||
// Send the first buffer
|
||||
std::size_t len = chunk_size;
|
||||
|
||||
if (!write(data, len))
|
||||
return;
|
||||
if (auto err = write(data, len); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
data += len;
|
||||
|
||||
@@ -149,11 +139,11 @@ public:
|
||||
{
|
||||
if (!configuration.chunking->wait(
|
||||
std::chrono::microseconds((chunk_size - available) * 320), written_bytes))
|
||||
return;
|
||||
return std::errc::protocol_error;
|
||||
};
|
||||
|
||||
if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes))
|
||||
return;
|
||||
return std::errc::protocol_error;
|
||||
|
||||
// Write more data
|
||||
len = end - data;
|
||||
@@ -165,11 +155,13 @@ public:
|
||||
if (len > chunk_size)
|
||||
len = chunk_size;
|
||||
|
||||
if (!write(data, len))
|
||||
return;
|
||||
if (auto err = write(data, len); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
data += len;
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
snd_rawmidi_t* midiport_{};
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include <libremidi/backends/linux/udev.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace libremidi::alsa_raw
|
||||
{
|
||||
template <typename Enumerator>
|
||||
class observer_impl_base : public observer_api
|
||||
class observer_impl_base
|
||||
: public observer_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
std::vector<libremidi::input_port> ret;
|
||||
Enumerator new_devs;
|
||||
Enumerator new_devs{*this};
|
||||
|
||||
new_devs.enumerate_cards();
|
||||
for (auto& d : new_devs.inputs)
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
std::vector<libremidi::output_port> ret;
|
||||
Enumerator new_devs;
|
||||
Enumerator new_devs{*this};
|
||||
|
||||
new_devs.enumerate_cards();
|
||||
for (auto& d : new_devs.outputs)
|
||||
@@ -147,7 +147,7 @@ private:
|
||||
|
||||
void check_devices()
|
||||
{
|
||||
Enumerator new_devs;
|
||||
Enumerator new_devs{*this};
|
||||
|
||||
new_devs.enumerate_cards();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace libremidi::alsa_raw_ump
|
||||
struct input_configuration
|
||||
{
|
||||
std::function<bool(const manual_poll_parameters&)> manual_poll;
|
||||
std::chrono::milliseconds poll_period{2};
|
||||
};
|
||||
|
||||
struct output_configuration
|
||||
|
||||
@@ -7,8 +7,10 @@ namespace libremidi::alsa_raw_ump
|
||||
{
|
||||
struct midi2_enumerator : alsa_raw::enumerator
|
||||
{
|
||||
using alsa_raw::enumerator::enumerator;
|
||||
void enumerate_devices(int card) override
|
||||
{
|
||||
using namespace std::literals;
|
||||
char name[128];
|
||||
|
||||
sprintf(name, "hw:%d", card);
|
||||
@@ -28,10 +30,8 @@ struct midi2_enumerator : alsa_raw::enumerator
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
error(
|
||||
"alsa_raw_ump::midi2_enumerator::enumerate_devices: "
|
||||
"cannot determine device number: ",
|
||||
snd.strerror(status));
|
||||
handler.libremidi_handle_error(
|
||||
configuration, "Cannot determine device number: "s + snd.strerror(status));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,32 +35,17 @@ public:
|
||||
|
||||
~midi_in_impl() override { }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "alsa_raw_ump::ump: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "alsa_raw_ump::ump: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "alsa_raw_ump::ump: set_port_name unsupported");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
|
||||
|
||||
// Must be a string such as: "hw:2,4,1"
|
||||
[[nodiscard]] int do_init_port(const char* portname)
|
||||
[[nodiscard]] stdx::error do_init_port(const char* portname)
|
||||
{
|
||||
constexpr int mode = 0;
|
||||
SND_RAWMIDI_NONBLOCK;
|
||||
SND_RAWMIDI_NONBLOCK; // fixme
|
||||
if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
|
||||
return err;
|
||||
libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
snd_rawmidi_params_t* params{};
|
||||
@@ -69,49 +54,52 @@ public:
|
||||
auto rawmidi = snd.ump.rawmidi(midiport_);
|
||||
|
||||
if (int err = snd.ump.rawmidi_params_current(midiport_, params); err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
if (int err = snd.rawmidi.params_set_no_active_sensing(rawmidi, params, 1); err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
|
||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||
{
|
||||
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_STANDARD);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_NONE);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_TSTAMP);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
if (int err
|
||||
= snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_MONOTONIC);
|
||||
err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
if (int err = snd.ump.rawmidi_params(midiport_, params); err < 0)
|
||||
return err;
|
||||
return from_errc(err);
|
||||
|
||||
return init_pollfd();
|
||||
}
|
||||
|
||||
[[nodiscard]] int init_port(const port_information& p)
|
||||
[[nodiscard]] stdx::error init_port(const port_information& p)
|
||||
{
|
||||
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||
}
|
||||
|
||||
[[nodiscard]] int init_pollfd()
|
||||
[[nodiscard]] stdx::error init_pollfd()
|
||||
{
|
||||
int num_fds = snd.ump.poll_descriptors_count(this->midiport_);
|
||||
|
||||
this->fds_.clear();
|
||||
this->fds_.resize(num_fds);
|
||||
|
||||
return snd.ump.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
||||
int ret = snd.ump.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
||||
if (ret < 0)
|
||||
return from_errc(ret);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
|
||||
@@ -143,95 +131,60 @@ public:
|
||||
|
||||
ssize_t read_input_buffer()
|
||||
{
|
||||
static const constexpr int nbytes = 1024;
|
||||
|
||||
unsigned char bytes[nbytes];
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = false,
|
||||
.absolute_is_monotonic = false,
|
||||
.has_samples = false,
|
||||
};
|
||||
static const constexpr int nwords = 64;
|
||||
uint32_t words[nwords];
|
||||
|
||||
ssize_t err = 0;
|
||||
while ((err = snd.ump.read(this->midiport_, bytes, nbytes)) > 0)
|
||||
while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0)
|
||||
{
|
||||
std::cerr << "We read: " << nbytes << "bytes !!!!";
|
||||
return 1;
|
||||
// err is the amount of bytes read
|
||||
// decoder_.add_bytes(bytes, err);
|
||||
const auto to_ns = [this] { return absolute_timestamp(); };
|
||||
m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void set_timestamp(struct timespec ts, timestamp& res)
|
||||
{
|
||||
static constexpr int64_t nanos = 1e9;
|
||||
switch (configuration.timestamps)
|
||||
{
|
||||
// Unneeded here
|
||||
case timestamp_mode::NoTimestamp:
|
||||
break;
|
||||
case timestamp_mode::Relative: {
|
||||
const auto t = static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec);
|
||||
if (firstMessage == true)
|
||||
{
|
||||
firstMessage = false;
|
||||
res = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = t - last_time;
|
||||
}
|
||||
last_time = t;
|
||||
break;
|
||||
}
|
||||
case timestamp_mode::Absolute:
|
||||
case timestamp_mode::SystemMonotonic:
|
||||
res = static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec);
|
||||
break;
|
||||
case timestamp_mode::Custom:
|
||||
res = configuration.get_timestamp(
|
||||
static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t read_input_buffer_with_timestamps()
|
||||
{
|
||||
static const constexpr int nbytes = 1024;
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = true,
|
||||
.has_samples = false,
|
||||
};
|
||||
|
||||
unsigned char bytes[nbytes];
|
||||
static const constexpr int nwords = 64;
|
||||
uint32_t words[nwords];
|
||||
struct timespec ts;
|
||||
|
||||
ssize_t err = 0;
|
||||
while ((err = snd.ump.tread(this->midiport_, &ts, bytes, nbytes)) > 0)
|
||||
while ((err = snd.ump.tread(this->midiport_, &ts, words, nwords * 4)) > 0)
|
||||
{
|
||||
// err is the amount of bytes read
|
||||
int64_t ns{};
|
||||
set_timestamp(ts, ns);
|
||||
std::cerr << "We read: " << nbytes << "bytes !!!!";
|
||||
return 1;
|
||||
// decoder_.add_bytes(bytes, err, ns);
|
||||
const auto to_ns = [ts] {
|
||||
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
|
||||
};
|
||||
m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (midiport_)
|
||||
snd.ump.close(midiport_);
|
||||
midiport_ = nullptr;
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
|
||||
|
||||
midi2_enumerator get_device_enumerator() const noexcept
|
||||
{
|
||||
midi2_enumerator device_list;
|
||||
device_list.error_callback
|
||||
= [this](std::string_view text) { this->error<driver_error>(this->configuration, text); };
|
||||
return device_list;
|
||||
}
|
||||
|
||||
snd_ump_t* midiport_{};
|
||||
std::vector<pollfd> fds_;
|
||||
// midi_stream_decoder decoder_{this->configuration.on_message};
|
||||
int64_t last_time{};
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
|
||||
class midi_in_impl_threaded : public midi_in_impl
|
||||
@@ -243,26 +196,32 @@ public:
|
||||
{
|
||||
if (this->termination_event < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
|
||||
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_impl_threaded()
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
this->close_port();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
private:
|
||||
void run_thread(auto parse_func)
|
||||
{
|
||||
fds_.push_back(this->termination_event);
|
||||
const auto period
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||
.count();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Poll
|
||||
ssize_t err = poll(fds_.data(), fds_.size(), -1);
|
||||
ssize_t err = poll(fds_.data(), fds_.size(), period);
|
||||
if (err == -EAGAIN)
|
||||
continue;
|
||||
else if (err < 0)
|
||||
@@ -278,7 +237,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int start_thread()
|
||||
[[nodiscard]] stdx::error start_thread()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -291,36 +250,37 @@ private:
|
||||
this->thread_ = std::thread{
|
||||
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
catch (const std::system_error& e)
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
error<thread_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
|
||||
return false;
|
||||
"error starting MIDI input thread: "s + e.what());
|
||||
return e.code();
|
||||
}
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_port(const input_port& port, [[maybe_unused]] std::string_view name) override
|
||||
stdx::error open_port(const input_port& port, [[maybe_unused]] std::string_view name) override
|
||||
{
|
||||
if (int err = midi_in_impl::init_port(port); err < 0)
|
||||
return false;
|
||||
if (!start_thread())
|
||||
return false;
|
||||
return true;
|
||||
if (auto err = midi_in_impl::init_port(port); err != stdx::error{})
|
||||
return err;
|
||||
if (auto err = start_thread(); err != stdx::error{})
|
||||
return err;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
termination_event.notify();
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
termination_event.consume(); // Reset to zero
|
||||
|
||||
midi_in_impl::close_port();
|
||||
return midi_in_impl::close_port();
|
||||
}
|
||||
|
||||
std::thread thread_;
|
||||
@@ -330,12 +290,18 @@ private:
|
||||
class midi_in_impl_manual : public midi_in_impl
|
||||
{
|
||||
public:
|
||||
using midi_in_impl::midi_in_impl;
|
||||
midi_in_impl_manual(ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
|
||||
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_impl_manual()
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
this->close_port();
|
||||
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -359,12 +325,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool open_port(const input_port& p, [[maybe_unused]] std::string_view name) override
|
||||
stdx::error open_port(const input_port& p, [[maybe_unused]] std::string_view name) override
|
||||
{
|
||||
if (midi_in_impl::init_port(p) < 0)
|
||||
return false;
|
||||
if (auto err = midi_in_impl::init_port(p); err != stdx::error{})
|
||||
return err;
|
||||
send_poll_callback();
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,76 +29,63 @@ public:
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
assert(snd.ump.available);
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_impl() override
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
midi_out_impl::close_port();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_alsa_raw: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_alsa_raw: set_port_name unsupported");
|
||||
}
|
||||
|
||||
int connect_port(const char* portname)
|
||||
stdx::error connect_port(const char* portname)
|
||||
{
|
||||
constexpr int mode = SND_RAWMIDI_SYNC;
|
||||
int status = snd.ump.open(NULL, &midiport_, portname, mode);
|
||||
if (status < 0)
|
||||
int ret = snd.ump.open(NULL, &midiport_, portname, mode);
|
||||
if (ret < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_out_alsa_raw::open_port: cannot open device.");
|
||||
return status;
|
||||
libremidi_handle_error(this->configuration, "cannot open device.");
|
||||
return from_errc(ret);
|
||||
}
|
||||
return status;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_port(const output_port& p, std::string_view) override
|
||||
stdx::error open_port(const output_port& p, std::string_view) override
|
||||
{
|
||||
return connect_port(raw_from_port_handle(p.port).to_string().c_str()) == 0;
|
||||
return connect_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (midiport_)
|
||||
snd.ump.close(midiport_);
|
||||
midiport_ = nullptr;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||
{
|
||||
if (!midiport_)
|
||||
error<invalid_use_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_alsa_raw::send_message: trying to send a message without an open "
|
||||
"trying to send a message without an open "
|
||||
"port.");
|
||||
|
||||
write(ump_stream, count * sizeof(uint32_t));
|
||||
return write(ump_stream, count * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
bool write(const uint32_t* ump_stream, size_t bytes)
|
||||
stdx::error write(const uint32_t* ump_stream, size_t bytes)
|
||||
{
|
||||
if (snd.ump.write(midiport_, ump_stream, bytes) < 0)
|
||||
if (auto err = snd.ump.write(midiport_, ump_stream, bytes); err < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_out_alsa_raw::send_message: cannot write message.");
|
||||
return false;
|
||||
libremidi_handle_error(this->configuration, "cannot write message.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
snd_ump_t* midiport_{};
|
||||
|
||||
@@ -38,6 +38,7 @@ struct input_configuration
|
||||
snd_seq_t* context{};
|
||||
std::function<bool(const poll_parameters&)> manual_poll;
|
||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||
std::chrono::milliseconds poll_period{2};
|
||||
|
||||
static constexpr int midi_version = 1;
|
||||
};
|
||||
@@ -56,6 +57,7 @@ struct observer_configuration
|
||||
snd_seq_t* context{};
|
||||
std::function<bool(const poll_parameters&)> manual_poll;
|
||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||
std::chrono::milliseconds poll_period{100};
|
||||
|
||||
static constexpr int midi_version = 1;
|
||||
};
|
||||
|
||||
@@ -177,18 +177,20 @@ struct alsa_data
|
||||
}
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view clientName)
|
||||
stdx::error set_client_name(std::string_view clientName)
|
||||
{
|
||||
snd.seq.set_client_name(seq, clientName.data());
|
||||
int ret = snd.seq.set_client_name(seq, clientName.data());
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
void set_port_name(std::string_view portName)
|
||||
stdx::error set_port_name(std::string_view portName)
|
||||
{
|
||||
snd_seq_port_info_t* pinfo;
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
snd.seq.get_port_info(seq, vport, pinfo);
|
||||
snd.seq.port_info_set_name(pinfo, portName.data());
|
||||
snd.seq.set_port_info(seq, vport, pinfo);
|
||||
int ret = snd.seq.set_port_info(seq, vport, pinfo);
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
unsigned int get_port_count(int caps) const
|
||||
@@ -209,9 +211,9 @@ struct alsa_data
|
||||
// snd.seq.port_info_set_port(src_pinfo, port);
|
||||
|
||||
// {
|
||||
// self.template error<invalid_parameter_error>(
|
||||
// self.libremidi_handle_error(
|
||||
// self.configuration,
|
||||
// "alsa::get_port_info: invalid 'portNumber' argument: " + std::to_string(portNumber));
|
||||
// "invalid 'portNumber' argument: " + std::to_string(portNumber));
|
||||
// return {};
|
||||
// }
|
||||
snd_seq_addr_t addr;
|
||||
@@ -270,8 +272,8 @@ struct alsa_data
|
||||
// Make subscription
|
||||
if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0)
|
||||
{
|
||||
self.template error<driver_error>(
|
||||
self.configuration, "create_connection: ALSA error allocation port subscription.");
|
||||
self.libremidi_handle_error(
|
||||
self.configuration, "ALSA error allocation port subscription.");
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ using midi_in_base
|
||||
= std::conditional_t<ConfigurationImpl::midi_version == 1, midi1::in_api, midi2::in_api>;
|
||||
template <typename ConfigurationImpl>
|
||||
using midi_in_processing = std::conditional_t<
|
||||
ConfigurationImpl::midi_version == 1, midi1::input_state_machine, dummy_processing>;
|
||||
ConfigurationImpl::midi_version == 1, midi1::input_state_machine, midi2::input_state_machine>;
|
||||
|
||||
template <typename ConfigurationBase, typename ConfigurationImpl>
|
||||
class midi_in_impl
|
||||
@@ -55,9 +55,9 @@ public:
|
||||
{
|
||||
if (init_client(configuration) < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_alsa::initialize: error creating ALSA sequencer client "
|
||||
"error creating ALSA sequencer client "
|
||||
"object.");
|
||||
return;
|
||||
}
|
||||
@@ -80,8 +80,7 @@ public:
|
||||
int result = snd.midi.event_new(0, &coder);
|
||||
if (result < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_in_alsa::initialize: error during snd_midi_event_new.");
|
||||
libremidi_handle_error(this->configuration, "error during snd_midi_event_new.");
|
||||
return;
|
||||
}
|
||||
snd.midi.event_init(coder);
|
||||
@@ -105,7 +104,13 @@ public:
|
||||
snd.seq.close(this->seq);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
|
||||
libremidi::API get_current_api() const noexcept override
|
||||
{
|
||||
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||
return libremidi::API::ALSA_SEQ;
|
||||
else
|
||||
return libremidi::API::ALSA_SEQ_UMP;
|
||||
}
|
||||
|
||||
[[nodiscard]] int create_port(std::string_view portName)
|
||||
{
|
||||
@@ -157,14 +162,13 @@ public:
|
||||
|
||||
if (int ret = create_port(portName); ret < 0)
|
||||
{
|
||||
error<driver_error>(configuration, "midi_in_alsa::create_port: ALSA error creating port.");
|
||||
libremidi_handle_error(configuration, "ALSA error creating port.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (int ret = connect_port(*source); ret < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_in_alsa::create_port: ALSA error making port connection.");
|
||||
libremidi_handle_error(configuration, "ALSA error making port connection.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -184,63 +188,21 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
unsubscribe();
|
||||
stop_queue();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view clientName) override
|
||||
stdx::error set_client_name(std::string_view clientName) override
|
||||
{
|
||||
alsa_data::set_client_name(clientName);
|
||||
return alsa_data::set_client_name(clientName);
|
||||
}
|
||||
|
||||
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
|
||||
|
||||
protected:
|
||||
void set_timestamp(const auto& ev, auto& msg) noexcept
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
static constexpr int64_t nanos = 1e9;
|
||||
switch (configuration.timestamps)
|
||||
{
|
||||
case timestamp_mode::NoTimestamp:
|
||||
msg.timestamp = 0;
|
||||
return;
|
||||
case timestamp_mode::Relative: {
|
||||
const auto t1 = static_cast<int64_t>(ev.time.time.tv_sec) * nanos
|
||||
+ static_cast<int64_t>(ev.time.time.tv_nsec);
|
||||
const auto t0 = static_cast<int64_t>(last_time.tv_sec) * nanos
|
||||
+ static_cast<int64_t>(last_time.tv_nsec);
|
||||
const auto time = t1 - t0;
|
||||
|
||||
last_time = ev.time.time;
|
||||
|
||||
if (this->firstMessage == true)
|
||||
{
|
||||
this->firstMessage = false;
|
||||
msg.timestamp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.timestamp = time;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case timestamp_mode::Absolute: {
|
||||
msg.timestamp = ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec;
|
||||
break;
|
||||
}
|
||||
case timestamp_mode::Custom: {
|
||||
msg.timestamp
|
||||
= configuration.get_timestamp(ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec);
|
||||
break;
|
||||
}
|
||||
case timestamp_mode::SystemMonotonic: {
|
||||
namespace clk = std::chrono;
|
||||
msg.timestamp = system_ns();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return alsa_data::set_port_name(portName);
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
@@ -303,12 +265,14 @@ protected:
|
||||
snd_seq_event_t* ev{};
|
||||
event_handle handle{snd};
|
||||
int result = 0;
|
||||
while ((result = snd.seq.event_input(seq, &ev)) > 0)
|
||||
if ((result = snd.seq.event_input(seq, &ev)) > 0)
|
||||
{
|
||||
handle.reset(ev);
|
||||
if (int err = process_event(*ev); err < 0)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
@@ -346,11 +310,17 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
// MIDI 2 : no decoder, we can just send the UMP data directly, yay
|
||||
libremidi::ump ump;
|
||||
std::memcpy(ump.data, ev.ump, sizeof(ev.ump));
|
||||
set_timestamp(ev, ump);
|
||||
configuration.on_message(std::move(ump));
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = false,
|
||||
.has_samples = false,
|
||||
};
|
||||
const auto to_ns = [ts = ev.time.time] {
|
||||
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
|
||||
};
|
||||
|
||||
m_processing.on_bytes_multi(
|
||||
{ev.ump, ev.ump + 4}, m_processing.template timestamp<timestamp_info>(to_ns, 0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -370,7 +340,6 @@ protected:
|
||||
#endif
|
||||
|
||||
int queue_id{}; // an input queue is needed to get timestamped events
|
||||
snd_seq_real_time_t last_time{};
|
||||
|
||||
// Only needed for midi 1
|
||||
std::vector<unsigned char> decoding_buffer = std::vector<unsigned char>(4096);
|
||||
@@ -386,62 +355,65 @@ public:
|
||||
{
|
||||
if (this->termination_event < 0)
|
||||
{
|
||||
this->template error<driver_error>(
|
||||
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
|
||||
}
|
||||
this->libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||
return;
|
||||
}
|
||||
|
||||
~midi_in_alsa_threaded() { this->close_port(); }
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_alsa_threaded()
|
||||
{
|
||||
this->close_port();
|
||||
this->client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
private:
|
||||
bool open_port(const input_port& pt, std::string_view local_port_name) override
|
||||
stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
|
||||
{
|
||||
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
|
||||
return false;
|
||||
return from_errc(err);
|
||||
|
||||
if (!start_thread())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return this->start_thread();
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
if (int err = this->init_virtual_port(portName); err < 0)
|
||||
return false;
|
||||
return from_errc(err);
|
||||
|
||||
if (!this->start_thread())
|
||||
return false;
|
||||
return true;
|
||||
return this->start_thread();
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
||||
// FIXME shouldn't we always close the thread
|
||||
auto err = midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
||||
|
||||
stop_thread();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
[[nodiscard]] int start_thread()
|
||||
[[nodiscard]] stdx::error start_thread()
|
||||
{
|
||||
try
|
||||
{
|
||||
this->thread = std::thread([this] { thread_handler(); });
|
||||
return stdx::error{};
|
||||
}
|
||||
catch (const std::system_error& e)
|
||||
{
|
||||
using namespace std::literals;
|
||||
this->unsubscribe();
|
||||
|
||||
this->template error<thread_error>(
|
||||
this->configuration,
|
||||
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
|
||||
return false;
|
||||
this->libremidi_handle_error(
|
||||
this->configuration, "error starting MIDI input thread: "s + e.what());
|
||||
return e.code();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void stop_thread()
|
||||
stdx::error stop_thread()
|
||||
{
|
||||
termination_event.notify();
|
||||
|
||||
@@ -449,6 +421,7 @@ private:
|
||||
this->thread.join();
|
||||
|
||||
termination_event.consume();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void thread_handler()
|
||||
@@ -458,12 +431,15 @@ private:
|
||||
poll_fds[0] = this->termination_event;
|
||||
alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);
|
||||
|
||||
const auto period
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||
.count();
|
||||
for (;;)
|
||||
{
|
||||
if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)
|
||||
{
|
||||
// No data pending
|
||||
if (poll(poll_fds, poll_fd_count, -1) >= 0)
|
||||
if (poll(poll_fds, poll_fd_count, period) >= 0)
|
||||
{
|
||||
// We got our stop-thread signal
|
||||
if (termination_event.ready(poll_fds[0]))
|
||||
@@ -486,12 +462,8 @@ private:
|
||||
}
|
||||
#endif
|
||||
|
||||
(void)res;
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
if (res < 0)
|
||||
std::cerr << "midi_in_alsa::thread_handler: MIDI input error: " << snd.strerror(res)
|
||||
<< "\n";
|
||||
#endif
|
||||
LIBREMIDI_LOG("MIDI input error: ", this->snd.strerror(res));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,31 +499,33 @@ public:
|
||||
|
||||
~midi_in_alsa_manual() { this->close_port(); }
|
||||
|
||||
bool open_port(const input_port& pt, std::string_view local_port_name) override
|
||||
stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
|
||||
{
|
||||
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
|
||||
return false;
|
||||
return from_errc(err);
|
||||
|
||||
if (int err = init_callback(); err < 0)
|
||||
return false;
|
||||
return true;
|
||||
return from_errc(err);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view name) override
|
||||
stdx::error open_virtual_port(std::string_view name) override
|
||||
{
|
||||
if (int err = this->init_virtual_port(name); err < 0)
|
||||
return false;
|
||||
return from_errc(err);
|
||||
|
||||
if (int err = init_callback(); err < 0)
|
||||
return false;
|
||||
return true;
|
||||
return from_errc(err);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
this->configuration.stop_poll(this->vaddr);
|
||||
|
||||
midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
||||
return midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,22 +23,21 @@ public:
|
||||
{
|
||||
if (init_client(configuration) < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_alsa::initialize: error creating ALSA sequencer client "
|
||||
"error creating ALSA sequencer client "
|
||||
"object.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration,
|
||||
"midi_out_alsa::initialize: error initializing MIDI event "
|
||||
"parser.");
|
||||
libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");
|
||||
return;
|
||||
}
|
||||
snd.midi.event_init(this->coder);
|
||||
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_impl() override
|
||||
@@ -54,6 +53,8 @@ public:
|
||||
|
||||
if (!configuration.context)
|
||||
snd.seq.close(this->seq);
|
||||
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
|
||||
@@ -65,55 +66,60 @@ public:
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
|
||||
}
|
||||
|
||||
bool open_port(const output_port& p, std::string_view portName) override
|
||||
stdx::error open_port(const output_port& p, std::string_view portName) override
|
||||
{
|
||||
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||
if (nSrc < 1)
|
||||
{
|
||||
error<no_devices_found_error>(
|
||||
this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!");
|
||||
return false;
|
||||
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
||||
return make_error_code(std::errc::no_such_device);
|
||||
}
|
||||
|
||||
auto sink = get_port_info(p);
|
||||
if (!sink)
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
if (int err = create_port(portName); err < 0)
|
||||
{
|
||||
error<driver_error>(configuration, "midi_out_alsa::create_port: ALSA error creating port.");
|
||||
return false;
|
||||
libremidi_handle_error(configuration, "ALSA error creating port.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
snd_seq_addr_t source{
|
||||
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
|
||||
if (int err = create_connection(*this, source, *sink, true); err < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_alsa::create_port: ALSA error making port connection.");
|
||||
return false;
|
||||
libremidi_handle_error(configuration, "ALSA error making port connection.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
if (int err = create_port(portName); err < 0)
|
||||
return false;
|
||||
return true;
|
||||
return from_errc(err);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override { unsubscribe(); }
|
||||
|
||||
void set_client_name(std::string_view clientName) override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
alsa_data::set_client_name(clientName);
|
||||
unsubscribe();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
|
||||
stdx::error set_client_name(std::string_view clientName) override
|
||||
{
|
||||
return alsa_data::set_client_name(clientName);
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, std::size_t size) override
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
return alsa_data::set_port_name(portName);
|
||||
}
|
||||
|
||||
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
||||
{
|
||||
int64_t result{};
|
||||
if (size > this->bufferSize)
|
||||
@@ -122,11 +128,11 @@ public:
|
||||
result = snd.midi.event_resize_buffer(this->coder, size);
|
||||
if (result != 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_alsa::send_message: ALSA error resizing MIDI event "
|
||||
"ALSA error resizing MIDI event "
|
||||
"buffer.");
|
||||
return;
|
||||
return std::errc::no_buffer_space;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,14 +150,14 @@ public:
|
||||
result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev);
|
||||
if (result < 0)
|
||||
{
|
||||
warning(this->configuration, "midi_out_alsa::send_message: event parsing error!");
|
||||
return;
|
||||
libremidi_handle_warning(this->configuration, "event parsing error!");
|
||||
return std::errc::bad_message;
|
||||
}
|
||||
|
||||
if (ev.type == SND_SEQ_EVENT_NONE)
|
||||
{
|
||||
warning(this->configuration, "midi_out_alsa::send_message: incomplete message!");
|
||||
return;
|
||||
libremidi_handle_warning(this->configuration, "incomplete message!");
|
||||
return std::errc::message_size;
|
||||
}
|
||||
|
||||
offset += result;
|
||||
@@ -159,13 +165,12 @@ public:
|
||||
result = snd.seq.event_output(this->seq, &ev);
|
||||
if (result < 0)
|
||||
{
|
||||
warning(
|
||||
this->configuration,
|
||||
"midi_out_alsa::send_message: error sending MIDI message to port.");
|
||||
return;
|
||||
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
||||
return std::errc::io_error;
|
||||
}
|
||||
}
|
||||
snd.seq.drain_output(this->seq);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <libremidi/backends/alsa_seq/config.hpp>
|
||||
#include <libremidi/backends/alsa_seq/helpers.hpp>
|
||||
#include <libremidi/backends/linux/helpers.hpp>
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
@@ -26,6 +27,7 @@ template <typename ConfigurationImpl>
|
||||
class observer_impl
|
||||
: public observer_api
|
||||
, public alsa_data
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
@@ -40,7 +42,11 @@ public:
|
||||
using namespace std::literals;
|
||||
if (int err = init_client(configuration); err < 0)
|
||||
{
|
||||
throw driver_error("observer_alsa: snd_seq_open failed");
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"error creating ALSA sequencer client "
|
||||
"object.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configuration.has_callbacks())
|
||||
@@ -65,7 +71,8 @@ public:
|
||||
*this, "libremidi-observe", caps, SND_SEQ_PORT_TYPE_APPLICATION, false);
|
||||
if (err < 0)
|
||||
{
|
||||
throw driver_error("observer: ALSA error creating port.");
|
||||
libremidi_handle_error(this->configuration, "error creating ALSA sequencer port.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +82,8 @@ public:
|
||||
= snd.seq.connect_from(seq, vport, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
|
||||
if (err < 0)
|
||||
{
|
||||
throw driver_error("observer_alsa: snd_seq_connect_from failed");
|
||||
libremidi_handle_error(this->configuration, "error connecting to ALSA sequencer.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,9 +106,13 @@ public:
|
||||
|
||||
const auto tp = snd.seq.port_info_get_type(pinfo);
|
||||
bool ok = this->configuration.track_any;
|
||||
|
||||
static constexpr auto virtual_port
|
||||
= SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER;
|
||||
|
||||
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
|
||||
ok = true;
|
||||
else if ((tp & SND_SEQ_PORT_TYPE_SOFTWARE) && this->configuration.track_virtual)
|
||||
else if ((tp & virtual_port) && this->configuration.track_virtual)
|
||||
ok = true;
|
||||
if (!ok)
|
||||
return {};
|
||||
@@ -271,9 +283,12 @@ public:
|
||||
// Start the listening thread
|
||||
thread = std::thread{[this] {
|
||||
auto& snd = alsa_data::snd;
|
||||
const auto period
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||
.count();
|
||||
for (;;)
|
||||
{
|
||||
int err = poll(descriptors_.data(), descriptors_.size(), -1);
|
||||
int err = poll(descriptors_.data(), descriptors_.size(), period);
|
||||
if (err >= 0)
|
||||
{
|
||||
// We got our stop-thread signal
|
||||
|
||||
@@ -28,6 +28,7 @@ struct input_configuration
|
||||
snd_seq_t* context{};
|
||||
std::function<bool(const poll_parameters&)> manual_poll;
|
||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||
std::chrono::milliseconds poll_period{2};
|
||||
|
||||
static constexpr int midi_version = 2;
|
||||
};
|
||||
@@ -46,6 +47,7 @@ struct observer_configuration
|
||||
snd_seq_t* context{};
|
||||
std::function<bool(const libremidi::alsa_seq::poll_parameters&)> manual_poll;
|
||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||
std::chrono::milliseconds poll_period{100};
|
||||
|
||||
static constexpr int midi_version = 2;
|
||||
};
|
||||
|
||||
@@ -27,22 +27,14 @@ public:
|
||||
assert(snd.seq.ump.available);
|
||||
if (init_client(configuration) < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_alsa::initialize: error creating ALSA sequencer client "
|
||||
"error creating ALSA sequencer client "
|
||||
"object.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration,
|
||||
"midi_out_alsa::initialize: error initializing MIDI event "
|
||||
"parser.");
|
||||
return;
|
||||
}
|
||||
snd.midi.event_init(this->coder);
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_impl() override
|
||||
@@ -53,14 +45,14 @@ public:
|
||||
// Cleanup.
|
||||
if (this->vport >= 0)
|
||||
snd.seq.delete_port(this->seq, this->vport);
|
||||
if (this->coder)
|
||||
snd.midi.event_free(this->coder);
|
||||
|
||||
if (!configuration.context)
|
||||
snd.seq.close(this->seq);
|
||||
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ_UMP; }
|
||||
|
||||
[[nodiscard]] int create_port(std::string_view portName)
|
||||
{
|
||||
@@ -70,55 +62,60 @@ public:
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
|
||||
}
|
||||
|
||||
bool open_port(const output_port& p, std::string_view portName) override
|
||||
stdx::error open_port(const output_port& p, std::string_view portName) override
|
||||
{
|
||||
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||
if (nSrc < 1)
|
||||
{
|
||||
error<no_devices_found_error>(
|
||||
this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!");
|
||||
return false;
|
||||
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
||||
return make_error_code(std::errc::no_such_device);
|
||||
}
|
||||
|
||||
auto sink = get_port_info(p);
|
||||
if (!sink)
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
if (int err = create_port(portName); err < 0)
|
||||
{
|
||||
error<driver_error>(configuration, "midi_out_alsa::create_port: ALSA error creating port.");
|
||||
return false;
|
||||
libremidi_handle_error(configuration, "ALSA error creating port.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
snd_seq_addr_t source{
|
||||
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
|
||||
if (int err = create_connection(*this, source, *sink, true); err < 0)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_alsa::create_port: ALSA error making port connection.");
|
||||
return false;
|
||||
libremidi_handle_error(configuration, "ALSA error making port connection.");
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
if (int err = create_port(portName); err < 0)
|
||||
return false;
|
||||
return true;
|
||||
return from_errc(err);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override { unsubscribe(); }
|
||||
|
||||
void set_client_name(std::string_view clientName) override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
alsa_data::set_client_name(clientName);
|
||||
unsubscribe();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
|
||||
stdx::error set_client_name(std::string_view clientName) override
|
||||
{
|
||||
return alsa_data::set_client_name(clientName);
|
||||
}
|
||||
|
||||
void send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
return alsa_data::set_port_name(portName);
|
||||
}
|
||||
|
||||
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||
{
|
||||
snd_seq_ump_event_t ev;
|
||||
|
||||
@@ -128,24 +125,20 @@ public:
|
||||
snd_seq_ev_set_subs(&ev);
|
||||
snd_seq_ev_set_direct(&ev);
|
||||
|
||||
auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) {
|
||||
auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) -> std::errc {
|
||||
std::memcpy(ev.ump, ump, bytes);
|
||||
const int result = snd.seq.ump.event_output_direct(this->seq, &ev);
|
||||
if (result < 0)
|
||||
const int ret = snd.seq.ump.event_output_direct(this->seq, &ev);
|
||||
if (ret < 0)
|
||||
{
|
||||
warning(
|
||||
this->configuration,
|
||||
"midi_out_alsa::send_message: error sending MIDI message to port.");
|
||||
return libremidi::segmentation_error::other;
|
||||
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
||||
return static_cast<std::errc>(-ret);
|
||||
}
|
||||
return libremidi::segmentation_error::no_error;
|
||||
return std::errc{0};
|
||||
};
|
||||
segment_ump_stream(ump_stream, count, write_func, []() {});
|
||||
|
||||
snd.seq.drain_output(this->seq);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int bufferSize{32};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef UInt32 MIDIObjectRef;
|
||||
typedef MIDIObjectRef MIDIClientRef;
|
||||
#else
|
||||
using MIDIClientRef = uint32_t;
|
||||
using MIDIObjectRef = uint32_t;
|
||||
#endif
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include <libremidi/error.hpp>
|
||||
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct coremidi_error_domain : public stdx::error_domain
|
||||
{
|
||||
public:
|
||||
constexpr coremidi_error_domain() noexcept
|
||||
: error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}
|
||||
{
|
||||
}
|
||||
|
||||
stdx::string_ref name() const noexcept override { return "coremidi"; }
|
||||
|
||||
bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override
|
||||
{
|
||||
if (lhs.domain() == rhs.domain())
|
||||
return error_cast<std::errc>(lhs) == error_cast<std::errc>(rhs);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
stdx::string_ref message(const stdx::error& e) const noexcept override
|
||||
{
|
||||
switch (error_cast<OSStatus>(e))
|
||||
{
|
||||
case kMIDIInvalidClient:
|
||||
return "Invalid Client";
|
||||
case kMIDIInvalidPort:
|
||||
return "Invalid Port";
|
||||
case kMIDIWrongEndpointType:
|
||||
return "Wrong EndpointT ype";
|
||||
case kMIDINoConnection:
|
||||
return "No Connection";
|
||||
case kMIDIUnknownEndpoint:
|
||||
return "Unknown Endpoint";
|
||||
case kMIDIUnknownProperty:
|
||||
return "Unknown Property";
|
||||
case kMIDIWrongPropertyType:
|
||||
return "Wrong Property Type";
|
||||
case kMIDINoCurrentSetup:
|
||||
return "No Current Setup";
|
||||
case kMIDIMessageSendErr:
|
||||
return "Message Send Error";
|
||||
case kMIDIServerStartErr:
|
||||
return "Server Start Error";
|
||||
case kMIDISetupFormatErr:
|
||||
return "Setup Format Error";
|
||||
case kMIDIWrongThread:
|
||||
return "Wrong Thread";
|
||||
case kMIDIObjectNotFound:
|
||||
return "Object Not Found";
|
||||
case kMIDIIDNotUnique:
|
||||
return "ID Not Unique";
|
||||
case kMIDINotPermitted:
|
||||
return "Not Permitted";
|
||||
case kMIDIUnknownError:
|
||||
return "Unknown Error";
|
||||
}
|
||||
return "Unknown error code";
|
||||
}
|
||||
};
|
||||
|
||||
inline stdx::error from_osstatus(OSStatus ret) noexcept
|
||||
{
|
||||
static constexpr coremidi_error_domain domain{};
|
||||
return {ret, domain};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
#include <libremidi/detail/memory.hpp>
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
#include <libremidi/error_handler.hpp>
|
||||
#include <libremidi/input_configuration.hpp>
|
||||
#include <libremidi/backends/coremidi/error_domain.hpp>
|
||||
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
@@ -28,7 +30,8 @@ namespace
|
||||
static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept
|
||||
{
|
||||
CFStringRef res;
|
||||
MIDIObjectGetStringProperty(object, property, &res);
|
||||
if (MIDIObjectGetStringProperty(object, property, &res) || !res)
|
||||
return {};
|
||||
|
||||
char name[256];
|
||||
CFStringGetCString(res, name, sizeof(name), kCFStringEncodingUTF8);
|
||||
@@ -215,15 +218,15 @@ locate_object(auto& self, const port_information& info, MIDIObjectType requested
|
||||
auto ret = MIDIObjectFindByUniqueID(uid, &object, &type);
|
||||
if (ret != noErr)
|
||||
{
|
||||
self.template error<invalid_parameter_error>(
|
||||
self.configuration, "coremidi::locate_object: cannot find port: " + info.port_name);
|
||||
self.libremidi_handle_error(
|
||||
self.configuration, "cannot find port: " + info.port_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (type != requested_type || object == 0)
|
||||
{
|
||||
self.template error<invalid_parameter_error>(
|
||||
self.configuration, "coremidi::locate_object: invalid object: " + info.port_name + " : "
|
||||
self.libremidi_handle_error(
|
||||
self.configuration, "invalid object: " + info.port_name + " : "
|
||||
+ std::to_string(object));
|
||||
return 0;
|
||||
}
|
||||
@@ -255,6 +258,13 @@ struct coremidi_data
|
||||
}
|
||||
}
|
||||
|
||||
void close_client(auto& self)
|
||||
{
|
||||
self.client_open_ = std::errc::not_connected;
|
||||
if (!self.configuration.context)
|
||||
MIDIClientDispose(self.client);
|
||||
}
|
||||
|
||||
static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept
|
||||
{
|
||||
if (tp == 0)
|
||||
@@ -319,6 +329,23 @@ struct coremidi_data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdx::error close_port()
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
this->endpoint = 0;
|
||||
}
|
||||
|
||||
if (this->port)
|
||||
{
|
||||
MIDIPortDispose(this->port);
|
||||
this->port = 0;
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ namespace libremidi
|
||||
{
|
||||
class midi_in_core final
|
||||
: public midi1::in_api
|
||||
, private coremidi_data
|
||||
, public coremidi_data
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: input_configuration
|
||||
, coremidi_input_configuration
|
||||
@@ -23,11 +24,14 @@ public:
|
||||
{
|
||||
if (auto result = init_client(configuration); result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
|
||||
"error creating MIDI client object: " + std::to_string(result));
|
||||
client_open_ = from_osstatus(result);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_core() override
|
||||
@@ -38,33 +42,18 @@ public:
|
||||
if (this->endpoint)
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
|
||||
close_client();
|
||||
}
|
||||
|
||||
void close_client()
|
||||
{
|
||||
if (!configuration.context)
|
||||
MIDIClientDispose(this->client);
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_core: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_core: set_port_name unsupported");
|
||||
close_client(*this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
||||
|
||||
bool open_port(const input_port& info, std::string_view portName) override
|
||||
stdx::error open_port(const input_port& info, std::string_view portName) override
|
||||
{
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||
|
||||
auto source = locate_object(*this, info, kMIDIObjectType_Source);
|
||||
if (source == 0)
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
// Create our local sink
|
||||
MIDIPortRef port;
|
||||
@@ -73,29 +62,29 @@ public:
|
||||
|
||||
if (result != noErr)
|
||||
{
|
||||
close_client();
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_in_core::open_port: error creating macOS MIDI input port: "
|
||||
close_client(*this);
|
||||
libremidi_handle_error(
|
||||
this->configuration, "error creating macOS MIDI input port: "
|
||||
+ std::to_string(result));
|
||||
return false;
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Make the connection.
|
||||
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
|
||||
{
|
||||
MIDIPortDispose(port);
|
||||
close_client();
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port.");
|
||||
return false;
|
||||
close_client(*this);
|
||||
libremidi_handle_error(
|
||||
this->configuration, "error connecting macOS MIDI input port.");
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Save our api-specific port information.
|
||||
this->port = port;
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
// Create a virtual MIDI input destination.
|
||||
MIDIEndpointRef endpoint;
|
||||
@@ -104,31 +93,21 @@ public:
|
||||
|
||||
if (result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_core::open_virtual_port: error creating virtual macOS MIDI "
|
||||
"error creating virtual macOS MIDI "
|
||||
"destination.");
|
||||
return false;
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Save our api-specific connection information.
|
||||
this->endpoint = endpoint;
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
this->endpoint = 0;
|
||||
}
|
||||
|
||||
if (this->port)
|
||||
{
|
||||
MIDIPortDispose(this->port);
|
||||
this->port = 0;
|
||||
}
|
||||
return coremidi_data::close_port();
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
@@ -158,16 +137,13 @@ public:
|
||||
// MIDIPacketLists, they must be handled by multiple calls to this
|
||||
// function.
|
||||
|
||||
if (packet->length == 0)
|
||||
if (packet->length > 0)
|
||||
{
|
||||
packet = MIDIPacketNext(packet);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };
|
||||
self.m_processing.on_bytes_multi(
|
||||
{packet->data, packet->data + packet->length},
|
||||
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
|
||||
packet = MIDIPacketNext(packet);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@ namespace libremidi
|
||||
{
|
||||
class midi_out_core final
|
||||
: public midi1::out_api
|
||||
, private coremidi_data
|
||||
, public coremidi_data
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: output_configuration
|
||||
, coremidi_output_configuration
|
||||
@@ -22,11 +23,14 @@ public:
|
||||
{
|
||||
if (auto result = init_client(configuration); result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_core: error creating MIDI client object: " + std::to_string(result));
|
||||
"error creating MIDI client object: " + std::to_string(result));
|
||||
client_open_ = from_osstatus(result);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_core()
|
||||
@@ -36,112 +40,79 @@ public:
|
||||
if (this->endpoint)
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
|
||||
close_client();
|
||||
}
|
||||
|
||||
void close_client()
|
||||
{
|
||||
if (!configuration.context)
|
||||
MIDIClientDispose(this->client);
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_core: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_core: set_port_name unsupported");
|
||||
close_client(*this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
||||
|
||||
bool open_port(const output_port& info, std::string_view portName) override
|
||||
stdx::error open_port(const output_port& info, std::string_view portName) override
|
||||
{
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||
|
||||
// Find where we want to send
|
||||
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
|
||||
if (destination == 0)
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
// Create our local source
|
||||
MIDIPortRef port;
|
||||
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
|
||||
if (result != noErr)
|
||||
{
|
||||
close_client();
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_out_core::open_port: error creating macOS MIDI output port.");
|
||||
return false;
|
||||
close_client(*this);
|
||||
libremidi_handle_error(
|
||||
this->configuration, "error creating macOS MIDI output port.");
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Save our api-specific connection information.
|
||||
this->port = port;
|
||||
this->destinationId = destination;
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
warning(
|
||||
configuration,
|
||||
"midi_out_core::open_virtual_port: a virtual output port already "
|
||||
"exists!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a virtual MIDI output source.
|
||||
MIDIEndpointRef endpoint;
|
||||
OSStatus result = MIDISourceCreate(this->client, toCFString(portName).get(), &endpoint);
|
||||
|
||||
if (result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_core::initialize: error creating macOS virtual MIDI source.");
|
||||
return false;
|
||||
"error creating macOS virtual MIDI source.");
|
||||
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Save our api-specific connection information.
|
||||
this->endpoint = endpoint;
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
this->endpoint = 0;
|
||||
return coremidi_data::close_port();
|
||||
}
|
||||
|
||||
if (this->port)
|
||||
{
|
||||
MIDIPortDispose(this->port);
|
||||
this->port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
unsigned int nBytes = static_cast<unsigned int>(size);
|
||||
if (nBytes == 0)
|
||||
{
|
||||
warning(configuration, "midi_out_core::send_message: no data in message argument!");
|
||||
return;
|
||||
libremidi_handle_warning(configuration, "no data in message argument!");
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
if (message[0] != 0xF0 && nBytes > 3)
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_warning(
|
||||
configuration,
|
||||
"midi_out_core::send_message: message format problem ... not sysex but "
|
||||
"message format problem ... not sysex but "
|
||||
"> 3 bytes?");
|
||||
return;
|
||||
return std::errc::bad_message;
|
||||
}
|
||||
|
||||
const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
|
||||
@@ -166,9 +137,10 @@ public:
|
||||
|
||||
if (!packet)
|
||||
{
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_out_core::send_message: could not allocate packet list");
|
||||
return;
|
||||
libremidi_handle_error(
|
||||
this->configuration, "could not allocate packet list");
|
||||
|
||||
return std::errc::message_size;
|
||||
}
|
||||
|
||||
// Send to any destinations that may have connected to us.
|
||||
@@ -177,10 +149,11 @@ public:
|
||||
auto result = MIDIReceived(this->endpoint, packetList);
|
||||
if (result != noErr)
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_warning(
|
||||
this->configuration,
|
||||
"midi_out_core::send_message: error sending MIDI to virtual "
|
||||
"error sending MIDI to virtual "
|
||||
"destinations.");
|
||||
return std::errc::io_error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,12 +163,14 @@ public:
|
||||
auto result = MIDISend(this->port, this->destinationId, packetList);
|
||||
if (result != noErr)
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_warning(
|
||||
this->configuration,
|
||||
"midi_out_core::send_message: error sending MIDI message to port.");
|
||||
"error sending MIDI message to port.");
|
||||
return std::errc::io_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
MIDIEndpointRef destinationId{};
|
||||
|
||||
@@ -38,9 +38,9 @@ public:
|
||||
|
||||
if (result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
|
||||
"error creating MIDI client object: " + std::to_string(result));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,11 +69,11 @@ public:
|
||||
MIDIEndpointGetEntity(obj, &e);
|
||||
bool physical = bool(e);
|
||||
|
||||
bool ok = false;
|
||||
bool ok = this->configuration.track_any;
|
||||
if (physical && this->configuration.track_hardware)
|
||||
ok = true;
|
||||
ok |= true;
|
||||
else if ((!physical) && this->configuration.track_virtual)
|
||||
ok = true;
|
||||
ok |= true;
|
||||
|
||||
if (!ok)
|
||||
return {};
|
||||
|
||||
@@ -10,9 +10,9 @@ struct backend
|
||||
using midi_in = midi_in_impl;
|
||||
using midi_out = midi_out_impl;
|
||||
using midi_observer = observer_impl;
|
||||
using midi_in_configuration = coremidi_input_configuration;
|
||||
using midi_out_configuration = coremidi_output_configuration;
|
||||
using midi_observer_configuration = coremidi_observer_configuration;
|
||||
using midi_in_configuration = coremidi_ump::input_configuration;
|
||||
using midi_out_configuration = coremidi_ump::output_configuration;
|
||||
using midi_observer_configuration = coremidi_ump::observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::COREMIDI_UMP;
|
||||
static const constexpr auto name = "core_ump";
|
||||
static const constexpr auto display_name = "CoreMIDI UMP";
|
||||
|
||||
@@ -3,7 +3,21 @@
|
||||
|
||||
namespace libremidi::coremidi_ump
|
||||
{
|
||||
using input_configuration = coremidi_input_configuration;
|
||||
using output_configuration = coremidi_output_configuration;
|
||||
using observer_configuration = coremidi_observer_configuration;
|
||||
struct input_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
std::optional<MIDIClientRef> context{};
|
||||
};
|
||||
|
||||
struct output_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
std::optional<MIDIClientRef> context{};
|
||||
};
|
||||
|
||||
struct observer_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
std::function<void(MIDIClientRef)> on_create_context{};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,32 +2,37 @@
|
||||
#include <libremidi/backends/coremidi_ump/config.hpp>
|
||||
#include <libremidi/backends/coremidi_ump/helpers.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
namespace libremidi::coremidi_ump
|
||||
{
|
||||
|
||||
class midi_in_impl final
|
||||
: public midi2::in_api
|
||||
, private coremidi_data
|
||||
, public coremidi_data
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: ump_input_configuration
|
||||
, coremidi_ump::input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
midi_in_impl(ump_input_configuration&& conf, coremidi_input_configuration&& apiconf)
|
||||
midi_in_impl(ump_input_configuration&& conf, coremidi_ump::input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
if (auto result = init_client(configuration); result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
|
||||
"error creating MIDI client object: " + std::to_string(result));
|
||||
client_open_ = from_osstatus(result);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_impl() override
|
||||
@@ -38,33 +43,18 @@ public:
|
||||
if (this->endpoint)
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
|
||||
close_client();
|
||||
}
|
||||
|
||||
void close_client()
|
||||
{
|
||||
if (!configuration.context)
|
||||
MIDIClientDispose(this->client);
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_core: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_core: set_port_name unsupported");
|
||||
close_client(*this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
|
||||
|
||||
bool open_port(const input_port& info, std::string_view portName) override
|
||||
stdx::error open_port(const input_port& info, std::string_view portName) override
|
||||
{
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||
|
||||
auto source = locate_object(*this, info, kMIDIObjectType_Source);
|
||||
if (source == 0)
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
// Create our local sink
|
||||
MIDIPortRef port;
|
||||
@@ -77,29 +67,29 @@ public:
|
||||
|
||||
if (result != noErr)
|
||||
{
|
||||
close_client();
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_in_core::open_port: error creating macOS MIDI input port: "
|
||||
close_client(*this);
|
||||
libremidi_handle_error(
|
||||
this->configuration, "error creating macOS MIDI input port: "
|
||||
+ std::to_string(result));
|
||||
return false;
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Make the connection.
|
||||
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
|
||||
{
|
||||
MIDIPortDispose(port);
|
||||
close_client();
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port.");
|
||||
return false;
|
||||
close_client(*this);
|
||||
libremidi_handle_error(
|
||||
this->configuration, "error connecting macOS MIDI input port.");
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Save our api-specific port information.
|
||||
this->port = port;
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
// Create a virtual MIDI input destination.
|
||||
MIDIEndpointRef endpoint;
|
||||
@@ -111,37 +101,21 @@ public:
|
||||
|
||||
if (result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_in_core::open_virtual_port: error creating virtual macOS MIDI "
|
||||
"error creating virtual macOS MIDI "
|
||||
"destination.");
|
||||
return false;
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Save our api-specific connection information.
|
||||
this->endpoint = endpoint;
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
this->endpoint = 0;
|
||||
}
|
||||
|
||||
if (this->port)
|
||||
{
|
||||
MIDIPortDispose(this->port);
|
||||
this->port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void set_timestamp(
|
||||
[[maybe_unused]] const MIDIEventPacket& packet,
|
||||
[[maybe_unused]] libremidi::ump& msg) noexcept
|
||||
stdx::error close_port() override
|
||||
{
|
||||
return coremidi_data::close_port();
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
@@ -151,33 +125,28 @@ public:
|
||||
|
||||
void midiInputCallback(const MIDIEventList* list, void* /*srcRef*/)
|
||||
{
|
||||
unsigned short nBytes{};
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = false,
|
||||
.has_samples = false,
|
||||
};
|
||||
|
||||
const MIDIEventPacket* packet = &list->packet[0];
|
||||
for (unsigned int i = 0; i < list->numPackets; ++i)
|
||||
{
|
||||
nBytes = packet->wordCount;
|
||||
if (nBytes == 0)
|
||||
if (packet->wordCount > 0)
|
||||
{
|
||||
packet = MIDIEventPacketNext(packet);
|
||||
continue;
|
||||
auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };
|
||||
m_processing.on_bytes_multi(
|
||||
{packet->words, packet->words + packet->wordCount},
|
||||
m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
|
||||
libremidi::ump msg;
|
||||
coremidi_data::set_timestamp(*this, packet->timeStamp, msg.timestamp);
|
||||
|
||||
if (packet->wordCount <= 4)
|
||||
{
|
||||
std::copy_n(packet->words, packet->wordCount, msg.data);
|
||||
configuration.on_message(std::move(msg));
|
||||
}
|
||||
last_time = time_in_nanos(packet->timeStamp);
|
||||
|
||||
packet = MIDIEventPacketNext(packet);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long long last_time{};
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ namespace libremidi::coremidi_ump
|
||||
{
|
||||
class midi_out_impl final
|
||||
: public midi2::out_api
|
||||
, private coremidi_data
|
||||
, public coremidi_data
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: libremidi::output_configuration
|
||||
, coremidi_ump::output_configuration
|
||||
@@ -25,11 +26,14 @@ public:
|
||||
{
|
||||
if (auto result = init_client(configuration); result != noErr)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_impl: error creating MIDI client object: " + std::to_string(result));
|
||||
"error creating MIDI client object: " + std::to_string(result));
|
||||
client_open_ = from_osstatus(result);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_impl()
|
||||
@@ -39,64 +43,40 @@ public:
|
||||
if (this->endpoint)
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
|
||||
close_client();
|
||||
}
|
||||
|
||||
void close_client()
|
||||
{
|
||||
if (!configuration.context)
|
||||
MIDIClientDispose(this->client);
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_impl: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_impl: set_port_name unsupported");
|
||||
close_client(*this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
|
||||
|
||||
bool open_port(const output_port& info, std::string_view portName) override
|
||||
stdx::error open_port(const output_port& info, std::string_view portName) override
|
||||
{
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||
|
||||
// Find where we want to send
|
||||
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
|
||||
if (destination == 0)
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
// Create our local source
|
||||
MIDIPortRef port;
|
||||
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
|
||||
if (result != noErr)
|
||||
{
|
||||
close_client();
|
||||
error<driver_error>(
|
||||
this->configuration, "midi_out_impl::open_port: error creating macOS MIDI output port.");
|
||||
return false;
|
||||
close_client(*this);
|
||||
libremidi_handle_error(
|
||||
this->configuration, "error creating macOS MIDI output port.");
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
// Save our api-specific connection information.
|
||||
this->port = port;
|
||||
this->destinationId = destination;
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
warning(
|
||||
configuration,
|
||||
"midi_out_impl::open_virtual_port: a virtual output port already "
|
||||
"exists!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a virtual MIDI output source.
|
||||
OSStatus result = MIDISourceCreateWithProtocol(
|
||||
this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &this->endpoint);
|
||||
@@ -104,42 +84,33 @@ public:
|
||||
if (result != noErr)
|
||||
{
|
||||
this->endpoint = 0;
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_impl::initialize: error creating macOS virtual MIDI source.");
|
||||
return false;
|
||||
"error creating macOS virtual MIDI source.");
|
||||
|
||||
return from_osstatus(result);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
MIDIEndpointDispose(this->endpoint);
|
||||
this->endpoint = 0;
|
||||
return coremidi_data::close_port();
|
||||
}
|
||||
|
||||
if (this->port)
|
||||
{
|
||||
MIDIPortDispose(this->port);
|
||||
this->port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||
{
|
||||
MIDIEventList* eventList = reinterpret_cast<MIDIEventList*>(m_eventListBuffer);
|
||||
MIDIEventPacket* packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);
|
||||
const MIDITimeStamp ts = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
|
||||
|
||||
auto write_fun = [ts, &packet, &eventList](const uint32_t* ump, int bytes) {
|
||||
auto write_fun = [ts, &packet, &eventList](const uint32_t* ump, int bytes) -> std::errc {
|
||||
packet = MIDIEventListAdd(eventList, event_list_max_size, packet, ts, bytes / 4, ump);
|
||||
if (packet)
|
||||
return segmentation_error::no_error;
|
||||
return std::errc{0};
|
||||
else
|
||||
return segmentation_error::need_space;
|
||||
return std::errc::not_enough_memory;
|
||||
};
|
||||
|
||||
auto realloc_fun = [this, &packet, &eventList] {
|
||||
@@ -148,20 +119,21 @@ public:
|
||||
};
|
||||
|
||||
segment_ump_stream(ump_stream, count, write_fun, realloc_fun);
|
||||
push_event_list(eventList);
|
||||
return push_event_list(eventList);
|
||||
}
|
||||
|
||||
void push_event_list(MIDIEventList* eventList)
|
||||
stdx::error push_event_list(MIDIEventList* eventList)
|
||||
{
|
||||
if (this->endpoint)
|
||||
{
|
||||
auto result = MIDIReceivedEventList(this->endpoint, eventList);
|
||||
if (result != noErr)
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_warning(
|
||||
this->configuration,
|
||||
"midi_out_core::send_message: error sending MIDI to virtual "
|
||||
"error sending MIDI to virtual "
|
||||
"destinations.");
|
||||
return std::errc::io_error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,11 +142,13 @@ public:
|
||||
auto result = MIDISendEventList(this->port, this->destinationId, eventList);
|
||||
if (result != noErr)
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_warning(
|
||||
this->configuration,
|
||||
"midi_out_core::send_message: error sending MIDI message to port.");
|
||||
"error sending MIDI message to port.");
|
||||
return std::errc::io_error;
|
||||
}
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
MIDIEndpointRef destinationId{};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/coremidi_ump/config.hpp>
|
||||
#include <libremidi/backends/coremidi/observer.hpp>
|
||||
|
||||
namespace libremidi::coremidi_ump
|
||||
@@ -6,7 +7,13 @@ namespace libremidi::coremidi_ump
|
||||
|
||||
class observer_impl final : public libremidi::observer_core
|
||||
{
|
||||
using observer_core::observer_core;
|
||||
public:
|
||||
explicit observer_impl(libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf)
|
||||
: observer_core{std::move(conf), coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
|
||||
};
|
||||
|
||||
|
||||
@@ -23,20 +23,33 @@ class midi_in_dummy final
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
explicit midi_in_dummy(const auto& configuration, const auto&)
|
||||
explicit midi_in_dummy(const input_configuration& configuration, std::any)
|
||||
{
|
||||
warning(configuration, "midi_in_dummy: This class provides no functionality.");
|
||||
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||
}
|
||||
explicit midi_in_dummy(const ump_input_configuration& configuration, std::any)
|
||||
{
|
||||
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
|
||||
bool open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override
|
||||
stdx::error open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override
|
||||
{
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
stdx::error open_virtual_port(std::string_view /*portName*/) override
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
stdx::error close_port() override { return stdx::error{}; }
|
||||
stdx::error set_client_name(std::string_view /*clientName*/) override
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
stdx::error set_port_name(std::string_view /*portName*/) override
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
|
||||
void close_port() override { }
|
||||
void set_client_name(std::string_view /*clientName*/) override { }
|
||||
void set_port_name(std::string_view /*portName*/) override { }
|
||||
timestamp absolute_timestamp() const noexcept override { return 0; }
|
||||
};
|
||||
|
||||
@@ -45,22 +58,34 @@ class midi_out_dummy final
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
explicit midi_out_dummy(const auto& configuration, const auto&)
|
||||
explicit midi_out_dummy(const output_configuration& configuration, std::any)
|
||||
{
|
||||
warning(configuration, "midi_out_dummy: This class provides no functionality.");
|
||||
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
|
||||
bool open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override
|
||||
stdx::error open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override
|
||||
{
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
stdx::error open_virtual_port(std::string_view /*portName*/) override
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
|
||||
|
||||
void close_port() override { }
|
||||
void set_client_name(std::string_view /*clientName*/) override { }
|
||||
void set_port_name(std::string_view /*portName*/) override { }
|
||||
void send_message(const unsigned char* /*message*/, size_t /*size*/) override { }
|
||||
stdx::error close_port() override { return stdx::error{}; }
|
||||
stdx::error set_client_name(std::string_view /*clientName*/) override
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
stdx::error set_port_name(std::string_view /*portName*/) override
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
};
|
||||
|
||||
struct dummy_backend
|
||||
|
||||
@@ -204,7 +204,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void send_message(int port_index, const char* bytes, int len)
|
||||
stdx::error send_message(int port_index, const char* bytes, int len)
|
||||
{
|
||||
const auto& id = m_current_outputs[port_index].id;
|
||||
EM_ASM(
|
||||
|
||||
@@ -11,12 +11,14 @@ LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten(
|
||||
input_configuration&& conf, emscripten_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_in_emscripten::~midi_in_emscripten()
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
midi_in_emscripten::close_port();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noexcept
|
||||
@@ -24,49 +26,35 @@ LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noex
|
||||
return libremidi::API::WEBMIDI;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE bool midi_in_emscripten::open_port(int portNumber, std::string_view)
|
||||
LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std::string_view)
|
||||
{
|
||||
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
||||
|
||||
if (portNumber < 0 || portNumber >= midi.input_count())
|
||||
{
|
||||
error<no_devices_found_error>(
|
||||
this->configuration, "midi_in_emscripten::open_port: no MIDI output sources found.");
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
this->configuration, "no MIDI output sources found.");
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
midi.open_input(portNumber, *this);
|
||||
portNumber_ = portNumber;
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE bool
|
||||
LIBREMIDI_INLINE stdx::error
|
||||
midi_in_emscripten::open_port(const libremidi::input_port& p, std::string_view nm)
|
||||
{
|
||||
return open_port(p.port, nm);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE bool midi_in_emscripten::open_virtual_port(std::string_view)
|
||||
{
|
||||
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE void midi_in_emscripten::close_port()
|
||||
LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port()
|
||||
{
|
||||
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
||||
|
||||
midi.close_input(portNumber_, *this);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE void midi_in_emscripten::set_client_name(std::string_view)
|
||||
{
|
||||
warning(configuration, "midi_in_emscripten::set_client_name: unsupported.");
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE void midi_in_emscripten::set_port_name(std::string_view)
|
||||
{
|
||||
warning(configuration, "midi_in_emscripten::set_port_name: unsupported.");
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE int64_t midi_in_emscripten::absolute_timestamp() const noexcept
|
||||
|
||||
@@ -22,13 +22,10 @@ public:
|
||||
|
||||
libremidi::API get_current_api() const noexcept override;
|
||||
|
||||
bool open_port(int portNumber, std::string_view);
|
||||
bool open_port(const input_port& p, std::string_view) override;
|
||||
bool open_virtual_port(std::string_view) override;
|
||||
void close_port() override;
|
||||
stdx::error open_port(int portNumber, std::string_view);
|
||||
stdx::error open_port(const input_port& p, std::string_view) override;
|
||||
stdx::error close_port() override;
|
||||
|
||||
void set_client_name(std::string_view clientName) override;
|
||||
void set_port_name(std::string_view portName) override;
|
||||
timestamp absolute_timestamp() const noexcept override;
|
||||
|
||||
void on_input(double ts, unsigned char* begin, unsigned char* end);
|
||||
|
||||
@@ -8,12 +8,14 @@ LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten(
|
||||
output_configuration&& conf, emscripten_output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out_emscripten::~midi_out_emscripten()
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
midi_out_emscripten::close_port();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noexcept
|
||||
@@ -21,54 +23,41 @@ LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noe
|
||||
return libremidi::API::WEBMIDI;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(unsigned int portNumber, std::string_view)
|
||||
LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std::string_view)
|
||||
{
|
||||
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
||||
|
||||
if (portNumber >= midi.output_count())
|
||||
{
|
||||
error<no_devices_found_error>(
|
||||
this->configuration, "midi_out_emscripten::open_port: no MIDI output sources found.");
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
this->configuration, "no MIDI output sources found.");
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
portNumber_ = portNumber;
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
|
||||
LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
|
||||
{
|
||||
return open_port(p.port, nm);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE void midi_out_emscripten::close_port() { }
|
||||
|
||||
LIBREMIDI_INLINE void midi_out_emscripten::set_client_name(std::string_view clientName)
|
||||
{
|
||||
warning(configuration, "midi_out_emscripten::set_client_name: unsupported.");
|
||||
LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() {
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE void midi_out_emscripten::set_port_name(std::string_view portName)
|
||||
{
|
||||
warning(configuration, "midi_out_emscripten::set_port_name: unsupported.");
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE bool midi_out_emscripten::open_virtual_port(std::string_view)
|
||||
{
|
||||
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE void midi_out_emscripten::send_message(const unsigned char* message, size_t size)
|
||||
LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size)
|
||||
{
|
||||
if (portNumber_ < 0)
|
||||
error<invalid_use_error>(
|
||||
libremidi_handle_error(
|
||||
this->configuration,
|
||||
"midi_out_emscripten::send_message: trying to send a message without an open "
|
||||
"trying to send a message without an open "
|
||||
"port.");
|
||||
|
||||
webmidi_helpers::midi_access_emscripten::instance().send_message(
|
||||
portNumber_, reinterpret_cast<const char*>(message), size);
|
||||
return stdx::error{};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -21,15 +21,11 @@ public:
|
||||
|
||||
libremidi::API get_current_api() const noexcept override;
|
||||
|
||||
bool open_port(unsigned int portNumber, std::string_view);
|
||||
bool open_port(const output_port& p, std::string_view) override;
|
||||
bool open_virtual_port(std::string_view) override;
|
||||
void close_port() override;
|
||||
stdx::error open_port(int portNumber, std::string_view);
|
||||
stdx::error open_port(const output_port& p, std::string_view) override;
|
||||
stdx::error close_port() override;
|
||||
|
||||
void set_client_name(std::string_view clientName) override;
|
||||
void set_port_name(std::string_view portName) override;
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override;
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override;
|
||||
|
||||
private:
|
||||
int portNumber_{-1};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -25,8 +24,8 @@ 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;
|
||||
std::function<void(jack_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
};
|
||||
|
||||
struct jack_output_configuration
|
||||
@@ -34,8 +33,8 @@ 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;
|
||||
std::function<void(jack_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
|
||||
int32_t ringbuffer_size = 16384;
|
||||
bool direct = false;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include <libremidi/error.hpp>
|
||||
|
||||
#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
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct jack_error_domain : public stdx::error_domain
|
||||
{
|
||||
public:
|
||||
constexpr jack_error_domain() noexcept
|
||||
: error_domain{{0xc714ea32b705080aULL, 0xe409a437daf5f921ULL}}
|
||||
{
|
||||
}
|
||||
|
||||
stdx::string_ref name() const noexcept override { return "jack"; }
|
||||
|
||||
bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override
|
||||
{
|
||||
if (lhs.domain() == rhs.domain())
|
||||
return error_cast<jack_status_t>(lhs) == error_cast<jack_status_t>(rhs);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
stdx::string_ref message(const stdx::error& e) const noexcept override
|
||||
{
|
||||
const auto status = error_cast<jack_status_t>(e);
|
||||
if(status == jack_status_t{})
|
||||
return "Success";
|
||||
|
||||
if(status & JackInvalidOption)
|
||||
return "The operation contained an invalid or unsupported option";
|
||||
if(status & JackServerFailed)
|
||||
return "Unable to connect to the JACK server";
|
||||
if(status & JackServerError)
|
||||
return "Communication error with the JACK server";
|
||||
if(status & JackNoSuchClient)
|
||||
return "Requested client does not exist";
|
||||
if(status & JackLoadFailure)
|
||||
return "Unable to load internal client";
|
||||
if(status & JackInitFailure)
|
||||
return "Unable to initialize client";
|
||||
if(status & JackShmFailure)
|
||||
return "Unable to access shared memory";
|
||||
if(status & JackVersionError)
|
||||
return "Client's protocol version does not match";
|
||||
if(status & JackBackendError)
|
||||
return "Backend error";
|
||||
if(status & JackClientZombie)
|
||||
return "Client zombified failure";
|
||||
if(status & JackFailure)
|
||||
return "Failure";
|
||||
if(status & JackNameNotUnique)
|
||||
return "The desired client name was not unique";
|
||||
|
||||
// Can't happen in libremidi as we set JackNoStartServer
|
||||
if(status & JackServerStarted)
|
||||
return "Server was started";
|
||||
|
||||
return "Unknown JACK status code";
|
||||
}
|
||||
};
|
||||
|
||||
inline stdx::error from_jack_status(jack_status_t ret) noexcept
|
||||
{
|
||||
static constexpr jack_error_domain domain{};
|
||||
return {ret, domain};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,6 @@
|
||||
#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/backends/jack/error_domain.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/semaphore.hpp>
|
||||
|
||||
@@ -157,6 +149,10 @@ struct jack_helpers : jack_client
|
||||
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
|
||||
if (this->client != nullptr)
|
||||
{
|
||||
if(status & JackNameNotUnique) {
|
||||
self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed.");
|
||||
}
|
||||
|
||||
jack_set_process_callback(
|
||||
this->client,
|
||||
+[](jack_nframes_t nf, void* ctx) -> int {
|
||||
@@ -192,9 +188,12 @@ struct jack_helpers : jack_client
|
||||
|
||||
if (this->client && !self.configuration.context)
|
||||
jack_client_close(this->client);
|
||||
|
||||
self.client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
bool create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)
|
||||
stdx::error
|
||||
create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)
|
||||
{
|
||||
// full name: "client_name:port_name\0"
|
||||
if (portName.empty())
|
||||
@@ -203,9 +202,9 @@ struct jack_helpers : jack_client
|
||||
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;
|
||||
self.libremidi_handle_error(
|
||||
self.configuration, "port name length limit exceeded");
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
if (!this->port)
|
||||
@@ -216,16 +215,16 @@ struct jack_helpers : jack_client
|
||||
|
||||
if (!this->port)
|
||||
{
|
||||
self.template error<driver_error>(self.configuration, "JACK: error creating port");
|
||||
return false;
|
||||
self.libremidi_handle_error(self.configuration, "error creating port");
|
||||
return std::errc::operation_not_supported;
|
||||
}
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void do_close_port()
|
||||
stdx::error do_close_port()
|
||||
{
|
||||
if (this->port == nullptr)
|
||||
return;
|
||||
return stdx::error{};
|
||||
|
||||
// 1. Ensure that the next time the cycle runs it sees the port as nullptr
|
||||
jack_port_t* port_ptr = this->port.impl->load();
|
||||
@@ -235,7 +234,8 @@ struct jack_helpers : jack_client
|
||||
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);
|
||||
int err = jack_port_unregister(this->client, port_ptr);
|
||||
return from_errc(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class midi_in_jack final
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: input_configuration
|
||||
, jack_input_configuration
|
||||
@@ -24,8 +25,14 @@ public:
|
||||
: 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{}));
|
||||
if (!this->client)
|
||||
{
|
||||
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||
client_open_ = from_jack_status(status);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_jack() override
|
||||
@@ -35,39 +42,35 @@ public:
|
||||
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
|
||||
stdx::error open_port(const input_port& port, std::string_view portName) override
|
||||
{
|
||||
if (!create_local_port(*this, portName, JackPortIsInput))
|
||||
return false;
|
||||
if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
if (auto ret = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
||||
ret != 0)
|
||||
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
||||
err != 0 && err != EEXIST)
|
||||
{
|
||||
error<invalid_parameter_error>(
|
||||
configuration, "JACK: could not connect to port: " + port.port_name + " -> "
|
||||
libremidi_handle_error(
|
||||
configuration, "could not connect to port: " + port.port_name + " -> "
|
||||
+ jack_port_name(this->port));
|
||||
return false;
|
||||
return from_errc(err);
|
||||
}
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
return create_local_port(*this, portName, JackPortIsInput);
|
||||
}
|
||||
|
||||
void close_port() override { return do_close_port(); }
|
||||
stdx::error close_port() override { return do_close_port(); }
|
||||
|
||||
void set_port_name(std::string_view portName) override
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
jack_port_rename(this->client, this->port, portName.data());
|
||||
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
|
||||
@@ -37,16 +37,18 @@ public:
|
||||
jack_ringbuffer_free(ringbuffer);
|
||||
}
|
||||
|
||||
void write(const unsigned char* data, int64_t sz) const noexcept
|
||||
stdx::error write(const unsigned char* data, int64_t sz) const noexcept
|
||||
{
|
||||
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
|
||||
return;
|
||||
return std::errc::no_buffer_space;
|
||||
|
||||
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);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void read(void* jack_events) const noexcept
|
||||
@@ -74,6 +76,7 @@ class midi_out_jack
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: output_configuration
|
||||
, jack_output_configuration
|
||||
@@ -87,39 +90,36 @@ public:
|
||||
|
||||
~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
|
||||
stdx::error open_port(const output_port& port, std::string_view portName) override
|
||||
{
|
||||
if (!create_local_port(*this, portName, JackPortIsOutput))
|
||||
return false;
|
||||
if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
// Connecting to the output
|
||||
if (jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()) != 0)
|
||||
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
|
||||
err != 0 && err != EEXIST)
|
||||
{
|
||||
error<invalid_parameter_error>(
|
||||
configuration, "JACK: could not connect to port" + port.port_name);
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
configuration, "could not connect to port" + port.port_name);
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view portName) override
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
return create_local_port(*this, portName, JackPortIsOutput);
|
||||
}
|
||||
|
||||
void close_port() override { return do_close_port(); }
|
||||
stdx::error close_port() override { return do_close_port(); }
|
||||
|
||||
void set_port_name(std::string_view portName) override
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
jack_port_rename(this->client, this->port, portName.data());
|
||||
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||
return from_errc(ret);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,8 +131,14 @@ public:
|
||||
, 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{}));
|
||||
if (!this->client)
|
||||
{
|
||||
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||
client_open_ = from_jack_status(status);
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_jack_queued() override
|
||||
@@ -142,9 +148,9 @@ public:
|
||||
disconnect(*this);
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, std::size_t size) override
|
||||
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
||||
{
|
||||
queue.write(message, size);
|
||||
return queue.write(message, size);
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
@@ -168,10 +174,15 @@ public:
|
||||
: 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{}));
|
||||
if (!this->client)
|
||||
{
|
||||
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||
client_open_ = from_jack_status(status);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer_size = jack_get_buffer_size(this->client);
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_jack_direct() override
|
||||
@@ -188,10 +199,11 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override
|
||||
stdx::error 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 ret = jack_midi_event_write(buff, 0, message, size);
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
int convert_timestamp(int64_t user) const noexcept
|
||||
@@ -207,10 +219,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void schedule_message(int64_t ts, const unsigned char* message, size_t size) override
|
||||
stdx::error 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 ret = jack_midi_event_write(buff, convert_timestamp(ts), message, size);
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
int buffer_size{};
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
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{}));
|
||||
libremidi_handle_error(configuration, std::to_string((int)status));
|
||||
|
||||
if (this->client != nullptr)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
#include <libremidi/backends/keyboard/midi_in.hpp>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct kbd_backend
|
||||
{
|
||||
using midi_in = midi_in_kbd;
|
||||
using midi_in_configuration = kbd_input_configuration;
|
||||
using midi_out = midi_out_dummy;
|
||||
using midi_observer = observer_dummy;
|
||||
using midi_out_configuration = dummy_configuration;
|
||||
using midi_observer_configuration = dummy_configuration;
|
||||
static const constexpr auto API = libremidi::API::KEYBOARD;
|
||||
static const constexpr auto name = "keyboard";
|
||||
static const constexpr auto display_name = "Computer keyboard";
|
||||
|
||||
static inline bool available() noexcept { return true; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
/**
|
||||
* Used to set up keyboard input.
|
||||
* Your app should pass a function that will give you a callback
|
||||
* that you should call whenever you are getting a key input (press or release).
|
||||
*/
|
||||
struct kbd_input_configuration
|
||||
{
|
||||
using scancode_callback = std::function<void(int)>;
|
||||
// First argument is on key press, second on key release
|
||||
std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks
|
||||
= [](scancode_callback, scancode_callback) {};
|
||||
|
||||
enum kbd_event
|
||||
{
|
||||
NOTE_0 = 0x0, // C
|
||||
VEL_0 = NOTE_0 + 128, // Set velocity to 0
|
||||
OCT_0 = VEL_0 + 128, // Set octave to 0
|
||||
OCTAVE_PLUS = OCT_0 + 128,
|
||||
OCTAVE_MINUS,
|
||||
VELOCITY_PLUS,
|
||||
VELOCITY_MINUS,
|
||||
};
|
||||
|
||||
// Default map
|
||||
//
|
||||
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
|
||||
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
|
||||
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
|
||||
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
|
||||
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
|
||||
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
|
||||
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
|
||||
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
|
||||
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
|
||||
// | ctrl | | alt | |altgr | | ctrl |
|
||||
// '------' '-----'--------------------------'------' '------'
|
||||
|
||||
// clang-format off
|
||||
// Use: https://kbdlayout.info/KBDUSX/scancodes
|
||||
std::map<int, int> scancode_map{
|
||||
{ 0x1E, NOTE_0}, // C0
|
||||
{ 0x11, NOTE_0 + 1},
|
||||
{ 0x1F, NOTE_0 + 2},
|
||||
{ 0x12, NOTE_0 + 3},
|
||||
{ 0x20, NOTE_0 + 4},
|
||||
{ 0x21, NOTE_0 + 5},
|
||||
{ 0x14, NOTE_0 + 6},
|
||||
{ 0x22, NOTE_0 + 7},
|
||||
{ 0x15, NOTE_0 + 8},
|
||||
{ 0x23, NOTE_0 + 9},
|
||||
{ 0x16, NOTE_0 + 10},
|
||||
{ 0x24, NOTE_0 + 11},
|
||||
{ 0x25, NOTE_0 + 12}, // C1
|
||||
{ 0x18, NOTE_0 + 13},
|
||||
{ 0x26, NOTE_0 + 14},
|
||||
{ 0x19, NOTE_0 + 15},
|
||||
{ 0x27, NOTE_0 + 16},
|
||||
{ 0x28, NOTE_0 + 17},
|
||||
{ 0x1B, NOTE_0 + 18},
|
||||
{ 0x2B, NOTE_0 + 19},
|
||||
|
||||
{ 0x29, VEL_0 + int(0)},
|
||||
{ 0x02, VEL_0 + int(1 * 127 / 12)},
|
||||
{ 0x03, VEL_0 + int(2 * 127 / 12)},
|
||||
{ 0x04, VEL_0 + int(3 * 127 / 12)},
|
||||
{ 0x05, VEL_0 + int(4 * 127 / 12)},
|
||||
{ 0x06, VEL_0 + int(5 * 127 / 12)},
|
||||
{ 0x07, VEL_0 + int(6 * 127 / 12)},
|
||||
{ 0x08, VEL_0 + int(7 * 127 / 12)},
|
||||
{ 0x09, VEL_0 + int(8 * 127 / 12)},
|
||||
{ 0x0A, VEL_0 + int(9 * 127 / 12)},
|
||||
{ 0x0B, VEL_0 + int(10 * 127 / 12)},
|
||||
{ 0x0C, VEL_0 + int(11 * 127 / 12)},
|
||||
{ 0x0D, VEL_0 + int(127)},
|
||||
|
||||
{ 0x2C, OCTAVE_MINUS },
|
||||
{ 0x2D, OCTAVE_PLUS },
|
||||
{ 0x2E, VELOCITY_MINUS },
|
||||
{ 0x2F, VELOCITY_PLUS},
|
||||
};
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/keyboard/config.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
class midi_in_kbd final
|
||||
: public midi1::in_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: input_configuration
|
||||
, kbd_input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
configuration.set_input_scancode_callbacks(
|
||||
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
|
||||
}
|
||||
|
||||
~midi_in_kbd() override { }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }
|
||||
|
||||
stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; }
|
||||
|
||||
stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; }
|
||||
|
||||
stdx::error close_port() override { return stdx::error{}; }
|
||||
|
||||
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
{
|
||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
void on_keypress(int scancode)
|
||||
{
|
||||
using kevent = kbd_input_configuration::kbd_event;
|
||||
|
||||
auto it = configuration.scancode_map.find(scancode);
|
||||
if (it == configuration.scancode_map.end())
|
||||
return;
|
||||
|
||||
if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))
|
||||
{
|
||||
int note = it->second - kevent::NOTE_0 + 12 * m_current_octave;
|
||||
this->configuration.on_message(
|
||||
libremidi::channel_events::note_on(0, note, m_current_velocity));
|
||||
m_current_notes_scancodes[scancode] = note;
|
||||
}
|
||||
else if (it->second >= kevent::VEL_0 && it->second < (kevent::VEL_0 + 128))
|
||||
{
|
||||
m_current_velocity = it->second - kevent::VEL_0;
|
||||
}
|
||||
else if (it->second >= kevent::OCT_0 && it->second < (kevent::OCT_0 + 128))
|
||||
{
|
||||
m_current_octave = it->second - kevent::OCT_0;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (it->second)
|
||||
{
|
||||
case kevent::VELOCITY_MINUS:
|
||||
m_current_velocity = std::clamp(m_current_velocity - 10, 0, 127);
|
||||
break;
|
||||
case kevent::VELOCITY_PLUS:
|
||||
m_current_velocity = std::clamp(m_current_velocity + 10, 0, 127);
|
||||
break;
|
||||
case kevent::OCTAVE_MINUS:
|
||||
m_current_octave = std::clamp(m_current_octave - 1, 0, 127);
|
||||
break;
|
||||
case kevent::OCTAVE_PLUS:
|
||||
m_current_octave = std::clamp(m_current_octave + 1, 0, 127);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_keyrelease(int scancode)
|
||||
{
|
||||
using kevent = kbd_input_configuration::kbd_event;
|
||||
|
||||
auto it = configuration.scancode_map.find(scancode);
|
||||
if (it == configuration.scancode_map.end())
|
||||
return;
|
||||
|
||||
if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))
|
||||
{
|
||||
if (auto note_it = m_current_notes_scancodes.find(scancode);
|
||||
note_it != m_current_notes_scancodes.end())
|
||||
{
|
||||
this->configuration.on_message(libremidi::channel_events::note_off(0, note_it->second, 0));
|
||||
m_current_notes_scancodes.erase(note_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int m_current_octave{3};
|
||||
int m_current_velocity{80};
|
||||
std::unordered_map<int, int> m_current_notes_scancodes;
|
||||
};
|
||||
}
|
||||
@@ -57,13 +57,13 @@ struct libasound
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(snd_card, get_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_card, next);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_card, get_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_card, next)
|
||||
}
|
||||
bool available{true};
|
||||
|
||||
LIBREMIDI_SYMBOL_DEF(snd_card, get_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_card, next);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_card, get_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_card, next)
|
||||
} card{library};
|
||||
|
||||
struct ctl_t
|
||||
@@ -80,13 +80,13 @@ struct libasound
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl, close);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl, open);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl, close)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl, open)
|
||||
}
|
||||
bool available{true};
|
||||
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl, close);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl, open);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl, close)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl, open)
|
||||
|
||||
struct rawmidi_t
|
||||
{
|
||||
@@ -98,12 +98,12 @@ struct libasound
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device)
|
||||
}
|
||||
bool available{true};
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device)
|
||||
} rawmidi;
|
||||
|
||||
#if LIBREMIDI_ALSA_HAS_UMP
|
||||
@@ -117,14 +117,14 @@ struct libasound
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, block_info);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, endpoint_info);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, next_device);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, block_info)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, endpoint_info)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, next_device)
|
||||
}
|
||||
bool available{true};
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, block_info);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, endpoint_info);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, next_device);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, block_info)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, endpoint_info)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, next_device)
|
||||
} ump;
|
||||
#endif
|
||||
} ctl{library};
|
||||
@@ -139,23 +139,23 @@ struct libasound
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_decode);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_encode);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_free);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_init);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_new);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_no_status);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_resize_buffer);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_decode)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_encode)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_free)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_init)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_new)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_no_status)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_resize_buffer)
|
||||
}
|
||||
|
||||
bool available{true};
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_decode);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_encode);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_free);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_init);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_new);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_no_status);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_resize_buffer);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_decode)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_encode)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_free)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_init)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_new)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_no_status)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_resize_buffer)
|
||||
} midi{library};
|
||||
|
||||
#if LIBREMIDI_ALSA_HAS_RAMWIDI
|
||||
@@ -168,59 +168,59 @@ struct libasound
|
||||
available = false;
|
||||
return;
|
||||
}
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, close);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevice_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevices_count);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_device);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_stream);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_subdevice);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, open);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_current);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_get_buffer_size);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_clock_type);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_no_active_sensing);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_read_mode);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_count);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_revents);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, read);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_get_avail);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, tread);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, write);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, close)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevice_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevices_count)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_device)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_stream)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_subdevice)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, open)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_current)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_get_buffer_size)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_clock_type)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_no_active_sensing)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_read_mode)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_count)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_revents)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, read)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_get_avail)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, tread)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, write)
|
||||
}
|
||||
|
||||
bool available{true};
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, close);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevice_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevices_count);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_device);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_stream);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_subdevice);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, open);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_current);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_get_buffer_size);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_clock_type);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_no_active_sensing);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_read_mode);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_count);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_revents);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, read);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_get_avail);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, tread);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, write);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, close)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevice_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevices_count)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_device)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_stream)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_subdevice)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_sizeof)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, open)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_current)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_get_buffer_size)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_clock_type)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_no_active_sensing)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_read_mode)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_sizeof)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_count)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_revents)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, read)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_get_avail)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_sizeof)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, tread)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, write)
|
||||
} rawmidi{library};
|
||||
#endif
|
||||
|
||||
@@ -237,118 +237,118 @@ struct libasound
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, alloc_queue);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_id);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, close);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, create_port);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_output);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, free_event);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, open);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, alloc_queue)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_id)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, close)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, create_port)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_output)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, free_event)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, open)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port)
|
||||
}
|
||||
|
||||
bool available{true};
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, alloc_queue);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_id);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, close);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, create_port);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_output);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, free_event);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, open);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, alloc_queue)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_id)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, close)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, create_port)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_output)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, free_event)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, open)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof)
|
||||
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_port_info);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_queue_tempo);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_port_info)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_queue_tempo)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port)
|
||||
|
||||
#if LIBREMIDI_ALSA_HAS_UMP
|
||||
struct ump_t
|
||||
@@ -360,18 +360,18 @@ struct libasound
|
||||
available = false;
|
||||
return;
|
||||
}
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct)
|
||||
}
|
||||
|
||||
bool available{true};
|
||||
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct)
|
||||
} ump;
|
||||
#endif
|
||||
} seq{library};
|
||||
@@ -387,39 +387,39 @@ struct libasound
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, close);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, open);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, read);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, tread);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, write);
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, close)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, open)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, read)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, tread)
|
||||
LIBREMIDI_SYMBOL_INIT(snd_ump, write)
|
||||
}
|
||||
|
||||
bool available{true};
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, close);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, open);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, read);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, tread);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, write);
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, close)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, open)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, read)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, tread)
|
||||
LIBREMIDI_SYMBOL_DEF(snd_ump, write)
|
||||
} ump{library};
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -23,16 +23,16 @@ struct libudev
|
||||
return;
|
||||
}
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_get_action);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_unref);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref);
|
||||
LIBREMIDI_SYMBOL_INIT2(udev, new, create);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, unref);
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_get_action)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_unref)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref)
|
||||
LIBREMIDI_SYMBOL_INIT2(udev, new, create)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, unref)
|
||||
}
|
||||
|
||||
static const libudev& instance()
|
||||
@@ -44,16 +44,16 @@ struct libudev
|
||||
dylib_loader library;
|
||||
bool available{true};
|
||||
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_action);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_unref);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref);
|
||||
LIBREMIDI_SYMBOL_DEF2(udev, new, create);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, unref);
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_action)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_unref)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref)
|
||||
LIBREMIDI_SYMBOL_DEF2(udev, new, create)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, unref)
|
||||
};
|
||||
|
||||
struct udev_helper
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <string>
|
||||
namespace boost::asio
|
||||
{
|
||||
struct io_context;
|
||||
}
|
||||
|
||||
namespace libremidi::net
|
||||
{
|
||||
enum class protocol
|
||||
{
|
||||
OSC_MIDI,
|
||||
};
|
||||
|
||||
struct dgram_input_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
enum protocol protocol = protocol::OSC_MIDI;
|
||||
std::string accept = "0.0.0.0";
|
||||
int port{};
|
||||
|
||||
boost::asio::io_context* io_context{};
|
||||
};
|
||||
|
||||
struct dgram_output_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
enum protocol protocol = protocol::OSC_MIDI;
|
||||
std::string host = "127.0.0.1";
|
||||
int port{};
|
||||
bool broadcast{};
|
||||
|
||||
boost::asio::io_context* io_context{};
|
||||
};
|
||||
|
||||
struct dgram_observer_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
boost::asio::io_context* io_context{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace libremidi::net_ump
|
||||
{
|
||||
enum class protocol
|
||||
{
|
||||
OSC_MIDI2,
|
||||
};
|
||||
|
||||
struct dgram_input_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
enum protocol protocol = protocol::OSC_MIDI2;
|
||||
std::string accept = "0.0.0.0";
|
||||
int port{};
|
||||
|
||||
boost::asio::io_context* io_context{};
|
||||
};
|
||||
|
||||
struct dgram_output_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
enum protocol protocol = protocol::OSC_MIDI2;
|
||||
std::string host = "127.0.0.1";
|
||||
int port{};
|
||||
bool broadcast{};
|
||||
|
||||
boost::asio::io_context* io_context{};
|
||||
};
|
||||
|
||||
struct dgram_observer_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
boost::asio::io_context* io_context{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
|
||||
#include <memory>
|
||||
namespace stdx
|
||||
{ /*
|
||||
|
||||
class boost_system_error_domain : public error_domain
|
||||
{
|
||||
public:
|
||||
constexpr boost_system_error_domain() noexcept
|
||||
: error_domain{
|
||||
{1 - 0x3c223c0aa3cf45e5ULL, 1 - 0x80dac24345cfb9fcULL},
|
||||
default_error_resource_management_t<detail::exception_ptr_wrapper>{}}
|
||||
{
|
||||
}
|
||||
|
||||
string_ref name() const noexcept override { return "boost::system_error domain"; }
|
||||
|
||||
bool equivalent(const error& lhs, const error& rhs) const noexcept override { return false; }
|
||||
|
||||
string_ref message(const error& e) const noexcept override {
|
||||
auto err = e.m_value;
|
||||
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_exception(const error& e) const override
|
||||
{
|
||||
assert(e.domain() == *this);
|
||||
std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());
|
||||
}
|
||||
};
|
||||
*/
|
||||
}
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
template <typename T>
|
||||
struct optionally_owned
|
||||
{
|
||||
public:
|
||||
explicit optionally_owned(T* maybe_existing)
|
||||
: storage{.ref = maybe_existing}
|
||||
{
|
||||
if (storage.ref)
|
||||
{
|
||||
ownership = unowned;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::destroy_at(&storage.ref);
|
||||
std::construct_at<T>(reinterpret_cast<T*>(storage.object));
|
||||
ownership = owned;
|
||||
}
|
||||
}
|
||||
|
||||
~optionally_owned()
|
||||
{
|
||||
if (is_owned())
|
||||
std::destroy_at(reinterpret_cast<T*>(storage.object));
|
||||
}
|
||||
|
||||
T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); }
|
||||
const T& get() const noexcept
|
||||
{
|
||||
return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref);
|
||||
}
|
||||
|
||||
optionally_owned(const optionally_owned&) = delete;
|
||||
optionally_owned(optionally_owned&&) noexcept = delete;
|
||||
optionally_owned& operator=(const optionally_owned&) = delete;
|
||||
optionally_owned& operator=(optionally_owned&&) noexcept = delete;
|
||||
|
||||
bool is_owned() const noexcept { return ownership == owned; }
|
||||
|
||||
private:
|
||||
union
|
||||
{
|
||||
alignas(T) unsigned char object[sizeof(T)];
|
||||
T* ref;
|
||||
} storage;
|
||||
enum
|
||||
{
|
||||
owned,
|
||||
unowned
|
||||
} ownership{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/net/config.hpp>
|
||||
#include <libremidi/backends/net/helpers.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
template <typename Impl, typename F>
|
||||
struct osc_parser
|
||||
{
|
||||
[[no_unique_address]]
|
||||
Impl impl;
|
||||
F on_message;
|
||||
std::string_view port_name;
|
||||
|
||||
stdx::error parse_bundle(const char* data, std::size_t sz)
|
||||
{
|
||||
std::string_view txt(data, sz);
|
||||
if (!txt.starts_with("#bundle"))
|
||||
return std::errc::bad_message;
|
||||
return std::errc::protocol_not_supported;
|
||||
}
|
||||
|
||||
stdx::error parse_message(const char* data, std::size_t sz)
|
||||
{
|
||||
const auto begin = data;
|
||||
const auto end = data + sz;
|
||||
std::size_t pattern_len = strnlen(data, sz);
|
||||
if (pattern_len == sz)
|
||||
return std::errc::bad_message;
|
||||
|
||||
if (auto pat = std::string_view(data, pattern_len); pat != port_name)
|
||||
return std::errc::bad_address;
|
||||
|
||||
data += pattern_len;
|
||||
for (; data < end; ++data)
|
||||
if (*data == 0)
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
|
||||
// Now we shold reach the typetag beginning, ","
|
||||
if (data == end)
|
||||
return std::errc::bad_message;
|
||||
if ((data - begin) % 4 != 0)
|
||||
return std::errc::bad_message;
|
||||
if (*data++ != ',')
|
||||
return std::errc::bad_message;
|
||||
|
||||
// Count the ,mmmmm arguments... yummy
|
||||
int num_msgs = 0;
|
||||
for (; data < end; ++data)
|
||||
if (*data == Impl::typetag)
|
||||
num_msgs++;
|
||||
else if (*data == 0)
|
||||
break;
|
||||
else
|
||||
return std::errc::bad_message;
|
||||
|
||||
if (num_msgs == 0)
|
||||
return std::errc::no_message;
|
||||
|
||||
// By now there's at least 5 bytes left as the ,mmm has to be round-up to 4 and padded with zeros:
|
||||
if (end - data < 4)
|
||||
return std::errc::bad_message;
|
||||
|
||||
switch ((1 + num_msgs) % 4)
|
||||
{
|
||||
case 0:
|
||||
data += 4;
|
||||
break;
|
||||
case 1:
|
||||
data += 3;
|
||||
break;
|
||||
case 2:
|
||||
data += 2;
|
||||
break;
|
||||
case 3:
|
||||
data += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Data starts
|
||||
return impl.process_midi_bytes(on_message, num_msgs, data, end - data);
|
||||
}
|
||||
|
||||
stdx::error parse_int_message(const char* /* data */, std::size_t /* sz */)
|
||||
{
|
||||
return std::errc::protocol_not_supported;
|
||||
}
|
||||
|
||||
stdx::error parse_packet(const char* data, std::size_t sz)
|
||||
{
|
||||
if (sz == 0)
|
||||
return std::errc::no_message;
|
||||
|
||||
switch (data[0])
|
||||
{
|
||||
case '#':
|
||||
return parse_bundle(data, sz);
|
||||
break;
|
||||
case '/':
|
||||
return parse_message(data, sz);
|
||||
break;
|
||||
case '\0':
|
||||
return parse_int_message(data, sz);
|
||||
break;
|
||||
default:
|
||||
return std::errc::bad_message;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace libremidi::net
|
||||
{
|
||||
|
||||
struct osc_parser_midi1
|
||||
{
|
||||
static constexpr char typetag = 'm';
|
||||
|
||||
stdx::error
|
||||
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t sz)
|
||||
{
|
||||
if (sz != num_msgs * 4)
|
||||
return std::errc::bad_message;
|
||||
|
||||
auto end = data + sz;
|
||||
for (; data < end; data += 4)
|
||||
on_message(data + 1);
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class midi_in final
|
||||
: public midi1::in_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: input_configuration
|
||||
, dgram_input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit midi_in(input_configuration&& conf, dgram_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, m_ctx{configuration.io_context}
|
||||
, m_socket{m_ctx.get()}
|
||||
{
|
||||
m_socket.open(boost::asio::ip::udp::v4());
|
||||
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||
|
||||
m_endpoint
|
||||
= {boost::asio::ip::make_address(this->configuration.accept),
|
||||
static_cast<unsigned short>(this->configuration.port)};
|
||||
|
||||
boost::system::error_code ec;
|
||||
m_socket.bind(m_endpoint, ec);
|
||||
if (ec != boost::system::error_code{})
|
||||
{
|
||||
client_open_ = std::errc::address_in_use;
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in() override { close_port(); }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }
|
||||
|
||||
stdx::error open_port(const input_port&, std::string_view port) override
|
||||
{
|
||||
return open_virtual_port(port);
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view port) override
|
||||
{
|
||||
m_portname = std::string(port);
|
||||
|
||||
receive();
|
||||
|
||||
if (m_ctx.is_owned())
|
||||
{
|
||||
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
|
||||
auto wg = boost::asio::make_work_guard(m_ctx);
|
||||
ctx.run();
|
||||
}};
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void receive()
|
||||
{
|
||||
m_socket.async_receive_from(
|
||||
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
|
||||
[this](auto ec, std::size_t sz) {
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
|
||||
if (!ec && sz > 0)
|
||||
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
|
||||
|
||||
this->receive();
|
||||
});
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
// FIXME async close
|
||||
if (m_socket.is_open())
|
||||
m_socket.close();
|
||||
return {};
|
||||
}
|
||||
|
||||
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
{
|
||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
void on_bytes(const char* data, std::size_t size)
|
||||
{
|
||||
const auto on_msg = [this](const char* bytes) { this->on_message(bytes); };
|
||||
osc_parser<osc_parser_midi1, decltype(on_msg)> parser{{}, on_msg, m_portname};
|
||||
parser.parse_packet(data, size);
|
||||
}
|
||||
|
||||
void on_message(const char* data)
|
||||
{
|
||||
// OSC enforces always 3 bytes
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = false,
|
||||
.absolute_is_monotonic = false,
|
||||
.has_samples = false,
|
||||
};
|
||||
|
||||
static constexpr auto to_ns = []() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count();
|
||||
};
|
||||
|
||||
auto ptr = reinterpret_cast<const uint8_t*>(data);
|
||||
m_processing.on_bytes({ptr, ptr + 3}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
|
||||
midi1::input_state_machine m_processing{this->configuration};
|
||||
|
||||
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
|
||||
boost::asio::ip::udp::endpoint m_endpoint;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
|
||||
std::jthread m_thread;
|
||||
|
||||
std::string m_portname;
|
||||
alignas(16) unsigned char m_data[65535];
|
||||
};
|
||||
}
|
||||
|
||||
namespace libremidi::net_ump
|
||||
{
|
||||
struct osc_parser_midi2
|
||||
{
|
||||
static constexpr char typetag = 'M';
|
||||
|
||||
stdx::error
|
||||
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t byte_sz)
|
||||
{
|
||||
if (byte_sz % 4 != 0)
|
||||
return std::errc::bad_message;
|
||||
|
||||
auto sz = byte_sz / 4;
|
||||
|
||||
auto begin = reinterpret_cast<const uint32_t*>(data);
|
||||
auto end = begin + sz;
|
||||
std::size_t accounted = 0;
|
||||
for (auto it = begin; it < end && accounted < num_msgs;)
|
||||
{
|
||||
const auto N = cmidi2_ump_get_message_size_bytes(it) / 4;
|
||||
switch (N)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
if (it + N <= end)
|
||||
{
|
||||
on_message(it, N);
|
||||
accounted++;
|
||||
}
|
||||
else
|
||||
return std::errc::bad_message;
|
||||
break;
|
||||
default:
|
||||
return std::errc::bad_message;
|
||||
}
|
||||
it += N;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class midi_in final
|
||||
: public midi2::in_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: ump_input_configuration
|
||||
, dgram_input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit midi_in(ump_input_configuration&& conf, dgram_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, m_ctx{configuration.io_context}
|
||||
, m_socket{m_ctx.get()}
|
||||
{
|
||||
m_socket.open(boost::asio::ip::udp::v4());
|
||||
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||
|
||||
m_endpoint
|
||||
= {boost::asio::ip::make_address(this->configuration.accept),
|
||||
static_cast<unsigned short>(this->configuration.port)};
|
||||
|
||||
boost::system::error_code ec;
|
||||
m_socket.bind(m_endpoint, ec);
|
||||
if (ec != boost::system::error_code{})
|
||||
{
|
||||
client_open_ = std::errc::address_in_use;
|
||||
return;
|
||||
}
|
||||
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in() override { close_port(); }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }
|
||||
|
||||
stdx::error open_port(const input_port&, std::string_view port) override
|
||||
{
|
||||
return open_virtual_port(port);
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view port) override
|
||||
{
|
||||
m_portname = std::string(port);
|
||||
|
||||
receive();
|
||||
|
||||
if (m_ctx.is_owned())
|
||||
{
|
||||
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
|
||||
auto wg = boost::asio::make_work_guard(m_ctx);
|
||||
ctx.run();
|
||||
}};
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void receive()
|
||||
{
|
||||
m_socket.async_receive_from(
|
||||
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
|
||||
[this](auto ec, std::size_t sz) {
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
|
||||
if (!ec && sz > 0)
|
||||
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
|
||||
|
||||
this->receive();
|
||||
});
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
// FIXME async close
|
||||
if (m_ctx.is_owned())
|
||||
m_ctx.get().stop();
|
||||
|
||||
if (m_socket.is_open())
|
||||
m_socket.close();
|
||||
return {};
|
||||
}
|
||||
|
||||
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
{
|
||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
void on_bytes(const char* data, std::size_t size)
|
||||
{
|
||||
const auto on_msg
|
||||
= [this](const uint32_t* bytes, std::size_t N) { this->on_message(bytes, N); };
|
||||
osc_parser<osc_parser_midi2, decltype(on_msg)> parser{{}, on_msg, m_portname};
|
||||
parser.parse_packet(data, size);
|
||||
}
|
||||
|
||||
void on_message(const uint32_t* ump, std::size_t sz)
|
||||
{
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = false,
|
||||
.absolute_is_monotonic = false,
|
||||
.has_samples = false,
|
||||
};
|
||||
|
||||
static constexpr auto to_ns = []() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count();
|
||||
};
|
||||
|
||||
m_processing.on_bytes({ump, sz}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
|
||||
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
|
||||
boost::asio::ip::udp::endpoint m_endpoint;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
|
||||
std::jthread m_thread;
|
||||
|
||||
std::string m_portname;
|
||||
alignas(16) unsigned char m_data[65535];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/net/config.hpp>
|
||||
#include <libremidi/backends/net/helpers.hpp>
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
#include <boost/endian.hpp>
|
||||
|
||||
namespace libremidi::net
|
||||
{
|
||||
|
||||
struct osc_midi1_packet
|
||||
{
|
||||
stdx::error init_packet(std::string_view osc_pattern)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < std::ssize(osc_pattern); i++)
|
||||
if (osc_pattern.data()[i] != 0)
|
||||
bytes[i] = osc_pattern.data()[i];
|
||||
else
|
||||
break;
|
||||
bytes[i] = 0;
|
||||
while (i % 4 != 3)
|
||||
{
|
||||
++i;
|
||||
bytes[i] = 0;
|
||||
}
|
||||
|
||||
bytes[++i] = ',';
|
||||
bytes[++i] = 'm';
|
||||
bytes[++i] = 0;
|
||||
bytes[++i] = 0;
|
||||
|
||||
// MIDI message (m) spec: port n°, status byte, data 1, data 2
|
||||
bytes[++i] = 0;
|
||||
message_size = ++i + 3;
|
||||
|
||||
return stdx::error{}; // FIXME
|
||||
}
|
||||
|
||||
stdx::error deinit()
|
||||
{
|
||||
message_size = 0;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error set_packet_content(const unsigned char* message, size_t size)
|
||||
{
|
||||
if (message_size == 0)
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (size != 3)
|
||||
return std::errc::message_size;
|
||||
|
||||
std::memcpy(this->bytes + message_size - 3, message, 3);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
std::span<const char> get_data() { return std::span(this->bytes, this->message_size); }
|
||||
char bytes[512 + 8 + 8];
|
||||
int message_size{};
|
||||
};
|
||||
|
||||
class midi_out final
|
||||
: public midi1::out_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: output_configuration
|
||||
, dgram_output_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, ctx{configuration.io_context}
|
||||
, m_socket{ctx.get()}
|
||||
{
|
||||
m_socket.open(boost::asio::ip::udp::v4());
|
||||
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||
m_socket.set_option(boost::asio::socket_base::broadcast(true));
|
||||
|
||||
m_endpoint
|
||||
= {boost::asio::ip::make_address(this->configuration.host),
|
||||
static_cast<unsigned short>(this->configuration.port)};
|
||||
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out() override { close_port(); }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }
|
||||
|
||||
stdx::error open_port(const output_port& /* port */, std::string_view portName) override
|
||||
{
|
||||
return open_virtual_port(portName);
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
// Random arbitrary limit to avoid abuse
|
||||
if (portName.size() >= 512)
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
pkt.init_packet(portName);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
pkt.deinit();
|
||||
// FIXME async close
|
||||
if (m_socket.is_open())
|
||||
m_socket.close();
|
||||
return {};
|
||||
}
|
||||
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
auto dat = pkt.get_data();
|
||||
boost::system::error_code ec;
|
||||
m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);
|
||||
return stdx::error{}; // FIXME
|
||||
}
|
||||
|
||||
stdx::error schedule_message(int64_t, const unsigned char*, size_t) override
|
||||
{
|
||||
int ret = 0;
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
libremidi::optionally_owned<boost::asio::io_context> ctx;
|
||||
boost::asio::ip::udp::endpoint m_endpoint;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
|
||||
osc_midi1_packet pkt;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace libremidi::net_ump
|
||||
{
|
||||
struct osc_midi2_packet
|
||||
{
|
||||
stdx::error init_packet(std::string_view osc_pattern)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < std::ssize(osc_pattern); i++)
|
||||
if (osc_pattern.data()[i] != 0)
|
||||
bytes[i] = osc_pattern.data()[i];
|
||||
else
|
||||
break;
|
||||
bytes[i] = 0;
|
||||
while (i % 4 != 3)
|
||||
{
|
||||
++i;
|
||||
bytes[i] = 0;
|
||||
}
|
||||
|
||||
bytes[++i] = ',';
|
||||
bytes[++i] = 'M';
|
||||
bytes[++i] = 0;
|
||||
bytes[++i] = 0;
|
||||
|
||||
// MIDI 2 message (M) spec: an UMP
|
||||
header_size = ++i;
|
||||
|
||||
return stdx::error{}; // FIXME
|
||||
}
|
||||
|
||||
stdx::error deinit()
|
||||
{
|
||||
header_size = 0;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error set_packet_content(const uint32_t* message, size_t size)
|
||||
{
|
||||
if (header_size == 0)
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (size > 4)
|
||||
return std::errc::message_size;
|
||||
|
||||
message_size = 4 * size;
|
||||
|
||||
std::memcpy(this->bytes + header_size, message, message_size);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
std::span<const char> get_data()
|
||||
{
|
||||
return std::span(this->bytes, this->header_size + this->message_size);
|
||||
}
|
||||
char bytes[512 + 8 + 64];
|
||||
int header_size{};
|
||||
int message_size{};
|
||||
};
|
||||
|
||||
class midi_out final
|
||||
: public midi2::out_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: output_configuration
|
||||
, dgram_output_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, ctx{configuration.io_context}
|
||||
, m_socket{ctx.get()}
|
||||
{
|
||||
m_socket.open(boost::asio::ip::udp::v4());
|
||||
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||
m_socket.set_option(boost::asio::socket_base::broadcast(true));
|
||||
|
||||
m_endpoint
|
||||
= {boost::asio::ip::make_address(this->configuration.host),
|
||||
static_cast<unsigned short>(this->configuration.port)};
|
||||
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out() override { close_port(); }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }
|
||||
|
||||
stdx::error open_port(const output_port& /* port */, std::string_view portName) override
|
||||
{
|
||||
return open_virtual_port(portName);
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
// Random arbitrary limit to avoid abuse
|
||||
if (portName.size() >= 512)
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
pkt.init_packet(portName);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
pkt.deinit();
|
||||
if (m_socket.is_open())
|
||||
m_socket.close();
|
||||
return {};
|
||||
}
|
||||
|
||||
stdx::error send_ump(const uint32_t* message, size_t size) override
|
||||
{
|
||||
if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
auto dat = pkt.get_data();
|
||||
boost::system::error_code ec;
|
||||
m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);
|
||||
return stdx::error{}; // FIXME
|
||||
}
|
||||
|
||||
stdx::error schedule_ump(int64_t, const uint32_t*, size_t) override
|
||||
{
|
||||
int ret = 0;
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
libremidi::optionally_owned<boost::asio::io_context> ctx;
|
||||
boost::asio::ip::udp::endpoint m_endpoint;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
|
||||
osc_midi2_packet pkt;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
|
||||
namespace libremidi::net
|
||||
{
|
||||
using net_observer_configuration = libremidi::dummy_configuration;
|
||||
using observer = libremidi::observer_dummy;
|
||||
}
|
||||
|
||||
namespace libremidi::net_ump
|
||||
{
|
||||
using net_observer_configuration = libremidi::dummy_configuration;
|
||||
using observer = libremidi::observer_dummy;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
#include <libremidi/backends/net/config.hpp>
|
||||
#include <libremidi/backends/net/midi_in.hpp>
|
||||
#include <libremidi/backends/net/midi_out.hpp>
|
||||
#include <libremidi/backends/net/observer.hpp>
|
||||
|
||||
namespace libremidi::net
|
||||
{
|
||||
struct backend
|
||||
{
|
||||
using midi_in = libremidi::net::midi_in;
|
||||
using midi_out = libremidi::net::midi_out;
|
||||
using midi_observer = libremidi::net::observer;
|
||||
using midi_in_configuration = libremidi::net::dgram_input_configuration;
|
||||
using midi_out_configuration = libremidi::net::dgram_output_configuration;
|
||||
using midi_observer_configuration = libremidi::net::net_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::NETWORK;
|
||||
static const constexpr auto name = "network";
|
||||
static const constexpr auto display_name = "Network";
|
||||
|
||||
static inline bool available() noexcept { return true; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/net/config.hpp>
|
||||
#include <libremidi/backends/net/midi_in.hpp>
|
||||
#include <libremidi/backends/net/midi_out.hpp>
|
||||
#include <libremidi/backends/net/observer.hpp>
|
||||
|
||||
namespace libremidi::net_ump
|
||||
{
|
||||
struct backend
|
||||
{
|
||||
using midi_in = libremidi::net_ump::midi_in;
|
||||
using midi_out = libremidi::net_ump::midi_out;
|
||||
using midi_observer = libremidi::net_ump::observer;
|
||||
using midi_in_configuration = libremidi::net_ump::dgram_input_configuration;
|
||||
using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;
|
||||
using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::NETWORK_UMP;
|
||||
static const constexpr auto name = "network (UMP)";
|
||||
static const constexpr auto display_name = "Network (UMP)";
|
||||
|
||||
static inline bool available() noexcept { return true; }
|
||||
};
|
||||
}
|
||||
@@ -27,8 +27,8 @@ struct pipewire_input_configuration
|
||||
|
||||
pw_main_loop* context{};
|
||||
pw_filter* filter{};
|
||||
std::function<void(pipewire_callback)> set_process_func;
|
||||
std::function<void(int64_t)> clear_process_func;
|
||||
std::function<void(pipewire_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
};
|
||||
|
||||
struct pipewire_output_configuration
|
||||
@@ -37,8 +37,8 @@ struct pipewire_output_configuration
|
||||
|
||||
pw_main_loop* context{};
|
||||
pw_filter* filter{};
|
||||
std::function<void(pipewire_callback)> set_process_func;
|
||||
std::function<void(int64_t)> clear_process_func;
|
||||
std::function<void(pipewire_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
|
||||
int64_t output_buffer_size{65536};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/linux/pipewire.hpp>
|
||||
#include <libremidi/detail/memory.hpp>
|
||||
#include <libremidi/error.hpp>
|
||||
|
||||
#include <pipewire/filter.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
@@ -128,7 +129,7 @@ struct pipewire_context
|
||||
this->main_loop = pw.main_loop_new(nullptr);
|
||||
if (!this->main_loop)
|
||||
{
|
||||
// libremidi::logger().error("PipeWire: main_loop_new failed!");
|
||||
// libremidi::logger().libremidi_handle_error("main_loop_new failed!");
|
||||
return;
|
||||
}
|
||||
initialize();
|
||||
@@ -139,28 +140,28 @@ struct pipewire_context
|
||||
this->lp = pw.main_loop_get_loop(this->main_loop);
|
||||
if (!lp)
|
||||
{
|
||||
// libremidi::logger().error("PipeWire: main_loop_get_loop failed!");
|
||||
// libremidi::logger().libremidi_handle_error("main_loop_get_loop failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
this->context = pw.context_new(lp, nullptr, 0);
|
||||
if (!this->context)
|
||||
{
|
||||
// libremidi::logger().error("PipeWire: context_new failed!");
|
||||
// libremidi::logger().libremidi_handle_error("context_new failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
this->core = pw.context_connect(this->context, nullptr, 0);
|
||||
if (!this->core)
|
||||
{
|
||||
// libremidi::logger().error("PipeWire: context_connect failed!");
|
||||
// libremidi::logger().libremidi_handle_error("context_connect failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);
|
||||
if (!this->registry)
|
||||
{
|
||||
// libremidi::logger().error("PipeWire: core_get_registry failed!");
|
||||
// libremidi::logger().libremidi_handle_error("core_get_registry failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -297,7 +298,6 @@ struct pipewire_context
|
||||
|
||||
if (!proxy)
|
||||
{
|
||||
std::cerr << "PipeWire: could not allocate link\n";
|
||||
pw.properties_free(props);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -476,7 +476,7 @@ struct pipewire_filter
|
||||
PW_KEY_MEDIA_TYPE, "Midi",
|
||||
PW_KEY_MEDIA_CATEGORY, "Filter",
|
||||
PW_KEY_MEDIA_ROLE, "DSP",
|
||||
PW_KEY_MEDIA_NAME, "libremidi",
|
||||
PW_KEY_MEDIA_NAME, "",
|
||||
#if defined(PW_KEY_NODE_LOCK_RATE)
|
||||
PW_KEY_NODE_LOCK_RATE, "true",
|
||||
#endif
|
||||
@@ -498,7 +498,7 @@ struct pipewire_filter
|
||||
pw.filter_destroy(this->filter);
|
||||
}
|
||||
|
||||
void create_local_port(std::string_view port_name, spa_direction direction)
|
||||
stdx::error create_local_port(std::string_view port_name, spa_direction direction)
|
||||
{
|
||||
// clang-format off
|
||||
this->port = (struct port*)pw.filter_add_port(
|
||||
@@ -512,7 +512,9 @@ struct pipewire_filter
|
||||
nullptr),
|
||||
nullptr, 0);
|
||||
// clang-format on
|
||||
assert(port);
|
||||
if (!port)
|
||||
return std::errc::invalid_argument;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void set_port_buffer(int bytes)
|
||||
@@ -537,31 +539,41 @@ struct pipewire_filter
|
||||
pw.filter_update_params(this->filter, this->port, params, 1);
|
||||
}
|
||||
|
||||
void remove_port()
|
||||
stdx::error remove_port()
|
||||
{
|
||||
assert(this->port);
|
||||
pw.filter_remove_port(this->port);
|
||||
int ret = pw.filter_remove_port(this->port);
|
||||
this->port = nullptr;
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
void rename_port(std::string_view port_name)
|
||||
stdx::error rename_port(std::string_view port_name)
|
||||
{
|
||||
if (this->port)
|
||||
{
|
||||
assert(this->port);
|
||||
spa_dict_item items[1] = {
|
||||
SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name.data()),
|
||||
};
|
||||
|
||||
auto properties = SPA_DICT_INIT(items, 1);
|
||||
pw.filter_update_properties(this->filter, this->port, &properties);
|
||||
this->port = nullptr;
|
||||
int ret = pw.filter_update_properties(this->filter, this->port, &properties);
|
||||
return from_errc(ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::errc::not_connected;
|
||||
}
|
||||
}
|
||||
|
||||
void start_filter()
|
||||
[[nodiscard]] stdx::error start_filter()
|
||||
{
|
||||
if (pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0)
|
||||
if (int ret = pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0); ret < 0)
|
||||
{
|
||||
std::cerr << "can't connect\n";
|
||||
return;
|
||||
return from_errc(ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <libremidi/backends/linux/helpers.hpp>
|
||||
#include <libremidi/backends/linux/pipewire.hpp>
|
||||
#include <libremidi/backends/pipewire/config.hpp>
|
||||
#include <libremidi/backends/pipewire/context.hpp>
|
||||
#include <libremidi/detail/memory.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
@@ -55,10 +56,10 @@ struct pipewire_helpers
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
void create_filter(Self& self)
|
||||
stdx::error create_filter(Self& self)
|
||||
{
|
||||
if (this->filter)
|
||||
return;
|
||||
return stdx::error{};
|
||||
|
||||
auto& configuration = self.configuration;
|
||||
if (configuration.context && configuration.filter && configuration.set_process_func)
|
||||
@@ -90,8 +91,9 @@ struct pipewire_helpers
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
this->filter->create_filter(self.configuration.client_name, filter_events, &self);
|
||||
this->filter->start_filter();
|
||||
return this->filter->start_filter();
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
@@ -117,10 +119,10 @@ struct pipewire_helpers
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
int create_context(Self& self)
|
||||
stdx::error create_context(Self& self)
|
||||
{
|
||||
if (this->global_context)
|
||||
return 0;
|
||||
return stdx::error{};
|
||||
|
||||
// Initialize PipeWire client
|
||||
auto& configuration = self.configuration;
|
||||
@@ -133,7 +135,10 @@ struct pipewire_helpers
|
||||
this->global_instance = std::make_shared<pipewire_instance>();
|
||||
this->global_context = std::make_shared<pipewire_context>(this->global_instance);
|
||||
}
|
||||
return 0;
|
||||
if (!this->global_context->main_loop)
|
||||
return std::errc::connection_refused;
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void destroy_context()
|
||||
@@ -170,7 +175,9 @@ struct pipewire_helpers
|
||||
{
|
||||
int result = pw_loop_iterate(lp, 0);
|
||||
if (result < 0)
|
||||
std::cerr << "pw_loop_iterate: " << spa_strerror(result) << "\n";
|
||||
{
|
||||
LIBREMIDI_LOG(spa_strerror(result));
|
||||
}
|
||||
}
|
||||
fds[0].revents = 0;
|
||||
}
|
||||
@@ -190,7 +197,7 @@ struct pipewire_helpers
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
bool create_local_port(Self& self, std::string_view portName, spa_direction direction)
|
||||
stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction)
|
||||
{
|
||||
assert(this->global_context);
|
||||
assert(this->filter);
|
||||
@@ -200,15 +207,15 @@ struct pipewire_helpers
|
||||
|
||||
if (!this->filter->port)
|
||||
{
|
||||
this->filter->create_local_port(portName.data(), direction);
|
||||
auto ret = this->filter->create_local_port(portName.data(), direction);
|
||||
if (ret != stdx::error{})
|
||||
{
|
||||
self.libremidi_handle_error(self.configuration, "error creating port");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->filter->port)
|
||||
{
|
||||
self.template error<driver_error>(self.configuration, "PipeWire: error creating port");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void add_callbacks(const observer_configuration& conf)
|
||||
@@ -293,12 +300,12 @@ struct pipewire_helpers
|
||||
}
|
||||
}
|
||||
|
||||
void do_close_port()
|
||||
stdx::error do_close_port()
|
||||
{
|
||||
if (!this->filter)
|
||||
return;
|
||||
return stdx::error{};
|
||||
if (!this->filter->port)
|
||||
return;
|
||||
return stdx::error{};
|
||||
|
||||
if (!this->global_context->owns_main_loop)
|
||||
{
|
||||
@@ -307,13 +314,19 @@ struct pipewire_helpers
|
||||
}
|
||||
|
||||
unlink_ports();
|
||||
this->filter->remove_port();
|
||||
return this->filter->remove_port();
|
||||
}
|
||||
|
||||
void rename_port(std::string_view port_name)
|
||||
stdx::error rename_port(std::string_view port_name)
|
||||
{
|
||||
if (this->filter)
|
||||
this->filter->rename_port(port_name);
|
||||
{
|
||||
return this->filter->rename_port(port_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::errc::not_connected;
|
||||
}
|
||||
}
|
||||
|
||||
void unlink_ports()
|
||||
@@ -325,7 +338,7 @@ struct pipewire_helpers
|
||||
}
|
||||
}
|
||||
|
||||
bool link_ports(auto& self, const input_port& in_port)
|
||||
stdx::error link_ports(auto& self, const input_port& in_port)
|
||||
{
|
||||
// Wait for the pipewire server to send us back our node's info
|
||||
for (int i = 0; i < 1000; i++)
|
||||
@@ -335,19 +348,13 @@ struct pipewire_helpers
|
||||
auto& midi = this->global_context->current_graph.software_midi;
|
||||
auto node_it = midi.find(this_node);
|
||||
if (node_it == midi.end())
|
||||
{
|
||||
std::cerr << "Node " << this_node << " not found! \n";
|
||||
return false;
|
||||
}
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
// Wait for the pipewire server to send us back our node's ports
|
||||
this->filter->synchronize_ports(node_it->second);
|
||||
|
||||
if (node_it->second.inputs.empty())
|
||||
{
|
||||
std::cerr << "Node " << this_node << " has no ports! \n";
|
||||
return false;
|
||||
}
|
||||
return std::errc::no_link;
|
||||
|
||||
// Link ports
|
||||
const auto& p = node_it->second.inputs.front();
|
||||
@@ -355,16 +362,16 @@ struct pipewire_helpers
|
||||
pw_loop_iterate(this->global_context->lp, 1);
|
||||
if (!link)
|
||||
{
|
||||
self.template error<invalid_parameter_error>(
|
||||
self.libremidi_handle_error(
|
||||
self.configuration,
|
||||
"PipeWire: could not connect to port: " + in_port.port_name + " -> " + p.port_name);
|
||||
return false;
|
||||
"could not connect to port: " + in_port.port_name + " -> " + p.port_name);
|
||||
return std::errc::no_link;
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool link_ports(auto& self, const output_port& out_port)
|
||||
stdx::error link_ports(auto& self, const output_port& out_port)
|
||||
{
|
||||
// Wait for the pipewire server to send us back our node's info
|
||||
for (int i = 0; i < 1000; i++)
|
||||
@@ -375,8 +382,7 @@ struct pipewire_helpers
|
||||
auto node_it = midi.find(this_node);
|
||||
if (node_it == midi.end())
|
||||
{
|
||||
std::cerr << "Node " << this_node << " not found! \n";
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
// Wait for the pipewire server to send us back our node's ports
|
||||
@@ -384,8 +390,7 @@ struct pipewire_helpers
|
||||
|
||||
if (node_it->second.outputs.empty())
|
||||
{
|
||||
std::cerr << "Node " << this_node << " has no ports! \n";
|
||||
return false;
|
||||
return std::errc::no_link;
|
||||
}
|
||||
|
||||
// Link ports
|
||||
@@ -394,13 +399,13 @@ struct pipewire_helpers
|
||||
pw_loop_iterate(this->global_context->lp, 1);
|
||||
if (!link)
|
||||
{
|
||||
self.template error<invalid_parameter_error>(
|
||||
self.libremidi_handle_error(
|
||||
self.configuration,
|
||||
"PipeWire: could not connect to port: " + p.port_name + " -> " + out_port.port_name);
|
||||
return false;
|
||||
"could not connect to port: " + p.port_name + " -> " + out_port.port_name);
|
||||
return std::errc::no_link;
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
template <spa_direction Direction>
|
||||
@@ -432,7 +437,8 @@ struct pipewire_helpers
|
||||
// Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer)
|
||||
// is an "output" port from the point of view of pipewire as data will come out of it
|
||||
template <spa_direction Direction>
|
||||
static auto get_ports(const pipewire_context& ctx) noexcept -> std::vector<
|
||||
static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept
|
||||
-> std::vector<
|
||||
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
||||
{
|
||||
std::vector<std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
||||
@@ -440,20 +446,23 @@ struct pipewire_helpers
|
||||
|
||||
{
|
||||
std::lock_guard _{ctx.current_graph.mtx};
|
||||
if (conf.track_any || conf.track_hardware)
|
||||
for (auto& node : ctx.current_graph.physical_midi)
|
||||
{
|
||||
for (auto& p :
|
||||
for (auto& port :
|
||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||
{
|
||||
ret.push_back(to_port_info<Direction>(p));
|
||||
ret.push_back(to_port_info<Direction>(port));
|
||||
}
|
||||
}
|
||||
|
||||
if (conf.track_any || conf.track_virtual)
|
||||
for (auto& node : ctx.current_graph.software_midi)
|
||||
{
|
||||
for (auto& p :
|
||||
for (auto& port :
|
||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||
{
|
||||
ret.push_back(to_port_info<Direction>(p));
|
||||
ret.push_back(to_port_info<Direction>(port));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
class midi_in_pipewire final
|
||||
@@ -23,8 +21,17 @@ public:
|
||||
explicit midi_in_pipewire(input_configuration&& conf, pipewire_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
create_context(*this);
|
||||
create_filter(*this);
|
||||
if (auto ret = create_context(*this); ret != stdx::error{})
|
||||
{
|
||||
client_open_ = ret;
|
||||
return;
|
||||
}
|
||||
if (auto ret = create_filter(*this); ret != stdx::error{})
|
||||
{
|
||||
client_open_ = ret;
|
||||
return;
|
||||
}
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_pipewire() override
|
||||
@@ -33,43 +40,42 @@ public:
|
||||
do_close_port();
|
||||
destroy_filter(*this);
|
||||
destroy_context();
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_pipewire: set_client_name unsupported");
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
|
||||
|
||||
bool open_port(const input_port& in_port, std::string_view name) override
|
||||
stdx::error open_port(const input_port& in_port, std::string_view name) override
|
||||
{
|
||||
if (!create_local_port(*this, name, SPA_DIRECTION_INPUT))
|
||||
return false;
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
if (!link_ports(*this, in_port))
|
||||
return false;
|
||||
if (auto err = link_ports(*this, in_port); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
start_thread();
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view name) override
|
||||
stdx::error open_virtual_port(std::string_view name) override
|
||||
{
|
||||
if (!create_local_port(*this, name, SPA_DIRECTION_INPUT))
|
||||
return false;
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
start_thread();
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
stop_thread();
|
||||
do_close_port();
|
||||
return do_close_port();
|
||||
}
|
||||
|
||||
void set_port_name(std::string_view port_name) override { rename_port(port_name); }
|
||||
stdx::error set_port_name(std::string_view port_name) override
|
||||
{
|
||||
return rename_port(port_name);
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
|
||||
|
||||
|
||||
@@ -24,8 +24,17 @@ public:
|
||||
midi_out_pipewire(output_configuration&& conf, pipewire_output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
create_context(*this);
|
||||
create_filter(*this);
|
||||
if (auto ret = create_context(*this); ret != stdx::error{})
|
||||
{
|
||||
client_open_ = ret;
|
||||
return;
|
||||
}
|
||||
if (auto ret = create_filter(*this); ret != stdx::error{})
|
||||
{
|
||||
client_open_ = ret;
|
||||
return;
|
||||
}
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_pipewire() override
|
||||
@@ -34,47 +43,46 @@ public:
|
||||
do_close_port();
|
||||
destroy_filter(*this);
|
||||
destroy_context();
|
||||
}
|
||||
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_pipewire: set_client_name unsupported");
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
|
||||
|
||||
bool open_port(const output_port& out_port, std::string_view name) override
|
||||
stdx::error open_port(const output_port& out_port, std::string_view name) override
|
||||
{
|
||||
if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT))
|
||||
return false;
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||
|
||||
if (!link_ports(*this, out_port))
|
||||
return false;
|
||||
if (auto err = link_ports(*this, out_port); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
start_thread();
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view name) override
|
||||
stdx::error open_virtual_port(std::string_view name) override
|
||||
{
|
||||
if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT))
|
||||
return false;
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||
|
||||
start_thread();
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
stop_thread();
|
||||
do_close_port();
|
||||
return do_close_port();
|
||||
}
|
||||
|
||||
void set_port_name(std::string_view port_name) override { rename_port(port_name); }
|
||||
stdx::error set_port_name(std::string_view port_name) override
|
||||
{
|
||||
return rename_port(port_name);
|
||||
}
|
||||
|
||||
int process(spa_io_position* pos)
|
||||
{
|
||||
@@ -120,6 +128,8 @@ public:
|
||||
if (res == -ENOSPC)
|
||||
break;
|
||||
|
||||
// Recycle the memory
|
||||
m_gcqueue.enqueue(std::move(m));
|
||||
m_queue.pop();
|
||||
}
|
||||
spa_pod_builder_pop(&build, &f);
|
||||
@@ -141,9 +151,14 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
m_queue.enqueue(libremidi::message(midi_bytes{message, message + size}, 0));
|
||||
libremidi::message m;
|
||||
m_gcqueue.try_dequeue(m);
|
||||
m.bytes.assign(message, message + size);
|
||||
m.timestamp = 0;
|
||||
m_queue.enqueue(std::move(m));
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
int convert_timestamp(int64_t user) const noexcept
|
||||
@@ -159,13 +174,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void schedule_message(int64_t ts, const unsigned char* message, size_t size) override
|
||||
stdx::error schedule_message(int64_t ts, const unsigned char* message, size_t size) override
|
||||
{
|
||||
m_queue.enqueue(
|
||||
libremidi::message(midi_bytes{message, message + size}, convert_timestamp(ts)));
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
moodycamel::ReaderWriterQueue<libremidi::message> m_queue;
|
||||
moodycamel::ReaderWriterQueue<libremidi::message> m_gcqueue;
|
||||
std::atomic_int64_t m_process_clock = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,18 +39,29 @@ public:
|
||||
this->add_callbacks(configuration);
|
||||
this->start_thread();
|
||||
}
|
||||
|
||||
if (configuration.notify_in_constructor)
|
||||
{
|
||||
if (configuration.input_added)
|
||||
for (const auto& p : get_input_ports())
|
||||
configuration.input_added(p);
|
||||
|
||||
if (configuration.output_added)
|
||||
for (const auto& p : get_output_ports())
|
||||
configuration.output_added(p);
|
||||
}
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
return get_ports<SPA_DIRECTION_OUTPUT>(*this->global_context);
|
||||
return get_ports<SPA_DIRECTION_OUTPUT>(this->configuration, *this->global_context);
|
||||
}
|
||||
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
return get_ports<SPA_DIRECTION_INPUT>(*this->global_context);
|
||||
return get_ports<SPA_DIRECTION_INPUT>(this->configuration, *this->global_context);
|
||||
}
|
||||
|
||||
~observer_pipewire()
|
||||
|
||||
@@ -5,15 +5,23 @@
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <guiddef.h>
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Devices.Enumeration.h>
|
||||
#include <winrt/Windows.Devices.Midi2.h>
|
||||
|
||||
|
||||
namespace midi2 = winrt::Windows::Devices::Midi2;
|
||||
namespace foundation = winrt::Windows::Foundation;
|
||||
namespace collections = winrt::Windows::Foundation::Collections;
|
||||
|
||||
|
||||
#include <winrt/Microsoft.Devices.Midi2.h>
|
||||
// clang-format on
|
||||
|
||||
namespace libremidi::winmidi
|
||||
@@ -22,6 +30,31 @@ using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Devices::Enumeration;
|
||||
using namespace winrt::Windows::Storage::Streams;
|
||||
using namespace Microsoft::Devices::Midi2;
|
||||
using namespace winrt::Windows::Devices::Midi2;
|
||||
using namespace Windows::Devices::Enumeration;
|
||||
|
||||
inline bool ichar_equals(char a, char b)
|
||||
{
|
||||
return std::tolower(static_cast<unsigned char>(a)) ==
|
||||
std::tolower(static_cast<unsigned char>(b));
|
||||
}
|
||||
inline bool iequals(std::string_view lhs, std::string_view rhs)
|
||||
{
|
||||
return std::ranges::equal(lhs, rhs, ichar_equals);
|
||||
}
|
||||
|
||||
inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation
|
||||
get_port_by_name(const std::string& port) {
|
||||
auto eps = MidiEndpointDeviceInformation::FindAll();
|
||||
for (const auto& ep : eps)
|
||||
{
|
||||
auto str = to_string(ep.Id());
|
||||
if (str.empty())
|
||||
continue;
|
||||
|
||||
if (iequals(str, port))
|
||||
return ep;
|
||||
}
|
||||
return {nullptr};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <libremidi/backends/winmidi/helpers.hpp>
|
||||
#include <libremidi/backends/winmidi/observer.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace libremidi::winmidi
|
||||
{
|
||||
@@ -21,23 +24,15 @@ public:
|
||||
explicit midi_in_impl(
|
||||
libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, m_session{MidiSession::CreateSession(L"libremidi session")}
|
||||
{
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_impl() override { close_port(); }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
~midi_in_impl() override
|
||||
{
|
||||
warning(configuration, "midi_in_winmidi: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmidi: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmidi: set_port_name unsupported");
|
||||
close_port();
|
||||
this->client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override
|
||||
@@ -45,50 +40,58 @@ public:
|
||||
return libremidi::API::WINDOWS_MIDI_SERVICES;
|
||||
}
|
||||
|
||||
bool open_port(const input_port& port, std::string_view) override
|
||||
stdx::error open_port(const input_port& port, std::string_view) override
|
||||
{
|
||||
#if 0
|
||||
const auto id = winrt::to_hstring(port.port_name);
|
||||
if (id.empty())
|
||||
return false;
|
||||
auto ep = get_port_by_name(port.port_name);
|
||||
if (!ep)
|
||||
return std::errc::address_not_available;
|
||||
|
||||
port_ = get(MidiInPort::FromIdAsync(id));
|
||||
if (!port_)
|
||||
return false;
|
||||
port_.MessageReceived(
|
||||
[=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort,
|
||||
const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) {
|
||||
this->process_message(args.Message());
|
||||
m_endpoint = m_session.CreateEndpointConnection(ep.Id());
|
||||
|
||||
m_revoke_token = m_endpoint.MessageReceived(
|
||||
[&](const foundation::IInspectable& sender,
|
||||
const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
|
||||
process_message(args);
|
||||
});
|
||||
|
||||
#endif
|
||||
return true;
|
||||
m_endpoint.Open();
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
#if 0
|
||||
void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)
|
||||
void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg)
|
||||
{
|
||||
auto reader = DataReader::FromBuffer(msg.RawData());
|
||||
auto begin = msg.RawData().data();
|
||||
auto end = begin + msg.RawData().Length();
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = false,
|
||||
.has_samples = false,
|
||||
};
|
||||
|
||||
auto t = msg.Timestamp().count();
|
||||
this->configuration.on_message(libremidi::message{{begin, end}, t});
|
||||
}
|
||||
#endif
|
||||
const auto& ump = msg.GetMessagePacket();
|
||||
const auto& b = ump.GetAllWords();
|
||||
|
||||
void close_port() override
|
||||
{
|
||||
#if 0
|
||||
if (port_)
|
||||
{
|
||||
port_.Close();
|
||||
port_ = nullptr;
|
||||
uint32_t ump_space[64];
|
||||
array_view<uint32_t> ref{ump_space};
|
||||
b.GetMany(0, ref);
|
||||
|
||||
auto to_ns = [t = ump.Timestamp()] { return t; };
|
||||
m_processing.on_bytes(
|
||||
{ump_space, ump_space + b.Size()}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
#endif
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
m_endpoint.MessageReceived(m_revoke_token);
|
||||
m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
virtual timestamp absolute_timestamp() const noexcept override { return {}; }
|
||||
|
||||
private:
|
||||
// winrt::Microsoft::Devices::Midi2::IMidiInPort port_{nullptr};
|
||||
MidiSession m_session;
|
||||
winrt::event_token m_revoke_token{};
|
||||
winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <libremidi/detail/ump_stream.hpp>
|
||||
#include <libremidi/backends/winmidi/config.hpp>
|
||||
#include <libremidi/backends/winmidi/helpers.hpp>
|
||||
#include <libremidi/backends/winmidi/observer.hpp>
|
||||
@@ -20,72 +21,69 @@ public:
|
||||
|
||||
midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, m_session{MidiSession::CreateSession(L"libremidi session")}
|
||||
{
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_impl() override { close_port(); }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmidi: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmidi: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmidi: set_port_name unsupported");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override
|
||||
{
|
||||
return libremidi::API::WINDOWS_MIDI_SERVICES;
|
||||
}
|
||||
|
||||
bool open_port(const output_port& port, std::string_view) override
|
||||
stdx::error open_port(const output_port& port, std::string_view) override
|
||||
{
|
||||
#if 0
|
||||
const auto id = winrt::to_hstring(port.port_name);
|
||||
if (id.empty())
|
||||
return false;
|
||||
auto ep = get_port_by_name(port.port_name);
|
||||
if (!ep)
|
||||
return std::errc::address_not_available;
|
||||
|
||||
port_ = get(MidiOutPort::FromIdAsync(id));
|
||||
return bool(port_);
|
||||
#endif
|
||||
return true;
|
||||
m_endpoint = m_session.CreateEndpointConnection(ep.Id());
|
||||
m_endpoint.Open();
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
#if 0
|
||||
if (port_)
|
||||
{
|
||||
port_.Close();
|
||||
port_ = {};
|
||||
}
|
||||
#endif
|
||||
m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_ump(const uint32_t* message, size_t size) override
|
||||
stdx::error send_ump(const uint32_t* message, size_t size) override
|
||||
{
|
||||
#if 0
|
||||
if (!port_)
|
||||
return;
|
||||
auto write_func = [this](const uint32_t* ump, int64_t bytes) -> std::errc {
|
||||
MidiSendMessageResults ret{};
|
||||
switch(bytes / 4)
|
||||
{
|
||||
case 1:
|
||||
ret = m_endpoint.SendSingleMessagePacket(MidiMessage32(0, ump[0]));
|
||||
break;
|
||||
case 2:
|
||||
ret = m_endpoint.SendSingleMessagePacket(MidiMessage64(0, ump[0], ump[1]));
|
||||
break;
|
||||
case 3:
|
||||
ret = m_endpoint.SendSingleMessagePacket(MidiMessage96(0, ump[0], ump[1], ump[2]));
|
||||
break;
|
||||
case 4:
|
||||
ret = m_endpoint.SendSingleMessagePacket(MidiMessage128(0, ump[0], ump[1], ump[2], ump[3]));
|
||||
break;
|
||||
default:
|
||||
return std::errc::bad_message;
|
||||
}
|
||||
|
||||
InMemoryRandomAccessStream str;
|
||||
DataWriter rb(str);
|
||||
rb.WriteBytes(
|
||||
winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size});
|
||||
port_.SendBuffer(rb.DetachBuffer());
|
||||
#endif
|
||||
if(ret != MidiSendMessageResults::Succeeded)
|
||||
return std::errc::bad_message;
|
||||
return std::errc{0};
|
||||
};
|
||||
|
||||
return segment_ump_stream(message, size, write_func, []() {});
|
||||
}
|
||||
|
||||
private:
|
||||
#if 0
|
||||
winrt::Windows::Devices::Midi2::IMidiOutPort port_{nullptr};
|
||||
#endif
|
||||
MidiSession m_session;
|
||||
winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -20,13 +20,11 @@ public:
|
||||
{
|
||||
} configuration;
|
||||
|
||||
MidiSession session;
|
||||
|
||||
explicit observer_impl(
|
||||
libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, session{
|
||||
MidiSession::CreateNewSession(L"libremidi session", MidiSessionSettings::Default())}
|
||||
, session{MidiSession::CreateSession(L"libremidi session")}
|
||||
{
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
@@ -36,6 +34,7 @@ public:
|
||||
if (configuration.input_added)
|
||||
for (const auto& p : get_input_ports())
|
||||
configuration.input_added(p);
|
||||
|
||||
if (configuration.output_added)
|
||||
for (const auto& p : get_output_ports())
|
||||
configuration.output_added(p);
|
||||
@@ -87,10 +86,14 @@ public:
|
||||
|
||||
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
|
||||
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
|
||||
for (const auto& selectedEndpointInformation : endpointDevices)
|
||||
for (const auto& ep : endpointDevices)
|
||||
{
|
||||
if(ep.Name().starts_with(L"Diagnostics")) {
|
||||
continue;
|
||||
}
|
||||
// FIXME if(has input...)
|
||||
ret.emplace_back(to_port_info(selectedEndpointInformation));
|
||||
|
||||
ret.emplace_back(to_port_info<true>(ep));
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -102,10 +105,13 @@ public:
|
||||
|
||||
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
|
||||
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
|
||||
for (const auto& selectedEndpointInformation : endpointDevices)
|
||||
for (const auto& ep : endpointDevices)
|
||||
{
|
||||
if(ep.Name().starts_with(L"Diagnostics")) {
|
||||
continue;
|
||||
}
|
||||
// FIXME if(has output...)
|
||||
ret.emplace_back(to_port_info(selectedEndpointInformation));
|
||||
ret.emplace_back(to_port_info<false>(ep));
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -114,38 +120,29 @@ public:
|
||||
void on_input_added(const DeviceInformation& name)
|
||||
{
|
||||
if (configuration.input_added)
|
||||
configuration.input_added(to_port_info(name));
|
||||
configuration.input_added(to_port_info<true>(name));
|
||||
}
|
||||
|
||||
void on_input_removed(const DeviceInformation& name)
|
||||
{
|
||||
if (configuration.input_removed)
|
||||
configuration.input_removed(to_port_info(name));
|
||||
configuration.input_removed(to_port_info<true>(name));
|
||||
}
|
||||
|
||||
void on_output_added(const DeviceInformation& name)
|
||||
{
|
||||
if (configuration.output_added)
|
||||
configuration.output_added(to_port_info(name));
|
||||
configuration.output_added(to_port_info<false>(name));
|
||||
}
|
||||
|
||||
void on_output_removed(const DeviceInformation& name)
|
||||
{
|
||||
if (configuration.output_removed)
|
||||
configuration.output_removed(to_port_info(name));
|
||||
configuration.output_removed(to_port_info<false>(name));
|
||||
}
|
||||
|
||||
private:
|
||||
#if 0
|
||||
static inline observer_winmidi_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};
|
||||
static inline observer_winmidi_internal internalOutPortObserver_{
|
||||
MidiOutPort::GetDeviceSelector()};
|
||||
|
||||
int evTokenOnInputAdded_{-1};
|
||||
int evTokenOnInputRemoved_{-1};
|
||||
int evTokenOnOutputAdded_{-1};
|
||||
int evTokenOnOutputRemoved_{-1};
|
||||
#endif
|
||||
MidiSession session;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
// clang-format off
|
||||
#define NOMINMAX 1
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
// clang-format on
|
||||
|
||||
#include <libremidi/error.hpp>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
struct winmm_error_domain : public stdx::error_domain
|
||||
{
|
||||
public:
|
||||
constexpr winmm_error_domain() noexcept
|
||||
: error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}
|
||||
{
|
||||
}
|
||||
|
||||
stdx::string_ref name() const noexcept override { return "winmm"; }
|
||||
|
||||
bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override
|
||||
{
|
||||
if (lhs.domain() == rhs.domain())
|
||||
return error_cast<int>(lhs) == error_cast<int>(rhs);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
stdx::string_ref message(const stdx::error& e) const noexcept override
|
||||
{
|
||||
switch (error_cast<int>(e))
|
||||
{
|
||||
case MMSYSERR_NOERROR: return "No error";
|
||||
case MMSYSERR_ERROR: return "Error";
|
||||
case MMSYSERR_BADDEVICEID: return "Bad device ID";
|
||||
case MMSYSERR_NOTENABLED: return "Not enabled";
|
||||
case MMSYSERR_ALLOCATED: return "Allocated";
|
||||
case MMSYSERR_INVALHANDLE: return "Invalid handle";
|
||||
case MMSYSERR_NODRIVER: return "No driver";
|
||||
case MMSYSERR_NOMEM: return "No memory";
|
||||
case MMSYSERR_NOTSUPPORTED: return "Not supported";
|
||||
case MMSYSERR_BADERRNUM: return "Bad errnum";
|
||||
case MMSYSERR_INVALFLAG: return "Invalid flag";
|
||||
case MMSYSERR_INVALPARAM: return "Invalid parameter";
|
||||
case MMSYSERR_HANDLEBUSY: return "Handle busy";
|
||||
case MMSYSERR_INVALIDALIAS: return "Invalid alias";
|
||||
case MMSYSERR_BADDB: return "Bad database";
|
||||
case MMSYSERR_KEYNOTFOUND: return "Key not found";
|
||||
case MMSYSERR_READERROR: return "Read error";
|
||||
case MMSYSERR_WRITEERROR: return "Write error";
|
||||
case MMSYSERR_DELETEERROR: return "Delete error";
|
||||
case MMSYSERR_VALNOTFOUND: return "Value not found";
|
||||
case MMSYSERR_NODRIVERCB: return "No driver callback";
|
||||
case MMSYSERR_MOREDATA: return "More data";
|
||||
}
|
||||
return "Unknown error code";
|
||||
}
|
||||
};
|
||||
|
||||
inline stdx::error from_mmerr(int ret) noexcept
|
||||
{
|
||||
static constexpr winmm_error_domain domain;
|
||||
return {ret, domain};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,8 @@
|
||||
#pragma once
|
||||
#define NOMINMAX 1
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#include <libremidi/backends/winmm/error_domain.hpp>
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// clang-format off
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
// clang-format on
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
@@ -44,10 +37,10 @@ inline std::string ConvertToUTF8(const TCHAR* str)
|
||||
// Next functions add the portNumber to the name so that
|
||||
// the device's names are sure to be listed with individual names
|
||||
// even when they have the same brand name
|
||||
inline void MakeUniqueInPortName(std::string& deviceName, unsigned int portNumber)
|
||||
inline void MakeUniqueInPortName(std::string& deviceName, std::size_t portNumber)
|
||||
{
|
||||
int x = 1;
|
||||
for (unsigned int i = 0; i < portNumber; i++)
|
||||
for (std::size_t i = 0; i < portNumber; i++)
|
||||
{
|
||||
MIDIINCAPS deviceCaps;
|
||||
midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS));
|
||||
@@ -61,10 +54,10 @@ inline void MakeUniqueInPortName(std::string& deviceName, unsigned int portNumbe
|
||||
deviceName += std::to_string(x);
|
||||
}
|
||||
|
||||
inline void MakeUniqueOutPortName(std::string& deviceName, unsigned int portNumber)
|
||||
inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumber)
|
||||
{
|
||||
int x = 1;
|
||||
for (unsigned int i = 0; i < portNumber; i++)
|
||||
for (std::size_t i = 0; i < portNumber; i++)
|
||||
{
|
||||
MIDIOUTCAPS deviceCaps;
|
||||
midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS));
|
||||
|
||||
@@ -22,20 +22,17 @@ public:
|
||||
explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
// We'll issue a warning here if no devices are available but not
|
||||
// throw an error since the user can plugin something later.
|
||||
if (midiInGetNumDevs() == 0)
|
||||
{
|
||||
warning(
|
||||
configuration, "midi_in_winmm::initialize: no MIDI input devices currently available.");
|
||||
}
|
||||
|
||||
if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400))
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_error(
|
||||
configuration,
|
||||
"midi_in_winmm::initialize: InitializeCriticalSectionAndSpinCount failed.");
|
||||
"InitializeCriticalSectionAndSpinCount failed.");
|
||||
|
||||
this->client_open_ = std::errc::too_many_files_open;
|
||||
return;
|
||||
}
|
||||
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_winmm() override
|
||||
@@ -43,35 +40,22 @@ public:
|
||||
// Close a connection if it exists.
|
||||
midi_in_winmm::close_port();
|
||||
|
||||
if(this->client_open_ == stdx::error{})
|
||||
DeleteCriticalSection(&(this->_mutex));
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmm: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmm: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmm: set_port_name unsupported");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
|
||||
|
||||
bool do_open(unsigned int portNumber)
|
||||
stdx::error do_open(std::size_t portNumber)
|
||||
{
|
||||
MMRESULT result = midiInOpen(
|
||||
&this->inHandle, portNumber, std::bit_cast<DWORD_PTR>(&midiInputCallback),
|
||||
std::bit_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_in_winmm::open_port: error creating Windows MM MIDI input port.");
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
configuration, "error creating Windows MM MIDI input port.");
|
||||
return from_mmerr(result);
|
||||
}
|
||||
|
||||
// Allocate and init the sysex buffers.
|
||||
@@ -90,11 +74,11 @@ public:
|
||||
{
|
||||
midiInClose(this->inHandle);
|
||||
this->inHandle = nullptr;
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
configuration,
|
||||
"midi_in_winmm::open_port: error starting Windows MM MIDI input port "
|
||||
"error starting Windows MM MIDI input port "
|
||||
"(PrepareHeader).");
|
||||
return false;
|
||||
return from_mmerr(result);
|
||||
}
|
||||
|
||||
// Register the buffer.
|
||||
@@ -103,11 +87,11 @@ public:
|
||||
{
|
||||
midiInClose(this->inHandle);
|
||||
this->inHandle = nullptr;
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
configuration,
|
||||
"midi_in_winmm::open_port: error starting Windows MM MIDI input port "
|
||||
"error starting Windows MM MIDI input port "
|
||||
"(AddBuffer).");
|
||||
return false;
|
||||
return from_mmerr(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,15 +101,15 @@ public:
|
||||
{
|
||||
midiInClose(this->inHandle);
|
||||
this->inHandle = nullptr;
|
||||
error<driver_error>(
|
||||
configuration, "midi_in_winmm::open_port: error starting Windows MM MIDI input port.");
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
configuration, "error starting Windows MM MIDI input port.");
|
||||
return from_mmerr(result);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_port(const input_port& p, std::string_view) override
|
||||
stdx::error open_port(const input_port& p, std::string_view) override
|
||||
{
|
||||
observer_winmm obs{{}, winmm_observer_configuration{}};
|
||||
auto ports = obs.get_input_ports();
|
||||
@@ -142,12 +126,12 @@ public:
|
||||
if (p.port_name == port.port_name)
|
||||
return do_open(port.port);
|
||||
}
|
||||
error<invalid_parameter_error>(
|
||||
configuration, "midi_in_winmm::open_port: port not found: " + p.port_name);
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
configuration, "port not found: " + p.port_name);
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (connected_)
|
||||
{
|
||||
@@ -170,9 +154,9 @@ public:
|
||||
|
||||
if (res != MMSYSERR_NOERROR)
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_warning(
|
||||
configuration,
|
||||
"midi_in_winmm::open_port: error closing Windows MM MIDI input "
|
||||
"error closing Windows MM MIDI input "
|
||||
"port (midiInUnprepareHeader).");
|
||||
continue;
|
||||
}
|
||||
@@ -187,6 +171,7 @@ public:
|
||||
this->inHandle = nullptr;
|
||||
LeaveCriticalSection(&(this->_mutex));
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -255,8 +240,7 @@ private:
|
||||
const auto* sysex = reinterpret_cast<MIDIHDR*>(midiMessage);
|
||||
if(inputStatus == MIM_LONGERROR)
|
||||
{
|
||||
self.m_processing.message.bytes.clear();
|
||||
self.m_processing.state = self.m_processing.main;
|
||||
self.m_processing.reset();
|
||||
}
|
||||
else if (!self.configuration.ignore_sysex)
|
||||
{
|
||||
@@ -286,10 +270,9 @@ private:
|
||||
LeaveCriticalSection(&(self._mutex));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "\nmidi_in::midiInputCallback: error sending sysex to "
|
||||
"Midi device!!\n\n";
|
||||
#endif
|
||||
LIBREMIDI_LOG(
|
||||
"error sending sysex to "
|
||||
"Midi device!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/winmm/config.hpp>
|
||||
#include <libremidi/backends/winmm/helpers.hpp>
|
||||
#include <libremidi/backends/winmm/observer.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
namespace libremidi
|
||||
@@ -20,55 +21,34 @@ public:
|
||||
midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
// We'll issue a warning here if no devices are available but not
|
||||
// throw an error since the user can plug something in later.
|
||||
if (midiOutGetNumDevs() == 0)
|
||||
{
|
||||
warning(
|
||||
configuration,
|
||||
"midi_out_winmm::initialize: no MIDI output devices currently "
|
||||
"available.");
|
||||
}
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_winmm() override
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
midi_out_winmm::close_port();
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmm: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmm: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmm: set_port_name unsupported");
|
||||
this->client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
|
||||
|
||||
[[nodiscard]] bool do_open(unsigned int portNumber)
|
||||
[[nodiscard]] stdx::error do_open(unsigned int portNumber)
|
||||
{
|
||||
MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL);
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
libremidi_handle_error(
|
||||
configuration,
|
||||
"midi_out_winmm::open_port: error creating Windows MM MIDI output "
|
||||
"error creating Windows MM MIDI output "
|
||||
"port.");
|
||||
return false;
|
||||
return from_mmerr(result);
|
||||
}
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool open_port(const output_port& p, std::string_view) override
|
||||
stdx::error open_port(const output_port& p, std::string_view) override
|
||||
{
|
||||
observer_winmm obs{{}, winmm_observer_configuration{}};
|
||||
auto ports = obs.get_output_ports();
|
||||
@@ -85,29 +65,30 @@ public:
|
||||
if (p.port_name == port.port_name)
|
||||
return do_open(port.port);
|
||||
}
|
||||
error<invalid_parameter_error>(
|
||||
configuration, "midi_out_winmm::open_port: port not found: " + p.port_name);
|
||||
return false;
|
||||
libremidi_handle_error(
|
||||
configuration, "port not found: " + p.port_name);
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (this->outHandle)
|
||||
midiOutClose(this->outHandle);
|
||||
|
||||
this->outHandle = nullptr;
|
||||
connected_ = false;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
if (!connected_)
|
||||
return;
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
warning(configuration, "midi_out_winmm::send_message: message argument is empty!");
|
||||
return;
|
||||
libremidi_handle_warning(configuration, "message argument is empty!");
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
if (message[0] == 0xF0)
|
||||
@@ -126,18 +107,18 @@ public:
|
||||
auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_winmm::send_message: error preparing sysex header.");
|
||||
return;
|
||||
libremidi_handle_error(
|
||||
configuration, "error preparing sysex header.");
|
||||
return from_mmerr(result);
|
||||
}
|
||||
|
||||
// Send the message.
|
||||
result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_winmm::send_message: error sending sysex message.");
|
||||
return;
|
||||
libremidi_handle_error(
|
||||
configuration, "error sending sysex message.");
|
||||
return from_mmerr(result);
|
||||
}
|
||||
|
||||
// Unprepare the buffer and MIDIHDR.
|
||||
@@ -152,11 +133,11 @@ public:
|
||||
// Make sure the message size isn't too big.
|
||||
if (size > 3)
|
||||
{
|
||||
warning(
|
||||
libremidi_handle_warning(
|
||||
configuration,
|
||||
"midi_out_winmm::send_message: message size is greater than 3 bytes "
|
||||
"message size is greater than 3 bytes "
|
||||
"(and not sysex)!");
|
||||
return;
|
||||
return std::errc::message_size;
|
||||
}
|
||||
|
||||
// Pack MIDI bytes into double word.
|
||||
@@ -167,10 +148,12 @@ public:
|
||||
auto result = midiOutShortMsg(this->outHandle, packet);
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_winmm::send_message: error sending MIDI message.");
|
||||
libremidi_handle_error(
|
||||
configuration, "error sending MIDI message.");
|
||||
return from_mmerr(result);
|
||||
}
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -21,38 +21,28 @@ public:
|
||||
|
||||
explicit midi_in_winuwp(input_configuration&& conf, winuwp_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
|
||||
{
|
||||
winrt_init();
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_winuwp() override { close_port(); }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
~midi_in_winuwp() override
|
||||
{
|
||||
warning(configuration, "midi_in_winuwp: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winuwp: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winuwp: set_port_name unsupported");
|
||||
close_port();
|
||||
this->client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
|
||||
|
||||
bool open_port(const input_port& port, std::string_view) override
|
||||
stdx::error open_port(const input_port& port, std::string_view) override
|
||||
{
|
||||
const auto id = winrt::to_hstring(port.port_name);
|
||||
if (id.empty())
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
port_ = get(MidiInPort::FromIdAsync(id));
|
||||
if (!port_)
|
||||
return false;
|
||||
return std::errc::io_error;
|
||||
|
||||
midi_start_timestamp = std::chrono::steady_clock::now();
|
||||
|
||||
@@ -62,7 +52,7 @@ public:
|
||||
this->process_message(args.Message());
|
||||
});
|
||||
|
||||
return true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)
|
||||
@@ -81,13 +71,14 @@ public:
|
||||
m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (port_)
|
||||
{
|
||||
port_.Close();
|
||||
port_ = nullptr;
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
|
||||
@@ -22,55 +22,52 @@ public:
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
winrt_init();
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out_winuwp() override { close_port(); }
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
~midi_out_winuwp() override
|
||||
{
|
||||
warning(configuration, "midi_out_winuwp: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winuwp: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winuwp: set_port_name unsupported");
|
||||
close_port();
|
||||
this->client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
|
||||
|
||||
bool open_port(const output_port& port, std::string_view) override
|
||||
stdx::error open_port(const output_port& port, std::string_view) override
|
||||
{
|
||||
const auto id = winrt::to_hstring(port.port_name);
|
||||
if (id.empty())
|
||||
return false;
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
port_ = get(MidiOutPort::FromIdAsync(id));
|
||||
return bool(port_);
|
||||
if (!bool(port_))
|
||||
return std::errc::io_error;
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (port_)
|
||||
{
|
||||
port_.Close();
|
||||
port_ = {};
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
if (!port_)
|
||||
return;
|
||||
return std::errc::not_connected;
|
||||
|
||||
InMemoryRandomAccessStream str;
|
||||
DataWriter rb(str);
|
||||
rb.WriteBytes(
|
||||
winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size});
|
||||
port_.SendBuffer(rb.DetachBuffer());
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -31,7 +31,7 @@ struct client_configuration
|
||||
best to set the error callback function before opening a port.
|
||||
*/
|
||||
midi_error_callback on_error{};
|
||||
midi_error_callback on_warning{};
|
||||
midi_warning_callback on_warning{};
|
||||
|
||||
//! Poll period for observation polling operations, if relevant to the backend
|
||||
std::chrono::milliseconds poll_period{100};
|
||||
@@ -115,9 +115,9 @@ public:
|
||||
port,
|
||||
input_configuration{
|
||||
.on_message
|
||||
= [this, port](libremidi::message&& m) {
|
||||
configuration.on_message(port, std::move(m));
|
||||
},
|
||||
= [this,
|
||||
port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); },
|
||||
.on_raw_data = {},
|
||||
.get_timestamp = {},
|
||||
|
||||
.on_error = configuration.on_error,
|
||||
@@ -153,32 +153,40 @@ public:
|
||||
void remove_input(const input_port& port) { m_inputs.erase(port); }
|
||||
void remove_output(const output_port& port) { m_outputs.erase(port); }
|
||||
|
||||
void send_message(const unsigned char* message, size_t size)
|
||||
stdx::error send_message(const unsigned char* message, size_t size)
|
||||
{
|
||||
for (auto& [_, out] : m_outputs)
|
||||
{
|
||||
out.send_message(message, size);
|
||||
if (auto err = out.send_message(message, size); err != stdx::error{})
|
||||
return err;
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_ump(const uint32_t* message, size_t size)
|
||||
stdx::error send_ump(const uint32_t* message, size_t size)
|
||||
{
|
||||
for (auto& [_, out] : m_outputs)
|
||||
{
|
||||
out.send_ump(message, size);
|
||||
if (auto err = out.send_ump(message, size); err != stdx::error{})
|
||||
return err;
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_message(const output_port& port, const unsigned char* message, size_t size)
|
||||
stdx::error send_message(const output_port& port, const unsigned char* message, size_t size)
|
||||
{
|
||||
if (auto it = m_outputs.find(port); it != m_outputs.end())
|
||||
it->second.send_message(message, size);
|
||||
return it->second.send_message(message, size);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void send_ump(const output_port& port, const uint32_t* message, size_t size)
|
||||
stdx::error send_ump(const output_port& port, const uint32_t* message, size_t size)
|
||||
{
|
||||
if (auto it = m_outputs.find(port); it != m_outputs.end())
|
||||
it->second.send_ump(message, size);
|
||||
return it->second.send_ump(message, size);
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -2559,6 +2559,7 @@ enum cmidi2_midi_conversion_result
|
||||
CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE = 0x11,
|
||||
CMIDI2_CONVERSION_RESULT_INVALID_STATUS = 0x13,
|
||||
CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7 = 0x20,
|
||||
CMIDI2_CONVERSION_RESULT_INVALID_INPUT = 0x40,
|
||||
};
|
||||
|
||||
static inline void
|
||||
@@ -2686,7 +2687,11 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context)
|
||||
else
|
||||
{
|
||||
// fixed sized message
|
||||
size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, sLen - *sIdx);
|
||||
size_t remaining = sLen - *sIdx;
|
||||
size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, remaining);
|
||||
if (len > remaining)
|
||||
return CMIDI2_CONVERSION_RESULT_INVALID_INPUT;
|
||||
|
||||
uint8_t byte2 = context->midi1[*sIdx + 1];
|
||||
uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0;
|
||||
uint8_t channel = context->midi1[*sIdx] & 0xF;
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
#define NOMINMAX 1
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#if defined(LIBREMIDI_EXPORTS)
|
||||
#if defined(_MSC_VER)
|
||||
@@ -51,6 +46,7 @@ using midi_bytes = boost::container::small_vector<unsigned char, small_vector_mi
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
#include <vector>
|
||||
namespace libremidi
|
||||
{
|
||||
using midi_bytes = std::vector<unsigned char>;
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <libremidi/backends/alsa_raw/config.hpp>
|
||||
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
||||
#include <libremidi/backends/alsa_seq/config.hpp>
|
||||
#include <libremidi/backends/alsa_seq_ump/config.hpp>
|
||||
#include <libremidi/backends/pipewire/config.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <libremidi/backends/coremidi/config.hpp>
|
||||
#include <libremidi/backends/coremidi_ump/config.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <libremidi/backends/winmidi/config.hpp>
|
||||
#include <libremidi/backends/winmm/config.hpp>
|
||||
#include <libremidi/backends/winuwp/config.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#include <libremidi/backends/emscripten/config.hpp>
|
||||
#endif
|
||||
|
||||
#include <libremidi/backends/alsa_raw/config.hpp>
|
||||
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
||||
#include <libremidi/backends/alsa_seq/config.hpp>
|
||||
#include <libremidi/backends/alsa_seq_ump/config.hpp>
|
||||
#include <libremidi/backends/coremidi/config.hpp>
|
||||
#include <libremidi/backends/coremidi_ump/config.hpp>
|
||||
#include <libremidi/backends/emscripten/config.hpp>
|
||||
#include <libremidi/backends/jack/config.hpp>
|
||||
#include <libremidi/backends/keyboard/config.hpp>
|
||||
#include <libremidi/backends/net/config.hpp>
|
||||
#include <libremidi/backends/pipewire/config.hpp>
|
||||
#include <libremidi/backends/winmidi/config.hpp>
|
||||
#include <libremidi/backends/winmm/config.hpp>
|
||||
#include <libremidi/backends/winuwp/config.hpp>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -31,5 +21,4 @@ namespace libremidi
|
||||
struct dummy_configuration
|
||||
{
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
// clang-format off
|
||||
#include <libremidi/config.hpp>
|
||||
// clang-format on
|
||||
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <libremidi/error.hpp>
|
||||
#include <libremidi/message.hpp>
|
||||
#include <libremidi/ump.hpp>
|
||||
namespace libremidi
|
||||
{
|
||||
struct midi1_to_midi2
|
||||
{
|
||||
stdx::error
|
||||
convert(const unsigned char* message, std::size_t size, int64_t timestamp, auto on_ump)
|
||||
{
|
||||
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 std::errc::invalid_argument;
|
||||
|
||||
return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp);
|
||||
}
|
||||
|
||||
cmidi2_midi_conversion_context context = [] {
|
||||
cmidi2_midi_conversion_context tmp;
|
||||
cmidi2_midi_conversion_context_initialize(&tmp);
|
||||
return tmp;
|
||||
}();
|
||||
uint32_t ump[65536 / 4];
|
||||
};
|
||||
|
||||
struct midi2_to_midi1
|
||||
{
|
||||
stdx::error
|
||||
convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi)
|
||||
{
|
||||
auto n
|
||||
= cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));
|
||||
if (n > 0)
|
||||
return on_midi(midi, n, timestamp);
|
||||
else
|
||||
return std::errc::no_buffer_space;
|
||||
}
|
||||
|
||||
cmidi2_midi_conversion_context context = [] {
|
||||
cmidi2_midi_conversion_context tmp;
|
||||
cmidi2_midi_conversion_context_initialize(&tmp);
|
||||
return tmp;
|
||||
}();
|
||||
uint8_t midi[65536];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -2,61 +2,11 @@
|
||||
#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:
|
||||
@@ -69,18 +19,29 @@ public:
|
||||
|
||||
[[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual bool open_virtual_port(std::string_view) = 0;
|
||||
[[nodiscard]] virtual stdx::error open_virtual_port(std::string_view)
|
||||
{
|
||||
return std::errc::function_not_supported;
|
||||
}
|
||||
virtual stdx::error set_client_name(std::string_view)
|
||||
{
|
||||
return std::errc::function_not_supported;
|
||||
}
|
||||
virtual stdx::error set_port_name(std::string_view)
|
||||
{
|
||||
return std::errc::function_not_supported;
|
||||
}
|
||||
|
||||
virtual void close_port() = 0;
|
||||
virtual void set_client_name(std::string_view) = 0;
|
||||
virtual void set_port_name(std::string_view) = 0;
|
||||
virtual stdx::error close_port() = 0;
|
||||
|
||||
bool is_port_open() const noexcept { return bool(port_open_); }
|
||||
bool is_port_connected() const noexcept { return bool(connected_); }
|
||||
stdx::error is_client_open() const noexcept { return client_open_; }
|
||||
bool is_port_open() const noexcept { return port_open_; }
|
||||
bool is_port_connected() const noexcept { return connected_; }
|
||||
|
||||
protected:
|
||||
friend class midi_in;
|
||||
friend class midi_out;
|
||||
stdx::error client_open_{std::errc::not_connected};
|
||||
bool port_open_{};
|
||||
bool connected_{};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
#include <libremidi/observer_configuration.hpp>
|
||||
#include <libremidi/input_configuration.hpp>
|
||||
#include <libremidi/error_handler.hpp>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -14,7 +16,9 @@ public:
|
||||
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 stdx::error
|
||||
open_port(const input_port& pt, std::string_view local_port_name)
|
||||
= 0;
|
||||
[[nodiscard]] virtual timestamp absolute_timestamp() const noexcept = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <libremidi/detail/conversion.hpp>
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
#include <libremidi/error_handler.hpp>
|
||||
#include <libremidi/output_configuration.hpp>
|
||||
|
||||
#include <string_view>
|
||||
@@ -18,19 +19,21 @@ public:
|
||||
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)
|
||||
[[nodiscard]] virtual stdx::error
|
||||
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)
|
||||
virtual stdx::error send_message(const unsigned char* message, std::size_t size) = 0;
|
||||
virtual stdx::error
|
||||
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)
|
||||
virtual stdx::error send_ump(const uint32_t* message, std::size_t size) = 0;
|
||||
virtual stdx::error schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t size)
|
||||
{
|
||||
return send_ump(ump, size);
|
||||
}
|
||||
@@ -45,14 +48,15 @@ class out_api : public midi_out_api
|
||||
public:
|
||||
using midi_out_api::midi_out_api;
|
||||
|
||||
void send_ump(const uint32_t* message, std::size_t /*size*/)
|
||||
stdx::error 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);
|
||||
return converter.convert(
|
||||
message, size, 0, [this](const unsigned char* midi, std::size_t n, int64_t /* ts */) {
|
||||
return send_message(midi, n);
|
||||
});
|
||||
}
|
||||
|
||||
midi2_to_midi1 converter;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,25 +69,15 @@ class out_api : public midi_out_api
|
||||
public:
|
||||
using midi_out_api::midi_out_api;
|
||||
|
||||
void send_message(const unsigned char* message, std::size_t size)
|
||||
stdx::error 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);
|
||||
return converter.convert(
|
||||
message, size, 0, [this](const uint32_t* ump, std::size_t count, int64_t /* ts */) {
|
||||
return send_ump(ump, count);
|
||||
});
|
||||
}
|
||||
|
||||
midi1_to_midi2 converter;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cinttypes>
|
||||
@@ -28,16 +29,87 @@ struct timestamp_backend_info
|
||||
bool has_samples{};
|
||||
};
|
||||
|
||||
namespace midi1
|
||||
template<typename Configuration>
|
||||
struct input_state_machine_base
|
||||
{
|
||||
struct input_state_machine
|
||||
{
|
||||
const input_configuration& configuration;
|
||||
explicit input_state_machine(const input_configuration& conf)
|
||||
const Configuration& configuration;
|
||||
|
||||
explicit input_state_machine_base(const Configuration& conf)
|
||||
: configuration{conf}
|
||||
{
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
int64_t last_time_ns = 0;
|
||||
bool first_message = true;
|
||||
};
|
||||
|
||||
namespace midi1
|
||||
{
|
||||
struct input_state_machine : input_state_machine_base<input_configuration>
|
||||
{
|
||||
using input_state_machine_base::input_state_machine_base;
|
||||
|
||||
void reset()
|
||||
{
|
||||
message.bytes.clear();
|
||||
message.timestamp = {};
|
||||
state = main;
|
||||
}
|
||||
|
||||
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
|
||||
{
|
||||
return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));
|
||||
@@ -46,6 +118,26 @@ struct input_state_machine
|
||||
// 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)
|
||||
{
|
||||
if (this->configuration.on_message)
|
||||
on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);
|
||||
if (this->configuration.on_raw_data)
|
||||
this->configuration.on_raw_data(bytes, timestamp);
|
||||
}
|
||||
|
||||
// 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 (this->configuration.on_message)
|
||||
on_bytes_segmented(this->configuration.on_message, bytes, timestamp);
|
||||
if (this->configuration.on_raw_data)
|
||||
this->configuration.on_raw_data(bytes, timestamp);
|
||||
}
|
||||
|
||||
private:
|
||||
void on_bytes_multi_segmented(
|
||||
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
|
||||
{
|
||||
int64_t nBytes = bytes.size();
|
||||
int64_t iByte = 0;
|
||||
@@ -54,7 +146,7 @@ struct input_state_machine
|
||||
switch (state)
|
||||
{
|
||||
case in_sysex: {
|
||||
return on_continue_sysex(bytes, finished_sysex);
|
||||
return on_continue_sysex(cb, bytes, finished_sysex);
|
||||
}
|
||||
case main: {
|
||||
while (iByte < nBytes)
|
||||
@@ -150,7 +242,7 @@ struct input_state_machine
|
||||
message.assign(begin, begin + size);
|
||||
message.timestamp = timestamp;
|
||||
|
||||
this->configuration.on_message(std::move(message));
|
||||
cb(std::move(message));
|
||||
message.clear();
|
||||
|
||||
iByte += size;
|
||||
@@ -160,7 +252,8 @@ struct input_state_machine
|
||||
}
|
||||
}
|
||||
|
||||
void on_continue_sysex(std::span<const uint8_t> bytes, bool finished_sysex)
|
||||
void on_continue_sysex(
|
||||
const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)
|
||||
{
|
||||
if (finished_sysex)
|
||||
state = main;
|
||||
@@ -174,14 +267,16 @@ struct input_state_machine
|
||||
message.insert(message.end(), bytes.begin(), bytes.end());
|
||||
if (finished_sysex)
|
||||
{
|
||||
this->configuration.on_message(std::move(message));
|
||||
cb(std::move(message));
|
||||
message.clear();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void on_main(std::span<const uint8_t> bytes, int64_t timestamp, bool finished_sysex)
|
||||
void on_main(
|
||||
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp,
|
||||
bool finished_sysex)
|
||||
{
|
||||
switch (bytes[0])
|
||||
{
|
||||
@@ -196,7 +291,7 @@ struct input_state_machine
|
||||
message.timestamp = timestamp;
|
||||
if (finished_sysex)
|
||||
{
|
||||
this->configuration.on_message(std::move(message));
|
||||
cb(std::move(message));
|
||||
message.clear();
|
||||
}
|
||||
}
|
||||
@@ -222,13 +317,12 @@ struct input_state_machine
|
||||
message.assign(bytes.begin(), bytes.end());
|
||||
message.timestamp = timestamp;
|
||||
|
||||
this->configuration.on_message(std::move(message));
|
||||
cb(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)
|
||||
void
|
||||
on_bytes_segmented(const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
|
||||
{
|
||||
if (bytes.empty())
|
||||
return;
|
||||
@@ -237,78 +331,135 @@ struct input_state_machine
|
||||
switch (state)
|
||||
{
|
||||
case in_sysex:
|
||||
return on_continue_sysex(bytes, finished_sysex);
|
||||
return on_continue_sysex(cb, 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());
|
||||
return on_main(cb, bytes, timestamp, finished_sysex);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
libremidi::message message;
|
||||
|
||||
int64_t last_time_ns = 0;
|
||||
private:
|
||||
enum
|
||||
{
|
||||
main,
|
||||
in_sysex
|
||||
} state{main};
|
||||
};
|
||||
}
|
||||
|
||||
bool first_message = true;
|
||||
namespace midi2
|
||||
{
|
||||
struct input_state_machine : input_state_machine_base<ump_input_configuration>
|
||||
{
|
||||
using input_state_machine_base::input_state_machine_base;
|
||||
|
||||
public:
|
||||
void on_bytes_multi(std::span<const unsigned char> bytes, int64_t timestamp)
|
||||
{
|
||||
auto ptr = reinterpret_cast<const uint32_t*>(bytes.data());
|
||||
auto sz = bytes.size() / 4;
|
||||
return on_bytes_multi({ptr, sz}, timestamp);
|
||||
}
|
||||
|
||||
void on_bytes_multi(std::span<const uint32_t> bytes, int64_t timestamp)
|
||||
{
|
||||
if (this->configuration.on_message)
|
||||
on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);
|
||||
if (this->configuration.on_raw_data)
|
||||
this->configuration.on_raw_data(bytes, timestamp);
|
||||
}
|
||||
|
||||
void on_bytes(std::span<const uint32_t> bytes, int64_t timestamp)
|
||||
{
|
||||
if (this->configuration.on_message)
|
||||
on_bytes_segmented(this->configuration.on_message, bytes, timestamp);
|
||||
if (this->configuration.on_raw_data)
|
||||
this->configuration.on_raw_data(bytes, timestamp);
|
||||
}
|
||||
|
||||
private:
|
||||
// Function to process a byte stream which may contain multiple successive
|
||||
// MIDI events (CoreMIDI, ALSA Sequencer can work like this)
|
||||
void on_bytes_multi_segmented(
|
||||
const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)
|
||||
{
|
||||
auto count = bytes.size();
|
||||
auto ump_stream = bytes.data();
|
||||
while (count > 0)
|
||||
{
|
||||
// Handle NOOP (or padding)
|
||||
while (count > 0 && ump_stream[0] == 0)
|
||||
{
|
||||
count--;
|
||||
ump_stream++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
break;
|
||||
|
||||
const auto ump_uints = cmidi2_ump_get_num_bytes(ump_stream[0]) / 4;
|
||||
on_bytes_segmented(cb, {ump_stream, ump_stream + ump_uints}, timestamp);
|
||||
|
||||
ump_stream += ump_uints;
|
||||
count -= ump_uints;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to process bytes corresponding to at most one midi event
|
||||
void
|
||||
on_bytes_segmented(const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)
|
||||
{
|
||||
// Filter according to message type
|
||||
switch(cmidi2_ump_get_message_type(bytes.data()))
|
||||
{
|
||||
case CMIDI2_MESSAGE_TYPE_UTILITY:
|
||||
{
|
||||
// All the utility messages are about timing
|
||||
if (this->configuration.ignore_timing)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
case CMIDI2_MESSAGE_TYPE_SYSTEM:
|
||||
{
|
||||
if (this->configuration.ignore_timing)
|
||||
{
|
||||
auto status = cmidi2_ump_get_system_message_byte2(bytes.data());
|
||||
switch(status)
|
||||
{
|
||||
case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE:
|
||||
case CMIDI2_SYSTEM_STATUS_SONG_POSITION:
|
||||
case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->configuration.ignore_sensing)
|
||||
{
|
||||
auto status = cmidi2_ump_get_system_message_byte2(bytes.data());
|
||||
if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING)
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CMIDI2_MESSAGE_TYPE_SYSEX7:
|
||||
case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:
|
||||
{
|
||||
if (this->configuration.ignore_sysex)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
libremidi::ump msg;
|
||||
std::copy(bytes.begin(), bytes.end(), msg.data);
|
||||
msg.timestamp = timestamp;
|
||||
cb(std::move(msg));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <libremidi/api.hpp>
|
||||
#include <libremidi/config.hpp>
|
||||
#include <libremidi/observer_configuration.hpp>
|
||||
#include <libremidi/error_handler.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
#include <libremidi/error.hpp>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
@@ -17,7 +18,7 @@ enum class segmentation_error
|
||||
* Utility function to segment an ump stream into individual messages.
|
||||
* Used to send a stream to APIs that work message-by-message.
|
||||
*/
|
||||
inline void
|
||||
inline stdx::error
|
||||
segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, auto realloc_func)
|
||||
{
|
||||
while (count > 0)
|
||||
@@ -35,24 +36,27 @@ segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, a
|
||||
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))
|
||||
switch (int err = static_cast<int>(write_func(ump_stream, ump_bytes)))
|
||||
{
|
||||
case segmentation_error::no_error:
|
||||
case 0:
|
||||
break;
|
||||
case segmentation_error::need_space:
|
||||
case -ENOMEM:
|
||||
case ENOMEM:
|
||||
// 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;
|
||||
if (auto err = write_func(ump_stream, ump_bytes); err != std::errc{})
|
||||
return std::make_error_code(err);
|
||||
break;
|
||||
case segmentation_error::other:
|
||||
return;
|
||||
default:
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
const auto ump_uints = ump_bytes / 4;
|
||||
ump_stream += ump_uints;
|
||||
count -= ump_uints;
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+46
-71
@@ -1,82 +1,37 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#include <libremidi/system_error2.hpp>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <version>
|
||||
|
||||
#if __has_include(<source_location>) && (__cpp_lib_source_location >= 201907L)
|
||||
#include <source_location>
|
||||
namespace libremidi { using source_location = std::source_location; }
|
||||
#else
|
||||
namespace libremidi {
|
||||
struct source_location {
|
||||
static source_location current() noexcept { return {}; }
|
||||
int line() const noexcept { return -1; }
|
||||
int offset() const noexcept { return -1; }
|
||||
const char* function_name() const noexcept { return "(unknown)"; }
|
||||
const char* file_name() const noexcept { return "(unknown)"; }
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
//! Defines various error types.
|
||||
enum midi_error
|
||||
inline auto from_errc(int ret) noexcept
|
||||
{
|
||||
WARNING, /*!< A non-critical error. */
|
||||
UNSPECIFIED, /*!< The default, unspecified error type. */
|
||||
NO_DEVICES_FOUND, /*!< No devices found on system. */
|
||||
INVALID_DEVICE, /*!< An invalid device ID was specified. */
|
||||
MEMORY_ERROR, /*!< An error occured during memory allocation. */
|
||||
INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */
|
||||
INVALID_USE, /*!< The function was called incorrectly. */
|
||||
DRIVER_ERROR, /*!< A system driver error occured. */
|
||||
SYSTEM_ERROR, /*!< A system error occured. */
|
||||
THREAD_ERROR /*!< A thread error occured. */
|
||||
};
|
||||
|
||||
//! Base exception class for MIDI problems
|
||||
struct LIBREMIDI_EXPORT midi_exception : public std::runtime_error
|
||||
{
|
||||
using std::runtime_error::runtime_error;
|
||||
~midi_exception() override;
|
||||
};
|
||||
|
||||
struct LIBREMIDI_EXPORT no_devices_found_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::NO_DEVICES_FOUND;
|
||||
using midi_exception::midi_exception;
|
||||
~no_devices_found_error() override;
|
||||
};
|
||||
struct LIBREMIDI_EXPORT invalid_device_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::INVALID_DEVICE;
|
||||
using midi_exception::midi_exception;
|
||||
~invalid_device_error() override;
|
||||
};
|
||||
struct LIBREMIDI_EXPORT memory_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::MEMORY_ERROR;
|
||||
using midi_exception::midi_exception;
|
||||
~memory_error() override;
|
||||
};
|
||||
struct LIBREMIDI_EXPORT invalid_parameter_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::INVALID_PARAMETER;
|
||||
using midi_exception::midi_exception;
|
||||
~invalid_parameter_error() override;
|
||||
};
|
||||
struct LIBREMIDI_EXPORT invalid_use_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::INVALID_USE;
|
||||
using midi_exception::midi_exception;
|
||||
~invalid_use_error() override;
|
||||
};
|
||||
struct LIBREMIDI_EXPORT driver_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::DRIVER_ERROR;
|
||||
using midi_exception::midi_exception;
|
||||
~driver_error() override;
|
||||
};
|
||||
struct LIBREMIDI_EXPORT system_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::SYSTEM_ERROR;
|
||||
using midi_exception::midi_exception;
|
||||
~system_error() override;
|
||||
};
|
||||
struct LIBREMIDI_EXPORT thread_error final : public midi_exception
|
||||
{
|
||||
static constexpr auto code = midi_error::THREAD_ERROR;
|
||||
using midi_exception::midi_exception;
|
||||
~thread_error() override;
|
||||
};
|
||||
return static_cast<std::errc>(-ret);
|
||||
}
|
||||
|
||||
/*! \brief Error callback function
|
||||
\param type Type of error.
|
||||
@@ -85,5 +40,25 @@ struct LIBREMIDI_EXPORT thread_error final : public midi_exception
|
||||
Note that class behaviour is undefined after a critical error (not
|
||||
a warning) is reported.
|
||||
*/
|
||||
using midi_error_callback = std::function<void(midi_error type, std::string_view errorText)>;
|
||||
using midi_error_callback = std::function<void(std::string_view errorText, const source_location&)>;
|
||||
using midi_warning_callback = std::function<void(std::string_view errorText, const source_location&)>;
|
||||
}
|
||||
|
||||
#if !defined(LIBREMIDI_LOG)
|
||||
#if !defined(__LIBREMIDI_DEBUG__)
|
||||
#define LIBREMIDI_LOG(...) \
|
||||
do \
|
||||
{ \
|
||||
} while (0)
|
||||
#else
|
||||
#include <iostream>
|
||||
#define LIBREMIDI_LOG(...) \
|
||||
do \
|
||||
{ \
|
||||
[](auto&&... args) { \
|
||||
(std::cerr << ... << args); \
|
||||
std::cerr << std::endl; \
|
||||
}(__VA_ARGS__); \
|
||||
} while (0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include <libremidi/api.hpp>
|
||||
#include <libremidi/config.hpp>
|
||||
#include <libremidi/error.hpp>
|
||||
#include <libremidi/observer_configuration.hpp>
|
||||
#include <libremidi/input_configuration.hpp>
|
||||
#include <libremidi/output_configuration.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct error_handler
|
||||
{
|
||||
//! Error reporting function for libremidi classes.
|
||||
void error_impl(
|
||||
const midi_error_callback& callback, std::string_view errorString,
|
||||
const source_location& location) const
|
||||
{
|
||||
if (callback)
|
||||
{
|
||||
if (first_error)
|
||||
return;
|
||||
|
||||
first_error = true;
|
||||
callback(errorString, location);
|
||||
first_error = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");
|
||||
}
|
||||
}
|
||||
|
||||
//! Warning reporting function for libremidi classes.
|
||||
void warning_impl(
|
||||
const midi_warning_callback& callback, std::string_view errorString,
|
||||
const source_location& location) const
|
||||
{
|
||||
if (callback)
|
||||
{
|
||||
if (first_warning)
|
||||
return;
|
||||
|
||||
first_warning = true;
|
||||
callback(errorString, location);
|
||||
first_warning = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");
|
||||
}
|
||||
}
|
||||
|
||||
// To prevent infinite error loops
|
||||
mutable bool first_error{};
|
||||
mutable bool first_warning{};
|
||||
};
|
||||
|
||||
// Needed as apple still doesn't support source_location in xcode 15.3
|
||||
#define libremidi_handle_error(config, str) error_impl(config.on_error, str, libremidi::source_location::current())
|
||||
#define libremidi_handle_warning(config, str) warning_impl(config.on_warning, str, libremidi::source_location::current())
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <libremidi/ump.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -41,19 +40,23 @@ enum timestamp_mode
|
||||
|
||||
using timestamp = int64_t;
|
||||
using message_callback = std::function<void(message&& message)>;
|
||||
using raw_callback = std::function<void(std::span<uint8_t>, timestamp)>;
|
||||
using raw_callback = std::function<void(std::span<const uint8_t>, timestamp)>;
|
||||
using timestamp_callback = std::function<timestamp(timestamp)>;
|
||||
struct input_configuration
|
||||
{
|
||||
//! Set a callback function to be invoked for incoming MIDI messages.
|
||||
//! Mandatory!
|
||||
message_callback on_message;
|
||||
//! Either this or on_raw_message must be set
|
||||
message_callback on_message{};
|
||||
|
||||
//! Invoked for incoming MIDI bytes. No transformation, no filtering, no packetization,
|
||||
//! just the MIDI data straight from the source.
|
||||
raw_callback on_raw_data{};
|
||||
|
||||
//! Set a custom callback function to be invoked for timestamping MIDI messages.
|
||||
//! Input: the API provided timestamp in nanoseconds, if available, for reference.
|
||||
//! (e.g. the same as "Absolute").
|
||||
//! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.
|
||||
timestamp_callback get_timestamp;
|
||||
timestamp_callback get_timestamp{};
|
||||
|
||||
//! Set an error callback function to be invoked when an error has occured.
|
||||
/*!
|
||||
@@ -61,7 +64,7 @@ struct input_configuration
|
||||
best to set the error callback function before opening a port.
|
||||
*/
|
||||
midi_error_callback on_error{};
|
||||
midi_error_callback on_warning{};
|
||||
midi_warning_callback on_warning{};
|
||||
|
||||
//! Specify whether certain MIDI message types should be queued or ignored
|
||||
//! during input.
|
||||
@@ -81,16 +84,22 @@ struct input_configuration
|
||||
};
|
||||
|
||||
using ump_callback = std::function<void(ump&&)>;
|
||||
using raw_ump_callback = std::function<void(std::span<const uint32_t>, timestamp)>;
|
||||
struct ump_input_configuration
|
||||
{
|
||||
//! Set a callback function to be invoked for incoming UMP messages.
|
||||
ump_callback on_message;
|
||||
//! Either this or on_raw_message must be set
|
||||
ump_callback on_message{};
|
||||
|
||||
//! Invoked for incoming UMP bytes. No transformation, no filtering, no packetization,
|
||||
//! just the UMP data straight from the source.
|
||||
raw_ump_callback on_raw_data{};
|
||||
|
||||
//! Set a custom callback function to be invoked for timestamping MIDI messages.
|
||||
//! Input: the API provided timestamp in nanoseconds, if available, for reference.
|
||||
//! (e.g. the same as "Absolute").
|
||||
//! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.
|
||||
timestamp_callback get_timestamp;
|
||||
timestamp_callback get_timestamp{};
|
||||
|
||||
//! Set an error callback function to be invoked when an error has occured.
|
||||
/*!
|
||||
@@ -98,7 +107,7 @@ struct ump_input_configuration
|
||||
best to set the error callback function before opening a port.
|
||||
*/
|
||||
midi_error_callback on_error{};
|
||||
midi_error_callback on_warning{};
|
||||
midi_warning_callback on_warning{};
|
||||
|
||||
//! Specify whether certain MIDI message types should be queued or ignored
|
||||
//! during input.
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
// clang-format off
|
||||
#include <libremidi/libremidi-c.h>
|
||||
#include <libremidi/libremidi.hpp>
|
||||
#include <libremidi/backends.hpp>
|
||||
// clang-format on
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
struct libremidi_midi_observer_handle
|
||||
{
|
||||
libremidi::observer self;
|
||||
};
|
||||
|
||||
struct libremidi_midi_in_handle
|
||||
{
|
||||
libremidi::midi_in self;
|
||||
};
|
||||
|
||||
struct libremidi_midi_out_handle
|
||||
{
|
||||
libremidi::midi_out self;
|
||||
};
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
static void assign_error_callback(const auto& src, auto& dst)
|
||||
{
|
||||
if (src.callback)
|
||||
{
|
||||
dst = [src](std::string_view errorText, const source_location& loc) {
|
||||
src.callback(src.context, errorText.data(), errorText.size(), &loc);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
const char* libremidi_get_version(void)
|
||||
{
|
||||
return LIBREMIDI_VERSION;
|
||||
}
|
||||
|
||||
void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||
{
|
||||
if (!cb)
|
||||
return;
|
||||
libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); });
|
||||
}
|
||||
|
||||
void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||
{
|
||||
if (!cb)
|
||||
return;
|
||||
libremidi::midi2::for_all_backends([=](auto b) { cb(ctx, b.API); });
|
||||
}
|
||||
|
||||
const char* libremidi_api_identifier(libremidi_api api)
|
||||
{
|
||||
return libremidi::get_api_name(api).data();
|
||||
}
|
||||
|
||||
const char* libremidi_api_display_name(libremidi_api api)
|
||||
{
|
||||
return libremidi::get_api_display_name(api).data();
|
||||
}
|
||||
|
||||
libremidi_api libremidi_get_compiled_api_by_identifier(const char* name)
|
||||
{
|
||||
libremidi_api ret = libremidi_api::UNSPECIFIED;
|
||||
libremidi::midi_any::for_all_backends([&](auto& b) {
|
||||
if (name == b.name)
|
||||
ret = b.API;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
int libremidi_midi_api_configuration_init(libremidi_api_configuration* conf)
|
||||
{
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_observer_configuration_init(libremidi_observer_configuration* conf)
|
||||
{
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_configuration_init(libremidi_midi_configuration* conf)
|
||||
{
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_in_port_clone(const libremidi_midi_in_port* port, libremidi_midi_in_port** dst)
|
||||
{
|
||||
if (!port || !dst)
|
||||
return -EINVAL;
|
||||
|
||||
auto copied = new libremidi::input_port{*reinterpret_cast<const libremidi::input_port*>(port)};
|
||||
*dst = reinterpret_cast<libremidi_midi_in_port*>(copied);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_in_port_free(libremidi_midi_in_port* port)
|
||||
{
|
||||
delete reinterpret_cast<libremidi::input_port*>(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** name, size_t* len)
|
||||
{
|
||||
if (!port || !name || !len)
|
||||
return -EINVAL;
|
||||
|
||||
auto& p = *reinterpret_cast<const libremidi::input_port*>(port);
|
||||
*name = p.port_name.data();
|
||||
*len = p.port_name.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_port_clone(
|
||||
const libremidi_midi_out_port* port, libremidi_midi_out_port** dst)
|
||||
{
|
||||
if (!port || !dst)
|
||||
return -EINVAL;
|
||||
|
||||
auto copied = new libremidi::output_port{*reinterpret_cast<const libremidi::output_port*>(port)};
|
||||
*dst = reinterpret_cast<libremidi_midi_out_port*>(copied);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_port_free(libremidi_midi_out_port* port)
|
||||
{
|
||||
delete reinterpret_cast<libremidi::output_port*>(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_port_name(
|
||||
const libremidi_midi_out_port* port, const char** name, size_t* len)
|
||||
{
|
||||
if (!port || !name || !len)
|
||||
return -EINVAL;
|
||||
|
||||
auto& p = *reinterpret_cast<const libremidi::output_port*>(port);
|
||||
*name = p.port_name.data();
|
||||
*len = p.port_name.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_observer_new(
|
||||
const libremidi_observer_configuration* c, libremidi_api_configuration* api,
|
||||
libremidi_midi_observer_handle** out)
|
||||
{
|
||||
if (!out || !c)
|
||||
return -EINVAL;
|
||||
|
||||
libremidi::observer_configuration conf;
|
||||
libremidi::assign_error_callback(c->on_error, conf.on_error);
|
||||
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
|
||||
|
||||
conf.track_hardware = c->track_hardware;
|
||||
conf.track_virtual = c->track_virtual;
|
||||
conf.track_any = c->track_any;
|
||||
conf.notify_in_constructor = c->notify_in_constructor;
|
||||
|
||||
auto api_conf = libremidi::observer_configuration_for(static_cast<libremidi::API>(api->api));
|
||||
|
||||
if (c->input_added.callback)
|
||||
{
|
||||
conf.input_added = [cb = c->input_added](const auto& port) {
|
||||
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_in_port*>(&port));
|
||||
};
|
||||
}
|
||||
if (c->input_removed.callback)
|
||||
{
|
||||
conf.input_removed = [cb = c->input_removed](const auto& port) {
|
||||
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_in_port*>(&port));
|
||||
};
|
||||
}
|
||||
if (c->output_added.callback)
|
||||
{
|
||||
conf.output_added = [cb = c->output_added](const auto& port) {
|
||||
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_out_port*>(&port));
|
||||
};
|
||||
}
|
||||
if (c->output_removed.callback)
|
||||
{
|
||||
conf.output_removed = [cb = c->output_removed](const auto& port) {
|
||||
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_out_port*>(&port));
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto ptr = new libremidi_midi_observer_handle{
|
||||
libremidi::observer{std::move(conf), std::move(api_conf)}};
|
||||
*out = ptr;
|
||||
return 0;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int libremidi_midi_observer_enumerate_input_ports(
|
||||
libremidi_midi_observer_handle* ptr, void* context,
|
||||
void (*cb)(void* ctx, const libremidi_midi_in_port*))
|
||||
{
|
||||
if (!ptr || !cb)
|
||||
return -EINVAL;
|
||||
|
||||
for (const auto& port : ptr->self.get_input_ports())
|
||||
{
|
||||
cb(context, reinterpret_cast<const libremidi_midi_in_port*>(&port));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_observer_enumerate_output_ports(
|
||||
libremidi_midi_observer_handle* ptr, void* context,
|
||||
void (*cb)(void* ctx, const libremidi_midi_out_port*))
|
||||
{
|
||||
if (!ptr || !cb)
|
||||
return -EINVAL;
|
||||
|
||||
for (const auto& port : ptr->self.get_output_ports())
|
||||
{
|
||||
cb(context, reinterpret_cast<const libremidi_midi_out_port*>(&port));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_observer_free(libremidi_midi_observer_handle* ptr)
|
||||
{
|
||||
delete ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_in_new(
|
||||
const libremidi_midi_configuration* c, const libremidi_api_configuration* api,
|
||||
libremidi_midi_in_handle** out)
|
||||
{
|
||||
if (!out || !c)
|
||||
return -EINVAL;
|
||||
if (!c->virtual_port && !c->in_port)
|
||||
return -EINVAL;
|
||||
|
||||
*out = nullptr;
|
||||
|
||||
auto api_conf = libremidi::midi_in_configuration_for(static_cast<libremidi::API>(api->api));
|
||||
|
||||
// Create the MIDI object
|
||||
switch (c->version)
|
||||
{
|
||||
case libremidi_midi_configuration::MIDI1:
|
||||
case libremidi_midi_configuration::MIDI1_RAW: {
|
||||
libremidi::input_configuration conf;
|
||||
libremidi::assign_error_callback(c->on_error, conf.on_error);
|
||||
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
|
||||
conf.ignore_sensing = c->ignore_sensing;
|
||||
conf.ignore_sysex = c->ignore_sysex;
|
||||
conf.ignore_timing = c->ignore_timing;
|
||||
conf.timestamps = c->timestamps;
|
||||
|
||||
if (c->get_timestamp.callback)
|
||||
{
|
||||
conf.get_timestamp
|
||||
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
||||
}
|
||||
|
||||
if (c->on_midi1_message.callback)
|
||||
conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) {
|
||||
cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size());
|
||||
};
|
||||
else if (c->on_midi1_raw_data.callback)
|
||||
{
|
||||
conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span<const uint8_t> msg, int64_t ts) {
|
||||
cb.callback(cb.context, ts, msg.data(), msg.size());
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto ptr = new libremidi_midi_in_handle{
|
||||
libremidi::midi_in{std::move(conf), std::move(api_conf)}};
|
||||
*out = ptr;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case libremidi_midi_configuration::MIDI2:
|
||||
case libremidi_midi_configuration::MIDI2_RAW: {
|
||||
libremidi::ump_input_configuration conf;
|
||||
libremidi::assign_error_callback(c->on_error, conf.on_error);
|
||||
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
|
||||
conf.ignore_sensing = c->ignore_sensing;
|
||||
conf.ignore_sysex = c->ignore_sysex;
|
||||
conf.ignore_timing = c->ignore_timing;
|
||||
conf.timestamps = c->timestamps;
|
||||
|
||||
if (c->get_timestamp.callback)
|
||||
{
|
||||
conf.get_timestamp
|
||||
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
||||
}
|
||||
|
||||
if (c->on_midi2_message.callback)
|
||||
conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) {
|
||||
cb.callback(cb.context, msg.timestamp, msg.data, msg.size());
|
||||
};
|
||||
else if (c->on_midi2_raw_data.callback)
|
||||
{
|
||||
conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span<const uint32_t> msg, int64_t ts) {
|
||||
cb.callback(cb.context, ts, msg.data(), msg.size());
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto ptr = new libremidi_midi_in_handle{
|
||||
libremidi::midi_in{std::move(conf), std::move(api_conf)}};
|
||||
*out = ptr;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// Open the port
|
||||
auto ptr = *out;
|
||||
if (!ptr)
|
||||
return -EINVAL;
|
||||
|
||||
if (c->virtual_port)
|
||||
{
|
||||
if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{})
|
||||
{
|
||||
*out = nullptr;
|
||||
delete ptr;
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto port = reinterpret_cast<const libremidi::input_port*>(c->in_port);
|
||||
if (auto ret = ptr->self.open_port(*port); ret != stdx::error{})
|
||||
{
|
||||
*out = nullptr;
|
||||
delete ptr;
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
int libremidi_midi_in_is_connected(const libremidi_midi_in_handle* in)
|
||||
{
|
||||
if (!in)
|
||||
return -EINVAL;
|
||||
return in->self.is_port_connected() ? 1 : 0;
|
||||
}
|
||||
|
||||
libremidi_timestamp libremidi_midi_in_absolute_timestamp(libremidi_midi_in_handle* in)
|
||||
{
|
||||
if (!in)
|
||||
return -EINVAL;
|
||||
return in->self.absolute_timestamp();
|
||||
}
|
||||
|
||||
int libremidi_midi_in_free(libremidi_midi_in_handle* ptr)
|
||||
{
|
||||
delete ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_new(
|
||||
const libremidi_midi_configuration* c, const libremidi_api_configuration* api,
|
||||
libremidi_midi_out_handle** out)
|
||||
{
|
||||
if (!out || !c)
|
||||
return -EINVAL;
|
||||
if (!c->virtual_port && !c->out_port)
|
||||
return -EINVAL;
|
||||
|
||||
*out = nullptr;
|
||||
|
||||
auto api_conf = libremidi::midi_out_configuration_for(static_cast<libremidi::API>(api->api));
|
||||
|
||||
// Create the MIDI object
|
||||
libremidi::output_configuration conf;
|
||||
libremidi::assign_error_callback(c->on_error, conf.on_error);
|
||||
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
|
||||
conf.timestamps = c->timestamps;
|
||||
try
|
||||
{
|
||||
auto ptr
|
||||
= new libremidi_midi_out_handle{libremidi::midi_out{std::move(conf), std::move(api_conf)}};
|
||||
*out = ptr;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// Open the port
|
||||
auto ptr = *out;
|
||||
if (!ptr)
|
||||
return -EINVAL;
|
||||
|
||||
if (c->virtual_port)
|
||||
{
|
||||
if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{})
|
||||
{
|
||||
*out = nullptr;
|
||||
delete ptr;
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto port = reinterpret_cast<const libremidi::output_port*>(c->in_port);
|
||||
if (auto ret = ptr->self.open_port(*port); ret != stdx::error{})
|
||||
{
|
||||
*out = nullptr;
|
||||
delete ptr;
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_is_connected(const libremidi_midi_out_handle* out)
|
||||
{
|
||||
if (!out)
|
||||
return -EINVAL;
|
||||
return out->self.is_port_connected() ? 1 : 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_send_message(
|
||||
libremidi_midi_out_handle* out, const libremidi_midi1_symbol* msg, size_t sz)
|
||||
{
|
||||
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
|
||||
return -EINVAL;
|
||||
|
||||
auto res = out->self.send_message(msg, sz);
|
||||
return res != stdx::error{} ? -EIO : 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz)
|
||||
{
|
||||
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
|
||||
return -EINVAL;
|
||||
|
||||
auto res = out->self.send_ump(msg, sz);
|
||||
return res != stdx::error{} ? -EIO : 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_schedule_message(
|
||||
libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi1_symbol* msg, size_t sz)
|
||||
{
|
||||
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
|
||||
return -EINVAL;
|
||||
|
||||
auto res = out->self.schedule_message(ts, msg, sz);
|
||||
return res != stdx::error{} ? -EIO : 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_schedule_ump(
|
||||
libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi2_symbol* msg, size_t sz)
|
||||
{
|
||||
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
|
||||
return -EINVAL;
|
||||
|
||||
auto res = out->self.schedule_ump(ts, msg, sz);
|
||||
return res != stdx::error{} ? -EIO : 0;
|
||||
}
|
||||
|
||||
int libremidi_midi_out_free(libremidi_midi_out_handle* ptr)
|
||||
{
|
||||
delete ptr;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
#pragma once
|
||||
#include <libremidi/api-c.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(LIBREMIDI_EXPORTS)
|
||||
#if defined(_MSC_VER)
|
||||
#define LIBREMIDI_EXPORT __declspec(dllexport)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define LIBREMIDI_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
#else
|
||||
#define LIBREMIDI_EXPORT
|
||||
#endif
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
typedef unsigned char libremidi_midi1_symbol;
|
||||
typedef libremidi_midi1_symbol* libremidi_midi1_message;
|
||||
|
||||
typedef uint32_t libremidi_midi2_symbol;
|
||||
typedef libremidi_midi2_symbol* libremidi_midi2_message;
|
||||
|
||||
typedef int64_t libremidi_timestamp;
|
||||
|
||||
typedef struct libremidi_midi_in_port libremidi_midi_in_port;
|
||||
typedef struct libremidi_midi_out_port libremidi_midi_out_port;
|
||||
typedef struct libremidi_midi_in_handle libremidi_midi_in_handle;
|
||||
typedef struct libremidi_midi_out_handle libremidi_midi_out_handle;
|
||||
typedef struct libremidi_midi_observer_handle libremidi_midi_observer_handle;
|
||||
|
||||
typedef struct libremidi_api_configuration libremidi_api_configuration;
|
||||
|
||||
enum libremidi_timestamp_mode
|
||||
{
|
||||
NoTimestamp,
|
||||
|
||||
Relative,
|
||||
Absolute,
|
||||
SystemMonotonic,
|
||||
AudioFrame,
|
||||
Custom
|
||||
};
|
||||
|
||||
typedef struct libremidi_api_configuration
|
||||
{
|
||||
enum libremidi_api api;
|
||||
enum
|
||||
{
|
||||
Observer,
|
||||
Input,
|
||||
Output
|
||||
} configuration_type;
|
||||
|
||||
void* data;
|
||||
} libremidi_api_configuration;
|
||||
|
||||
typedef struct libremidi_observer_configuration
|
||||
{
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
|
||||
} on_error;
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
|
||||
} on_warning;
|
||||
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const libremidi_midi_in_port*);
|
||||
} input_added;
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const libremidi_midi_in_port*);
|
||||
} input_removed;
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const libremidi_midi_out_port*);
|
||||
} output_added;
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const libremidi_midi_out_port*);
|
||||
} output_removed;
|
||||
|
||||
bool track_hardware;
|
||||
bool track_virtual;
|
||||
bool track_any;
|
||||
bool notify_in_constructor;
|
||||
} libremidi_observer_configuration;
|
||||
|
||||
typedef struct libremidi_midi1_callback
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, libremidi_timestamp, const libremidi_midi1_symbol*, size_t len);
|
||||
} libremidi_midi1_callback;
|
||||
|
||||
typedef struct libremidi_midi2_callback
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, libremidi_timestamp, const libremidi_midi2_symbol*, size_t len);
|
||||
} libremidi_midi2_callback;
|
||||
|
||||
typedef struct libremidi_midi_configuration
|
||||
{
|
||||
// Indicates the kind of callback requested, e.g. set MIDI1_RAW if you use the on_midi1_raw_data
|
||||
enum
|
||||
{
|
||||
MIDI1 = (1 << 1),
|
||||
MIDI1_RAW = (1 << 2),
|
||||
MIDI2 = (1 << 3),
|
||||
MIDI2_RAW = (1 << 4)
|
||||
} version;
|
||||
|
||||
union
|
||||
{
|
||||
libremidi_midi_in_port* in_port;
|
||||
libremidi_midi_out_port* out_port;
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
libremidi_midi1_callback on_midi1_message;
|
||||
libremidi_midi1_callback on_midi1_raw_data;
|
||||
libremidi_midi2_callback on_midi2_message;
|
||||
libremidi_midi2_callback on_midi2_raw_data;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
libremidi_timestamp (*callback)(void* ctx, libremidi_timestamp);
|
||||
} get_timestamp;
|
||||
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
|
||||
} on_error;
|
||||
struct
|
||||
{
|
||||
void* context;
|
||||
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
|
||||
} on_warning;
|
||||
|
||||
const char* port_name;
|
||||
bool virtual_port;
|
||||
|
||||
bool ignore_sysex;
|
||||
bool ignore_timing;
|
||||
bool ignore_sensing;
|
||||
|
||||
enum libremidi_timestamp_mode timestamps;
|
||||
} libremidi_midi_configuration;
|
||||
|
||||
/// API utilities
|
||||
LIBREMIDI_EXPORT
|
||||
const char* libremidi_get_version(void);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
void libremidi_midi1_available_apis(void* ctx, void (*)(void*, libremidi_api));
|
||||
LIBREMIDI_EXPORT
|
||||
void libremidi_midi2_available_apis(void* ctx, void (*)(void*, libremidi_api));
|
||||
LIBREMIDI_EXPORT
|
||||
const char* libremidi_api_identifier(libremidi_api);
|
||||
LIBREMIDI_EXPORT
|
||||
const char* libremidi_api_display_name(libremidi_api);
|
||||
LIBREMIDI_EXPORT
|
||||
libremidi_api libremidi_get_compiled_api_by_identifier(const char*);
|
||||
|
||||
/// Create configurations
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_api_configuration_init(libremidi_api_configuration*);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_observer_configuration_init(libremidi_observer_configuration*);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_configuration_init(libremidi_midi_configuration*);
|
||||
|
||||
/// Read information about port objects
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_in_port_clone(const libremidi_midi_in_port* port, libremidi_midi_in_port** dst);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_in_port_free(libremidi_midi_in_port* port);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_in_port_name(
|
||||
const libremidi_midi_in_port* port, const char** name, size_t* len);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_port_clone(
|
||||
const libremidi_midi_out_port* port, libremidi_midi_out_port** dst);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_port_free(libremidi_midi_out_port* port);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_port_name(
|
||||
const libremidi_midi_out_port* port, const char** name, size_t* len);
|
||||
|
||||
/// Observer API
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_observer_new(
|
||||
const libremidi_observer_configuration*, libremidi_api_configuration*,
|
||||
libremidi_midi_observer_handle**);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_observer_enumerate_input_ports(
|
||||
libremidi_midi_observer_handle*, void* context,
|
||||
void (*)(void* ctx, const libremidi_midi_in_port*));
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_observer_enumerate_output_ports(
|
||||
libremidi_midi_observer_handle*, void* context,
|
||||
void (*)(void* ctx, const libremidi_midi_out_port*));
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_observer_free(libremidi_midi_observer_handle*);
|
||||
|
||||
/// MIDI input API (read MIDI messages)
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_in_new(
|
||||
const libremidi_midi_configuration*, const libremidi_api_configuration*,
|
||||
libremidi_midi_in_handle**);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_in_is_connected(const libremidi_midi_in_handle*);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
libremidi_timestamp libremidi_midi_in_absolute_timestamp(libremidi_midi_in_handle*);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_in_free(libremidi_midi_in_handle*);
|
||||
|
||||
/// MIDI output API (send MIDI messages)
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_new(
|
||||
const libremidi_midi_configuration*, const libremidi_api_configuration*,
|
||||
libremidi_midi_out_handle**);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_is_connected(const libremidi_midi_out_handle*);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_send_message(
|
||||
libremidi_midi_out_handle*, const libremidi_midi1_symbol*, size_t);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_send_ump(libremidi_midi_out_handle*, const libremidi_midi2_symbol*, size_t);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_schedule_message(
|
||||
libremidi_midi_out_handle*, int64_t ts, const libremidi_midi1_symbol*, size_t);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_schedule_ump(
|
||||
libremidi_midi_out_handle*, int64_t ts, const libremidi_midi2_symbol*, size_t);
|
||||
|
||||
LIBREMIDI_EXPORT
|
||||
int libremidi_midi_out_free(libremidi_midi_out_handle*);
|
||||
|
||||
#if __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -7,9 +7,6 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <array>
|
||||
#include <thread>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
@@ -56,17 +53,20 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name)
|
||||
midi2::for_all_backends([&](auto b) { apis.push_back(b.API); });
|
||||
return apis;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_exception::~midi_exception() = default;
|
||||
LIBREMIDI_INLINE no_devices_found_error::~no_devices_found_error() = default;
|
||||
LIBREMIDI_INLINE invalid_device_error::~invalid_device_error() = default;
|
||||
LIBREMIDI_INLINE memory_error::~memory_error() = default;
|
||||
LIBREMIDI_INLINE invalid_parameter_error::~invalid_parameter_error() = default;
|
||||
LIBREMIDI_INLINE invalid_use_error::~invalid_use_error() = default;
|
||||
LIBREMIDI_INLINE driver_error::~driver_error() = default;
|
||||
LIBREMIDI_INLINE system_error::~system_error() = default;
|
||||
LIBREMIDI_INLINE thread_error::~thread_error() = default;
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
libremidi::API midi_api(const std::any& conf)
|
||||
{
|
||||
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
||||
midi_any::for_all_backends([&]<typename T>(T) {
|
||||
if (std::any_cast<typename T::midi_in_configuration>(&conf)
|
||||
|| std::any_cast<typename T::midi_out_configuration>(&conf)
|
||||
|| std::any_cast<typename T::midi_observer_configuration>(&conf))
|
||||
{
|
||||
ret = T::API;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
LIBREMIDI_INLINE
|
||||
std::any midi_in_configuration_for(libremidi::API api)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,6 @@
|
||||
#include <midi/universal_packet.h>
|
||||
#endif
|
||||
#include <any>
|
||||
#include <optional>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -132,19 +131,20 @@ public:
|
||||
[[nodiscard]] libremidi::API get_current_api() const noexcept;
|
||||
|
||||
//! Open a MIDI input connection
|
||||
void open_port(const input_port& pt, std::string_view local_port_name = "libremidi input");
|
||||
stdx::error
|
||||
open_port(const input_port& pt, std::string_view local_port_name = "libremidi input");
|
||||
|
||||
//! Create a virtual input port, with optional name, to allow software
|
||||
//! connections.
|
||||
//!
|
||||
//! \param portName An optional name for the application port that is
|
||||
//! used to connect to portId can be specified.
|
||||
void open_virtual_port(std::string_view portName = "libremidi virtual port");
|
||||
stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port");
|
||||
|
||||
void set_port_name(std::string_view portName);
|
||||
stdx::error set_port_name(std::string_view portName);
|
||||
|
||||
//! Close an open MIDI connection (if one exists).
|
||||
void close_port();
|
||||
stdx::error close_port();
|
||||
|
||||
//! Returns true if a port has been opened successfully with open_port or open_virtual_port
|
||||
[[nodiscard]] bool is_port_open() const noexcept;
|
||||
@@ -182,11 +182,11 @@ public:
|
||||
[[nodiscard]] libremidi::API get_current_api() const noexcept;
|
||||
|
||||
//! Open a MIDI output connection.
|
||||
void
|
||||
stdx::error
|
||||
open_port(const output_port& pt, std::string_view local_port_name = "libremidi output") const;
|
||||
|
||||
//! Close an open MIDI connection (if one exists).
|
||||
void close_port() const;
|
||||
stdx::error close_port() const;
|
||||
|
||||
//! Returns true if a port has been opened successfully with open_port or open_virtual_port
|
||||
[[nodiscard]] bool is_port_open() const noexcept;
|
||||
@@ -200,48 +200,56 @@ public:
|
||||
//!
|
||||
//! \param portName An optional name for the application port that is
|
||||
//! used to connect to portId can be specified.
|
||||
void open_virtual_port(std::string_view portName = "libremidi virtual port") const;
|
||||
stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port") const;
|
||||
|
||||
void set_port_name(std::string_view portName) const;
|
||||
stdx::error set_port_name(std::string_view portName) const;
|
||||
|
||||
//! Immediately send a single message out an open MIDI output port.
|
||||
/*!
|
||||
An exception is thrown if an error occurs during output or an
|
||||
output connection was not previously established.
|
||||
*/
|
||||
void send_message(const libremidi::message& message) const;
|
||||
stdx::error send_message(const libremidi::message& message) const;
|
||||
|
||||
//! Immediately send a single message to an open MIDI output port.
|
||||
void send_message(const unsigned char* message, size_t size) const;
|
||||
void send_message(std::span<const unsigned char>) const;
|
||||
void send_message(unsigned char b0) const;
|
||||
void send_message(unsigned char b0, unsigned char b1) const;
|
||||
void send_message(unsigned char b0, unsigned char b1, unsigned char b2) const;
|
||||
stdx::error send_message(const unsigned char* message, size_t size) const;
|
||||
stdx::error send_message(std::span<const unsigned char>) const;
|
||||
stdx::error send_message(unsigned char b0) const;
|
||||
stdx::error send_message(unsigned char b0, unsigned char b1) const;
|
||||
stdx::error send_message(unsigned char b0, unsigned char b1, unsigned char b2) const;
|
||||
|
||||
// Avoid silly mistakes:
|
||||
stdx::error send_message(auto* message) const noexcept = delete;
|
||||
stdx::error send_message(const auto* message) const noexcept = delete;
|
||||
|
||||
//! Current time in the timestamp referential
|
||||
int64_t current_time();
|
||||
|
||||
//! Try to schedule a message later in time if the underlying API supports it
|
||||
//! (currently not implemented anywhere)
|
||||
void schedule_message(int64_t timestamp, const unsigned char* message, size_t size);
|
||||
stdx::error schedule_message(int64_t timestamp, const unsigned char* message, size_t size) const;
|
||||
|
||||
//! Immediately send a single UMP packet to an open MIDI output port.
|
||||
void send_ump(const uint32_t* message, size_t size) const;
|
||||
void send_ump(const libremidi::ump&) const;
|
||||
void send_ump(std::span<const uint32_t>) const;
|
||||
void send_ump(uint32_t b0) const;
|
||||
void send_ump(uint32_t b0, uint32_t b1) const;
|
||||
void send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const;
|
||||
void send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const;
|
||||
stdx::error send_ump(const uint32_t* message, size_t size) const;
|
||||
stdx::error send_ump(const libremidi::ump&) const;
|
||||
stdx::error send_ump(std::span<const uint32_t>) const;
|
||||
stdx::error send_ump(uint32_t b0) const;
|
||||
stdx::error send_ump(uint32_t b0, uint32_t b1) const;
|
||||
stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const;
|
||||
stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const;
|
||||
// Better compat with cmidi2
|
||||
stdx::error send_ump(int32_t b0) const;
|
||||
stdx::error send_ump(int64_t b01) const;
|
||||
stdx::error send_ump(uint64_t b01) const;
|
||||
|
||||
// Interop with ni-midi2
|
||||
#if LIBREMIDI_NI_MIDI2_COMPAT
|
||||
void send_ump(const midi::universal_packet& pkt) const { send_ump(pkt.data, pkt.size()); }
|
||||
void send_ump(const midi::sysex7& msg, int group = 0)
|
||||
stdx::error send_ump(const midi::universal_packet& pkt) const { send_ump(pkt.data, pkt.size()); }
|
||||
stdx::error send_ump(const midi::sysex7& msg, int group = 0)
|
||||
{
|
||||
midi::send_sysex7(msg, group, [&](const midi::sysex7_packet& x) { send_ump(x.data); });
|
||||
}
|
||||
void send_ump(const midi::sysex8& msg, int stream, int group = 0)
|
||||
stdx::error send_ump(const midi::sysex8& msg, int stream, int group = 0)
|
||||
{
|
||||
midi::send_sysex8(msg, stream, group, [&](const midi::sysex8_packet& x) { send_ump(x.data); });
|
||||
}
|
||||
@@ -249,7 +257,7 @@ public:
|
||||
|
||||
//! Try to schedule an UMP packet later in time if the underlying API supports it
|
||||
//! (currently not implemented anywhere)
|
||||
void schedule_ump(int64_t timestamp, const uint32_t* message, size_t size);
|
||||
stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<class midi_out_api> impl_;
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#if defined(__cpp_exceptions)
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -120,7 +125,7 @@ struct message
|
||||
{
|
||||
#if defined(__cpp_exceptions)
|
||||
if (channel <= 0 || channel > 16)
|
||||
throw std::range_error("message::uses_channel: out of range");
|
||||
throw std::range_error("out of range");
|
||||
#endif
|
||||
return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0);
|
||||
}
|
||||
@@ -240,7 +245,9 @@ struct meta_events
|
||||
static_cast<uint8_t>(mpqn)};
|
||||
}
|
||||
|
||||
static message time_signature(int numerator, int denominator)
|
||||
static message time_signature(
|
||||
int numerator, int denominator, int clocks_per_click = 1,
|
||||
int notated_32nd_notes_per_beat = 96)
|
||||
{
|
||||
int n = 1;
|
||||
int powTwo = 0;
|
||||
@@ -251,8 +258,14 @@ struct meta_events
|
||||
++powTwo;
|
||||
}
|
||||
|
||||
return {0xff, 0x58, 0x04, static_cast<uint8_t>(numerator), static_cast<uint8_t>(powTwo),
|
||||
1, 96};
|
||||
return {
|
||||
0xff,
|
||||
0x58,
|
||||
0x04,
|
||||
static_cast<uint8_t>(numerator),
|
||||
static_cast<uint8_t>(powTwo),
|
||||
static_cast<uint8_t>(clocks_per_click),
|
||||
static_cast<uint8_t>(notated_32nd_notes_per_beat)};
|
||||
}
|
||||
|
||||
// Where key index goes from -7 (7 flats, C♭ Major) to +7 (7 sharps, C♯
|
||||
@@ -261,7 +274,7 @@ struct meta_events
|
||||
{
|
||||
#if defined(__cpp_exceptions)
|
||||
if (keyIndex < -7 || keyIndex > 7)
|
||||
throw std::range_error("meta_events::key_signature: out of range");
|
||||
throw std::range_error("out of range");
|
||||
#endif
|
||||
return {
|
||||
0xff, 0x59, 0x02, static_cast<uint8_t>(keyIndex),
|
||||
|
||||
+243
-34
@@ -9,11 +9,63 @@
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
LIBREMIDI_INLINE auto make_midi_in(auto base_conf, std::any api_conf, auto backends)
|
||||
|
||||
static libremidi::ump_input_configuration
|
||||
convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) noexcept
|
||||
{
|
||||
libremidi::ump_input_configuration c2;
|
||||
c2.on_message = [cb = base_conf.on_message,
|
||||
converter = midi2_to_midi1{}](libremidi::ump&& msg) mutable -> void {
|
||||
converter.convert(
|
||||
msg.data, 1, msg.timestamp, [cb](const unsigned char* midi, std::size_t n, int64_t ts) {
|
||||
cb(libremidi::message{{midi, midi + n}, ts});
|
||||
return stdx::error{};
|
||||
});
|
||||
};
|
||||
c2.get_timestamp = base_conf.get_timestamp;
|
||||
c2.on_error = base_conf.on_error;
|
||||
c2.on_warning = base_conf.on_warning;
|
||||
c2.ignore_sysex = base_conf.ignore_sysex;
|
||||
c2.ignore_timing = base_conf.ignore_timing;
|
||||
c2.ignore_sensing = base_conf.ignore_sensing;
|
||||
c2.timestamps = base_conf.timestamps;
|
||||
return c2;
|
||||
}
|
||||
|
||||
static libremidi::input_configuration
|
||||
convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_conf) noexcept
|
||||
{
|
||||
libremidi::input_configuration c2;
|
||||
c2.on_message = [cb = base_conf.on_message,
|
||||
converter = midi1_to_midi2{}](libremidi::message&& msg) mutable -> void {
|
||||
converter.convert(
|
||||
msg.bytes.data(), msg.bytes.size(), msg.timestamp,
|
||||
[cb](const uint32_t* ump, std::size_t n, int64_t ts) {
|
||||
if(n >= 4)
|
||||
{
|
||||
libremidi::ump u{ump[0],ump[1],ump[2],ump[3]};
|
||||
u.timestamp = ts;
|
||||
cb(std::move(u));
|
||||
}
|
||||
return stdx::error{};
|
||||
});
|
||||
};
|
||||
c2.get_timestamp = base_conf.get_timestamp;
|
||||
c2.on_error = base_conf.on_error;
|
||||
c2.on_warning = base_conf.on_warning;
|
||||
c2.ignore_sysex = base_conf.ignore_sysex;
|
||||
c2.ignore_timing = base_conf.ignore_timing;
|
||||
c2.ignore_sensing = base_conf.ignore_sensing;
|
||||
c2.timestamps = base_conf.timestamps;
|
||||
return c2;
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi_in(auto base_conf, std::any api_conf, auto backends)
|
||||
{
|
||||
std::unique_ptr<midi_in_api> ptr;
|
||||
|
||||
assert(base_conf.on_message);
|
||||
assert(base_conf.on_message || base_conf.on_raw_data);
|
||||
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
|
||||
@@ -27,58 +79,199 @@ LIBREMIDI_INLINE auto make_midi_in(auto base_conf, std::any api_conf, auto backe
|
||||
return ptr;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept
|
||||
/// MIDI 1 helpers
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi1_in(const input_configuration& base_conf)
|
||||
{
|
||||
for (const auto& api : available_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
impl_ = make_midi_in(base_conf, midi_in_configuration_for(api), midi1::available_backends);
|
||||
auto impl
|
||||
= make_midi_in(base_conf, midi_in_configuration_for(api), midi1::available_backends);
|
||||
if (impl)
|
||||
return impl;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
|
||||
if (impl_)
|
||||
return;
|
||||
}
|
||||
if (!impl_)
|
||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
|
||||
// No MIDI 1 backend, try the MIDI 2 ones with a wrap:
|
||||
{
|
||||
auto c2 = convert_midi1_to_midi2_input_configuration(base_conf);
|
||||
for (const auto& api : available_ump_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto impl = make_midi_in(c2, midi_in_configuration_for(api), midi2::available_backends);
|
||||
if (impl)
|
||||
return impl;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
|
||||
{
|
||||
if (libremidi::is_midi1(api))
|
||||
{
|
||||
return make_midi_in(base_conf, api_conf, midi1::available_backends);
|
||||
}
|
||||
else if (libremidi::is_midi2(api))
|
||||
{
|
||||
auto c2 = convert_midi1_to_midi2_input_configuration(base_conf);
|
||||
return make_midi_in(c2, api_conf, midi2::available_backends);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf)
|
||||
{
|
||||
if (!api_conf.has_value())
|
||||
{
|
||||
return make_midi1_in(base_conf);
|
||||
}
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
return make_midi1_in(base_conf);
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_midi1_in(base_conf, midi_in_configuration_for(*api_p), *api_p);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
|
||||
return {};
|
||||
else
|
||||
return make_midi1_in(base_conf, api_conf, libremidi::midi_api(api_conf));
|
||||
}
|
||||
}
|
||||
|
||||
/// MIDI 1 constructors
|
||||
LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept
|
||||
: impl_{make_midi1_in(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
midi_in::midi_in(input_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_midi_in(base_conf, api_conf, midi1::available_backends)}
|
||||
: impl_{make_midi1_in(base_conf, api_conf)}
|
||||
{
|
||||
if (!impl_)
|
||||
throw midi_exception("Could not open midi in for the given api");
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept
|
||||
/// MIDI 2 helpers
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi2_in(const ump_input_configuration& base_conf)
|
||||
{
|
||||
for (const auto& api : available_apis())
|
||||
for (const auto& api : available_ump_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
impl_ = make_midi_in(base_conf, midi_in_configuration_for(api), midi2::available_backends);
|
||||
if (auto ret
|
||||
= make_midi_in(base_conf, midi_in_configuration_for(api), midi2::available_backends))
|
||||
return ret;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
|
||||
if (impl_)
|
||||
return;
|
||||
}
|
||||
if (!impl_)
|
||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
|
||||
// No MIDI 2 backend, try the MIDI 1 ones with a wrap:
|
||||
{
|
||||
auto c2 = convert_midi2_to_midi1_input_configuration(base_conf);
|
||||
for (const auto& api : available_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auto ret = make_midi_in(c2, midi_in_configuration_for(api), midi1::available_backends))
|
||||
return ret;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(
|
||||
const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
|
||||
{
|
||||
if (is_midi2(api))
|
||||
{
|
||||
return make_midi_in(base_conf, api_conf, midi2::available_backends);
|
||||
}
|
||||
else if (is_midi1(api))
|
||||
{
|
||||
auto c2 = convert_midi2_to_midi1_input_configuration(base_conf);
|
||||
return make_midi_in(c2, api_conf, midi1::available_backends);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||
make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf)
|
||||
{
|
||||
if (!api_conf.has_value())
|
||||
{
|
||||
return make_midi2_in(base_conf);
|
||||
}
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
return make_midi2_in(base_conf);
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_midi2_in(base_conf, midi_in_configuration_for(*api_p), *api_p);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
|
||||
return {};
|
||||
else
|
||||
return make_midi2_in(base_conf, api_conf, libremidi::midi_api(api_conf));
|
||||
}
|
||||
}
|
||||
|
||||
/// MIDI 2 constructors
|
||||
LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept
|
||||
: impl_{make_midi2_in(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_midi_in(base_conf, api_conf, midi2::available_backends)}
|
||||
: impl_{make_midi2_in(base_conf, api_conf)}
|
||||
{
|
||||
if (!impl_)
|
||||
throw midi_exception("Could not open midi in for the given api");
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_in::~midi_in() = default;
|
||||
@@ -91,9 +284,12 @@ LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_in::set_port_name(std::string_view portName)
|
||||
stdx::error midi_in::set_port_name(std::string_view portName)
|
||||
{
|
||||
impl_->set_port_name(portName);
|
||||
if(impl_->is_port_open())
|
||||
return impl_->set_port_name(portName);
|
||||
|
||||
return std::errc::not_connected;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept
|
||||
@@ -111,37 +307,50 @@ libremidi::API midi_in::get_current_api() const noexcept
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_in::open_port(const input_port& port, std::string_view portName)
|
||||
stdx::error midi_in::open_port(const input_port& port, std::string_view portName)
|
||||
{
|
||||
if (impl_->is_port_open())
|
||||
return;
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->open_port(port, portName))
|
||||
if (impl_->is_port_open())
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
auto ret = impl_->open_port(port, portName);
|
||||
if (ret == stdx::error{})
|
||||
{
|
||||
impl_->connected_ = true;
|
||||
impl_->port_open_ = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_in::open_virtual_port(std::string_view portName)
|
||||
stdx::error midi_in::open_virtual_port(std::string_view portName)
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->is_port_open())
|
||||
return;
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
if (impl_->open_virtual_port(portName))
|
||||
{
|
||||
auto ret = impl_->open_virtual_port(portName);
|
||||
if (ret == stdx::error{})
|
||||
impl_->port_open_ = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_in::close_port()
|
||||
stdx::error midi_in::close_port()
|
||||
{
|
||||
impl_->close_port();
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
auto ret = impl_->close_port();
|
||||
|
||||
impl_->connected_ = false;
|
||||
impl_->port_open_ = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
|
||||
+159
-48
@@ -10,7 +10,8 @@
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
LIBREMIDI_INLINE auto make_midi_out(auto base_conf, std::any api_conf)
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
||||
make_midi_out_impl(auto base_conf, std::any api_conf)
|
||||
{
|
||||
std::unique_ptr<midi_out_api> ptr;
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
@@ -26,23 +27,66 @@ LIBREMIDI_INLINE auto make_midi_out(auto base_conf, std::any api_conf)
|
||||
return ptr;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
||||
make_midi_out(const output_configuration& base_conf)
|
||||
{
|
||||
for (const auto& api : available_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
impl_ = make_midi_out(base_conf, midi_out_configuration_for(api));
|
||||
if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api)))
|
||||
return ret;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
|
||||
if (impl_)
|
||||
return;
|
||||
}
|
||||
if (!impl_)
|
||||
impl_ = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
|
||||
for (const auto& api : available_ump_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api)))
|
||||
return ret;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
||||
make_midi_out(const output_configuration& base_conf, const std::any& api_conf)
|
||||
{
|
||||
if (!api_conf.has_value())
|
||||
{
|
||||
return make_midi_out(base_conf);
|
||||
}
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
return make_midi_out(base_conf);
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_midi_out_impl(base_conf, midi_out_configuration_for(*api_p));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
|
||||
return make_midi_out(base_conf);
|
||||
else
|
||||
return make_midi_out_impl(base_conf, api_conf);
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept
|
||||
: impl_{make_midi_out(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
@@ -50,7 +94,11 @@ midi_out::midi_out(output_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_midi_out(base_conf, api_conf)}
|
||||
{
|
||||
if (!impl_)
|
||||
throw midi_exception("Could not open midi out for the given api");
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open midi out for the given api");
|
||||
impl_ = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out::~midi_out() = default;
|
||||
@@ -61,6 +109,7 @@ LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept
|
||||
other.impl_
|
||||
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
|
||||
{
|
||||
this->impl_ = std::move(other.impl_);
|
||||
@@ -70,9 +119,12 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::set_port_name(std::string_view portName) const
|
||||
stdx::error midi_out::set_port_name(std::string_view portName) const
|
||||
{
|
||||
impl_->set_port_name(portName);
|
||||
if(impl_->is_port_open())
|
||||
return impl_->set_port_name(portName);
|
||||
|
||||
return std::errc::not_connected;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
@@ -82,36 +134,48 @@ libremidi::API midi_out::get_current_api() const noexcept
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::open_port(const output_port& port, std::string_view portName) const
|
||||
stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const
|
||||
{
|
||||
if (impl_->is_port_open())
|
||||
return;
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->open_port(port, portName))
|
||||
if (impl_->is_port_open())
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
auto ret = impl_->open_port(port, portName);
|
||||
if (ret == stdx::error{})
|
||||
{
|
||||
impl_->connected_ = true;
|
||||
impl_->port_open_ = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::open_virtual_port(std::string_view portName) const
|
||||
stdx::error midi_out::open_virtual_port(std::string_view portName) const
|
||||
{
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (impl_->is_port_open())
|
||||
return;
|
||||
return std::errc::operation_not_supported;
|
||||
|
||||
if (impl_->open_virtual_port(portName))
|
||||
{
|
||||
auto ret = impl_->open_virtual_port(portName);
|
||||
if (ret == stdx::error{})
|
||||
impl_->port_open_ = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::close_port() const
|
||||
stdx::error midi_out::close_port() const
|
||||
{
|
||||
impl_->close_port();
|
||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
||||
return std::errc::not_connected;
|
||||
|
||||
auto ret = impl_->close_port();
|
||||
impl_->connected_ = false;
|
||||
impl_->port_open_ = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
@@ -127,89 +191,136 @@ bool midi_out::is_port_connected() const noexcept
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_message(const libremidi::message& message) const
|
||||
stdx::error midi_out::send_message(const libremidi::message& message) const
|
||||
{
|
||||
send_message(message.bytes.data(), message.bytes.size());
|
||||
return send_message(message.bytes.data(), message.bytes.size());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_message(std::span<const unsigned char> message) const
|
||||
stdx::error midi_out::send_message(std::span<const unsigned char> message) const
|
||||
{
|
||||
send_message(message.data(), message.size());
|
||||
return send_message(message.data(), message.size());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_message(unsigned char b0) const
|
||||
stdx::error midi_out::send_message(unsigned char b0) const
|
||||
{
|
||||
send_message(&b0, 1);
|
||||
return send_message(&b0, 1);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_message(unsigned char b0, unsigned char b1) const
|
||||
stdx::error midi_out::send_message(unsigned char b0, unsigned char b1) const
|
||||
{
|
||||
send_message(std::to_array({b0, b1}));
|
||||
return send_message(std::to_array({b0, b1}));
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_message(unsigned char b0, unsigned char b1, unsigned char b2) const
|
||||
stdx::error midi_out::send_message(unsigned char b0, unsigned char b1, unsigned char b2) const
|
||||
{
|
||||
send_message(std::to_array({b0, b1, b2}));
|
||||
return send_message(std::to_array({b0, b1, b2}));
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_message(const unsigned char* message, size_t size) const
|
||||
stdx::error midi_out::send_message(const unsigned char* message, size_t size) const
|
||||
{
|
||||
#if defined(LIBREMIDI_ASSERTIONS)
|
||||
assert(size > 0);
|
||||
#endif
|
||||
|
||||
impl_->send_message(message, size);
|
||||
return impl_->send_message(message, size);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_ump(const uint32_t* message, size_t size) const
|
||||
int64_t midi_out::current_time()
|
||||
{
|
||||
return impl_->current_time();
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message, size_t size) const
|
||||
{
|
||||
#if defined(LIBREMIDI_ASSERTIONS)
|
||||
assert(size > 0);
|
||||
#endif
|
||||
|
||||
return impl_->schedule_message(ts, message, size);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const
|
||||
{
|
||||
#if defined(LIBREMIDI_ASSERTIONS)
|
||||
assert(size > 0);
|
||||
assert(size <= 4);
|
||||
#endif
|
||||
|
||||
impl_->send_ump(message, size);
|
||||
return impl_->send_ump(message, size);
|
||||
}
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_ump(const libremidi::ump& message) const
|
||||
stdx::error midi_out::send_ump(const libremidi::ump& message) const
|
||||
{
|
||||
send_ump(message.data, message.size());
|
||||
return send_ump(message.data, message.size());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_ump(std::span<const uint32_t> message) const
|
||||
stdx::error midi_out::send_ump(std::span<const uint32_t> message) const
|
||||
{
|
||||
send_ump(message.data(), message.size());
|
||||
return send_ump(message.data(), message.size());
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_ump(uint32_t b0) const
|
||||
stdx::error midi_out::send_ump(uint32_t b0) const
|
||||
{
|
||||
send_ump(&b0, 1);
|
||||
return send_ump(&b0, 1);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_ump(uint32_t b0, uint32_t b1) const
|
||||
stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1) const
|
||||
{
|
||||
send_ump(std::to_array({b0, b1}));
|
||||
return send_ump(std::to_array({b0, b1}));
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const
|
||||
stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const
|
||||
{
|
||||
send_ump(std::to_array({b0, b1, b2}));
|
||||
return send_ump(std::to_array({b0, b1, b2}));
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
void midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const
|
||||
stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const
|
||||
{
|
||||
send_ump(std::to_array({b0, b1, b2, b3}));
|
||||
return send_ump(std::to_array({b0, b1, b2, b3}));
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::send_ump(int32_t b0) const
|
||||
{
|
||||
return send_ump(reinterpret_cast<uint32_t*>(&b0), 1);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::send_ump(int64_t b01) const
|
||||
{
|
||||
auto ptr = reinterpret_cast<uint32_t*>(&b01);
|
||||
uint32_t msg[2]{ptr[1], ptr[0]};
|
||||
return send_ump(msg, 2);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::send_ump(uint64_t b01) const
|
||||
{
|
||||
auto ptr = reinterpret_cast<uint32_t*>(&b01);
|
||||
uint32_t msg[2]{ptr[1], ptr[0]};
|
||||
return send_ump(msg, 2);
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE
|
||||
stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t size) const
|
||||
{
|
||||
#if defined(LIBREMIDI_ASSERTIONS)
|
||||
assert(size > 0);
|
||||
#endif
|
||||
|
||||
return impl_->schedule_ump(ts, message, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,37 @@
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
LIBREMIDI_INLINE auto make_observer(auto base_conf, std::any api_conf)
|
||||
|
||||
static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf)
|
||||
{
|
||||
for (const auto& api : available_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
|
||||
return impl_;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& api : available_ump_apis())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
|
||||
return impl_;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf)
|
||||
{
|
||||
std::unique_ptr<observer_api> ptr;
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
@@ -23,30 +53,46 @@ LIBREMIDI_INLINE auto make_observer(auto base_conf, std::any api_conf)
|
||||
return ptr;
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept
|
||||
LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf, std::any api_conf)
|
||||
{
|
||||
for (const auto& api : available_apis())
|
||||
if (!api_conf.has_value())
|
||||
{
|
||||
try
|
||||
{
|
||||
impl_ = make_observer(base_conf, observer_configuration_for(api));
|
||||
return make_observer(base_conf);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
||||
{
|
||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||
{
|
||||
return make_observer(base_conf);
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_observer_impl(base_conf, observer_configuration_for(*api_p));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
|
||||
return {};
|
||||
else
|
||||
return make_observer_impl(base_conf, api_conf);
|
||||
}
|
||||
}
|
||||
|
||||
if (impl_)
|
||||
return;
|
||||
}
|
||||
if (!impl_)
|
||||
impl_ = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||
LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept
|
||||
: impl_{make_observer(base_conf)}
|
||||
{
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf)
|
||||
: impl_{make_observer(base_conf, api_conf)}
|
||||
{
|
||||
if (!impl_)
|
||||
throw midi_exception("Could not open observer for the given API");
|
||||
{
|
||||
error_handler e;
|
||||
e.libremidi_handle_error(base_conf, "Could not open observer for the given api");
|
||||
impl_ = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||
}
|
||||
}
|
||||
|
||||
LIBREMIDI_INLINE observer::observer(observer&& other) noexcept
|
||||
|
||||
@@ -22,22 +22,22 @@ struct LIBREMIDI_EXPORT port_information
|
||||
// PipeWire: unused
|
||||
// WinMM: unused
|
||||
// WinUWP: unused
|
||||
client_handle client;
|
||||
client_handle client = static_cast<client_handle>(-1);
|
||||
|
||||
// ALSA Raw: { uint16_t card, device, sub, padding; }
|
||||
// ALSA Seq: { uint32_t client, uint32_t port; }
|
||||
// CoreMIDI: MidiObjectRef
|
||||
// CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t)
|
||||
// WebMIDI: unused
|
||||
// JACK: jack_port_id_t
|
||||
// PipeWire: port.id
|
||||
// WinMM: unset, identified by port_name
|
||||
// WinUWP: unused
|
||||
port_handle port;
|
||||
port_handle port = static_cast<port_handle>(-1);
|
||||
|
||||
std::string manufacturer;
|
||||
std::string device_name;
|
||||
std::string port_name;
|
||||
std::string display_name;
|
||||
std::string manufacturer{};
|
||||
std::string device_name{};
|
||||
std::string port_name{};
|
||||
std::string display_name{};
|
||||
|
||||
bool operator==(const port_information& other) const noexcept = default;
|
||||
std::strong_ordering operator<=>(const port_information& other) const noexcept = default;
|
||||
@@ -59,12 +59,12 @@ using output_port_callback = std::function<void(const output_port&)>;
|
||||
struct observer_configuration
|
||||
{
|
||||
midi_error_callback on_error{};
|
||||
midi_error_callback on_warning{};
|
||||
midi_warning_callback on_warning{};
|
||||
|
||||
input_port_callback input_added;
|
||||
input_port_callback input_removed;
|
||||
output_port_callback output_added;
|
||||
output_port_callback output_removed;
|
||||
input_port_callback input_added{};
|
||||
input_port_callback input_removed{};
|
||||
output_port_callback output_added{};
|
||||
output_port_callback output_removed{};
|
||||
|
||||
// Observe hardware ports
|
||||
uint32_t track_hardware : 1 = true;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <libremidi/error.hpp>
|
||||
#include <libremidi/input_configuration.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct output_configuration
|
||||
@@ -15,7 +13,7 @@ struct output_configuration
|
||||
best to set the error callback function before opening a port.
|
||||
*/
|
||||
midi_error_callback on_error{};
|
||||
midi_error_callback on_warning{};
|
||||
midi_warning_callback on_warning{};
|
||||
|
||||
//! Timestamp mode for the timestamps passed to schedule_message
|
||||
uint32_t timestamps : 3 = timestamp_mode::Absolute;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user