Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96bd5f30e6 | |||
| bd642576bb | |||
| 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,6 @@
|
||||
build/**
|
||||
.cache
|
||||
viseq
|
||||
viseq.exe
|
||||
compile_commands.json
|
||||
*.json
|
||||
|
||||
@@ -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,57 @@
|
||||
# VISEQ - THE VISUAL SEQUENCER
|
||||
|
||||
## BUILD
|
||||
|
||||
### LINUX
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
### WINDOWS
|
||||
|
||||
You need MingW64.
|
||||
|
||||
```
|
||||
make -f MakefileMingW64
|
||||
```
|
||||
|
||||
## USAGE
|
||||
|
||||
### CHANGING NODES
|
||||
|
||||
Nodes can be moved around, connected and modified. A right-cick opens a context menu with settings. Midi settings can also be changed with the mouse-wheel and modifier keys while hovering a node. All selected nodes can be changed at once. Draw a selectionbox around all nodes that you want to change, or select them by holding shift while clicking or dragging.
|
||||
|
||||
#### MOUSE WHEEL
|
||||
|
||||
- ctrl + wheel -> change midi note
|
||||
- shift + wheel -> change midi velocity
|
||||
- alt + wheel -> change midi gate
|
||||
|
||||
#### DRAGGING NODES
|
||||
|
||||
While dragging nodes one can use ctrl and shift to activate two different snapping modes. Ctrl enables snap to grid, while shift enables snap to next nodes. The grid is divided into 32nd notes. Snap to next nodes shows the time divisions to the closest nodes and can snap to two nodes at once.
|
||||
|
||||
#### SELECTING NODES
|
||||
|
||||
Dragging the mouse on the canvas, creates a selection rectangle. All nodes in the rectanlge will be highlighted. Using the above mentioned modification methods, one can change all selected nodes at once.
|
||||
|
||||
All selected nodes can also be moved together.
|
||||
|
||||
### CONNECTIONS
|
||||
|
||||
Connect nodes by dragging from the orbiting circle to another node. A new connection appears. Connections can be closed for signals in one, or both directions. Click the small green circle and it will block traffic in this direction.
|
||||
|
||||
#### DISTRIBUTION MODES
|
||||
|
||||
Nodes can have three distribution modes, "Geometric", "RR" (Round Robin), and "Random".
|
||||
|
||||
**Geometric Distribution**
|
||||
Geometric distribution means that signals take the straightest path that is available, when more than two paths are available. By moving around nodes so that another connection becomes the straighter path, one can divert signals to other areas of the canvas.
|
||||
|
||||
**Round Robin Distribution**
|
||||
Round Robin means, that every signal that arrives at a node with RR distribution switches the node to the next connection. With multiple signals in one path, this can lead to some beautiful patterns emerging.
|
||||
|
||||
**Random Distribution**
|
||||
Random distribution sends every arriving signal to a random connection.
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#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 */
|
||||
JACK_UMP, /*!< MIDI2 over JACK, type "32 bit raw UMP". Requires PipeWire v1.4+. */
|
||||
PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */
|
||||
|
||||
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
|
||||
} libremidi_api;
|
||||
|
||||
#if __cplusplus
|
||||
}
|
||||
#endif
|
||||
+15
-24
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <libremidi/api-c.h>
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <string_view>
|
||||
@@ -9,28 +10,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.
|
||||
@@ -60,6 +40,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.
|
||||
@@ -69,7 +60,7 @@ inline constexpr libremidi::API default_api() noexcept
|
||||
return API::COREMIDI;
|
||||
#elif defined(_WIN32)
|
||||
return API::WINDOWS_MM;
|
||||
#elif defined(__linux__)
|
||||
#elif defined(LIBREMIDI_ALSA)
|
||||
return API::ALSA_SEQ;
|
||||
#elif defined(__emscripten__)
|
||||
return API::EMSCRIPTEN_WEBMIDI;
|
||||
@@ -88,7 +79,7 @@ inline constexpr libremidi::API default_api() noexcept
|
||||
return API::COREMIDI_UMP;
|
||||
#elif defined(_WIN32)
|
||||
return API::WINDOWS_MIDI_SERVICES;
|
||||
#elif defined(__linux__)
|
||||
#elif defined(LIBREMIDI_ALSA)
|
||||
return API::ALSA_SEQ_UMP;
|
||||
#elif defined(__emscripten__)
|
||||
return API::DUMMY;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
|
||||
@@ -27,10 +29,14 @@
|
||||
|
||||
#if defined(LIBREMIDI_JACK)
|
||||
#include <libremidi/backends/jack.hpp>
|
||||
#include <libremidi/backends/jack_ump.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_PIPEWIRE)
|
||||
#include <libremidi/backends/pipewire.hpp>
|
||||
#if defined(LIBREMIDI_PIPEWIRE_UMP)
|
||||
#include <libremidi/backends/pipewire_ump.hpp>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_COREMIDI)
|
||||
@@ -44,9 +50,10 @@
|
||||
|
||||
#if defined(LIBREMIDI_WINUWP)
|
||||
#include <libremidi/backends/winuwp.hpp>
|
||||
#if __has_include(<winrt/Microsoft.Devices.Midi2.h>)
|
||||
#include <libremidi/backends/winmidi.hpp>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_WINMIDI)
|
||||
#include <libremidi/backends/winmidi.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(LIBREMIDI_EMSCRIPTEN)
|
||||
@@ -55,6 +62,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 +116,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 +159,21 @@ 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
|
||||
#if defined(LIBREMIDI_JACK)
|
||||
,
|
||||
jack_ump::backend{}
|
||||
#endif
|
||||
#if defined(LIBREMIDI_PIPEWIRE_UMP)
|
||||
,
|
||||
pipewire_ump::backend{}
|
||||
#endif
|
||||
,
|
||||
dummy_backend{});
|
||||
@@ -154,7 +184,7 @@ static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);
|
||||
template <typename F>
|
||||
auto for_all_backends(F&& f)
|
||||
{
|
||||
std::apply([&](auto&&... x) { (f(x), ...); }, available_backends);
|
||||
std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
@@ -183,5 +213,49 @@ auto for_backend(libremidi::API api, F&& f)
|
||||
midi2::for_backend(api, f);
|
||||
}
|
||||
|
||||
void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::get_if<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, libremidi::output_api_configuration& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::get_if<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, libremidi::observer_api_configuration& api_conf)
|
||||
{
|
||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||
if (auto conf = std::get_if<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <libremidi/backends/alsa_raw/midi_out.hpp>
|
||||
#include <libremidi/backends/alsa_raw/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
// Credits: greatly inspired from
|
||||
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c
|
||||
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
|
||||
@@ -19,8 +20,8 @@ struct backend
|
||||
using midi_out_configuration = alsa_raw_output_configuration;
|
||||
using midi_observer_configuration = alsa_raw_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::ALSA_RAW;
|
||||
static const constexpr auto name = "alsa_raw";
|
||||
static const constexpr auto display_name = "ALSA (raw)";
|
||||
static const constexpr std::string_view name = "alsa_raw";
|
||||
static const constexpr std::string_view display_name = "ALSA (raw)";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
@@ -61,6 +62,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
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
#include <libremidi/backends/linux/alsa.hpp>
|
||||
#include <libremidi/config.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
#include <libremidi/observer_configuration.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
// Credits: greatly inspired from
|
||||
@@ -32,14 +34,14 @@ struct alsa_raw_port_id
|
||||
};
|
||||
inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept
|
||||
{
|
||||
return (uint64_t(id.card) << 32) + (uint64_t(id.dev) << 16) + id.port;
|
||||
return (uint64_t(id.port) << 32) + (uint64_t(id.dev) << 16) + id.card;
|
||||
}
|
||||
inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept
|
||||
{
|
||||
alsa_raw_port_id ret;
|
||||
ret.card = (p & 0x00'00'FF'FF'00'00'00'00) >> 32;
|
||||
ret.port = (p & 0x00'00'FF'FF'00'00'00'00) >> 32;
|
||||
ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16;
|
||||
ret.port = (p & 0x00'00'00'00'00'00'FF'FF);
|
||||
ret.card = (p & 0x00'00'00'00'00'00'FF'FF);
|
||||
return ret;
|
||||
}
|
||||
static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102);
|
||||
@@ -87,39 +89,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 +120,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 +164,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 +189,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 +203,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 +238,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
|
||||
@@ -208,33 +200,40 @@ public:
|
||||
midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
|
||||
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
if (this->termination_event < 0)
|
||||
if (this->m_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);
|
||||
fds_.push_back(this->m_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(), static_cast<int32_t>(period));
|
||||
if (err == -EAGAIN)
|
||||
continue;
|
||||
else if (err < 0)
|
||||
return;
|
||||
else if (termination_event.ready(fds_.back()))
|
||||
else if (m_termination_event.ready(fds_.back()))
|
||||
break;
|
||||
|
||||
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
|
||||
@@ -245,64 +244,71 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] ssize_t start_thread()
|
||||
[[nodiscard]] stdx::error start_thread()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||
{
|
||||
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
||||
this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
||||
}
|
||||
else
|
||||
{
|
||||
this->thread_ = std::thread{
|
||||
this->m_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
|
||||
m_termination_event.notify();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
m_termination_event.consume(); // Reset to zero
|
||||
|
||||
midi_in_impl::close_port();
|
||||
return midi_in_impl::close_port();
|
||||
}
|
||||
|
||||
std::thread thread_;
|
||||
eventfd_notifier termination_event{};
|
||||
std::thread m_thread;
|
||||
eventfd_notifier m_termination_event{};
|
||||
};
|
||||
|
||||
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_{};
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
#include <libremidi/backends/linux/helpers.hpp>
|
||||
#include <libremidi/backends/linux/udev.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
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
|
||||
@@ -31,30 +32,36 @@ public:
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
|
||||
fds[0] = this->udev;
|
||||
fds[1] = termination_event;
|
||||
fds[2] = timer_fd;
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
m_fds[0] = this->m_udev;
|
||||
m_fds[1] = m_termination_event;
|
||||
m_fds[2] = m_timer_fd;
|
||||
#endif
|
||||
|
||||
// Set-up initial state
|
||||
if (configuration.notify_in_constructor)
|
||||
this->check_devices();
|
||||
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
// Start thread
|
||||
thread = std::thread{[this] { this->run(); }};
|
||||
m_thread = std::thread{[this] { this->run(); }};
|
||||
#endif
|
||||
}
|
||||
|
||||
~observer_impl_base()
|
||||
{
|
||||
termination_event.notify();
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
m_termination_event.notify();
|
||||
|
||||
if (thread.joinable())
|
||||
thread.join();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
#endif
|
||||
}
|
||||
|
||||
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 +74,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)
|
||||
@@ -78,11 +85,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
void run()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (int err = poll(fds, 3, -1); err < 0)
|
||||
if (int err = poll(m_fds, 3, -1); err < 0)
|
||||
{
|
||||
if (err == -EAGAIN)
|
||||
continue;
|
||||
@@ -91,67 +99,86 @@ private:
|
||||
}
|
||||
|
||||
// Check udev
|
||||
if (fds[0].revents & POLLIN)
|
||||
if (m_fds[0].revents & POLLIN)
|
||||
{
|
||||
udev_device* dev = udev.udev.monitor_receive_device(udev.monitor);
|
||||
udev_device* dev = m_udev.udev.monitor_receive_device(m_udev.monitor);
|
||||
if (!dev)
|
||||
continue;
|
||||
|
||||
std::string_view act = udev.udev.device_get_action(dev);
|
||||
std::string_view ss = udev.udev.device_get_subsystem(dev);
|
||||
std::string_view act = m_udev.udev.device_get_action(dev);
|
||||
std::string_view ss = m_udev.udev.device_get_subsystem(dev);
|
||||
if (!act.empty() && ss == "snd_seq")
|
||||
{
|
||||
if (act == "add" || act == "remove")
|
||||
{
|
||||
// Check every 100 milliseconds for ten seconds
|
||||
this->timer_fd.restart(configuration.poll_period.count());
|
||||
timer_check_counts = 100;
|
||||
this->m_timer_fd.restart(configuration.poll_period.count());
|
||||
m_timer_check_counts = 100;
|
||||
}
|
||||
}
|
||||
|
||||
udev.udev.device_unref(dev);
|
||||
m_udev.udev.device_unref(dev);
|
||||
|
||||
fds[0].revents = 0;
|
||||
m_fds[0].revents = 0;
|
||||
}
|
||||
|
||||
// Check eventfd
|
||||
if (fds[1].revents & POLLIN)
|
||||
if (m_fds[1].revents & POLLIN)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Check timer
|
||||
if (fds[2].revents & POLLIN)
|
||||
if (m_fds[2].revents & POLLIN)
|
||||
{
|
||||
if (this->timer_check_counts-- <= 0)
|
||||
this->timer_fd.cancel();
|
||||
fds[2].revents = 0;
|
||||
if (this->m_timer_check_counts-- <= 0)
|
||||
this->m_timer_fd.cancel();
|
||||
m_fds[2].revents = 0;
|
||||
|
||||
check_devices();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <bool Input>
|
||||
auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept
|
||||
auto to_port_info(const alsa_raw::alsa_raw_port_info& p) const noexcept
|
||||
-> std::conditional_t<Input, input_port, output_port>
|
||||
{
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
auto [container, device, type] = get_udev_soundcard_info(m_udev, p.card);
|
||||
#else
|
||||
container_identifier container{};
|
||||
device_identifier device{};
|
||||
libremidi::port_information::port_type type{};
|
||||
#endif
|
||||
std::string display_name = p.subdevice_name;
|
||||
if (display_name.empty())
|
||||
display_name = p.device_name;
|
||||
if (display_name.empty())
|
||||
display_name = p.card_name;
|
||||
if (display_name.empty())
|
||||
display_name = "unknown";
|
||||
|
||||
return {
|
||||
{.client = 0,
|
||||
.container = container,
|
||||
.device = device,
|
||||
.port = raw_to_port_handle({p.card, p.dev, p.sub}),
|
||||
.manufacturer = p.card_name,
|
||||
.device_name = p.device_name,
|
||||
.port_name = p.subdevice_name,
|
||||
.display_name = p.subdevice_name}};
|
||||
.display_name = std::move(display_name),
|
||||
.type = type}};
|
||||
}
|
||||
|
||||
void check_devices()
|
||||
{
|
||||
Enumerator new_devs;
|
||||
Enumerator new_devs{*this};
|
||||
|
||||
new_devs.enumerate_cards();
|
||||
|
||||
for (auto& in_prev : current_inputs)
|
||||
for (auto& in_prev : m_current_inputs)
|
||||
{
|
||||
if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);
|
||||
it == new_devs.inputs.end())
|
||||
@@ -165,8 +192,8 @@ private:
|
||||
|
||||
for (auto& in_next : new_devs.inputs)
|
||||
{
|
||||
if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next);
|
||||
it == current_inputs.end())
|
||||
if (auto it = std::find(m_current_inputs.begin(), m_current_inputs.end(), in_next);
|
||||
it == m_current_inputs.end())
|
||||
{
|
||||
if (auto& cb = this->configuration.input_added)
|
||||
{
|
||||
@@ -175,7 +202,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& out_prev : current_outputs)
|
||||
for (auto& out_prev : m_current_outputs)
|
||||
{
|
||||
if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);
|
||||
it == new_devs.outputs.end())
|
||||
@@ -189,8 +216,8 @@ private:
|
||||
|
||||
for (auto& out_next : new_devs.outputs)
|
||||
{
|
||||
if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next);
|
||||
it == current_outputs.end())
|
||||
if (auto it = std::find(m_current_outputs.begin(), m_current_outputs.end(), out_next);
|
||||
it == m_current_outputs.end())
|
||||
{
|
||||
if (auto& cb = this->configuration.output_added)
|
||||
{
|
||||
@@ -198,38 +225,24 @@ private:
|
||||
}
|
||||
}
|
||||
}
|
||||
current_inputs = std::move(new_devs.inputs);
|
||||
current_outputs = std::move(new_devs.outputs);
|
||||
m_current_inputs = std::move(new_devs.inputs);
|
||||
m_current_outputs = std::move(new_devs.outputs);
|
||||
}
|
||||
|
||||
udev_helper udev{};
|
||||
eventfd_notifier termination_event{};
|
||||
timerfd_timer timer_fd{};
|
||||
int timer_check_counts = 0;
|
||||
std::thread thread;
|
||||
std::vector<alsa_raw_port_info> current_inputs;
|
||||
std::vector<alsa_raw_port_info> current_outputs;
|
||||
|
||||
pollfd fds[3]{};
|
||||
};
|
||||
}
|
||||
#else
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
namespace libremidi::alsa_raw
|
||||
{
|
||||
template <typename Enumerator>
|
||||
struct observer_impl_base : observer_dummy
|
||||
{
|
||||
explicit observer_impl_base(
|
||||
[[maybe_unused]] observer_configuration&& conf,
|
||||
[[maybe_unused]] alsa_raw_observer_configuration&& apiconf)
|
||||
: observer_dummy{dummy_configuration{}, dummy_configuration{}}
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
udev_helper m_udev{};
|
||||
eventfd_notifier m_termination_event{};
|
||||
timerfd_timer m_timer_fd{};
|
||||
int m_timer_check_counts = 0;
|
||||
std::thread m_thread;
|
||||
pollfd m_fds[3]{};
|
||||
#endif
|
||||
|
||||
std::vector<alsa_raw_port_info> m_current_inputs;
|
||||
std::vector<alsa_raw_port_info> m_current_outputs;
|
||||
};
|
||||
}
|
||||
|
||||
namespace libremidi::alsa_raw
|
||||
{
|
||||
struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <libremidi/backends/alsa_raw_ump/observer.hpp>
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::alsa_raw_ump
|
||||
{
|
||||
struct backend
|
||||
@@ -14,12 +16,11 @@ struct backend
|
||||
using midi_observer = observer_impl;
|
||||
using midi_in_configuration = alsa_raw_ump::input_configuration;
|
||||
using midi_out_configuration = alsa_raw_ump::output_configuration;
|
||||
struct midi_observer_configuration : alsa_raw_observer_configuration
|
||||
{
|
||||
};
|
||||
using midi_observer_configuration = alsa_raw_ump::observer_configuration;
|
||||
|
||||
static const constexpr auto API = libremidi::API::ALSA_RAW_UMP;
|
||||
static const constexpr auto name = "alsa_raw_ump";
|
||||
static const constexpr auto display_name = "ALSA (raw UMP)";
|
||||
static const constexpr std::string_view name = "alsa_raw_ump";
|
||||
static const constexpr std::string_view display_name = "ALSA (raw UMP)";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/alsa_raw/config.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
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
|
||||
@@ -17,8 +21,7 @@ struct output_configuration
|
||||
std::optional<chunking_parameters> chunking;
|
||||
};
|
||||
|
||||
struct observer_configuration
|
||||
struct observer_configuration : public alsa_raw_observer_configuration
|
||||
{
|
||||
std::chrono::milliseconds poll_period{100};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
#include <libremidi/config.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
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 +32,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
|
||||
@@ -241,33 +194,39 @@ public:
|
||||
libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
|
||||
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
if (this->termination_event < 0)
|
||||
if (this->m_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();
|
||||
midi_in_impl_threaded::close_port();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
private:
|
||||
void run_thread(auto parse_func)
|
||||
{
|
||||
fds_.push_back(this->termination_event);
|
||||
fds_.push_back(this->m_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(), static_cast<int32_t>(period));
|
||||
if (err == -EAGAIN)
|
||||
continue;
|
||||
else if (err < 0)
|
||||
return;
|
||||
else if (termination_event.ready(fds_.back()))
|
||||
else if (m_termination_event.ready(fds_.back()))
|
||||
break;
|
||||
|
||||
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
|
||||
@@ -278,64 +237,71 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int start_thread()
|
||||
[[nodiscard]] stdx::error start_thread()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||
{
|
||||
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
||||
this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
||||
}
|
||||
else
|
||||
{
|
||||
this->thread_ = std::thread{
|
||||
this->m_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
|
||||
m_termination_event.notify();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
m_termination_event.consume(); // Reset to zero
|
||||
|
||||
midi_in_impl::close_port();
|
||||
return midi_in_impl::close_port();
|
||||
}
|
||||
|
||||
std::thread thread_;
|
||||
eventfd_notifier termination_event{};
|
||||
std::thread m_thread;
|
||||
eventfd_notifier m_termination_event{};
|
||||
};
|
||||
|
||||
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();
|
||||
midi_in_impl_manual::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{};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
||||
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
|
||||
#include <libremidi/backends/linux/alsa.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
#include <libremidi/error_handler.hpp>
|
||||
#include <libremidi/output_configuration.hpp>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
namespace libremidi::alsa_raw_ump
|
||||
{
|
||||
@@ -29,76 +34,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_{};
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
#include <libremidi/backends/alsa_seq/midi_out.hpp>
|
||||
#include <libremidi/backends/alsa_seq/observer.hpp>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::alsa_seq
|
||||
{
|
||||
|
||||
@@ -29,13 +33,16 @@ struct backend
|
||||
using midi_out_configuration = alsa_seq::output_configuration;
|
||||
using midi_observer_configuration = alsa_seq::observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::ALSA_SEQ;
|
||||
static const constexpr auto name = "alsa_seq";
|
||||
static const constexpr auto display_name = "ALSA (sequencer)";
|
||||
static const constexpr std::string_view name = "alsa_seq";
|
||||
static const constexpr std::string_view display_name = "ALSA (sequencer)";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
static const libasound& snd = libasound::instance();
|
||||
return snd.available && snd.seq.available;
|
||||
if (!snd.available || !snd.seq.available)
|
||||
return false;
|
||||
|
||||
return ::access("/dev/snd/seq", F_OK) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -46,19 +46,19 @@ namespace
|
||||
{
|
||||
inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept
|
||||
{
|
||||
return (client << 32) + port;
|
||||
return (port << 32) + client;
|
||||
}
|
||||
|
||||
inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept
|
||||
{
|
||||
int client = p >> 32;
|
||||
int port = p & 0xFFFFFFFF;
|
||||
int port = p >> 32;
|
||||
int client = p & 0xFFFFFFFF;
|
||||
return {client, port};
|
||||
}
|
||||
|
||||
inline void for_all_ports(
|
||||
const libasound& snd, snd_seq_t* seq,
|
||||
std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)> func)
|
||||
const std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)>& func)
|
||||
{
|
||||
snd_seq_client_info_t* cinfo{};
|
||||
snd_seq_client_info_alloca(&cinfo);
|
||||
@@ -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
|
||||
@@ -250,7 +212,7 @@ protected:
|
||||
.count();
|
||||
}
|
||||
|
||||
int process_event(const snd_seq_event_t& ev)
|
||||
int64_t process_event(const snd_seq_event_t& ev)
|
||||
{
|
||||
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||
{
|
||||
@@ -296,18 +258,20 @@ protected:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_events()
|
||||
int64_t process_events()
|
||||
{
|
||||
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||
{
|
||||
snd_seq_event_t* ev{};
|
||||
event_handle handle{snd};
|
||||
int result = 0;
|
||||
while ((result = snd.seq.event_input(seq, &ev)) > 0)
|
||||
int64_t result = 0;
|
||||
if ((result = snd.seq.event_input(seq, &ev)) > 0)
|
||||
{
|
||||
handle.reset(ev);
|
||||
if (int err = process_event(*ev); err < 0)
|
||||
if (auto err = process_event(*ev); err < 0)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -384,89 +353,96 @@ public:
|
||||
midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)
|
||||
: midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
if (this->termination_event < 0)
|
||||
if (this->m_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;
|
||||
}
|
||||
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_in_alsa_threaded() { this->close_port(); }
|
||||
~midi_in_alsa_threaded()
|
||||
{
|
||||
midi_in_alsa_threaded::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(); });
|
||||
this->m_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();
|
||||
m_termination_event.notify();
|
||||
|
||||
if (this->thread.joinable())
|
||||
this->thread.join();
|
||||
if (this->m_thread.joinable())
|
||||
this->m_thread.join();
|
||||
|
||||
termination_event.consume();
|
||||
m_termination_event.consume();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void thread_handler()
|
||||
{
|
||||
int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1;
|
||||
auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd));
|
||||
poll_fds[0] = this->termination_event;
|
||||
poll_fds[0] = this->m_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, static_cast<int32_t>(period)) >= 0)
|
||||
{
|
||||
// We got our stop-thread signal
|
||||
if (termination_event.ready(poll_fds[0]))
|
||||
if (m_termination_event.ready(poll_fds[0]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -474,7 +450,7 @@ private:
|
||||
continue;
|
||||
}
|
||||
|
||||
int res{};
|
||||
int64_t res{};
|
||||
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||
{
|
||||
res = this->process_events();
|
||||
@@ -486,17 +462,13 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
std::thread thread{};
|
||||
eventfd_notifier termination_event{};
|
||||
std::thread m_thread{};
|
||||
eventfd_notifier m_termination_event{};
|
||||
};
|
||||
|
||||
template <typename ConfigurationBase, typename ConfigurationImpl>
|
||||
@@ -525,33 +497,35 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
~midi_in_alsa_manual() { this->close_port(); }
|
||||
~midi_in_alsa_manual() { midi_in_alsa_manual::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)
|
||||
if (snd.midi.event_new(this->m_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,68 +66,74 @@ 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)
|
||||
unsigned int n_src
|
||||
= this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||
if (n_src < 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)
|
||||
if (size > this->m_bufferSize)
|
||||
{
|
||||
this->bufferSize = size;
|
||||
this->m_bufferSize = size;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,18 +147,18 @@ public:
|
||||
// FIXME direct is set but snd_seq_event_output_direct is not used...
|
||||
snd_seq_ev_set_direct(&ev);
|
||||
|
||||
const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below
|
||||
result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev);
|
||||
const int64_t n_bytes = size; // signed to avoir potential overflow with size - offset below
|
||||
result = snd.midi.event_encode(this->coder, message + offset, (long)(n_bytes - 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,16 +166,15 @@ 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:
|
||||
uint64_t bufferSize{32};
|
||||
uint64_t m_bufferSize{32};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
#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>
|
||||
|
||||
#include <bitset>
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
#include <libremidi/backends/linux/udev.hpp>
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace libremidi::alsa_seq
|
||||
@@ -20,12 +24,15 @@ struct port_info
|
||||
int port{};
|
||||
bool isInput{};
|
||||
bool isOutput{};
|
||||
std::optional<int> card{};
|
||||
libremidi::port_information::port_type type{};
|
||||
};
|
||||
|
||||
template <typename ConfigurationImpl>
|
||||
class observer_impl
|
||||
: public observer_api
|
||||
, public alsa_data
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
@@ -40,7 +47,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 +76,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 +87,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,10 +111,20 @@ 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
|
||||
| SND_SEQ_PORT_TYPE_APPLICATION;
|
||||
|
||||
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
|
||||
{
|
||||
p.type = libremidi::port_information::port_type::hardware;
|
||||
ok = true;
|
||||
else if ((tp & SND_SEQ_PORT_TYPE_SOFTWARE) && this->configuration.track_virtual)
|
||||
}
|
||||
else if ((tp & virtual_port) && this->configuration.track_virtual)
|
||||
{
|
||||
p.type = libremidi::port_information::port_type::software;
|
||||
ok = true;
|
||||
}
|
||||
if (!ok)
|
||||
return {};
|
||||
|
||||
@@ -111,7 +134,10 @@ public:
|
||||
if (auto name = snd.seq.port_info_get_name(pinfo))
|
||||
p.port_name = name;
|
||||
|
||||
auto cap = snd.seq.port_info_get_capability(pinfo);
|
||||
if (int card = snd.seq.client_info_get_card(cinfo); card >= 0)
|
||||
p.card = card;
|
||||
|
||||
const auto cap = snd.seq.port_info_get_capability(pinfo);
|
||||
p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ);
|
||||
p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE);
|
||||
|
||||
@@ -119,18 +145,34 @@ public:
|
||||
}
|
||||
|
||||
template <bool Input>
|
||||
auto to_port_info(port_info p) const noexcept
|
||||
auto to_port_info(const port_info& p) const noexcept
|
||||
-> std::conditional_t<Input, input_port, output_port>
|
||||
{
|
||||
static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle));
|
||||
static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle));
|
||||
|
||||
container_identifier container{};
|
||||
device_identifier device{};
|
||||
libremidi::port_information::port_type type = p.type;
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
if (p.card)
|
||||
{
|
||||
auto res = get_udev_soundcard_info(m_udev, *p.card);
|
||||
container = res.container;
|
||||
device = res.path;
|
||||
type = res.type;
|
||||
}
|
||||
#endif
|
||||
return {
|
||||
{.client = std::uintptr_t(this->seq),
|
||||
.container = container,
|
||||
.device = device,
|
||||
.port = alsa_seq::seq_to_port_handle(p.client, p.port),
|
||||
.manufacturer = "",
|
||||
.device_name = p.client_name,
|
||||
.port_name = p.port_name,
|
||||
.display_name = p.port_name}};
|
||||
.display_name = p.port_name,
|
||||
.type = type}};
|
||||
}
|
||||
|
||||
void init_all_ports()
|
||||
@@ -188,7 +230,7 @@ public:
|
||||
if (p.client == snd.seq.client_id(seq))
|
||||
return;
|
||||
|
||||
knownClients_[{p.client, p.port}] = p;
|
||||
m_knownClients[{p.client, p.port}] = p;
|
||||
if (p.isInput && configuration.input_added)
|
||||
{
|
||||
configuration.input_added(to_port_info<true>(p));
|
||||
@@ -202,11 +244,11 @@ public:
|
||||
|
||||
void unregister_port(int client, int port)
|
||||
{
|
||||
auto it = knownClients_.find({client, port});
|
||||
if (it != knownClients_.end())
|
||||
auto it = m_knownClients.find({client, port});
|
||||
if (it != m_knownClients.end())
|
||||
{
|
||||
auto p = it->second;
|
||||
knownClients_.erase(it);
|
||||
m_knownClients.erase(it);
|
||||
|
||||
if (p.isInput && configuration.input_removed)
|
||||
{
|
||||
@@ -251,7 +293,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::pair<int, int>, port_info> knownClients_;
|
||||
std::map<std::pair<int, int>, port_info> m_knownClients;
|
||||
|
||||
#if LIBREMIDI_HAS_UDEV
|
||||
udev_helper m_udev{};
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename ConfigurationImpl>
|
||||
@@ -263,17 +309,20 @@ public:
|
||||
{
|
||||
// Create relevant descriptors
|
||||
auto& snd = alsa_data::snd;
|
||||
const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN);
|
||||
descriptors_.resize(N + 1);
|
||||
snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN);
|
||||
const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN);
|
||||
descriptors_.resize(n + 1);
|
||||
snd.seq.poll_descriptors(this->seq, descriptors_.data(), n, POLLIN);
|
||||
descriptors_.back() = this->termination_event;
|
||||
|
||||
// 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(), static_cast<int32_t>(period));
|
||||
if (err >= 0)
|
||||
{
|
||||
// We got our stop-thread signal
|
||||
|
||||
@@ -61,7 +61,7 @@ struct shared_handler : public libremidi::shared_context
|
||||
{
|
||||
auto clt = std::make_shared<shared_handler>(client_name);
|
||||
|
||||
auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) {
|
||||
auto cb = [client = std::weak_ptr{clt}](libremidi::alsa_seq::poll_parameters params) {
|
||||
if (auto clt = client.lock())
|
||||
{
|
||||
clt->events.push(
|
||||
@@ -74,8 +74,7 @@ struct shared_handler : public libremidi::shared_context
|
||||
auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) {
|
||||
if (auto clt = client.lock())
|
||||
{
|
||||
clt->events.push(
|
||||
{.type = shared_handler::event_type::callback_removed, .payload = std::move(id)});
|
||||
clt->events.push({.type = shared_handler::event_type::callback_removed, .payload = id});
|
||||
clt->queue_event.notify();
|
||||
}
|
||||
return true;
|
||||
@@ -134,7 +133,7 @@ struct shared_handler : public libremidi::shared_context
|
||||
break;
|
||||
}
|
||||
case callback_removed:
|
||||
auto addr = std::move(*std::get_if<snd_seq_addr_t>(&ev.payload));
|
||||
auto addr = *std::get_if<snd_seq_addr_t>(&ev.payload);
|
||||
if (auto index = index_of_address(addr); index >= 0)
|
||||
{
|
||||
addresses.erase(addresses.begin() + index);
|
||||
@@ -146,15 +145,14 @@ struct shared_handler : public libremidi::shared_context
|
||||
}
|
||||
|
||||
// Look for who's ready
|
||||
for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++)
|
||||
for (int64_t i = 0, n = std::ssize(fds) - 2; i < n; i++)
|
||||
{
|
||||
if (fds[i].revents & POLLIN)
|
||||
{
|
||||
// Read alsa event
|
||||
snd_seq_event_t* ev{};
|
||||
event_handle handle{snd};
|
||||
int result = 0;
|
||||
while ((result = snd.seq.event_input(client, &ev)) > 0)
|
||||
while (snd.seq.event_input(client, &ev) > 0)
|
||||
{
|
||||
handle.reset(ev);
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#include <libremidi/backends/alsa_seq_ump/config.hpp>
|
||||
#include <libremidi/backends/alsa_seq_ump/midi_out.hpp>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
template <>
|
||||
@@ -50,13 +54,16 @@ struct backend
|
||||
using midi_out_configuration = alsa_seq_ump::output_configuration;
|
||||
using midi_observer_configuration = alsa_seq_ump::observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP;
|
||||
static const constexpr auto name = "alsa_seq_ump";
|
||||
static const constexpr auto display_name = "ALSA (sequencer, UMP)";
|
||||
static const constexpr std::string_view name = "alsa_seq_ump";
|
||||
static const constexpr std::string_view display_name = "ALSA (sequencer, UMP)";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
static const libasound& snd = libasound::instance();
|
||||
return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available;
|
||||
if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available)
|
||||
return false;
|
||||
|
||||
return ::access("/dev/snd/seq", F_OK) == 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,61 @@ 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)
|
||||
unsigned int n_src
|
||||
= this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||
if (n_src < 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 +126,21 @@ 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;
|
||||
static_assert(std::errc{0} == std::errc{});
|
||||
return std::errc{};
|
||||
};
|
||||
segment_ump_stream(ump_stream, count, write_func, []() {});
|
||||
|
||||
snd.seq.drain_output(this->seq);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int bufferSize{32};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <libremidi/backends/coremidi/midi_out.hpp>
|
||||
#include <libremidi/backends/coremidi/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct core_backend
|
||||
@@ -14,8 +16,8 @@ struct core_backend
|
||||
using midi_out_configuration = coremidi_output_configuration;
|
||||
using midi_observer_configuration = coremidi_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::COREMIDI;
|
||||
static const constexpr auto name = "core";
|
||||
static const constexpr auto display_name = "CoreMIDI";
|
||||
static const constexpr std::string_view name = "core";
|
||||
static const constexpr std::string_view display_name = "CoreMIDI";
|
||||
|
||||
static constexpr inline bool available() noexcept { return true; }
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -68,20 +71,22 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)
|
||||
return static_cast<uint64_t>(res);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name)
|
||||
{
|
||||
CFStringRef str = nullptr;
|
||||
MIDIObjectGetStringProperty(prop, name, &str);
|
||||
return CFString_handle{str};
|
||||
};
|
||||
|
||||
// This function was submitted by Douglas Casey Tucker and apparently
|
||||
// derived largely from PortMidi.
|
||||
inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
||||
{
|
||||
CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);
|
||||
|
||||
static constexpr auto getProp = [](MIDIObjectRef prop) {
|
||||
CFStringRef str = nullptr;
|
||||
MIDIObjectGetStringProperty(prop, kMIDIPropertyName, &str);
|
||||
return CFString_handle{str};
|
||||
};
|
||||
|
||||
// Begin with the endpoint's name.
|
||||
if (auto endpoint_name = getProp(endpoint))
|
||||
if (auto endpoint_name = get_cfstring_property(endpoint, kMIDIPropertyName))
|
||||
{
|
||||
CFStringAppend(result, endpoint_name.get());
|
||||
}
|
||||
@@ -98,7 +103,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
||||
if (CFStringGetLength(result) == 0)
|
||||
{
|
||||
// endpoint name has zero length -- try the entity
|
||||
if (auto entity_name = getProp(entity))
|
||||
if (auto entity_name = get_cfstring_property(entity, kMIDIPropertyName))
|
||||
{
|
||||
CFStringAppend(result, entity_name.get());
|
||||
}
|
||||
@@ -109,7 +114,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
||||
if (device == 0)
|
||||
goto finish;
|
||||
|
||||
if (auto dev_name = getProp(device))
|
||||
if (auto dev_name = get_cfstring_property(device, kMIDIPropertyName))
|
||||
{
|
||||
const auto dev_strlen = CFStringGetLength(dev_name.get());
|
||||
|
||||
@@ -215,15 +220,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 +260,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 +331,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,17 +137,14 @@ 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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (this->port)
|
||||
{
|
||||
MIDIPortDispose(this->port);
|
||||
this->port = 0;
|
||||
}
|
||||
return coremidi_data::close_port();
|
||||
}
|
||||
|
||||
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,22 +69,64 @@ 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 {};
|
||||
|
||||
// Get the MIDI device from the entity
|
||||
libremidi::port_information::port_type type{};
|
||||
libremidi::container_identifier usb_location_id{};
|
||||
libremidi::device_identifier usb_vendor_product{};
|
||||
{
|
||||
MIDIDeviceRef device{};
|
||||
if (MIDIEntityGetDevice(e, &device) == noErr)
|
||||
{
|
||||
if (device)
|
||||
{
|
||||
using enum libremidi::port_information::port_type;
|
||||
SInt32 devid_res{};
|
||||
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr)
|
||||
{
|
||||
type = (libremidi::port_information::port_type)(hardware | usb);
|
||||
usb_location_id = (uint64_t)devid_res;
|
||||
}
|
||||
|
||||
SInt32 vendor_res{};
|
||||
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res)
|
||||
== noErr)
|
||||
{
|
||||
type = (libremidi::port_information::port_type)(hardware | usb);
|
||||
usb_vendor_product = (uint64_t)vendor_res;
|
||||
}
|
||||
|
||||
const auto driver = get_string_property(device, kMIDIPropertyDriverOwner);
|
||||
if (driver == "com.apple.AppleMIDIUSBDriver")
|
||||
type = (libremidi::port_information::port_type)(hardware | usb);
|
||||
if (driver == "com.apple.AppleMIDIBluetoothDriver")
|
||||
type = (libremidi::port_information::port_type)(hardware | bluetooth);
|
||||
if (driver == "com.apple.AppleMIDIIACDriver")
|
||||
type = (libremidi::port_information::port_type)(software);
|
||||
if (driver == "com.apple.AppleMIDIRTPDriver")
|
||||
type = (libremidi::port_information::port_type)(network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::conditional_t<Input, input_port, output_port>{
|
||||
{.client = (std::uintptr_t)this->client,
|
||||
.container = usb_location_id,
|
||||
.device = usb_vendor_product,
|
||||
.port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),
|
||||
.manufacturer = get_string_property(obj, kMIDIPropertyManufacturer),
|
||||
.device_name = get_string_property(obj, kMIDIPropertyModel),
|
||||
.port_name = get_string_property(obj, kMIDIPropertyName),
|
||||
.display_name = get_string_property(obj, kMIDIPropertyDisplayName)}};
|
||||
.display_name = get_string_property(obj, kMIDIPropertyDisplayName),
|
||||
.type = type}};
|
||||
}
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <libremidi/backends/coremidi_ump/midi_out.hpp>
|
||||
#include <libremidi/backends/coremidi_ump/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::coremidi_ump
|
||||
{
|
||||
struct backend
|
||||
@@ -10,12 +12,12 @@ 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";
|
||||
static const constexpr std::string_view name = "core_ump";
|
||||
static const constexpr std::string_view display_name = "CoreMIDI UMP";
|
||||
|
||||
static constexpr inline bool available() noexcept { return true; /* todo? */ }
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
if (this->port)
|
||||
{
|
||||
MIDIPortDispose(this->port);
|
||||
this->port = 0;
|
||||
}
|
||||
return coremidi_data::close_port();
|
||||
}
|
||||
|
||||
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; }
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
class observer_dummy : public observer_api
|
||||
@@ -23,20 +25,34 @@ 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, const input_api_configuration&)
|
||||
{
|
||||
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, const input_api_configuration&)
|
||||
{
|
||||
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 +61,35 @@ 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, const output_api_configuration&)
|
||||
{
|
||||
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
|
||||
@@ -72,8 +101,8 @@ struct dummy_backend
|
||||
using midi_out_configuration = dummy_configuration;
|
||||
using midi_observer_configuration = dummy_configuration;
|
||||
static const constexpr auto API = libremidi::API::DUMMY;
|
||||
static const constexpr auto name = "dummy";
|
||||
static const constexpr auto display_name = "Dummy";
|
||||
static const constexpr std::string_view name = "dummy";
|
||||
static const constexpr std::string_view display_name = "Dummy";
|
||||
|
||||
static constexpr inline bool available() noexcept { return true; }
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <libremidi/backends/emscripten/midi_out.hpp>
|
||||
#include <libremidi/backends/emscripten/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
@@ -19,8 +21,8 @@ struct emscripten_backend
|
||||
using midi_out_configuration = emscripten_output_configuration;
|
||||
using midi_observer_configuration = emscripten_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::WEBMIDI;
|
||||
static const constexpr auto name = "webmidi";
|
||||
static const constexpr auto display_name = "WebMIDI";
|
||||
static const constexpr std::string_view name = "webmidi";
|
||||
static const constexpr std::string_view display_name = "WebMIDI";
|
||||
|
||||
static constexpr inline bool available() noexcept { return true; }
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
m_portNumber = portNumber;
|
||||
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);
|
||||
}
|
||||
midi.close_input(m_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,19 +22,16 @@ 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);
|
||||
|
||||
private:
|
||||
int portNumber_{};
|
||||
int m_portNumber{};
|
||||
|
||||
midi1::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
m_portNumber = portNumber;
|
||||
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)
|
||||
LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (portNumber_ < 0)
|
||||
error<invalid_use_error>(
|
||||
if (m_portNumber < 0)
|
||||
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);
|
||||
m_portNumber, reinterpret_cast<const char*>(message), size);
|
||||
return stdx::error{};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -21,17 +21,13 @@ 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};
|
||||
int m_portNumber{-1};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <libremidi/backends/jack/midi_out.hpp>
|
||||
#include <libremidi/backends/jack/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
//*********************************************************************//
|
||||
// API: UNIX JACK
|
||||
//
|
||||
@@ -22,8 +24,8 @@ struct jack_backend
|
||||
using midi_out_configuration = jack_output_configuration;
|
||||
using midi_observer_configuration = jack_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::JACK_MIDI;
|
||||
static const constexpr auto name = "jack";
|
||||
static const constexpr auto display_name = "JACK";
|
||||
static const constexpr std::string_view name = "jack";
|
||||
static const constexpr std::string_view display_name = "JACK";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
|
||||
@@ -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,19 +1,13 @@
|
||||
#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>
|
||||
|
||||
#include <atomic>
|
||||
#include <semaphore>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -62,16 +56,16 @@ struct jack_client
|
||||
}
|
||||
|
||||
template <bool Input>
|
||||
static auto
|
||||
get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept
|
||||
-> std::vector<std::conditional_t<Input, input_port, output_port>>
|
||||
static auto get_ports(
|
||||
jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags,
|
||||
bool midi2) noexcept -> std::vector<std::conditional_t<Input, input_port, output_port>>
|
||||
{
|
||||
std::vector<std::conditional_t<Input, input_port, output_port>> ret;
|
||||
|
||||
if (!client)
|
||||
return {};
|
||||
|
||||
const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags);
|
||||
const char** ports = jack_get_ports(client, pattern, type, flags);
|
||||
|
||||
if (ports == nullptr)
|
||||
return {};
|
||||
@@ -81,7 +75,11 @@ struct jack_client
|
||||
{
|
||||
// FIXME this does not take into account filtering sw / hw ports
|
||||
auto port = jack_port_by_name(client, ports[i]);
|
||||
ret.push_back(to_port_info<Input>(client, port));
|
||||
if (port)
|
||||
{
|
||||
if (bool(midi2) == bool(jack_port_flags(port) & 0x20))
|
||||
ret.push_back(to_port_info<Input>(client, port));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -140,7 +138,7 @@ struct jack_helpers : jack_client
|
||||
{.token = this_instance,
|
||||
.callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {
|
||||
if (auto pt = p.lock())
|
||||
if (auto ppt = pt->load())
|
||||
if (pt->load())
|
||||
self.process(nf);
|
||||
|
||||
self.thread_lock.check_client_released();
|
||||
@@ -157,6 +155,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 +194,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, const char* type, JackPortFlags flags)
|
||||
{
|
||||
// full name: "client_name:port_name\0"
|
||||
if (portName.empty())
|
||||
@@ -203,29 +208,28 @@ 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)
|
||||
{
|
||||
this->port
|
||||
= jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0);
|
||||
this->port = jack_port_register(this->client, portName.data(), type, flags, 0);
|
||||
}
|
||||
|
||||
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 +239,81 @@ 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);
|
||||
}
|
||||
};
|
||||
|
||||
struct jack_queue
|
||||
{
|
||||
public:
|
||||
static constexpr auto size_sz = sizeof(int32_t);
|
||||
|
||||
jack_queue() = default;
|
||||
jack_queue(const jack_queue&) = delete;
|
||||
jack_queue(jack_queue&&) = delete;
|
||||
jack_queue& operator=(const jack_queue&) = delete;
|
||||
|
||||
jack_queue& operator=(jack_queue&& other) noexcept
|
||||
{
|
||||
ringbuffer = other.ringbuffer;
|
||||
ringbuffer_space = other.ringbuffer_space;
|
||||
other.ringbuffer = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit jack_queue(int64_t sz) noexcept
|
||||
{
|
||||
ringbuffer = jack_ringbuffer_create(sz);
|
||||
ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);
|
||||
}
|
||||
|
||||
~jack_queue() noexcept
|
||||
{
|
||||
if (ringbuffer)
|
||||
jack_ringbuffer_free(ringbuffer);
|
||||
}
|
||||
|
||||
stdx::error write(const unsigned char* data, int64_t sz) const noexcept
|
||||
{
|
||||
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
|
||||
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
|
||||
{
|
||||
int32_t sz;
|
||||
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
|
||||
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
|
||||
{
|
||||
jack_ringbuffer_read_advance(ringbuffer, size_sz);
|
||||
|
||||
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
|
||||
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
|
||||
else
|
||||
jack_ringbuffer_read_advance(ringbuffer, sz);
|
||||
}
|
||||
}
|
||||
|
||||
jack_ringbuffer_t* ringbuffer{};
|
||||
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
|
||||
};
|
||||
|
||||
struct jack_midi1
|
||||
{
|
||||
static constexpr const char* port_type = "8 bit raw midi";
|
||||
};
|
||||
|
||||
struct jack_midi2
|
||||
{
|
||||
static constexpr const char* port_type = "32 bit raw UMP";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
class midi_in_jack final
|
||||
: public midi1::in_api
|
||||
, public jack_helpers
|
||||
, public jack_midi1
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: input_configuration
|
||||
, jack_input_configuration
|
||||
@@ -24,8 +24,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 +41,36 @@ 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, port_type, 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);
|
||||
return create_local_port(*this, portName, port_type, 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
|
||||
@@ -93,8 +96,8 @@ public:
|
||||
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
||||
|
||||
// We have midi events in buffer
|
||||
uint32_t evCount = jack_midi_get_event_count(buff);
|
||||
for (uint32_t j = 0; j < evCount; j++)
|
||||
uint32_t ev_count = jack_midi_get_event_count(buff);
|
||||
for (uint32_t j = 0; j < ev_count; j++)
|
||||
{
|
||||
jack_midi_event_t event{};
|
||||
jack_midi_event_get(&event, buff, j);
|
||||
|
||||
@@ -3,77 +3,16 @@
|
||||
#include <libremidi/backends/jack/helpers.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
#include <semaphore>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct jack_queue
|
||||
{
|
||||
public:
|
||||
static constexpr auto size_sz = sizeof(int32_t);
|
||||
|
||||
jack_queue() = default;
|
||||
jack_queue(const jack_queue&) = delete;
|
||||
jack_queue(jack_queue&&) = delete;
|
||||
jack_queue& operator=(const jack_queue&) = delete;
|
||||
|
||||
jack_queue& operator=(jack_queue&& other) noexcept
|
||||
{
|
||||
ringbuffer = other.ringbuffer;
|
||||
ringbuffer_space = other.ringbuffer_space;
|
||||
other.ringbuffer = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit jack_queue(int64_t sz) noexcept
|
||||
{
|
||||
ringbuffer = jack_ringbuffer_create(sz);
|
||||
ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);
|
||||
}
|
||||
|
||||
~jack_queue() noexcept
|
||||
{
|
||||
if (ringbuffer)
|
||||
jack_ringbuffer_free(ringbuffer);
|
||||
}
|
||||
|
||||
void write(const unsigned char* data, int64_t sz) const noexcept
|
||||
{
|
||||
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
|
||||
return;
|
||||
|
||||
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
|
||||
sched_yield();
|
||||
|
||||
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
|
||||
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
|
||||
}
|
||||
|
||||
void read(void* jack_events) const noexcept
|
||||
{
|
||||
int32_t sz;
|
||||
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
|
||||
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
|
||||
{
|
||||
jack_ringbuffer_read_advance(ringbuffer, size_sz);
|
||||
|
||||
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
|
||||
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
|
||||
else
|
||||
jack_ringbuffer_read_advance(ringbuffer, sz);
|
||||
}
|
||||
}
|
||||
|
||||
jack_ringbuffer_t* ringbuffer{};
|
||||
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
|
||||
};
|
||||
|
||||
class midi_out_jack
|
||||
: public midi1::out_api
|
||||
, public jack_helpers
|
||||
, public jack_midi1
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: output_configuration
|
||||
, jack_output_configuration
|
||||
@@ -87,39 +26,37 @@ 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, port_type, 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);
|
||||
return create_local_port(*this, portName, port_type, 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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,11 +65,17 @@ class midi_out_jack_queued final : public midi_out_jack
|
||||
public:
|
||||
midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf)
|
||||
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||
, queue{configuration.ringbuffer_size}
|
||||
, m_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 +85,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 m_queue.write(message, size);
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
@@ -152,13 +95,13 @@ public:
|
||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||
jack_midi_clear_buffer(buff);
|
||||
|
||||
this->queue.read(buff);
|
||||
this->m_queue.read(buff);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
jack_queue queue;
|
||||
jack_queue m_queue;
|
||||
};
|
||||
|
||||
class midi_out_jack_direct final : public midi_out_jack
|
||||
@@ -168,10 +111,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 +136,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 +156,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{};
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace libremidi
|
||||
class observer_jack final
|
||||
: public observer_api
|
||||
, private jack_client
|
||||
, public jack_midi1
|
||||
, private error_handler
|
||||
{
|
||||
public:
|
||||
@@ -34,7 +35,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)
|
||||
{
|
||||
@@ -48,8 +49,7 @@ public:
|
||||
void initial_callback()
|
||||
{
|
||||
{
|
||||
const char** ports
|
||||
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);
|
||||
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
|
||||
|
||||
if (ports != nullptr)
|
||||
{
|
||||
@@ -80,8 +80,7 @@ public:
|
||||
}
|
||||
|
||||
{
|
||||
const char** ports
|
||||
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
|
||||
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
|
||||
|
||||
if (ports != nullptr)
|
||||
{
|
||||
@@ -119,7 +118,7 @@ public:
|
||||
if (reg)
|
||||
{
|
||||
std::string_view type = jack_port_type(port);
|
||||
if (type != JACK_DEFAULT_MIDI_TYPE)
|
||||
if (type != port_type)
|
||||
return;
|
||||
|
||||
bool physical = flags & JackPortIsPhysical;
|
||||
@@ -199,12 +198,12 @@ public:
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
return get_ports<true>(this->client, nullptr, JackPortIsOutput);
|
||||
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, false);
|
||||
}
|
||||
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
return get_ports<false>(this->client, nullptr, JackPortIsInput);
|
||||
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, false);
|
||||
}
|
||||
|
||||
~observer_jack()
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack_ump/midi_in.hpp>
|
||||
#include <libremidi/backends/jack_ump/midi_out.hpp>
|
||||
#include <libremidi/backends/jack_ump/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
struct backend
|
||||
{
|
||||
using midi_in = midi_in_jack;
|
||||
using midi_out = midi_out_jack;
|
||||
using midi_observer = observer_jack;
|
||||
using midi_in_configuration = jack_ump::input_configuration;
|
||||
using midi_out_configuration = jack_ump::output_configuration;
|
||||
using midi_observer_configuration = jack_ump::observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::JACK_UMP;
|
||||
static const constexpr std::string_view name = "jack_ump";
|
||||
static const constexpr std::string_view display_name = "JACK (UMP)";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
#if LIBREMIDI_WEAKJACK
|
||||
if (WeakJack::instance().available() != 0)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
int major, minor, micro, patch;
|
||||
jack_get_version(&major, &minor, µ, &patch);
|
||||
return (major >= 3 && minor >= 1 && micro >= 4);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/config.hpp>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
struct input_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
jack_client_t* context{};
|
||||
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
};
|
||||
|
||||
struct output_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
jack_client_t* context{};
|
||||
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
|
||||
int32_t ringbuffer_size = 16384;
|
||||
bool direct = false;
|
||||
};
|
||||
|
||||
struct observer_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
jack_client_t* context{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/helpers.hpp>
|
||||
#include <libremidi/backends/jack_ump/config.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
class midi_in_jack final
|
||||
: public midi2::in_api
|
||||
, public jack_helpers
|
||||
, private jack_midi1
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: libremidi::ump_input_configuration
|
||||
, jack_ump::input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit midi_in_jack(
|
||||
libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
auto status = connect(*this);
|
||||
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
|
||||
{
|
||||
midi_in_jack::close_port();
|
||||
|
||||
disconnect(*this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||
|
||||
stdx::error open_port(const input_port& port, std::string_view portName) override
|
||||
{
|
||||
if (auto err
|
||||
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
||||
err != 0 && err != EEXIST)
|
||||
{
|
||||
libremidi_handle_error(
|
||||
configuration,
|
||||
"could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port));
|
||||
return from_errc(err);
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||
}
|
||||
|
||||
stdx::error close_port() override { return do_close_port(); }
|
||||
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
{
|
||||
return 1000 * jack_frames_to_time(client, jack_frame_time(client));
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
{
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = true,
|
||||
.has_samples = true,
|
||||
};
|
||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||
|
||||
// Timing
|
||||
jack_nframes_t current_frames{};
|
||||
jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC
|
||||
jack_time_t next_usecs{};
|
||||
float period_usecs{};
|
||||
jack_get_cycle_times(
|
||||
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
||||
|
||||
// We have midi events in buffer
|
||||
uint32_t ev_count = jack_midi_get_event_count(buff);
|
||||
for (uint32_t j = 0; j < ev_count; j++)
|
||||
{
|
||||
jack_midi_event_t event{};
|
||||
jack_midi_event_get(&event, buff, j);
|
||||
const auto to_ns
|
||||
= [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); };
|
||||
|
||||
m_processing.on_bytes(
|
||||
{(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)},
|
||||
m_processing.timestamp<timestamp_info>(to_ns, event.time));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/helpers.hpp>
|
||||
#include <libremidi/backends/jack_ump/config.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
class midi_out_jack
|
||||
: public midi2::out_api
|
||||
, public jack_helpers
|
||||
, private jack_midi1
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
using midi_api::client_open_;
|
||||
struct
|
||||
: libremidi::output_configuration
|
||||
, jack_ump::output_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
}
|
||||
|
||||
~midi_out_jack() override { }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||
|
||||
stdx::error open_port(const output_port& port, std::string_view portName) override
|
||||
{
|
||||
if (auto err
|
||||
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
// Connecting to the output
|
||||
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
|
||||
err != 0 && err != EEXIST)
|
||||
{
|
||||
libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
|
||||
return from_errc(err);
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view portName) override
|
||||
{
|
||||
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||
}
|
||||
|
||||
stdx::error close_port() override { return do_close_port(); }
|
||||
|
||||
stdx::error set_port_name(std::string_view portName) override
|
||||
{
|
||||
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||
return from_errc(ret);
|
||||
}
|
||||
};
|
||||
|
||||
class midi_out_jack_queued final : public midi_out_jack
|
||||
{
|
||||
public:
|
||||
midi_out_jack_queued(
|
||||
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||
, m_queue{configuration.ringbuffer_size}
|
||||
{
|
||||
auto status = connect(*this);
|
||||
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
|
||||
{
|
||||
midi_out_jack::close_port();
|
||||
|
||||
disconnect(*this);
|
||||
}
|
||||
|
||||
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||
{
|
||||
return m_queue.write((unsigned char*)message, size * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||
jack_midi_clear_buffer(buff);
|
||||
|
||||
this->m_queue.read(buff);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
libremidi::jack_queue m_queue;
|
||||
};
|
||||
|
||||
class midi_out_jack_direct final : public midi_out_jack
|
||||
{
|
||||
public:
|
||||
midi_out_jack_direct(
|
||||
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
auto status = connect(*this);
|
||||
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
|
||||
{
|
||||
midi_out_jack::close_port();
|
||||
|
||||
disconnect(*this);
|
||||
}
|
||||
|
||||
int process(jack_nframes_t nframes)
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||
jack_midi_clear_buffer(buff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||
int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t));
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
int convert_timestamp(int64_t user) const noexcept
|
||||
{
|
||||
switch (configuration.timestamps)
|
||||
{
|
||||
case timestamp_mode::AudioFrame:
|
||||
return static_cast<int>(user);
|
||||
|
||||
default:
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
|
||||
{
|
||||
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||
int ret = jack_midi_event_write(
|
||||
buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t));
|
||||
return from_errc(ret);
|
||||
}
|
||||
|
||||
int buffer_size{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
template <>
|
||||
inline std::unique_ptr<midi_out_api>
|
||||
make<jack_ump::midi_out_jack>(output_configuration&& conf, jack_ump::output_configuration&& api)
|
||||
{
|
||||
if (api.direct)
|
||||
return std::make_unique<jack_ump::midi_out_jack_direct>(std::move(conf), std::move(api));
|
||||
else
|
||||
return std::make_unique<jack_ump::midi_out_jack_queued>(std::move(conf), std::move(api));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/jack/helpers.hpp>
|
||||
#include <libremidi/backends/jack_ump/config.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace libremidi::jack_ump
|
||||
{
|
||||
class observer_jack final
|
||||
: public observer_api
|
||||
, private jack_client
|
||||
, private jack_midi1 // seems like a bug in PipeWire? MIDI 2 flag is set but the MIDI 1 type string is used
|
||||
, private error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: libremidi::observer_configuration
|
||||
, jack_ump::observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer_jack(
|
||||
libremidi::observer_configuration&& conf, jack_ump::observer_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
// Initialize JACK client
|
||||
if (configuration.context)
|
||||
{
|
||||
this->client = configuration.context;
|
||||
set_callbacks();
|
||||
}
|
||||
else
|
||||
{
|
||||
jack_status_t status{};
|
||||
this->client
|
||||
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
|
||||
if (status != jack_status_t{})
|
||||
libremidi_handle_error(configuration, std::to_string((int)status));
|
||||
|
||||
if (this->client != nullptr)
|
||||
{
|
||||
set_callbacks();
|
||||
|
||||
jack_activate(this->client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initial_callback()
|
||||
{
|
||||
{
|
||||
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
|
||||
|
||||
if (ports != nullptr)
|
||||
{
|
||||
int i = 0;
|
||||
while (ports[i] != nullptr)
|
||||
{
|
||||
const auto port = jack_port_by_name(client, ports[i]);
|
||||
const auto flags = jack_port_flags(port);
|
||||
|
||||
if (!(flags & 0x20)) // midi 2 check
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool physical = flags & JackPortIsPhysical;
|
||||
bool ok = configuration.track_any;
|
||||
if (configuration.track_hardware)
|
||||
ok |= physical;
|
||||
if (configuration.track_virtual)
|
||||
ok |= !physical;
|
||||
|
||||
if (ok)
|
||||
{
|
||||
seen_input_ports.insert(ports[i]);
|
||||
if (this->configuration.input_added && configuration.notify_in_constructor)
|
||||
this->configuration.input_added(to_port_info<true>(client, port));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
jack_free(ports);
|
||||
}
|
||||
|
||||
{
|
||||
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
|
||||
|
||||
if (ports != nullptr)
|
||||
{
|
||||
int i = 0;
|
||||
while (ports[i] != nullptr)
|
||||
{
|
||||
const auto port = jack_port_by_name(client, ports[i]);
|
||||
const auto flags = jack_port_flags(port);
|
||||
|
||||
if (!(flags & 0x20)) // midi 2 check
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool physical = flags & JackPortIsPhysical;
|
||||
bool ok = configuration.track_any;
|
||||
if (configuration.track_hardware)
|
||||
ok |= physical;
|
||||
if (configuration.track_virtual)
|
||||
ok |= !physical;
|
||||
|
||||
if (ok)
|
||||
{
|
||||
seen_output_ports.insert(ports[i]);
|
||||
if (this->configuration.output_added && configuration.notify_in_constructor)
|
||||
this->configuration.output_added(to_port_info<false>(client, port));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
jack_free(ports);
|
||||
}
|
||||
}
|
||||
|
||||
void on_port_callback(jack_port_t* port, bool reg)
|
||||
{
|
||||
auto flags = jack_port_flags(port);
|
||||
std::string name = jack_port_name(port);
|
||||
if (reg)
|
||||
{
|
||||
std::string_view type = jack_port_type(port);
|
||||
if (type != port_type)
|
||||
return;
|
||||
|
||||
if (!(flags & 0x20)) // midi 2 check
|
||||
return;
|
||||
|
||||
bool physical = flags & JackPortIsPhysical;
|
||||
bool ok = configuration.track_any;
|
||||
if (configuration.track_hardware)
|
||||
ok |= physical;
|
||||
if (configuration.track_virtual)
|
||||
ok |= !physical;
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
// Note: we keep track of the ports as
|
||||
// when disconnecting, jack_port_type and jack_port_flags aren't correctly
|
||||
// set anymore.
|
||||
|
||||
if (flags & JackPortIsOutput)
|
||||
{
|
||||
seen_input_ports.insert(name);
|
||||
if (this->configuration.input_added)
|
||||
this->configuration.input_added(to_port_info<true>(client, port));
|
||||
}
|
||||
else if (flags & JackPortIsInput)
|
||||
{
|
||||
seen_output_ports.insert(name);
|
||||
if (this->configuration.output_added)
|
||||
this->configuration.output_added(to_port_info<false>(client, port));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())
|
||||
{
|
||||
if (this->configuration.input_removed)
|
||||
this->configuration.input_removed(to_port_info<true>(client, port));
|
||||
seen_input_ports.erase(it);
|
||||
}
|
||||
if (auto it = seen_output_ports.find(name); it != seen_output_ports.end())
|
||||
{
|
||||
if (this->configuration.output_removed)
|
||||
this->configuration.output_removed(to_port_info<false>(client, port));
|
||||
seen_output_ports.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_callbacks()
|
||||
{
|
||||
initial_callback();
|
||||
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
|
||||
jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) {
|
||||
auto& self = *(observer_jack*)arg;
|
||||
if (auto port = jack_port_by_id(self.client, p))
|
||||
{
|
||||
self.on_port_callback(port, r != 0);
|
||||
}
|
||||
}, this);
|
||||
|
||||
jack_set_port_rename_callback(
|
||||
this->client,
|
||||
+[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) {
|
||||
const auto& self = *static_cast<observer_jack*>(arg);
|
||||
|
||||
auto port = jack_port_by_id(self.client, p);
|
||||
if (!port)
|
||||
return;
|
||||
}, this);
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, true);
|
||||
}
|
||||
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, true);
|
||||
}
|
||||
|
||||
~observer_jack()
|
||||
{
|
||||
if (client && !configuration.context)
|
||||
{
|
||||
// If we own the client, deactivate it
|
||||
jack_deactivate(this->client);
|
||||
jack_client_close(this->client);
|
||||
this->client = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seen_input_ports;
|
||||
std::unordered_set<std::string> seen_output_ports;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
#include <libremidi/backends/keyboard/midi_in.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
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 std::string_view name = "keyboard";
|
||||
static const constexpr std::string_view display_name = "Computer keyboard";
|
||||
|
||||
static inline bool available() noexcept { return true; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
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
|
||||
|
||||
// actually based on macOS virtual codes as it seems
|
||||
// impossible to get raw scan codes.
|
||||
// https://eastmanreference.com/complete-list-of-applescript-key-codes
|
||||
static inline const std::map<int, int>& scancode_map_macos(){
|
||||
static const std::map<int, int> ret{
|
||||
{ 0, NOTE_0}, // C0
|
||||
{ 13, NOTE_0 + 1},
|
||||
{ 1, NOTE_0 + 2},
|
||||
{ 14, NOTE_0 + 3},
|
||||
{ 2, NOTE_0 + 4},
|
||||
{ 3, NOTE_0 + 5},
|
||||
{ 17, NOTE_0 + 6},
|
||||
{ 5, NOTE_0 + 7},
|
||||
{ 16, NOTE_0 + 8},
|
||||
{ 4, NOTE_0 + 9},
|
||||
{ 32, NOTE_0 + 10},
|
||||
{ 38, NOTE_0 + 11},
|
||||
{ 40, NOTE_0 + 12}, // C1
|
||||
{ 31, NOTE_0 + 13},
|
||||
{ 37, NOTE_0 + 14},
|
||||
{ 35, NOTE_0 + 15},
|
||||
{ 41, NOTE_0 + 16},
|
||||
{ 39, NOTE_0 + 17},
|
||||
{ 30, NOTE_0 + 18},
|
||||
{ 42, NOTE_0 + 19},
|
||||
|
||||
{ 50, VEL_0 + int(0)},
|
||||
{ 18, VEL_0 + int(1 * 127 / 12)},
|
||||
{ 19, VEL_0 + int(2 * 127 / 12)},
|
||||
{ 20, VEL_0 + int(3 * 127 / 12)},
|
||||
{ 21, VEL_0 + int(4 * 127 / 12)},
|
||||
{ 23, VEL_0 + int(5 * 127 / 12)},
|
||||
{ 22, VEL_0 + int(6 * 127 / 12)},
|
||||
{ 26, VEL_0 + int(7 * 127 / 12)},
|
||||
{ 28, VEL_0 + int(8 * 127 / 12)},
|
||||
{ 25, VEL_0 + int(9 * 127 / 12)},
|
||||
{ 29, VEL_0 + int(10 * 127 / 12)},
|
||||
{ 27, VEL_0 + int(11 * 127 / 12)},
|
||||
{ 24, VEL_0 + int(127)},
|
||||
|
||||
{ 6, OCTAVE_MINUS },
|
||||
{ 7, OCTAVE_PLUS },
|
||||
{ 8, VELOCITY_MINUS },
|
||||
{ 9, VELOCITY_PLUS},
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Use: https://kbdlayout.info/KBDUSX/scancodes
|
||||
// Also valid for windows
|
||||
static inline const std::map<int, int>& scancode_map_linux() {
|
||||
static const std::map<int, int> ret{
|
||||
{ 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},
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
// clang-format on
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
// Note that on Linux with X11 scancodes should be offset by 8 (an X11 legacy quirk)
|
||||
// ex.: pass QKeyEvent::nativeScanCode() - 8 to this API
|
||||
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
|
||||
= [](const scancode_callback&, const scancode_callback&) { };
|
||||
|
||||
std::map<int, int> scancode_map
|
||||
#if defined(__APPLE__)
|
||||
= scancode_map_macos()
|
||||
#else
|
||||
= scancode_map_linux()
|
||||
#endif
|
||||
;
|
||||
};
|
||||
}
|
||||
@@ -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)}
|
||||
{
|
||||
}
|
||||
|
||||
~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
|
||||
{
|
||||
configuration.set_input_scancode_callbacks(
|
||||
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error close_port() 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 = libremidi::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 = libremidi::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;
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <libremidi/backends/linux/dylib_loader.hpp>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/asoundlib.h> // IWYU pragma: export
|
||||
|
||||
#if defined(SND_LIB_VERSION)
|
||||
#if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)
|
||||
@@ -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,120 @@ 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_get_card)
|
||||
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_get_card)
|
||||
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 +362,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 +389,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
|
||||
};
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace libremidi
|
||||
class libpipewire
|
||||
{
|
||||
public:
|
||||
decltype(&::pw_get_library_version) get_library_version{};
|
||||
|
||||
decltype(&::pw_init) init{};
|
||||
decltype(&::pw_deinit) deinit{};
|
||||
|
||||
@@ -67,12 +69,12 @@ public:
|
||||
bool available{true};
|
||||
|
||||
private:
|
||||
dylib_loader library;
|
||||
dylib_loader m_library;
|
||||
|
||||
libpipewire()
|
||||
: library("libpipewire-0.3.so.0")
|
||||
: m_library("libpipewire-0.3.so.0")
|
||||
{
|
||||
if (!library)
|
||||
if (!m_library)
|
||||
{
|
||||
available = false;
|
||||
return;
|
||||
@@ -81,52 +83,59 @@ private:
|
||||
// in terms of regex:
|
||||
// decltype\(&::([a-z_]+)\) [a-z_]+{};
|
||||
// \1 = library.symbol<decltype(&::\1)>("\1");
|
||||
init = library.symbol<decltype(&::pw_init)>("pw_init");
|
||||
deinit = library.symbol<decltype(&::pw_deinit)>("pw_deinit");
|
||||
get_library_version
|
||||
= m_library.symbol<decltype(&::pw_get_library_version)>("pw_get_library_version");
|
||||
|
||||
context_new = library.symbol<decltype(&::pw_context_new)>("pw_context_new");
|
||||
context_connect = library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");
|
||||
context_destroy = library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
|
||||
init = m_library.symbol<decltype(&::pw_init)>("pw_init");
|
||||
deinit = m_library.symbol<decltype(&::pw_deinit)>("pw_deinit");
|
||||
|
||||
core_disconnect = library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");
|
||||
context_new = m_library.symbol<decltype(&::pw_context_new)>("pw_context_new");
|
||||
context_connect = m_library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");
|
||||
context_destroy = m_library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
|
||||
|
||||
core_disconnect = m_library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");
|
||||
|
||||
proxy_add_listener
|
||||
= library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
|
||||
proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");
|
||||
= m_library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
|
||||
proxy_destroy = m_library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");
|
||||
|
||||
main_loop_new = library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");
|
||||
main_loop_destroy = library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");
|
||||
main_loop_quit = library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");
|
||||
main_loop_run = library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");
|
||||
main_loop_new = m_library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");
|
||||
main_loop_destroy
|
||||
= m_library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");
|
||||
main_loop_quit = m_library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");
|
||||
main_loop_run = m_library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");
|
||||
main_loop_get_loop
|
||||
= library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");
|
||||
= m_library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");
|
||||
|
||||
properties_new = library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
|
||||
properties_free = library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
|
||||
properties_get = library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");
|
||||
properties_new = m_library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
|
||||
properties_free = m_library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
|
||||
properties_get = m_library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");
|
||||
|
||||
filter_new_simple = library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");
|
||||
filter_new_simple
|
||||
= m_library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");
|
||||
filter_get_node_id
|
||||
= library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");
|
||||
= m_library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");
|
||||
filter_get_properties
|
||||
= library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");
|
||||
filter_add_port = library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");
|
||||
= m_library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");
|
||||
filter_add_port = m_library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");
|
||||
filter_remove_port
|
||||
= library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");
|
||||
filter_update_properties
|
||||
= library.symbol<decltype(&::pw_filter_update_properties)>("pw_filter_update_properties");
|
||||
= m_library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");
|
||||
filter_update_properties = m_library.symbol<decltype(&::pw_filter_update_properties)>(
|
||||
"pw_filter_update_properties");
|
||||
filter_update_params
|
||||
= library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");
|
||||
filter_get_time = library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");
|
||||
filter_destroy = library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");
|
||||
filter_connect = library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");
|
||||
= m_library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");
|
||||
filter_get_time = m_library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");
|
||||
filter_destroy = m_library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");
|
||||
filter_connect = m_library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");
|
||||
filter_get_dsp_buffer
|
||||
= library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");
|
||||
= m_library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");
|
||||
filter_dequeue_buffer
|
||||
= library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");
|
||||
= m_library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");
|
||||
filter_queue_buffer
|
||||
= library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");
|
||||
filter_flush = library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");
|
||||
= m_library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");
|
||||
filter_flush = m_library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");
|
||||
|
||||
assert(get_library_version);
|
||||
|
||||
assert(init);
|
||||
assert(deinit);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/linux/dylib_loader.hpp>
|
||||
#include <libremidi/backends/linux/helpers.hpp>
|
||||
#include <libremidi/observer_configuration.hpp>
|
||||
|
||||
#include <libudev.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -23,16 +25,32 @@ 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_get_property_value)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_new_from_syspath)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, device_get_parent_with_subsystem_devtype)
|
||||
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_INIT(udev, enumerate_new)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, enumerate_unref)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_is_initialized)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_subsystem)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_sysname)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, enumerate_scan_devices)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, enumerate_get_list_entry)
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_name)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_next)
|
||||
|
||||
LIBREMIDI_SYMBOL_INIT2(udev, new, create)
|
||||
LIBREMIDI_SYMBOL_INIT(udev, unref)
|
||||
}
|
||||
|
||||
static const libudev& instance()
|
||||
@@ -44,16 +62,32 @@ 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_new_from_syspath)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_property_value)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_parent_with_subsystem_devtype)
|
||||
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_DEF(udev, enumerate_new)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, enumerate_unref)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_is_initialized)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_subsystem)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_sysname)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, enumerate_scan_devices)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, enumerate_get_list_entry)
|
||||
|
||||
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_name)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_next)
|
||||
|
||||
LIBREMIDI_SYMBOL_DEF2(udev, new, create)
|
||||
LIBREMIDI_SYMBOL_DEF(udev, unref)
|
||||
};
|
||||
|
||||
struct udev_helper
|
||||
@@ -89,4 +123,72 @@ struct udev_helper
|
||||
udev_monitor* monitor{};
|
||||
};
|
||||
|
||||
struct udev_enumerator
|
||||
{
|
||||
explicit udev_enumerator(const udev_helper& h)
|
||||
: helper{h}
|
||||
{
|
||||
auto& udev = helper.udev;
|
||||
auto& instance = helper.instance;
|
||||
enumerator = udev.enumerate_new(instance);
|
||||
assert(enumerator);
|
||||
}
|
||||
|
||||
~udev_enumerator()
|
||||
{
|
||||
auto& udev = helper.udev;
|
||||
udev.enumerate_unref(enumerator);
|
||||
}
|
||||
|
||||
const udev_helper& helper;
|
||||
udev_enumerate* enumerator{};
|
||||
operator udev_enumerate*() const noexcept { return enumerator; }
|
||||
};
|
||||
|
||||
struct udev_soundcard_info
|
||||
{
|
||||
container_identifier container;
|
||||
device_identifier path;
|
||||
port_information::port_type type{};
|
||||
};
|
||||
|
||||
inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card)
|
||||
{
|
||||
auto& udev = helper.udev;
|
||||
udev_enumerator e{helper};
|
||||
udev.enumerate_add_match_subsystem(e, "sound");
|
||||
udev.enumerate_add_match_sysname(e, ("controlC" + std::to_string(card)).c_str());
|
||||
if (udev.enumerate_scan_devices(e) != 0)
|
||||
return {};
|
||||
|
||||
for (auto entry = udev.enumerate_get_list_entry(e); entry;
|
||||
entry = udev.list_entry_get_next(entry))
|
||||
{
|
||||
auto path = udev.list_entry_get_name(entry);
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
auto dev = udev.device_new_from_syspath(helper.instance, path);
|
||||
if (!dev)
|
||||
continue;
|
||||
|
||||
const auto usb_device
|
||||
= udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
|
||||
|
||||
port_information::port_type type = port_information::port_type::hardware;
|
||||
if (usb_device)
|
||||
type = (port_information::port_type)(type | port_information::port_type::usb);
|
||||
else
|
||||
type = (port_information::port_type)(type | port_information::port_type::pci);
|
||||
|
||||
auto id_path = udev.device_get_property_value(dev, "ID_PATH");
|
||||
if (!id_path)
|
||||
id_path = "";
|
||||
|
||||
auto ret = udev_soundcard_info{.container = id_path, .path = path, .type = type};
|
||||
udev.device_unref(dev);
|
||||
return ret;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,94 @@
|
||||
#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)
|
||||
: m_storage{.ref = maybe_existing}
|
||||
{
|
||||
if (m_storage.ref)
|
||||
{
|
||||
m_ownership = unowned;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::destroy_at(&m_storage.ref);
|
||||
std::construct_at<T>(reinterpret_cast<T*>(m_storage.object));
|
||||
m_ownership = owned;
|
||||
}
|
||||
}
|
||||
|
||||
~optionally_owned()
|
||||
{
|
||||
if (is_owned())
|
||||
std::destroy_at(reinterpret_cast<T*>(m_storage.object));
|
||||
}
|
||||
|
||||
T& get() noexcept
|
||||
{
|
||||
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
|
||||
}
|
||||
const T& get() const noexcept
|
||||
{
|
||||
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_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 m_ownership == owned; }
|
||||
|
||||
private:
|
||||
union
|
||||
{
|
||||
alignas(T) unsigned char object[sizeof(T)];
|
||||
T* ref;
|
||||
} m_storage;
|
||||
enum
|
||||
{
|
||||
owned,
|
||||
unowned
|
||||
} m_ownership{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
#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>
|
||||
|
||||
#include <thread>
|
||||
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 {};
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
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,26 @@
|
||||
#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>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
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 std::string_view name = "network";
|
||||
static const constexpr std::string_view display_name = "Network";
|
||||
|
||||
static inline bool available() noexcept { return true; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#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>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
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 std::string_view name = "network (UMP)";
|
||||
static const constexpr std::string_view display_name = "Network (UMP)";
|
||||
|
||||
static inline bool available() noexcept { return true; }
|
||||
};
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <libremidi/backends/pipewire/midi_out.hpp>
|
||||
#include <libremidi/backends/pipewire/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::pipewire
|
||||
{
|
||||
struct backend
|
||||
@@ -18,8 +20,8 @@ struct backend
|
||||
using midi_out_configuration = pipewire_output_configuration;
|
||||
using midi_observer_configuration = pipewire_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::PIPEWIRE;
|
||||
static const constexpr auto name = "pipewire";
|
||||
static const constexpr auto display_name = "PipeWire";
|
||||
static const constexpr std::string_view name = "pipewire";
|
||||
static const constexpr std::string_view display_name = "PipeWire";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
@@ -78,11 +80,11 @@ struct pipewire_context
|
||||
struct graph
|
||||
{
|
||||
mutable std::mutex mtx;
|
||||
libremidi::hash_map<uint32_t, node> physical_audio;
|
||||
libremidi::hash_map<uint32_t, node> physical_midi;
|
||||
libremidi::hash_map<uint32_t, node> software_audio;
|
||||
libremidi::hash_map<uint32_t, node> software_midi;
|
||||
libremidi::hash_map<uint32_t, port_info> port_cache;
|
||||
libremidi::hash_map<uint64_t, node> physical_audio;
|
||||
libremidi::hash_map<uint64_t, node> physical_midi;
|
||||
libremidi::hash_map<uint64_t, node> software_audio;
|
||||
libremidi::hash_map<uint64_t, node> software_midi;
|
||||
libremidi::hash_map<uint64_t, port_info> port_cache;
|
||||
|
||||
void for_each_port(auto func)
|
||||
{
|
||||
@@ -122,13 +124,13 @@ struct pipewire_context
|
||||
}
|
||||
|
||||
explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)
|
||||
: global_instance{inst}
|
||||
: global_instance{std::move(inst)}
|
||||
, owns_main_loop{true}
|
||||
{
|
||||
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 +141,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;
|
||||
}
|
||||
|
||||
@@ -286,7 +288,7 @@ struct pipewire_context
|
||||
spa_hook_remove(&core_listener);
|
||||
}
|
||||
|
||||
[[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_t in_port)
|
||||
[[nodiscard]] pw_proxy* link_ports(uint64_t out_port, uint64_t in_port)
|
||||
{
|
||||
auto props = pw.properties_new(
|
||||
PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT,
|
||||
@@ -297,7 +299,6 @@ struct pipewire_context
|
||||
|
||||
if (!proxy)
|
||||
{
|
||||
std::cerr << "PipeWire: could not allocate link\n";
|
||||
pw.properties_free(props);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -361,6 +362,8 @@ struct pipewire_context
|
||||
return &this->current_graph.physical_audio[nid];
|
||||
else if (p.format.find("midi") != p.format.npos)
|
||||
return &this->current_graph.physical_midi[nid];
|
||||
else if (p.format.find("UMP") != p.format.npos)
|
||||
return &this->current_graph.physical_midi[nid];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -368,6 +371,8 @@ struct pipewire_context
|
||||
return &this->current_graph.software_audio[nid];
|
||||
else if (p.format.find("midi") != p.format.npos)
|
||||
return &this->current_graph.software_midi[nid];
|
||||
else if (p.format.find("UMP") != p.format.npos)
|
||||
return &this->current_graph.software_midi[nid];
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
@@ -453,12 +458,12 @@ struct pipewire_filter
|
||||
}* port{};
|
||||
|
||||
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop)
|
||||
: loop{loop}
|
||||
: loop{std::move(loop)}
|
||||
{
|
||||
}
|
||||
|
||||
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)
|
||||
: loop{loop}
|
||||
: loop{std::move(loop)}
|
||||
, filter{filter}
|
||||
{
|
||||
}
|
||||
@@ -476,7 +481,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 +503,8 @@ 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, const char* format)
|
||||
{
|
||||
// clang-format off
|
||||
this->port = (struct port*)pw.filter_add_port(
|
||||
@@ -507,15 +513,17 @@ struct pipewire_filter
|
||||
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
|
||||
sizeof(struct port),
|
||||
pw.properties_new(
|
||||
PW_KEY_FORMAT_DSP, "8 bit raw midi",
|
||||
PW_KEY_FORMAT_DSP, format,
|
||||
PW_KEY_PORT_NAME, port_name.data(),
|
||||
nullptr),
|
||||
nullptr, 0);
|
||||
// clang-format on
|
||||
assert(port);
|
||||
if (!port)
|
||||
return std::errc::invalid_argument;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
void set_port_buffer(int bytes)
|
||||
void set_port_buffer(int64_t bytes)
|
||||
{
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder builder;
|
||||
@@ -537,31 +545,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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void start_filter()
|
||||
{
|
||||
if (pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0)
|
||||
if (this->port)
|
||||
{
|
||||
std::cerr << "can't connect\n";
|
||||
return;
|
||||
spa_dict_item items[1] = {
|
||||
SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name.data()),
|
||||
};
|
||||
|
||||
auto properties = SPA_DICT_INIT(items, 1);
|
||||
int ret = pw.filter_update_properties(this->filter, this->port, &properties);
|
||||
return from_errc(ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::errc::not_connected;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] stdx::error start_filter()
|
||||
{
|
||||
if (int ret = pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0); ret < 0)
|
||||
{
|
||||
return from_errc(ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
return stdx::error{};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
#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>
|
||||
#include <libremidi/detail/semaphore.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <semaphore>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
namespace libremidi
|
||||
@@ -55,24 +54,24 @@ 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)
|
||||
{
|
||||
this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);
|
||||
|
||||
pipewire_callback cbs{
|
||||
libremidi::pipewire_callback cbs{
|
||||
.token = this_instance,
|
||||
.callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void {
|
||||
if (auto pt = p.lock())
|
||||
self.process(nf);
|
||||
if (auto pt = p.lock())
|
||||
self.process(nf);
|
||||
|
||||
self.thread_lock.check_client_released();
|
||||
}};
|
||||
self.thread_lock.check_client_released();
|
||||
}};
|
||||
configuration.set_process_func(cbs);
|
||||
}
|
||||
else
|
||||
@@ -90,8 +89,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 +117,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 +133,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 +173,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 +195,8 @@ 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, const char* format)
|
||||
{
|
||||
assert(this->global_context);
|
||||
assert(this->filter);
|
||||
@@ -200,22 +206,22 @@ 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, format);
|
||||
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)
|
||||
void add_callbacks(std::string format, const observer_configuration& conf)
|
||||
{
|
||||
assert(global_context);
|
||||
global_context->on_port_added = [&conf](const pipewire_context::port_info& port) {
|
||||
if (port.format.find("midi") == std::string::npos)
|
||||
global_context->on_port_added = [format, &conf](const pipewire_context::port_info& port) {
|
||||
if (port.format.find(format) == std::string::npos)
|
||||
return;
|
||||
|
||||
bool unfiltered = conf.track_any;
|
||||
@@ -236,8 +242,8 @@ struct pipewire_helpers
|
||||
}
|
||||
};
|
||||
|
||||
global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) {
|
||||
if (port.format.find("midi") == std::string::npos)
|
||||
global_context->on_port_removed = [format, &conf](const pipewire_context::port_info& port) {
|
||||
if (port.format.find(format) == std::string::npos)
|
||||
return;
|
||||
|
||||
bool unfiltered = conf.track_any;
|
||||
@@ -293,12 +299,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 +313,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 +337,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 +347,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 +361,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 +381,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 +389,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 +398,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,30 +436,38 @@ 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<
|
||||
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
||||
static auto get_ports(
|
||||
std::string_view format, 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>>
|
||||
ret;
|
||||
|
||||
{
|
||||
std::lock_guard _{ctx.current_graph.mtx};
|
||||
for (auto& node : ctx.current_graph.physical_midi)
|
||||
{
|
||||
for (auto& p :
|
||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||
if (conf.track_any || conf.track_hardware)
|
||||
for (auto& node : ctx.current_graph.physical_midi)
|
||||
{
|
||||
ret.push_back(to_port_info<Direction>(p));
|
||||
for (auto& port :
|
||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||
{
|
||||
if (port.format.find(format) != std::string::npos)
|
||||
ret.push_back(to_port_info<Direction>(port));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& node : ctx.current_graph.software_midi)
|
||||
{
|
||||
for (auto& p :
|
||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||
|
||||
if (conf.track_any || conf.track_virtual)
|
||||
for (auto& node : ctx.current_graph.software_midi)
|
||||
{
|
||||
ret.push_back(to_port_info<Direction>(p));
|
||||
for (auto& port :
|
||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||
{
|
||||
if (port.format.find(format) != std::string::npos)
|
||||
ret.push_back(to_port_info<Direction>(port));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -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,44 @@ 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, "8 bit raw midi");
|
||||
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, "8 bit raw midi");
|
||||
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(); }
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include <readerwriterqueue.h>
|
||||
|
||||
#include <semaphore>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
class midi_out_pipewire
|
||||
@@ -24,8 +22,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 +41,48 @@ 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, "8 bit raw midi");
|
||||
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, "8 bit raw midi");
|
||||
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)
|
||||
{
|
||||
@@ -113,13 +121,16 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi);
|
||||
int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size());
|
||||
spa_pod_builder_control(&build, static_cast<int32_t>(m.timestamp), SPA_CONTROL_Midi);
|
||||
int res
|
||||
= spa_pod_builder_bytes(&build, m.bytes.data(), static_cast<uint32_t>(m.bytes.size()));
|
||||
|
||||
// Try again next buffer
|
||||
if (res == -ENOSPC)
|
||||
break;
|
||||
|
||||
// Recycle the memory
|
||||
m_gcqueue.enqueue(std::move(m));
|
||||
m_queue.pop();
|
||||
}
|
||||
spa_pod_builder_pop(&build, &f);
|
||||
@@ -141,9 +152,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 +175,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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,48 +24,40 @@ public:
|
||||
{
|
||||
create_context(*this);
|
||||
|
||||
// FIXME notify_in_constructor
|
||||
// FIXME port rename callback
|
||||
#if 0
|
||||
// Initialize PipeWire client
|
||||
if (configuration.context)
|
||||
{
|
||||
this->client = configuration.context;
|
||||
set_callbacks();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
this->add_callbacks(configuration);
|
||||
this->add_callbacks("midi", 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>("midi", 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>("midi", this->configuration, *this->global_context);
|
||||
}
|
||||
|
||||
~observer_pipewire()
|
||||
{
|
||||
stop_thread();
|
||||
destroy_context();
|
||||
#if 0
|
||||
if (client && !configuration.context)
|
||||
{
|
||||
// If we own the client, deactivate it
|
||||
pipewire_deactivate(this->client);
|
||||
pipewire_client_close(this->client);
|
||||
this->client = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seen_input_ports;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
#include <libremidi/backends/linux/pipewire.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/midi_in.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/midi_out.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::pipewire_ump
|
||||
{
|
||||
struct backend
|
||||
{
|
||||
using midi_in = libremidi::pipewire_ump::midi_in_pipewire;
|
||||
using midi_out = libremidi::pipewire_ump::midi_out_pipewire;
|
||||
using midi_observer = libremidi::pipewire_ump::observer_pipewire;
|
||||
using midi_in_configuration = libremidi::pipewire_ump::input_configuration;
|
||||
using midi_out_configuration = libremidi::pipewire_ump::output_configuration;
|
||||
using midi_observer_configuration = libremidi::pipewire_ump::observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::PIPEWIRE_UMP;
|
||||
static const constexpr std::string_view name = "pipewire_ump";
|
||||
static const constexpr std::string_view display_name = "PipeWire (UMP)";
|
||||
|
||||
static inline bool available() noexcept
|
||||
{
|
||||
static const libpipewire& pw = libpipewire::instance();
|
||||
if (!pw.available)
|
||||
return false;
|
||||
const std::string_view version = pw.get_library_version();
|
||||
if (version.size() >= 3)
|
||||
{
|
||||
if (version[0] > '1' || version[2] >= '4')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/pipewire/config.hpp>
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
namespace libremidi::pipewire_ump
|
||||
{
|
||||
struct input_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
pw_main_loop* context{};
|
||||
pw_filter* filter{};
|
||||
std::function<void(libremidi::pipewire_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
};
|
||||
|
||||
struct output_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
pw_main_loop* context{};
|
||||
pw_filter* filter{};
|
||||
std::function<void(libremidi::pipewire_callback)> set_process_func{};
|
||||
std::function<void(int64_t)> clear_process_func{};
|
||||
|
||||
int64_t output_buffer_size{65536};
|
||||
};
|
||||
|
||||
struct observer_configuration
|
||||
{
|
||||
std::string client_name = "libremidi client";
|
||||
|
||||
pw_main_loop* context{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/pipewire/helpers.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
namespace libremidi::pipewire_ump
|
||||
{
|
||||
class midi_in_pipewire final
|
||||
: public midi2::in_api
|
||||
, public pipewire_helpers
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: ump_input_configuration
|
||||
, libremidi::pipewire_ump::input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit midi_in_pipewire(
|
||||
ump_input_configuration&& conf, libremidi::pipewire_ump::input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
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
|
||||
{
|
||||
stop_thread();
|
||||
do_close_port();
|
||||
destroy_filter(*this);
|
||||
destroy_context();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
|
||||
|
||||
stdx::error open_port(const input_port& in_port, std::string_view name) override
|
||||
{
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP");
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
if (auto err = link_ports(*this, in_port); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
start_thread();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view name) override
|
||||
{
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP");
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
start_thread();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
stop_thread();
|
||||
return do_close_port();
|
||||
}
|
||||
|
||||
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(); }
|
||||
|
||||
void process(struct spa_io_position* position)
|
||||
{
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = true,
|
||||
.has_samples = true,
|
||||
};
|
||||
|
||||
assert(this->filter);
|
||||
assert(this->filter->port);
|
||||
const auto b = pw.filter_dequeue_buffer(this->filter->port);
|
||||
if (!b)
|
||||
return;
|
||||
|
||||
const auto buf = b->buffer;
|
||||
const auto d = &buf->datas[0];
|
||||
|
||||
if (d->data == nullptr)
|
||||
return;
|
||||
|
||||
const auto pod
|
||||
= (spa_pod*)spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size);
|
||||
if (!pod)
|
||||
return;
|
||||
if (!spa_pod_is_sequence(pod))
|
||||
return;
|
||||
|
||||
struct spa_pod_control* c{};
|
||||
SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c)
|
||||
{
|
||||
if (c->type != SPA_CONTROL_UMP)
|
||||
continue;
|
||||
|
||||
auto data = (uint8_t*)SPA_POD_BODY(&c->value);
|
||||
auto size = SPA_POD_BODY_SIZE(&c->value);
|
||||
|
||||
const auto to_ns = [=, clk = position->clock] {
|
||||
return 1e9 * ((clk.position + c->offset) / (double)clk.rate.denom);
|
||||
};
|
||||
|
||||
m_processing.on_bytes(
|
||||
{(uint32_t*)data, (uint32_t*)(data + size)},
|
||||
m_processing.timestamp<timestamp_info>(to_ns, c->offset));
|
||||
}
|
||||
|
||||
pw.filter_queue_buffer(this->filter->port, b);
|
||||
}
|
||||
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/pipewire/helpers.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
#include <readerwriterqueue.h>
|
||||
|
||||
namespace libremidi::pipewire_ump
|
||||
{
|
||||
class midi_out_pipewire
|
||||
: public midi2::out_api
|
||||
, public pipewire_helpers
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: libremidi::output_configuration
|
||||
, libremidi::pipewire_ump::output_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
midi_out_pipewire(
|
||||
libremidi::output_configuration&& conf,
|
||||
libremidi::pipewire_ump::output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
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
|
||||
{
|
||||
stop_thread();
|
||||
do_close_port();
|
||||
destroy_filter(*this);
|
||||
destroy_context();
|
||||
client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
|
||||
|
||||
stdx::error open_port(const output_port& out_port, std::string_view name) override
|
||||
{
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP");
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||
|
||||
if (auto err = link_ports(*this, out_port); err != stdx::error{})
|
||||
return err;
|
||||
|
||||
start_thread();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error open_virtual_port(std::string_view name) override
|
||||
{
|
||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP");
|
||||
err != stdx::error{})
|
||||
return err;
|
||||
|
||||
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||
|
||||
start_thread();
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
stop_thread();
|
||||
return do_close_port();
|
||||
}
|
||||
|
||||
stdx::error set_port_name(std::string_view port_name) override
|
||||
{
|
||||
return rename_port(port_name);
|
||||
}
|
||||
|
||||
int process(spa_io_position* pos)
|
||||
{
|
||||
m_process_clock.store(pos->clock.nsec, std::memory_order_relaxed);
|
||||
const auto b = pw.filter_dequeue_buffer(this->filter->port);
|
||||
if (!b)
|
||||
return 1;
|
||||
|
||||
const auto buf = b->buffer;
|
||||
const auto d = &buf->datas[0];
|
||||
|
||||
if (d->data == nullptr)
|
||||
return 1;
|
||||
|
||||
spa_pod_builder build;
|
||||
spa_zero(build);
|
||||
spa_pod_builder_init(&build, d->data, d->maxsize);
|
||||
|
||||
spa_pod_frame f;
|
||||
spa_pod_builder_push_sequence(&build, &f, 0);
|
||||
|
||||
// for all events
|
||||
while (auto m_ptr = m_queue.peek())
|
||||
{
|
||||
auto& m = *m_ptr;
|
||||
|
||||
spa_pod_builder_control(&build, static_cast<int32_t>(m.timestamp), SPA_CONTROL_UMP);
|
||||
int res = spa_pod_builder_bytes(&build, m.data, cmidi2_ump_get_message_size_bytes(m.data));
|
||||
|
||||
// Try again next buffer
|
||||
if (res == -ENOSPC)
|
||||
break;
|
||||
|
||||
// Recycle the memory
|
||||
m_queue.pop();
|
||||
}
|
||||
spa_pod_builder_pop(&build, &f);
|
||||
|
||||
int n_fill_frames = build.state.offset;
|
||||
if (n_fill_frames > 0)
|
||||
{
|
||||
d->chunk->offset = 0;
|
||||
d->chunk->stride = 1;
|
||||
d->chunk->size = n_fill_frames;
|
||||
b->size = n_fill_frames;
|
||||
|
||||
pw.filter_queue_buffer(this->filter->port, b);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pw.filter_flush(this->filter->filter, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
stdx::error send_ump(const uint32_t* message, size_t size) override
|
||||
{
|
||||
// FIXME
|
||||
if (size > 4)
|
||||
return std::errc::message_size;
|
||||
|
||||
libremidi::ump m;
|
||||
std::copy_n(message, size, m.data);
|
||||
m.timestamp = 0;
|
||||
m_queue.enqueue(std::move(m));
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
int convert_timestamp(int64_t user) const noexcept
|
||||
{
|
||||
switch (configuration.timestamps)
|
||||
{
|
||||
case timestamp_mode::AudioFrame:
|
||||
return static_cast<int>(user);
|
||||
|
||||
default:
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
|
||||
{
|
||||
// FIXME
|
||||
if (size > 4)
|
||||
return std::errc::message_size;
|
||||
|
||||
libremidi::ump m;
|
||||
std::copy_n(message, size, m.data);
|
||||
m.timestamp = convert_timestamp(ts);
|
||||
m_queue.enqueue(std::move(m)); // FIXME actually send them scheudled
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
moodycamel::ReaderWriterQueue<libremidi::ump> m_queue;
|
||||
std::atomic_int64_t m_process_clock = 0;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/pipewire/helpers.hpp>
|
||||
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace libremidi::pipewire_ump
|
||||
{
|
||||
class observer_pipewire final
|
||||
: public observer_api
|
||||
, private pipewire_helpers
|
||||
, private error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: libremidi::observer_configuration
|
||||
, libremidi::pipewire_ump::observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer_pipewire(
|
||||
libremidi::observer_configuration&& conf,
|
||||
libremidi::pipewire_ump::observer_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
create_context(*this);
|
||||
|
||||
// FIXME port rename callback
|
||||
{
|
||||
this->add_callbacks("UMP", 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_UMP; }
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
return get_ports<SPA_DIRECTION_OUTPUT>("UMP", this->configuration, *this->global_context);
|
||||
}
|
||||
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
return get_ports<SPA_DIRECTION_INPUT>("UMP", this->configuration, *this->global_context);
|
||||
}
|
||||
|
||||
~observer_pipewire()
|
||||
{
|
||||
stop_thread();
|
||||
destroy_context();
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seen_input_ports;
|
||||
std::unordered_set<std::string> seen_output_ports;
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <libremidi/backends/winmidi/midi_out.hpp>
|
||||
#include <libremidi/backends/winmidi/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::winmidi
|
||||
{
|
||||
struct backend
|
||||
@@ -14,8 +16,8 @@ struct backend
|
||||
using midi_out_configuration = winmidi::output_configuration;
|
||||
using midi_observer_configuration = winmidi::observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES;
|
||||
static const constexpr auto name = "winmidi";
|
||||
static const constexpr auto display_name = "Windows MIDI Services";
|
||||
static const constexpr std::string_view name = "winmidi";
|
||||
static const constexpr std::string_view display_name = "Windows MIDI Services";
|
||||
|
||||
static constexpr inline bool available() noexcept { return true; }
|
||||
};
|
||||
|
||||
@@ -1,19 +1,34 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace winrt::Microsoft::Windows::Devices::Midi2
|
||||
{
|
||||
class MidiSession;
|
||||
class MidiEndpointDeviceInformation;
|
||||
class MidiEndpointConnection;
|
||||
}
|
||||
|
||||
// TODO allow to share midi session and endpoints
|
||||
namespace libremidi::winmidi
|
||||
{
|
||||
|
||||
struct input_configuration
|
||||
{
|
||||
std::string client_name = "libremidi input";
|
||||
winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{};
|
||||
};
|
||||
|
||||
struct output_configuration
|
||||
{
|
||||
std::string client_name = "libremidi output";
|
||||
winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{};
|
||||
};
|
||||
|
||||
struct observer_configuration
|
||||
{
|
||||
std::string client_name = "libremidi observer";
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -3,25 +3,94 @@
|
||||
#define NOMINMAX 1
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
#include <libremidi/detail/memory.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/Microsoft.Windows.Devices.Midi2.h>
|
||||
#include <libremidi/cmidi2.hpp>
|
||||
|
||||
#include <winrt/Microsoft.Devices.Midi2.h>
|
||||
// MinGW support
|
||||
#if !defined(_MSC_VER)
|
||||
namespace Microsoft::Windows::Devices::Midi2::Initialization
|
||||
{
|
||||
struct IMidiClientInitializer;
|
||||
struct MidiClientInitializerUuid;
|
||||
}
|
||||
|
||||
__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::IMidiClientInitializer,
|
||||
0x8087b303, 0xd551, 0xbce2, 0x1e, 0xad, 0xa2, 0x50, 0x0d, 0x50, 0xc5, 0x80);
|
||||
__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::MidiClientInitializerUuid,
|
||||
0xc3263827, 0xc3b0, 0xbdbd, 0x25, 0x00, 0xce, 0x63, 0xa3, 0xf3, 0xf2, 0xc3);
|
||||
#endif
|
||||
|
||||
#include <init/Microsoft.Windows.Devices.Midi2.Initialization.hpp>
|
||||
// clang-format on
|
||||
|
||||
namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2;
|
||||
namespace foundation = winrt::Windows::Foundation;
|
||||
|
||||
namespace libremidi::winmidi
|
||||
{
|
||||
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::Microsoft::Windows::Devices::Midi2;
|
||||
using namespace Windows::Devices::Enumeration;
|
||||
using namespace ::Microsoft::Windows::Devices::Midi2::Initialization;
|
||||
|
||||
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 std::pair<
|
||||
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointDeviceInformation,
|
||||
winrt::Microsoft::Windows::Devices::Midi2::MidiGroupTerminalBlock>
|
||||
get_port(const std::string& device_name, int group_terminal_block)
|
||||
{
|
||||
auto eps = MidiEndpointDeviceInformation::FindAll();
|
||||
for (const auto& ep : eps)
|
||||
{
|
||||
auto str = to_string(ep.EndpointDeviceId());
|
||||
if (str.empty())
|
||||
continue;
|
||||
|
||||
if (iequals(str, device_name))
|
||||
{
|
||||
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||
{
|
||||
if (gp.Number() == group_terminal_block)
|
||||
{
|
||||
return std::make_pair(ep, gp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
struct winmidi_shared_data
|
||||
{
|
||||
std::shared_ptr<MidiDesktopAppSdkInitializer> initializer;
|
||||
bool m_ready{false};
|
||||
winmidi_shared_data()
|
||||
: initializer{libremidi::instance<MidiDesktopAppSdkInitializer>()}
|
||||
, m_ready{
|
||||
initializer && initializer->InitializeSdkRuntime()
|
||||
&& initializer->EnsureServiceAvailable()}
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
#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>
|
||||
|
||||
namespace libremidi::winmidi
|
||||
{
|
||||
|
||||
class midi_in_impl final
|
||||
: public midi2::in_api
|
||||
, public error_handler
|
||||
, public winmidi_shared_data
|
||||
{
|
||||
public:
|
||||
struct
|
||||
@@ -21,23 +22,17 @@ public:
|
||||
explicit midi_in_impl(
|
||||
libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, m_session{
|
||||
configuration.context ? *configuration.context
|
||||
: MidiSession::Create(to_hstring(configuration.client_name))}
|
||||
{
|
||||
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,75 @@ 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 device_id = std::get_if<std::string>(&port.device);
|
||||
if (!device_id)
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
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());
|
||||
auto [ep, gp] = get_port(*device_id, port.port);
|
||||
if (!ep || !gp)
|
||||
return std::errc::address_not_available;
|
||||
|
||||
m_group_filter = port.port - 1;
|
||||
|
||||
// TODO use a MidiGroupEndpointListener for the filtering
|
||||
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
|
||||
|
||||
m_revoke_token = m_endpoint.MessageReceived(
|
||||
[this](
|
||||
const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&,
|
||||
const winrt::Microsoft::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::Microsoft::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
|
||||
|
||||
void close_port() override
|
||||
{
|
||||
#if 0
|
||||
if (port_)
|
||||
const auto& ump = msg.GetMessagePacket();
|
||||
auto pk = msg.PeekFirstWord();
|
||||
if (m_group_filter >= 0)
|
||||
{
|
||||
port_.Close();
|
||||
port_ = nullptr;
|
||||
int group = cmidi2_ump_get_group(&pk);
|
||||
if (group != m_group_filter)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto& b = ump.GetAllWords();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
||||
midi2::input_state_machine m_processing{this->configuration};
|
||||
int m_group_filter = -1;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -10,6 +11,7 @@ namespace libremidi::winmidi
|
||||
class midi_out_impl final
|
||||
: public midi2::out_api
|
||||
, public error_handler
|
||||
, public winmidi_shared_data
|
||||
{
|
||||
public:
|
||||
struct
|
||||
@@ -20,72 +22,75 @@ public:
|
||||
|
||||
midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
, m_session{
|
||||
configuration.context ? *configuration.context
|
||||
: MidiSession::Create(to_hstring(configuration.client_name))}
|
||||
{
|
||||
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 device_id = std::get_if<std::string>(&port.device);
|
||||
if (!device_id)
|
||||
return std::errc::invalid_argument;
|
||||
|
||||
port_ = get(MidiOutPort::FromIdAsync(id));
|
||||
return bool(port_);
|
||||
#endif
|
||||
return true;
|
||||
auto [ep, gp] = get_port(*device_id, port.port);
|
||||
if (!ep || !gp)
|
||||
return std::errc::address_not_available;
|
||||
|
||||
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
|
||||
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::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <libremidi/backends/winmidi/config.hpp>
|
||||
#include <libremidi/backends/winmidi/helpers.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
;
|
||||
|
||||
namespace libremidi::winmidi
|
||||
{
|
||||
struct port_info
|
||||
@@ -11,7 +11,9 @@ struct port_info
|
||||
hstring name;
|
||||
};
|
||||
|
||||
class observer_impl final : public observer_api
|
||||
class observer_impl final
|
||||
: public observer_api
|
||||
, public winmidi_shared_data
|
||||
{
|
||||
public:
|
||||
struct
|
||||
@@ -20,13 +22,17 @@ public:
|
||||
{
|
||||
} configuration;
|
||||
|
||||
MidiSession session;
|
||||
MidiEndpointDeviceWatcher watcher = nullptr;
|
||||
MidiEndpointDeviceWatcher::Added_revoker m_addHandler;
|
||||
MidiEndpointDeviceWatcher::Updated_revoker m_updHandler;
|
||||
MidiEndpointDeviceWatcher::Removed_revoker m_delHandler;
|
||||
std::map<hstring, std::vector<input_port>> m_known_input_devices;
|
||||
std::map<hstring, std::vector<output_port>> m_known_output_devices;
|
||||
std::mutex m_devices_mtx;
|
||||
|
||||
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())}
|
||||
{
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
@@ -36,31 +42,41 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
evTokenOnInputAdded_
|
||||
= internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); });
|
||||
evTokenOnInputRemoved_
|
||||
= internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); });
|
||||
evTokenOnOutputAdded_
|
||||
= internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); });
|
||||
evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved(
|
||||
[this](const port_info& p) { on_output_removed(p); });
|
||||
*/
|
||||
if ((watcher = MidiEndpointDeviceWatcher::Create()))
|
||||
{
|
||||
namespace enumeration = winrt::Windows::Devices::Enumeration;
|
||||
auto addHandler = foundation::TypedEventHandler<
|
||||
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationAddedEventArgs>(
|
||||
this, &observer_impl::on_device_added);
|
||||
auto updHandler = foundation::TypedEventHandler<
|
||||
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationUpdatedEventArgs>(
|
||||
this, &observer_impl::on_device_updated);
|
||||
auto delHandler = foundation::TypedEventHandler<
|
||||
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationRemovedEventArgs>(
|
||||
this, &observer_impl::on_device_removed);
|
||||
|
||||
m_addHandler = watcher.Added(winrt::auto_revoke, addHandler);
|
||||
m_updHandler = watcher.Updated(winrt::auto_revoke, updHandler);
|
||||
m_delHandler = watcher.Removed(winrt::auto_revoke, delHandler);
|
||||
|
||||
watcher.Start();
|
||||
}
|
||||
}
|
||||
|
||||
~observer_impl()
|
||||
{
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
// internalInPortObserver_.PortAdded(evTokenOnInputAdded_);
|
||||
// internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);
|
||||
// internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);
|
||||
// internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);
|
||||
|
||||
m_addHandler.revoke();
|
||||
m_updHandler.revoke();
|
||||
m_delHandler.revoke();
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override
|
||||
@@ -68,29 +84,56 @@ public:
|
||||
return libremidi::API::WINDOWS_MIDI_SERVICES;
|
||||
}
|
||||
|
||||
template <bool Input>
|
||||
auto to_port_info(const DeviceInformation& p) const noexcept
|
||||
-> std::conditional_t<Input, input_port, output_port>
|
||||
static port_information::port_type code_to_type(std::string_view str) noexcept
|
||||
{
|
||||
using enum port_information::port_type;
|
||||
|
||||
if (str.starts_with("KS"))
|
||||
return hardware;
|
||||
if (str == "BLE")
|
||||
return port_information::port_type(hardware | bluetooth);
|
||||
if (str == "VPB" || str == "APP")
|
||||
return software;
|
||||
if (str == "LOOP")
|
||||
return port_information::port_type(software | loopback);
|
||||
if (str.starts_with("NET"))
|
||||
return network;
|
||||
return unknown;
|
||||
}
|
||||
|
||||
template <bool Input>
|
||||
auto to_port_info(const MidiEndpointDeviceInformation& p, const MidiGroupTerminalBlock& gp)
|
||||
const noexcept -> std::conditional_t<Input, input_port, output_port>
|
||||
{
|
||||
const auto& tinfo = p.GetTransportSuppliedInfo();
|
||||
|
||||
return {
|
||||
{.client = 0,
|
||||
.port = 0,
|
||||
.manufacturer = "",
|
||||
.device_name = "",
|
||||
.port_name = to_string(p.Id()),
|
||||
.display_name = to_string(p.Name())}};
|
||||
.container = std::bit_cast<libremidi::uuid>(p.ContainerId()),
|
||||
.device = to_string(p.EndpointDeviceId()),
|
||||
.port = gp.Number(),
|
||||
.manufacturer = to_string(tinfo.ManufacturerName),
|
||||
.device_name = to_string(p.Name()),
|
||||
.port_name = to_string(gp.Name()),
|
||||
.display_name = to_string(gp.Name()) + " " + std::to_string(gp.Number()),
|
||||
.type = code_to_type(to_string(p.GetTransportSuppliedInfo().TransportCode))}};
|
||||
}
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
std::vector<libremidi::input_port> ret;
|
||||
|
||||
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
|
||||
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
|
||||
for (const auto& selectedEndpointInformation : endpointDevices)
|
||||
for (const auto& ep : MidiEndpointDeviceInformation::FindAll())
|
||||
{
|
||||
// FIXME if(has input...)
|
||||
ret.emplace_back(to_port_info(selectedEndpointInformation));
|
||||
if(ep.Name().starts_with(L"Diagnostics")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||
{
|
||||
if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockOutput)
|
||||
ret.emplace_back(to_port_info<true>(ep, gp));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -100,52 +143,132 @@ public:
|
||||
{
|
||||
std::vector<libremidi::output_port> ret;
|
||||
|
||||
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
|
||||
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
|
||||
for (const auto& selectedEndpointInformation : endpointDevices)
|
||||
for (const auto& ep : MidiEndpointDeviceInformation::FindAll())
|
||||
{
|
||||
// FIXME if(has output...)
|
||||
ret.emplace_back(to_port_info(selectedEndpointInformation));
|
||||
if(ep.Name().starts_with(L"Diagnostics")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||
{
|
||||
if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockInput)
|
||||
ret.emplace_back(to_port_info<false>(ep, gp));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void on_input_added(const DeviceInformation& name)
|
||||
// Note: these callbacks are called from some random thread!
|
||||
void on_device_added(
|
||||
const MidiEndpointDeviceWatcher&, const MidiEndpointDeviceInformationAddedEventArgs& result)
|
||||
{
|
||||
if (configuration.input_added)
|
||||
configuration.input_added(to_port_info(name));
|
||||
add_device(result.AddedDevice());
|
||||
}
|
||||
|
||||
void on_input_removed(const DeviceInformation& name)
|
||||
void on_device_updated(
|
||||
const MidiEndpointDeviceWatcher& e,
|
||||
const MidiEndpointDeviceInformationUpdatedEventArgs& result)
|
||||
{
|
||||
// OPTIMIZEME
|
||||
remove_device(result.EndpointDeviceId());
|
||||
|
||||
add_device(
|
||||
MidiEndpointDeviceInformation::CreateFromEndpointDeviceId(result.EndpointDeviceId()));
|
||||
}
|
||||
|
||||
void on_device_removed(
|
||||
const MidiEndpointDeviceWatcher&,
|
||||
const MidiEndpointDeviceInformationRemovedEventArgs& result)
|
||||
{
|
||||
remove_device(result.EndpointDeviceId());
|
||||
}
|
||||
|
||||
void add_device(const MidiEndpointDeviceInformation& ep)
|
||||
{
|
||||
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||
{
|
||||
MidiGroupTerminalBlockDirection direction = gp.Direction();
|
||||
switch (direction)
|
||||
{
|
||||
case MidiGroupTerminalBlockDirection::Bidirectional: {
|
||||
if (configuration.input_added)
|
||||
{
|
||||
auto ip = to_port_info<true>(ep, gp);
|
||||
{
|
||||
std::lock_guard _{m_devices_mtx};
|
||||
m_known_input_devices[ep.EndpointDeviceId()].push_back(ip);
|
||||
}
|
||||
configuration.input_added(std::move(ip));
|
||||
}
|
||||
if (configuration.output_added)
|
||||
{
|
||||
auto op = to_port_info<false>(ep, gp);
|
||||
{
|
||||
std::lock_guard _{m_devices_mtx};
|
||||
m_known_output_devices[ep.EndpointDeviceId()].push_back(op);
|
||||
}
|
||||
configuration.output_added(std::move(op));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MidiGroupTerminalBlockDirection::BlockInput:
|
||||
if (configuration.input_added)
|
||||
{
|
||||
auto ip = to_port_info<true>(ep, gp);
|
||||
{
|
||||
std::lock_guard _{m_devices_mtx};
|
||||
m_known_input_devices[ep.EndpointDeviceId()].push_back(ip);
|
||||
}
|
||||
configuration.input_added(std::move(ip));
|
||||
}
|
||||
break;
|
||||
case MidiGroupTerminalBlockDirection::BlockOutput:
|
||||
if (configuration.output_added)
|
||||
{
|
||||
auto op = to_port_info<false>(ep, gp);
|
||||
{
|
||||
std::lock_guard _{m_devices_mtx};
|
||||
m_known_output_devices[ep.EndpointDeviceId()].push_back(op);
|
||||
}
|
||||
configuration.output_added(std::move(op));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void remove_device(hstring eid)
|
||||
{
|
||||
std::vector<input_port> to_remove_in;
|
||||
std::vector<output_port> to_remove_out;
|
||||
|
||||
{
|
||||
std::lock_guard _{m_devices_mtx};
|
||||
if (auto it = m_known_input_devices.find(eid); it != m_known_input_devices.end())
|
||||
{
|
||||
for (auto& ip : it->second)
|
||||
{
|
||||
to_remove_in.push_back(ip);
|
||||
}
|
||||
m_known_input_devices.erase(it);
|
||||
}
|
||||
if (auto it = m_known_output_devices.find(eid); it != m_known_output_devices.end())
|
||||
{
|
||||
for (auto& op : it->second)
|
||||
{
|
||||
to_remove_out.push_back(op);
|
||||
}
|
||||
m_known_output_devices.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.input_removed)
|
||||
configuration.input_removed(to_port_info(name));
|
||||
}
|
||||
|
||||
void on_output_added(const DeviceInformation& name)
|
||||
{
|
||||
if (configuration.output_added)
|
||||
configuration.output_added(to_port_info(name));
|
||||
}
|
||||
|
||||
void on_output_removed(const DeviceInformation& name)
|
||||
{
|
||||
for (auto& port : to_remove_in)
|
||||
configuration.input_removed(port);
|
||||
if (configuration.output_removed)
|
||||
configuration.output_removed(to_port_info(name));
|
||||
for (auto& port : to_remove_out)
|
||||
configuration.output_removed(port);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <libremidi/backends/winmm/midi_out.hpp>
|
||||
#include <libremidi/backends/winmm/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
// Default for Windows is to add an identifier to the port names; this
|
||||
// flag can be defined (e.g. in your project file) to disable this behaviour.
|
||||
// #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES
|
||||
@@ -28,8 +30,8 @@ struct winmm_backend
|
||||
using midi_out_configuration = winmm_output_configuration;
|
||||
using midi_observer_configuration = winmm_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::WINDOWS_MM;
|
||||
static const constexpr auto name = "winmm";
|
||||
static const constexpr auto display_name = "Windows Multimedia";
|
||||
static const constexpr std::string_view name = "winmm";
|
||||
static const constexpr std::string_view display_name = "Windows Multimedia";
|
||||
|
||||
static constexpr inline bool available() noexcept { return true; }
|
||||
};
|
||||
|
||||
@@ -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,14 +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
|
||||
#include <string>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
@@ -44,10 +38,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 +55,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));
|
||||
@@ -77,5 +71,4 @@ inline void MakeUniqueOutPortName(std::string& deviceName, unsigned int portNumb
|
||||
deviceName += " ";
|
||||
deviceName += std::to_string(x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
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");
|
||||
if(this->client_open_ == stdx::error{})
|
||||
DeleteCriticalSection(&(this->_mutex));
|
||||
}
|
||||
|
||||
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:
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
#include <libremidi/backends/winmm/helpers.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
|
||||
namespace libremidi
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <libremidi/backends/winuwp/midi_out.hpp>
|
||||
#include <libremidi/backends/winuwp/observer.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
struct winuwp_backend
|
||||
@@ -14,8 +16,8 @@ struct winuwp_backend
|
||||
using midi_out_configuration = winuwp_output_configuration;
|
||||
using midi_observer_configuration = winuwp_observer_configuration;
|
||||
static const constexpr auto API = libremidi::API::WINDOWS_UWP;
|
||||
static const constexpr auto name = "winuwp";
|
||||
static const constexpr auto display_name = "Windows UWP";
|
||||
static const constexpr std::string_view name = "winuwp";
|
||||
static const constexpr std::string_view display_name = "Windows UWP";
|
||||
|
||||
static constexpr inline bool available() noexcept { return true; }
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user