99 Commits

Author SHA1 Message Date
Sebastian ce675b1430 virtual ports working - ui needs some work 2026-06-24 00:38:49 +02:00
Sebastian 3de17a3b43 Merge branch 'refactor-orbit-ui' into orbit-ui 2026-06-23 09:52:30 +02:00
Sebastian 5a1f5acae8 window resizing sorted sort of 2026-06-23 01:26:20 +02:00
Sebastian 51cd271b6f libremidi update to v5.4.3 and added virtual midi out as default 2026-06-23 00:05:41 +02:00
Sebastian dc0003232a working again 2026-05-04 22:24:29 +02:00
Sebastian cae4acfdef Merge branch 'refactor-orbit-ui' of ssh://code.sebastiankujas.com:20002/sebastian/viseq into refactor-orbit-ui 2026-05-04 20:34:42 +02:00
Sebastian b8438ade34 merge conflict stuff 2026-05-04 20:30:16 +02:00
Sebastian 657c23702c moved event_queue to state singleton 2026-05-04 18:40:00 +02:00
Sebastian 5886052228 makefile updated 2026-05-04 14:21:48 +02:00
Sebastian d69368322e show ui only on selected nodes 2026-05-04 13:42:31 +02:00
Sebastian d881ef43a3 compiling but mouse interaction is dead 2026-01-12 00:16:36 +01:00
Sebastian 77a227f40d more refactoring and breaking things 2026-01-06 02:44:18 +01:00
Sebastian f086834da6 refctoring WIP 2026-01-05 02:56:30 +01:00
Sebastian @ HfG 2cc6839009 upgrade to libremidi 5.1, raylib and raygui latest 2025-06-13 19:16:35 +02:00
Sebastian @ HfG 62454960d1 cmath define added for windows - failing at coroutine handle void 2025-06-13 19:14:16 +02:00
Sebastian e5f614ce06 More UI tweaks and some refactoring. Nice! 2024-12-29 01:17:44 +01:00
Sebastian 011552c020 UI tweaks done. Code tweaks now 2024-12-28 15:13:16 +01:00
Sebastian 2725806749 UI tweaks 2024-12-28 14:45:57 +01:00
Sebastian bf2f34c902 Exp scale for gate time 2024-12-28 02:10:30 +01:00
Sebastian 0e6f2c7256 Values and indicators match now 2024-12-28 01:02:29 +01:00
Sebastian 9e9c32bff4 orbit ui done 2024-12-25 02:05:29 +01:00
Sebastian 6aef199984 Serialization of handles working! 2024-12-20 01:57:28 +01:00
Sebastian c3543cffce Workaround for vector_shared_ptr_templated serialization mess. 2024-12-19 01:00:38 +01:00
Sebastian 9b75b36c41 Serialization of NodeHandle<T> stays a mystery... 2024-12-16 02:10:58 +01:00
Sebastian 996ed16c01 Better centered text. Pass by reference for midi variable is not going to work with json serialization. 2024-12-15 03:17:10 +01:00
Sebastian 96c2bde0e1 Vector UI on its way! 2024-12-15 01:47:53 +01:00
Sebastian b2ed35a704 ControlNodes init. Feels like a good idea! 2024-12-13 01:15:55 +01:00
Sebastian 7f2d16932f Resize on hover taken out. ModNode introduced but needs a better ui approach. 2024-12-13 00:30:31 +01:00
Sebastian bc8c433e47 Fun with config nodes that modifiy other config nodes. Circular connections lead to a crash. 2024-12-11 01:16:06 +01:00
Sebastian 456528f230 Some tweaks, not much progress. 2024-12-10 00:00:53 +01:00
Sebastian ddf9e640b1 Good working protoype with config nodes and a experimental channel setting ui. 2024-12-09 01:40:13 +01:00
Sebastian 3b08037f92 Config nodes do what they should do now. UX is still off, modifier keys do different things on MidiNodes and ConfigNodes. 2024-12-08 14:21:46 +01:00
Sebastian 558829f619 Config nodes not working yet, but everything is prepared. 2024-12-08 02:25:57 +01:00
Sebastian d9c97be326 Inbetween states. Midi nodes and config nodes use same interface now. 2024-12-07 22:09:06 +01:00
Sebastian 3b0e7e986f Config nodes change midi notes. Better would be midi nodes read from config nodes. 2024-12-07 15:28:33 +01:00
Sebastian 36273ba7ac Working on config nodes. They exist now, but do nothing yet. 2024-12-07 03:44:03 +01:00
Sebastian ade33b0608 Context menu is back, but not quite yet. 2024-12-05 23:39:01 +01:00
Sebastian 638df54ee6 Connecting works again (almost). Connectors need to be copied when splitting edges. 2024-12-05 01:25:00 +01:00
Sebastian b6c2029386 Running again. Next step: making connections work again! 2024-12-05 00:01:42 +01:00
Sebastian 036c6f4723 Running again after weak_ptr refactoring. No connecting of nodes atm. Copy/Paste might not work, too. 2024-12-04 01:43:06 +01:00
Sebastian 1fde652495 More weak_ptr and clipboard shenanigans :) 2024-12-03 11:08:40 +01:00
Sebastian 0040956801 Rework to weak_ptr from lots of shared_ptr. 2024-12-03 01:25:49 +01:00
Sebastian 722848d46b Deleting Connectable from the connectables vector is resulting in segfault. 2024-12-02 00:28:44 +01:00
Sebastian f926b95092 Still the ownership problem. 5, why 5? It should be 3... 2024-12-01 01:45:36 +01:00
Sebastian b6a049ea0d Connector shared_ptr keeps one owner and i don't know where it is! 2024-12-01 00:55:59 +01:00
Sebastian c63ee3eabe Double connections fixed 2024-11-30 12:47:33 +01:00
Sebastian c7e76ae7ac Tidied up main 2024-11-30 03:01:26 +01:00
Sebastian fcc5d74e18 Node interactions working again, graph editing functions not complete yet 2024-11-30 02:54:40 +01:00
Sebastian 635c94e3cf On the road to connector -> connector with edges... 2024-11-28 08:49:35 +01:00
Sebastian 492971ed4f Dragging and selecting works. Copy / Paste does not copy connectors yet. 2024-11-28 01:08:04 +01:00
Sebastian 813958002b Drag selection and snap to grid ok. Deselect not working though... 2024-11-26 01:58:09 +01:00
Sebastian 9c0aac8618 Dragging and selecting works. Dragging the selection not yet. 2024-11-25 00:53:10 +01:00
Sebastian f04838bffa Super simple events added. Seems to be a good way for handling interaction. Modified a few base classes. 2024-11-24 03:08:06 +01:00
Sebastian 62a5c53969 Mini commit 2024-11-23 00:54:31 +01:00
Sebastian b12f1d0f9d Might be a better way to handle hovering, dragging and mouse interaction in general 2024-11-22 00:24:31 +01:00
Sebastian a6b6ff1d16 Introduction of mouse.h for z-index mouse events 2024-11-21 23:58:35 +01:00
Sebastian bfc14d5f6e copy/paste partially. z-index experiments, but not working yet. 2024-11-21 02:03:32 +01:00
Sebastian df91a1b107 Back to work! 2024-11-20 11:13:13 +01:00
Sebastian 5779c93339 Clipboard introduced, but not incorporated yet. Some defines for convenience and better readability. 2024-11-19 01:04:06 +01:00
Sebastian 22842f9deb Drag selection working again - not much progress 2024-11-18 01:57:10 +01:00
Sebastian e9633ff19d Some progress on selecting, hovering but still buggy and not a good design really. 2024-11-17 02:40:31 +01:00
Sebastian 55926a5021 Selection working partially. Needs global z-index hovering to work reliably. 2024-11-16 00:29:01 +01:00
Sebastian 96526f709a preparing for node->connector->edge->connector->node 2024-11-15 02:09:06 +01:00
Sebastian 9cccbab703 refactored from main to canvas.h - basic functionality working 2024-11-15 01:49:10 +01:00
Sebastian ab34081dbb raw_mouse changed to screen_mouse 2024-11-14 23:21:04 +01:00
Sebastian 2e103dc561 global mouse moved to state and removed some GetMousePosition()s. 2024-11-14 01:37:16 +01:00
Sebastian 5d3b72d4ef Major refactoring: State, mouse and more... 2024-11-14 00:55:09 +01:00
Sebastian 300001d385 Global snap to grid. 2024-11-13 15:35:39 +01:00
Sebastian e6f370ba4f Connectables working now. On to more refactoring! 2024-11-13 14:39:46 +01:00
Sebastian 1e0a131169 connecting works, but position is off. addConnector is the problem 2024-11-13 01:58:44 +01:00
Sebastian 96082e1190 connecting does weird things! 2024-11-12 01:42:05 +01:00
Sebastian 31a1a264d4 little progress on the connector idea 2024-11-11 02:11:54 +01:00
Sebastian 4f48aa2d5a dragging works. introducing connectors maybe 2024-11-10 01:56:33 +01:00
Sebastian 71dc08d21f interactable base class 2024-11-09 23:56:40 +01:00
Sebastian cdc0fb7075 working on node ui 2024-11-08 01:43:06 +01:00
Sebastian 529106e815 introduction of config nodes 2024-11-07 00:56:33 +01:00
Sebastian 96a19d55ef signal ui is next! 2024-11-07 00:01:54 +01:00
Sebastian e519b7c758 scale groups of nodes is so cool! 2024-11-06 15:35:25 +01:00
Sebastian 95620a5f07 two node snap for triangles 2024-11-06 02:28:37 +01:00
Sebastian @ HfG 9878b7b2a4 alsa lib removed 2024-11-05 17:44:12 +01:00
Sebastian 4a346350f4 static id fixed 2024-11-05 00:51:25 +01:00
Sebastian @ HfG dc7eae1931 changed the name! 2024-11-04 19:28:12 +01:00
Sebastian 0c797807dd id is not set correctly, fix needed 2024-11-04 09:53:08 +01:00
Sebastian 374efbe47c state save/load working! signal pause/play button added, and speed control is now better. 2024-11-04 01:00:59 +01:00
Sebastian bfd8bb192e partial reload from json working, but edges make problems 2024-11-03 15:15:42 +01:00
Sebastian 7bdd68bdcc json is loading, but does nothing yet 2024-11-03 03:48:49 +01:00
Sebastian a4ea20b2b3 still in the middle of json export, last attempt did not work 2024-11-02 23:12:38 +01:00
Sebastian 522e9d456a json serialization beginnings, but still much to do 2024-11-02 15:52:16 +01:00
Sebastian 70d0a186a4 split edge: check, copy paste: check with a little bug 2024-11-02 02:06:26 +01:00
Sebastian 41cad27806 copy paste mysteries with ghost nodes and disappearing connections 2024-11-01 14:46:51 +01:00
Sebastian a10335ed47 polar snapping now very goood! 2024-11-01 14:08:27 +01:00
Sebastian 19b2c9549f conversion to smart pointers done but still buggy 2024-11-01 02:38:47 +01:00
Sebastian c0eb7c98ba heavy restructuring with smart pointers, not finished 2024-10-31 01:43:00 +01:00
Sebastian f7a13e48b7 fishy loss of changes here... 2024-10-30 14:55:24 +01:00
Sebastian ba5874837c connection split button added, but no functionality yes 2024-10-30 11:27:37 +01:00
Sebastian c0f5138765 a little clean up 2024-10-30 02:03:03 +01:00
Sebastian ad7b0e4524 GEO mode for signals takes direction of next connection into account 2024-10-30 02:00:40 +01:00
Sebastian c63eeed1b4 polar snapping (almost bug free) 2024-10-29 00:50:39 +01:00
Sebastian fc48c2e7ed merge win32 2024-10-28 21:52:28 +01:00
235 changed files with 43101 additions and 4355 deletions
+3
View File
@@ -0,0 +1,3 @@
CompileFlags:
Add: [-I/home/sebastian/code/viseq/include, -std=c++23]
+2
View File
@@ -1,4 +1,6 @@
build/** build/**
.cache .cache
viseq viseq
viseq.exe
compile_commands.json compile_commands.json
*.json
+2924
View File
File diff suppressed because it is too large Load Diff
+13 -24
View File
@@ -1,32 +1,21 @@
CXX = g++ .PHONY: clean
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1
CXXINC = -Iinclude
LDLIBS = -lraylib -lasound -pthread CXX := g++
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib CXXFLAGS := -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1
CXXINC := -Iinclude -isysteminclude
hdr_files = ${wildcard src/*.h} LDLIBS := -lraylib -lasound -llibremidi -pthread
LDFLAGS := -Llib -L/usr/lib -L/usr/local/lib
src_files = ${wildcard src/*.cpp} SRCS := $(wildcard src/*.cpp)
src_files += ${wildcard include/libremidi/*.cpp} OBJECTS := $(patsubst src/%.cpp,build/%.o, $(SRCS))
all: viseq viseq: $(OBJECTS)
$(CXX) $(OBJECTS) -o $@ $(LDFLAGS) $(LDLIBS)
viseq: $(src_files) $(hdr_files) $(OBJECTS): build/%.o : src/%.cpp
mkdir -p build @mkdir -p build
$(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS) $(CXX) -c $< -o $@ $(CXXFLAGS) $(CXXINC)
$(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: clean:
rm -f build/* rm -f build/*
+13 -4
View File
@@ -1,13 +1,15 @@
#pragma once #pragma once
#ifndef LIBREMIDI_API_C_H
#define LIBREMIDI_API_C_H
#if __cplusplus #if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD)
extern "C" { extern "C" {
#endif #endif
//! MIDI API specifier arguments. //! MIDI API specifier arguments.
//! To get information on which feature is supported by each back-end, check their backend file //! To get information on which feature is supported by each back-end, check their backend file
//! in e.g. backends/winmm.hpp, etc. //! in e.g. backends/winmm.hpp, etc.
typedef enum libremidi_api enum libremidi_api
{ {
UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */ UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */
@@ -22,6 +24,8 @@ typedef enum libremidi_api
PIPEWIRE, /*!< PipeWire */ PIPEWIRE, /*!< PipeWire */
KEYBOARD, /*!< Computer keyboard input */ KEYBOARD, /*!< Computer keyboard input */
NETWORK, /*!< MIDI over IP */ NETWORK, /*!< MIDI over IP */
ANDROID_AMIDI, /*!< Android AMidi API */
KDMAPI, /*!< OmniMIDI KDMAPI (Windows) */
// MIDI 2.0 APIs // MIDI 2.0 APIs
ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */ ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */
@@ -30,10 +34,15 @@ typedef enum libremidi_api
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */ WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
KEYBOARD_UMP, /*!< Computer keyboard input */ KEYBOARD_UMP, /*!< Computer keyboard input */
NETWORK_UMP, /*!< MIDI2 over IP */ 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. */ DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
} libremidi_api; };
#if __cplusplus typedef enum libremidi_api libremidi_api;
#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD)
} }
#endif #endif
#endif
+5 -7
View File
@@ -2,11 +2,10 @@
#include <libremidi/api-c.h> #include <libremidi/api-c.h>
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <any>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
//! MIDI API specifier arguments. //! MIDI API specifier arguments.
//! To get information on which feature is supported by each back-end, check their backend file //! To get information on which feature is supported by each back-end, check their backend file
@@ -31,9 +30,6 @@ LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;
*/ */
LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept; LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;
LIBREMIDI_EXPORT
libremidi::API midi_api(const std::any& conf);
//! A static function to determine the current version. //! A static function to determine the current version.
LIBREMIDI_EXPORT std::string_view get_version() noexcept; LIBREMIDI_EXPORT std::string_view get_version() noexcept;
@@ -64,10 +60,12 @@ inline constexpr libremidi::API default_api() noexcept
return API::COREMIDI; return API::COREMIDI;
#elif defined(_WIN32) #elif defined(_WIN32)
return API::WINDOWS_MM; return API::WINDOWS_MM;
#elif defined(__linux__) #elif defined(LIBREMIDI_ALSA)
return API::ALSA_SEQ; return API::ALSA_SEQ;
#elif defined(__emscripten__) #elif defined(__emscripten__)
return API::EMSCRIPTEN_WEBMIDI; return API::EMSCRIPTEN_WEBMIDI;
#elif defined(LIBREMIDI_ANDROID)
return API::ANDROID_AMIDI;
#else #else
return API::DUMMY; return API::DUMMY;
#endif #endif
@@ -83,7 +81,7 @@ inline constexpr libremidi::API default_api() noexcept
return API::COREMIDI_UMP; return API::COREMIDI_UMP;
#elif defined(_WIN32) #elif defined(_WIN32)
return API::WINDOWS_MIDI_SERVICES; return API::WINDOWS_MIDI_SERVICES;
#elif defined(__linux__) #elif defined(LIBREMIDI_ALSA)
return API::ALSA_SEQ_UMP; return API::ALSA_SEQ_UMP;
#elif defined(__emscripten__) #elif defined(__emscripten__)
return API::DUMMY; return API::DUMMY;
+40 -11
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <any>
#include <tuple> #include <tuple>
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>) #if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
@@ -30,10 +29,16 @@
#if defined(LIBREMIDI_JACK) #if defined(LIBREMIDI_JACK)
#include <libremidi/backends/jack.hpp> #include <libremidi/backends/jack.hpp>
#if defined(LIBREMIDI_HAS_JACK_GET_VERSION)
#include <libremidi/backends/jack_ump.hpp>
#endif
#endif #endif
#if defined(LIBREMIDI_PIPEWIRE) #if defined(LIBREMIDI_PIPEWIRE)
#include <libremidi/backends/pipewire.hpp> #include <libremidi/backends/pipewire.hpp>
#if defined(LIBREMIDI_PIPEWIRE_UMP)
#include <libremidi/backends/pipewire_ump.hpp>
#endif
#endif #endif
#if defined(LIBREMIDI_COREMIDI) #if defined(LIBREMIDI_COREMIDI)
@@ -45,6 +50,10 @@
#include <libremidi/backends/winmm.hpp> #include <libremidi/backends/winmm.hpp>
#endif #endif
#if defined(LIBREMIDI_KDMAPI)
#include <libremidi/backends/kdmapi.hpp>
#endif
#if defined(LIBREMIDI_WINUWP) #if defined(LIBREMIDI_WINUWP)
#include <libremidi/backends/winuwp.hpp> #include <libremidi/backends/winuwp.hpp>
#endif #endif
@@ -68,7 +77,11 @@
#include <libremidi/backends/network_ump.hpp> #include <libremidi/backends/network_ump.hpp>
#endif #endif
namespace libremidi #if defined(LIBREMIDI_ANDROID)
#include <libremidi/backends/android/android.hpp>
#endif
NAMESPACE_LIBREMIDI
{ {
// The order here will control the order of the API search in // The order here will control the order of the API search in
// the constructor. // the constructor.
@@ -80,7 +93,7 @@ constexpr auto make_tl(unused, Args...)
namespace midi1 namespace midi1
{ {
static constexpr auto available_backends = make_tl( LIBREMIDI_STATIC constexpr auto available_backends = make_tl(
0 0
#if defined(LIBREMIDI_ALSA) #if defined(LIBREMIDI_ALSA)
, ,
@@ -98,6 +111,10 @@ static constexpr auto available_backends = make_tl(
, ,
winmm_backend{} winmm_backend{}
#endif #endif
#if defined(LIBREMIDI_KDMAPI)
,
kdmapi_backend{}
#endif
#if defined(LIBREMIDI_WINUWP) #if defined(LIBREMIDI_WINUWP)
, ,
winuwp_backend{} winuwp_backend{}
@@ -121,6 +138,10 @@ static constexpr auto available_backends = make_tl(
#if defined(LIBREMIDI_NETWORK) #if defined(LIBREMIDI_NETWORK)
, ,
net::backend{} net::backend{}
#endif
#if defined(LIBREMIDI_ANDROID)
,
android::backend{}
#endif #endif
, ,
dummy_backend{}); dummy_backend{});
@@ -146,7 +167,7 @@ auto for_backend(libremidi::API api, F&& f)
namespace midi2 namespace midi2
{ {
static constexpr auto available_backends = make_tl( LIBREMIDI_STATIC constexpr auto available_backends = make_tl(
0 0
#if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP #if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP
, ,
@@ -163,6 +184,14 @@ static constexpr auto available_backends = make_tl(
#if defined(LIBREMIDI_NETWORK) #if defined(LIBREMIDI_NETWORK)
, ,
net_ump::backend{} net_ump::backend{}
#endif
#if defined(LIBREMIDI_JACK) && defined(LIBREMIDI_HAS_JACK_GET_VERSION)
,
jack_ump::backend{}
#endif
#if defined(LIBREMIDI_PIPEWIRE_UMP)
,
pipewire_ump::backend{}
#endif #endif
, ,
dummy_backend{}); dummy_backend{});
@@ -173,7 +202,7 @@ static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);
template <typename F> template <typename F>
auto for_all_backends(F&& 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> template <typename F>
@@ -202,10 +231,10 @@ auto for_backend(libremidi::API api, F&& f)
midi2::for_backend(api, f); midi2::for_backend(api, f);
} }
void for_input_configuration(auto f, std::any& api_conf) void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf)
{ {
auto from_api = [&]<typename T>(T& /*backend*/) mutable { auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf)) if (auto conf = get_if<typename T::midi_in_configuration>(&api_conf))
{ {
f(*conf); f(*conf);
return true; return true;
@@ -217,10 +246,10 @@ void for_input_configuration(auto f, std::any& api_conf)
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
} }
void for_output_configuration(auto f, std::any& api_conf) void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf)
{ {
auto from_api = [&]<typename T>(T& /*backend*/) mutable { auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf)) if (auto conf = get_if<typename T::midi_out_configuration>(&api_conf))
{ {
f(*conf); f(*conf);
return true; return true;
@@ -232,10 +261,10 @@ void for_output_configuration(auto f, std::any& api_conf)
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
} }
void for_observer_configuration(auto f, std::any& api_conf) void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf)
{ {
auto from_api = [&]<typename T>(T& /*backend*/) mutable { auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf)) if (auto conf = get_if<typename T::midi_observer_configuration>(&api_conf))
{ {
f(*conf); f(*conf);
return true; return true;
+4 -3
View File
@@ -3,12 +3,13 @@
#include <libremidi/backends/alsa_raw/midi_out.hpp> #include <libremidi/backends/alsa_raw/midi_out.hpp>
#include <libremidi/backends/alsa_raw/observer.hpp> #include <libremidi/backends/alsa_raw/observer.hpp>
#include <string_view>
// Credits: greatly inspired from // 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/alsarawmidiout.c
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu> // Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>
namespace libremidi::alsa_raw NAMESPACE_LIBREMIDI::alsa_raw
{ {
struct backend struct backend
{ {
@@ -19,8 +20,8 @@ struct backend
using midi_out_configuration = alsa_raw_output_configuration; using midi_out_configuration = alsa_raw_output_configuration;
using midi_observer_configuration = alsa_raw_observer_configuration; using midi_observer_configuration = alsa_raw_observer_configuration;
static const constexpr auto API = libremidi::API::ALSA_RAW; static const constexpr auto API = libremidi::API::ALSA_RAW;
static const constexpr auto name = "alsa_raw"; static const constexpr std::string_view name = "alsa_raw";
static const constexpr auto display_name = "ALSA (raw)"; static const constexpr std::string_view display_name = "ALSA (raw)";
static inline bool available() noexcept static inline bool available() noexcept
{ {
@@ -2,6 +2,7 @@
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <chrono> #include <chrono>
#include <cstdint>
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <span> #include <span>
@@ -9,12 +10,12 @@
#if __has_include(<poll.h>) #if __has_include(<poll.h>)
#include <poll.h> #include <poll.h>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
using poll_descriptors = pollfd; using poll_descriptors = pollfd;
} }
#else #else
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct poll_descriptors struct poll_descriptors
{ {
@@ -25,7 +26,7 @@ struct poll_descriptors
} }
#endif #endif
namespace libremidi NAMESPACE_LIBREMIDI
{ {
/** /**
+18 -12
View File
@@ -2,10 +2,17 @@
#include <libremidi/backends/linux/alsa.hpp> #include <libremidi/backends/linux/alsa.hpp>
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp> #include <libremidi/detail/observer.hpp>
#include <libremidi/observer_configuration.hpp>
#include <functional> #include <alsa/asoundlib.h>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <system_error>
#include <vector> #include <vector>
// Credits: greatly inspired from // Credits: greatly inspired from
@@ -13,9 +20,7 @@
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu> // Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>
namespace libremidi NAMESPACE_LIBREMIDI
{
namespace
{ {
struct alsa_raw_port_id struct alsa_raw_port_id
{ {
@@ -27,25 +32,25 @@ struct alsa_raw_port_id
}; };
inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept 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 inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept
{ {
alsa_raw_port_id ret; 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.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; return ret;
} }
static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102); static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102);
static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7);
static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3);
}
namespace alsa_raw namespace alsa_raw
{ {
struct alsa_raw_port_info struct alsa_raw_port_info
{ {
// hw:1,2,0
std::string device; std::string device;
std::string card_name; std::string card_name;
std::string device_name; std::string device_name;
@@ -91,7 +96,6 @@ struct enumerator
: handler{self} : handler{self}
, configuration{self.configuration} , configuration{self.configuration}
{ {
} }
// 1: is an input / output // 1: is an input / output
@@ -114,8 +118,9 @@ struct enumerator
else if (status < 0 && status != -ENXIO) else if (status < 0 && status != -ENXIO)
{ {
handler.libremidi_handle_error( handler.libremidi_handle_error(
configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub) configuration,
+ " : " + snd.strerror(status)); "Cannot get rawmidi information: " + device_identifier(card, device, sub) + " : "
+ snd.strerror(status));
return status; return status;
} }
else else
@@ -201,7 +206,8 @@ inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)
if (status < 0) if (status < 0)
{ {
self.handler.libremidi_handle_error( self.handler.libremidi_handle_error(
self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status)); self.configuration,
"cannot open control for card"s + name + " : " + snd.strerror(status));
} }
} }
+27 -26
View File
@@ -10,7 +10,7 @@
#include <chrono> #include <chrono>
#include <thread> #include <thread>
namespace libremidi::alsa_raw NAMESPACE_LIBREMIDI::alsa_raw
{ {
class midi_in_impl class midi_in_impl
: public midi1::in_api : public midi1::in_api
@@ -200,7 +200,7 @@ public:
midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf) midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
: midi_in_impl{std::move(conf), std::move(apiconf)} : midi_in_impl{std::move(conf), std::move(apiconf)}
{ {
if (this->termination_event < 0) if (this->m_termination_event < 0)
{ {
libremidi_handle_error(this->configuration, "error creating eventfd."); libremidi_handle_error(this->configuration, "error creating eventfd.");
return; return;
@@ -220,7 +220,7 @@ public:
private: private:
void run_thread(auto parse_func) void run_thread(auto parse_func)
{ {
fds_.push_back(this->termination_event); fds_.push_back(this->m_termination_event);
const auto period const auto period
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period) = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
.count(); .count();
@@ -228,12 +228,12 @@ private:
for (;;) for (;;)
{ {
// Poll // Poll
ssize_t err = poll(fds_.data(), fds_.size(), period); ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
if (err == -EAGAIN) if (err == -EAGAIN)
continue; continue;
else if (err < 0) else if (err < 0)
return; return;
else if (termination_event.ready(fds_.back())) else if (m_termination_event.ready(fds_.back()))
break; break;
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
@@ -250,11 +250,11 @@ private:
{ {
if (configuration.timestamps == timestamp_mode::NoTimestamp) 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 else
{ {
this->thread_ = std::thread{ this->m_thread = std::thread{
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
} }
return stdx::error{}; return stdx::error{};
@@ -264,8 +264,7 @@ private:
using namespace std::literals; using namespace std::literals;
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration, "error starting MIDI input thread: "s + e.what());
"error starting MIDI input thread: "s + e.what());
return e.code(); return e.code();
} }
return stdx::error{}; return stdx::error{};
@@ -282,16 +281,16 @@ private:
stdx::error close_port() override stdx::error close_port() override
{ {
termination_event.notify(); m_termination_event.notify();
if (thread_.joinable()) if (m_thread.joinable())
thread_.join(); m_thread.join();
termination_event.consume(); // Reset to zero m_termination_event.consume(); // Reset to zero
return midi_in_impl::close_port(); return midi_in_impl::close_port();
} }
std::thread thread_; std::thread m_thread;
eventfd_notifier termination_event{}; eventfd_notifier m_termination_event{};
}; };
class midi_in_alsa_raw_manual : public midi_in_impl class midi_in_alsa_raw_manual : public midi_in_impl
@@ -316,19 +315,21 @@ private:
{ {
if (configuration.timestamps == timestamp_mode::NoTimestamp) if (configuration.timestamps == timestamp_mode::NoTimestamp)
{ {
configuration.manual_poll(manual_poll_parameters{ configuration.manual_poll(
.fds = {this->fds_.data(), this->fds_.size()}, manual_poll_parameters{
.callback = [this](std::span<pollfd> fds) { .fds = {this->fds_.data(), this->fds_.size()},
return do_read_events(&midi_in_impl::read_input_buffer, fds); .callback = [this](std::span<pollfd> fds) {
}}); return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
} }
else else
{ {
configuration.manual_poll(manual_poll_parameters{ configuration.manual_poll(
.fds = {this->fds_.data(), this->fds_.size()}, manual_poll_parameters{
.callback = [this](std::span<pollfd> fds) { .fds = {this->fds_.data(), this->fds_.size()},
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); .callback = [this](std::span<pollfd> fds) {
}}); return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
} }
} }
@@ -342,7 +343,7 @@ private:
}; };
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
template <> template <>
inline std::unique_ptr<midi_in_api> make<alsa_raw::midi_in_impl>( inline std::unique_ptr<midi_in_api> make<alsa_raw::midi_in_impl>(
@@ -8,7 +8,7 @@
#include <atomic> #include <atomic>
#include <thread> #include <thread>
namespace libremidi::alsa_raw NAMESPACE_LIBREMIDI::alsa_raw
{ {
class midi_out_impl final class midi_out_impl final
: public midi1::out_api : public midi1::out_api
@@ -44,8 +44,7 @@ public:
int status = snd.rawmidi.open(NULL, &midiport_, portname, mode); int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);
if (status < 0) if (status < 0)
{ {
libremidi_handle_error( libremidi_handle_error(this->configuration, "cannot open device.");
this->configuration, "cannot open device.");
return from_errc(status); return from_errc(status);
} }
return stdx::error{}; return stdx::error{};
@@ -87,8 +86,7 @@ public:
{ {
if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0) if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0)
{ {
libremidi_handle_error( libremidi_handle_error(this->configuration, "cannot write message.");
this->configuration, "cannot write message.");
return from_errc(err); return from_errc(err);
} }
+106 -75
View File
@@ -6,9 +6,10 @@
#if LIBREMIDI_HAS_UDEV #if LIBREMIDI_HAS_UDEV
#include <libremidi/backends/linux/helpers.hpp> #include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/backends/linux/udev.hpp> #include <libremidi/backends/linux/udev.hpp>
#include <libremidi/detail/observer.hpp> #endif
#include <libremidi/detail/observer.hpp>
namespace libremidi::alsa_raw NAMESPACE_LIBREMIDI::alsa_raw
{ {
template <typename Enumerator> template <typename Enumerator>
class observer_impl_base class observer_impl_base
@@ -27,28 +28,39 @@ public:
explicit observer_impl_base( explicit observer_impl_base(
observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf) observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{
}
~observer_impl_base()
{
#if LIBREMIDI_HAS_UDEV
m_termination_event.notify();
if (m_thread.joinable())
m_thread.join();
#endif
}
// Must be called for child classes due to virtual function calls
void finish_init()
{ {
if (!configuration.has_callbacks()) if (!configuration.has_callbacks())
return; return;
fds[0] = this->udev; #if LIBREMIDI_HAS_UDEV
fds[1] = termination_event; m_fds[0] = this->m_udev;
fds[2] = timer_fd; m_fds[1] = m_termination_event;
m_fds[2] = m_timer_fd;
#endif
// Set-up initial state // Set-up initial state
if (configuration.notify_in_constructor) if (configuration.notify_in_constructor)
this->check_devices(); this->check_devices();
#if LIBREMIDI_HAS_UDEV
// Start thread // Start thread
thread = std::thread{[this] { this->run(); }}; m_thread = std::thread{[this] { this->run(); }};
} #endif
~observer_impl_base()
{
termination_event.notify();
if (thread.joinable())
thread.join();
} }
std::vector<libremidi::input_port> get_input_ports() const noexcept override std::vector<libremidi::input_port> get_input_ports() const noexcept override
@@ -77,12 +89,12 @@ public:
return ret; return ret;
} }
private: #if LIBREMIDI_HAS_UDEV
void run() void run()
{ {
for (;;) for (;;)
{ {
if (int err = poll(fds, 3, -1); err < 0) if (int err = poll(m_fds, 3, -1); err < 0)
{ {
if (err == -EAGAIN) if (err == -EAGAIN)
continue; continue;
@@ -90,59 +102,87 @@ private:
return; return;
} }
// Check udev // Check eventfd to see if we need to exit
if (fds[0].revents & POLLIN) if (m_fds[1].revents & POLLIN)
{ {
udev_device* dev = udev.udev.monitor_receive_device(udev.monitor); break;
}
// Check udev
if (m_fds[0].revents & POLLIN)
{
udev_device* dev = m_udev.udev.monitor_receive_device(m_udev.monitor);
if (!dev) if (!dev)
continue; continue;
std::string_view act = udev.udev.device_get_action(dev); std::string_view act = m_udev.udev.device_get_action(dev);
std::string_view ss = udev.udev.device_get_subsystem(dev); std::string_view ss = m_udev.udev.device_get_subsystem(dev);
if (!act.empty() && ss == "snd_seq") if (!act.empty() && ss == "snd_seq")
{ {
if (act == "add" || act == "remove") if (act == "add" || act == "remove")
{ {
// Check every 100 milliseconds for ten seconds // Check every 100 milliseconds for ten seconds
this->timer_fd.restart(configuration.poll_period.count()); this->m_timer_fd.restart(configuration.poll_period.count());
timer_check_counts = 100; 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)
{
break;
} }
// Check timer // Check timer
if (fds[2].revents & POLLIN) if (m_fds[2].revents & POLLIN)
{ {
if (this->timer_check_counts-- <= 0) if (this->m_timer_check_counts-- <= 0)
this->timer_fd.cancel(); this->m_timer_fd.cancel();
fds[2].revents = 0; m_fds[2].revents = 0;
check_devices(); check_devices();
} }
} }
} }
#endif
template <bool Input> 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> -> std::conditional_t<Input, input_port, output_port>
{ {
#if LIBREMIDI_HAS_UDEV
auto [container, device, type, manufacturer, product, serial]
= get_udev_soundcard_info(m_udev, p.card);
#else
container_identifier container{};
device_identifier device{};
libremidi::transport_type type{};
std::string manufacturer;
std::string product;
std::string serial;
#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 = product;
if (display_name.empty())
display_name = "unknown";
return { return {
{.client = 0, {.api = get_current_api(),
.client = 0,
.container = container,
.device = device,
.port = raw_to_port_handle({p.card, p.dev, p.sub}), .port = raw_to_port_handle({p.card, p.dev, p.sub}),
.manufacturer = p.card_name, .manufacturer = manufacturer,
.product = product,
.serial = serial,
.device_name = p.device_name, .device_name = p.device_name,
.port_name = p.subdevice_name, .port_name = p.subdevice_name,
.display_name = p.subdevice_name}}; .display_name = std::move(display_name),
.type = type}};
} }
void check_devices() void check_devices()
@@ -151,7 +191,7 @@ private:
new_devs.enumerate_cards(); 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); if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);
it == new_devs.inputs.end()) it == new_devs.inputs.end())
@@ -165,8 +205,8 @@ private:
for (auto& in_next : new_devs.inputs) for (auto& in_next : new_devs.inputs)
{ {
if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next); if (auto it = std::find(m_current_inputs.begin(), m_current_inputs.end(), in_next);
it == current_inputs.end()) it == m_current_inputs.end())
{ {
if (auto& cb = this->configuration.input_added) if (auto& cb = this->configuration.input_added)
{ {
@@ -175,7 +215,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); if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);
it == new_devs.outputs.end()) it == new_devs.outputs.end())
@@ -189,8 +229,8 @@ private:
for (auto& out_next : new_devs.outputs) for (auto& out_next : new_devs.outputs)
{ {
if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next); if (auto it = std::find(m_current_outputs.begin(), m_current_outputs.end(), out_next);
it == current_outputs.end()) it == m_current_outputs.end())
{ {
if (auto& cb = this->configuration.output_added) if (auto& cb = this->configuration.output_added)
{ {
@@ -198,43 +238,34 @@ private:
} }
} }
} }
current_inputs = std::move(new_devs.inputs); m_current_inputs = std::move(new_devs.inputs);
current_outputs = std::move(new_devs.outputs); m_current_outputs = std::move(new_devs.outputs);
} }
udev_helper udev{}; #if LIBREMIDI_HAS_UDEV
eventfd_notifier termination_event{}; udev_helper m_udev{};
timerfd_timer timer_fd{}; eventfd_notifier m_termination_event{};
int timer_check_counts = 0; timerfd_timer m_timer_fd{};
std::thread thread; int m_timer_check_counts = 0;
std::vector<alsa_raw_port_info> current_inputs; std::thread m_thread;
std::vector<alsa_raw_port_info> current_outputs; pollfd m_fds[3]{};
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{}}
{
}
};
}
#endif #endif
namespace libremidi::alsa_raw 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> struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>
{ {
using alsa_raw::observer_impl_base<midi1_enumerator>::observer_impl_base; observer_impl(observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf)
: observer_impl_base<alsa_raw::midi1_enumerator>{std::move(conf), std::move(apiconf)}
{
finish_init();
}
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; }
}; };
} }
+7 -6
View File
@@ -5,7 +5,9 @@
#include <libremidi/backends/alsa_raw_ump/observer.hpp> #include <libremidi/backends/alsa_raw_ump/observer.hpp>
#include <libremidi/backends/dummy.hpp> #include <libremidi/backends/dummy.hpp>
namespace libremidi::alsa_raw_ump #include <string_view>
NAMESPACE_LIBREMIDI::alsa_raw_ump
{ {
struct backend struct backend
{ {
@@ -14,12 +16,11 @@ struct backend
using midi_observer = observer_impl; using midi_observer = observer_impl;
using midi_in_configuration = alsa_raw_ump::input_configuration; using midi_in_configuration = alsa_raw_ump::input_configuration;
using midi_out_configuration = alsa_raw_ump::output_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 API = libremidi::API::ALSA_RAW_UMP;
static const constexpr auto name = "alsa_raw_ump"; static const constexpr std::string_view name = "alsa_raw_ump";
static const constexpr auto display_name = "ALSA (raw UMP)"; static const constexpr std::string_view display_name = "ALSA (raw UMP)";
static inline bool available() noexcept static inline bool available() noexcept
{ {
@@ -1,7 +1,10 @@
#pragma once #pragma once
#include <libremidi/backends/alsa_raw/config.hpp> #include <libremidi/backends/alsa_raw/config.hpp>
namespace libremidi::alsa_raw_ump #include <chrono>
#include <functional>
NAMESPACE_LIBREMIDI::alsa_raw_ump
{ {
struct input_configuration struct input_configuration
{ {
@@ -18,8 +21,7 @@ struct output_configuration
std::optional<chunking_parameters> chunking; std::optional<chunking_parameters> chunking;
}; };
struct observer_configuration struct observer_configuration : public alsa_raw_observer_configuration
{ {
std::chrono::milliseconds poll_period{100};
}; };
} }
@@ -3,7 +3,9 @@
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp> #include <libremidi/detail/observer.hpp>
namespace libremidi::alsa_raw_ump #include <cstdio>
NAMESPACE_LIBREMIDI::alsa_raw_ump
{ {
struct midi2_enumerator : alsa_raw::enumerator struct midi2_enumerator : alsa_raw::enumerator
{ {
@@ -10,7 +10,7 @@
#include <atomic> #include <atomic>
#include <thread> #include <thread>
namespace libremidi::alsa_raw_ump NAMESPACE_LIBREMIDI::alsa_raw_ump
{ {
class midi_in_impl class midi_in_impl
: public midi2::in_api : public midi2::in_api
@@ -44,7 +44,8 @@ public:
SND_RAWMIDI_NONBLOCK; // fixme SND_RAWMIDI_NONBLOCK; // fixme
if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0) if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0)
{ {
libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); libremidi_handle_error(
this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
return from_errc(err); return from_errc(err);
} }
@@ -143,7 +144,8 @@ public:
while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0) while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0)
{ {
const auto to_ns = [this] { return absolute_timestamp(); }; const auto to_ns = [this] { return absolute_timestamp(); };
m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0)); m_processing.on_bytes(
{words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
} }
return err; return err;
} }
@@ -166,7 +168,8 @@ public:
const auto to_ns = [ts] { const auto to_ns = [ts] {
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec); 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)); m_processing.on_bytes(
{words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
} }
return err; return err;
} }
@@ -194,7 +197,7 @@ public:
libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf) libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
: midi_in_impl{std::move(conf), std::move(apiconf)} : midi_in_impl{std::move(conf), std::move(apiconf)}
{ {
if (this->termination_event < 0) if (this->m_termination_event < 0)
{ {
libremidi_handle_error(this->configuration, "error creating eventfd."); libremidi_handle_error(this->configuration, "error creating eventfd.");
return; return;
@@ -206,14 +209,14 @@ public:
~midi_in_impl_threaded() ~midi_in_impl_threaded()
{ {
// Close a connection if it exists. // Close a connection if it exists.
this->close_port(); midi_in_impl_threaded::close_port();
client_open_ = std::errc::not_connected; client_open_ = std::errc::not_connected;
} }
private: private:
void run_thread(auto parse_func) void run_thread(auto parse_func)
{ {
fds_.push_back(this->termination_event); fds_.push_back(this->m_termination_event);
const auto period const auto period
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period) = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
.count(); .count();
@@ -221,12 +224,12 @@ private:
for (;;) for (;;)
{ {
// Poll // Poll
ssize_t err = poll(fds_.data(), fds_.size(), period); ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
if (err == -EAGAIN) if (err == -EAGAIN)
continue; continue;
else if (err < 0) else if (err < 0)
return; return;
else if (termination_event.ready(fds_.back())) else if (m_termination_event.ready(fds_.back()))
break; break;
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
@@ -243,11 +246,11 @@ private:
{ {
if (configuration.timestamps == timestamp_mode::NoTimestamp) 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 else
{ {
this->thread_ = std::thread{ this->m_thread = std::thread{
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
} }
return stdx::error{}; return stdx::error{};
@@ -257,8 +260,7 @@ private:
using namespace std::literals; using namespace std::literals;
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration, "error starting MIDI input thread: "s + e.what());
"error starting MIDI input thread: "s + e.what());
return e.code(); return e.code();
} }
return stdx::error{}; return stdx::error{};
@@ -275,16 +277,16 @@ private:
stdx::error close_port() override stdx::error close_port() override
{ {
termination_event.notify(); m_termination_event.notify();
if (thread_.joinable()) if (m_thread.joinable())
thread_.join(); m_thread.join();
termination_event.consume(); // Reset to zero m_termination_event.consume(); // Reset to zero
return midi_in_impl::close_port(); return midi_in_impl::close_port();
} }
std::thread thread_; std::thread m_thread;
eventfd_notifier termination_event{}; eventfd_notifier m_termination_event{};
}; };
class midi_in_impl_manual : public midi_in_impl class midi_in_impl_manual : public midi_in_impl
@@ -299,7 +301,7 @@ public:
~midi_in_impl_manual() ~midi_in_impl_manual()
{ {
// Close a connection if it exists. // Close a connection if it exists.
this->close_port(); midi_in_impl_manual::close_port();
client_open_ = std::errc::not_connected; client_open_ = std::errc::not_connected;
} }
@@ -309,19 +311,21 @@ private:
{ {
if (configuration.timestamps == timestamp_mode::NoTimestamp) if (configuration.timestamps == timestamp_mode::NoTimestamp)
{ {
configuration.manual_poll(manual_poll_parameters{ configuration.manual_poll(
.fds = {this->fds_.data(), this->fds_.size()}, manual_poll_parameters{
.callback = [this](std::span<pollfd> fds) { .fds = {this->fds_.data(), this->fds_.size()},
return do_read_events(&midi_in_impl::read_input_buffer, fds); .callback = [this](std::span<pollfd> fds) {
}}); return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
} }
else else
{ {
configuration.manual_poll(manual_poll_parameters{ configuration.manual_poll(
.fds = {this->fds_.data(), this->fds_.size()}, manual_poll_parameters{
.callback = [this](std::span<pollfd> fds) { .fds = {this->fds_.data(), this->fds_.size()},
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); .callback = [this](std::span<pollfd> fds) {
}}); return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
} }
} }
@@ -335,7 +339,7 @@ private:
}; };
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
template <> template <>
inline std::unique_ptr<midi_in_api> make<alsa_raw_ump::midi_in_impl>( inline std::unique_ptr<midi_in_api> make<alsa_raw_ump::midi_in_impl>(
@@ -1,14 +1,19 @@
#pragma once #pragma once
#include <libremidi/backends/alsa_raw_ump/config.hpp> #include <libremidi/backends/alsa_raw_ump/config.hpp>
#include <libremidi/backends/alsa_raw_ump/helpers.hpp> #include <libremidi/backends/alsa_raw_ump/helpers.hpp>
#include <libremidi/backends/linux/alsa.hpp>
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
#include <libremidi/error_handler.hpp>
#include <libremidi/output_configuration.hpp>
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <atomic> #include <cassert>
#include <thread> #include <cstdint>
#include <system_error>
#include <utility>
namespace libremidi::alsa_raw_ump NAMESPACE_LIBREMIDI::alsa_raw_ump
{ {
class midi_out_impl final class midi_out_impl final
: public midi2::out_api : public midi2::out_api
@@ -2,13 +2,18 @@
#include <libremidi/backends/alsa_raw/observer.hpp> #include <libremidi/backends/alsa_raw/observer.hpp>
#include <libremidi/backends/alsa_raw_ump/helpers.hpp> #include <libremidi/backends/alsa_raw_ump/helpers.hpp>
namespace libremidi::alsa_raw_ump NAMESPACE_LIBREMIDI::alsa_raw_ump
{ {
class observer_impl : public alsa_raw::observer_impl_base<midi2_enumerator> class observer_impl : public alsa_raw::observer_impl_base<midi2_enumerator>
{ {
using alsa_raw::observer_impl_base<midi2_enumerator>::observer_impl_base; public:
observer_impl(
libremidi::observer_configuration&& conf, alsa_raw_ump::observer_configuration&& apiconf)
: observer_impl_base<alsa_raw_ump::midi2_enumerator>{std::move(conf), std::move(apiconf)}
{
finish_init();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; } libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
}; };
} }
+11 -4
View File
@@ -16,7 +16,11 @@
#include <libremidi/backends/alsa_seq/midi_out.hpp> #include <libremidi/backends/alsa_seq/midi_out.hpp>
#include <libremidi/backends/alsa_seq/observer.hpp> #include <libremidi/backends/alsa_seq/observer.hpp>
namespace libremidi::alsa_seq #include <unistd.h>
#include <string_view>
NAMESPACE_LIBREMIDI::alsa_seq
{ {
struct backend struct backend
@@ -29,13 +33,16 @@ struct backend
using midi_out_configuration = alsa_seq::output_configuration; using midi_out_configuration = alsa_seq::output_configuration;
using midi_observer_configuration = alsa_seq::observer_configuration; using midi_observer_configuration = alsa_seq::observer_configuration;
static const constexpr auto API = libremidi::API::ALSA_SEQ; static const constexpr auto API = libremidi::API::ALSA_SEQ;
static const constexpr auto name = "alsa_seq"; static const constexpr std::string_view name = "alsa_seq";
static const constexpr auto display_name = "ALSA (sequencer)"; static const constexpr std::string_view display_name = "ALSA (sequencer)";
static inline bool available() noexcept static inline bool available() noexcept
{ {
static const libasound& snd = libasound::instance(); 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 | R_OK | W_OK) == 0;
} }
}; };
@@ -15,7 +15,7 @@ typedef struct snd_seq_addr
} }
#endif #endif
namespace libremidi::alsa_seq NAMESPACE_LIBREMIDI::alsa_seq
{ {
// Possible parameters: // Possible parameters:
+42 -13
View File
@@ -9,7 +9,7 @@
#include <optional> #include <optional>
#include <string> #include <string>
namespace libremidi::alsa_seq NAMESPACE_LIBREMIDI::alsa_seq
{ {
struct event_handle struct event_handle
{ {
@@ -42,23 +42,39 @@ struct event_handle
~event_handle() { snd.seq.free_event(ev); } ~event_handle() { snd.seq.free_event(ev); }
}; };
namespace
{
inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept 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 inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept
{ {
int client = p >> 32; int port = p >> 32;
int port = p & 0xFFFFFFFF; int client = p & 0xFFFFFFFF;
return {client, port}; return {client, port};
} }
inline void for_all_clients(
const libasound& snd, snd_seq_t* seq, const std::function<void(snd_seq_client_info_t&)>& func)
{
snd_seq_client_info_t* cinfo{};
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_t* pinfo{};
snd_seq_port_info_alloca(&pinfo);
snd.seq.client_info_set_client(cinfo, -1);
while (snd.seq.query_next_client(seq, cinfo) >= 0)
{
int client = snd.seq.client_info_get_client(cinfo);
if (client == 0)
continue;
func(*cinfo);
}
}
inline void for_all_ports( inline void for_all_ports(
const libasound& snd, snd_seq_t* seq, 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_t* cinfo{};
snd_seq_client_info_alloca(&cinfo); snd_seq_client_info_alloca(&cinfo);
@@ -82,6 +98,21 @@ inline void for_all_ports(
} }
} }
inline void for_all_ports(
const libasound& snd, snd_seq_t* seq, int client,
const std::function<void(snd_seq_port_info_t&)>& func)
{
snd_seq_port_info_t* pinfo{};
snd_seq_port_info_alloca(&pinfo);
snd.seq.port_info_set_client(pinfo, client);
snd.seq.port_info_set_port(pinfo, -1);
while (snd.seq.query_next_port(seq, pinfo) >= 0)
{
func(*pinfo);
}
}
// This function is used to count or get the pinfo structure for a given port // This function is used to count or get the pinfo structure for a given port
// number. // number.
inline unsigned int iterate_port_info( inline unsigned int iterate_port_info(
@@ -125,7 +156,6 @@ inline unsigned int iterate_port_info(
return count; return count;
return 0; return 0;
} }
}
// A structure to hold variables related to the ALSA API // A structure to hold variables related to the ALSA API
// implementation. // implementation.
@@ -159,15 +189,15 @@ struct alsa_data
snd.seq.set_client_name(seq, configuration.client_name.data()); snd.seq.set_client_name(seq, configuration.client_name.data());
#if __has_include(<alsa/ump.h>) #if __has_include(<alsa/ump.h>)
if (snd.seq.ump.set_client_midi_version) if (snd.seq.set_client_midi_version)
{ {
switch (configuration.midi_version) switch (configuration.midi_version)
{ {
case 1: case 1:
snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI); snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI);
break; break;
case 2: case 2:
snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0);
break; break;
} }
} }
@@ -272,8 +302,7 @@ struct alsa_data
// Make subscription // Make subscription
if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0) if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0)
{ {
self.libremidi_handle_error( self.libremidi_handle_error(self.configuration, "ALSA error allocation port subscription.");
self.configuration, "ALSA error allocation port subscription.");
return err; return err;
} }
+25 -30
View File
@@ -5,7 +5,7 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp> #include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi::alsa_seq NAMESPACE_LIBREMIDI::alsa_seq
{ {
struct dummy_processing struct dummy_processing
{ {
@@ -195,11 +195,6 @@ public:
return stdx::error{}; return stdx::error{};
} }
stdx::error set_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
stdx::error set_port_name(std::string_view portName) override stdx::error set_port_name(std::string_view portName) override
{ {
return alsa_data::set_port_name(portName); return alsa_data::set_port_name(portName);
@@ -212,7 +207,7 @@ public:
.count(); .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) if constexpr (ConfigurationImpl::midi_version == 1)
{ {
@@ -258,17 +253,17 @@ public:
return 0; return 0;
} }
int process_events() int64_t process_events()
{ {
if constexpr (ConfigurationImpl::midi_version == 1) if constexpr (ConfigurationImpl::midi_version == 1)
{ {
snd_seq_event_t* ev{}; snd_seq_event_t* ev{};
event_handle handle{snd}; event_handle handle{snd};
int result = 0; int64_t result = 0;
if ((result = snd.seq.event_input(seq, &ev)) > 0) if ((result = snd.seq.event_input(seq, &ev)) > 0)
{ {
handle.reset(ev); handle.reset(ev);
if (int err = process_event(*ev); err < 0) if (auto err = process_event(*ev); err < 0)
{ {
return err; return err;
} }
@@ -353,7 +348,7 @@ public:
midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf) midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)
: midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)} : midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}
{ {
if (this->termination_event < 0) if (this->m_termination_event < 0)
{ {
this->libremidi_handle_error(this->configuration, "error creating eventfd."); this->libremidi_handle_error(this->configuration, "error creating eventfd.");
return; return;
@@ -364,7 +359,7 @@ public:
~midi_in_alsa_threaded() ~midi_in_alsa_threaded()
{ {
this->close_port(); midi_in_alsa_threaded::close_port();
this->client_open_ = std::errc::not_connected; this->client_open_ = std::errc::not_connected;
} }
@@ -399,7 +394,7 @@ private:
{ {
try try
{ {
this->thread = std::thread([this] { thread_handler(); }); this->m_thread = std::thread([this] { thread_handler(); });
return stdx::error{}; return stdx::error{};
} }
catch (const std::system_error& e) catch (const std::system_error& e)
@@ -415,12 +410,12 @@ private:
stdx::error stop_thread() stdx::error stop_thread()
{ {
termination_event.notify(); m_termination_event.notify();
if (this->thread.joinable()) if (this->m_thread.joinable())
this->thread.join(); this->m_thread.join();
termination_event.consume(); m_termination_event.consume();
return stdx::error{}; return stdx::error{};
} }
@@ -428,7 +423,7 @@ private:
{ {
int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1; 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)); 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); alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);
const auto period const auto period
@@ -439,10 +434,10 @@ private:
if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0) if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)
{ {
// No data pending // No data pending
if (poll(poll_fds, poll_fd_count, period) >= 0) if (poll(poll_fds, poll_fd_count, static_cast<int32_t>(period)) >= 0)
{ {
// We got our stop-thread signal // We got our stop-thread signal
if (termination_event.ready(poll_fds[0])) if (m_termination_event.ready(poll_fds[0]))
{ {
break; break;
} }
@@ -450,7 +445,7 @@ private:
continue; continue;
} }
int res{}; int64_t res{};
if constexpr (ConfigurationImpl::midi_version == 1) if constexpr (ConfigurationImpl::midi_version == 1)
{ {
res = this->process_events(); res = this->process_events();
@@ -467,8 +462,8 @@ private:
} }
} }
std::thread thread{}; std::thread m_thread{};
eventfd_notifier termination_event{}; eventfd_notifier m_termination_event{};
}; };
template <typename ConfigurationBase, typename ConfigurationImpl> template <typename ConfigurationBase, typename ConfigurationImpl>
@@ -487,17 +482,17 @@ public:
using poll_params = typename ConfigurationImpl::poll_parameters_type; using poll_params = typename ConfigurationImpl::poll_parameters_type;
this->configuration.manual_poll( this->configuration.manual_poll(
poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) { poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) {
if constexpr (ConfigurationImpl::midi_version == 1) if constexpr (ConfigurationImpl::midi_version == 1)
return this->process_event(ev); return this->process_event(ev);
#if __has_include(<alsa/ump.h>) #if __has_include(<alsa/ump.h>)
else else
return this->process_ump_event(ev); return this->process_ump_event(ev);
#endif #endif
}}); }});
return 0; return 0;
} }
~midi_in_alsa_manual() { this->close_port(); } ~midi_in_alsa_manual() { midi_in_alsa_manual::close_port(); }
stdx::error 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
{ {
@@ -530,7 +525,7 @@ public:
}; };
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
template <> template <>
inline std::unique_ptr<midi_in_api> inline std::unique_ptr<midi_in_api>
@@ -3,7 +3,7 @@
#include <libremidi/backends/alsa_seq/helpers.hpp> #include <libremidi/backends/alsa_seq/helpers.hpp>
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
namespace libremidi::alsa_seq NAMESPACE_LIBREMIDI::alsa_seq
{ {
class midi_out_impl final class midi_out_impl final
@@ -30,7 +30,7 @@ public:
return; return;
} }
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0) if (snd.midi.event_new(this->m_bufferSize, &this->coder) < 0)
{ {
libremidi_handle_error(this->configuration, "error initializing MIDI event parser."); libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");
return; return;
@@ -68,8 +68,9 @@ public:
stdx::error 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); unsigned int n_src
if (nSrc < 1) = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (n_src < 1)
{ {
libremidi_handle_error(this->configuration, "no MIDI output sources found!"); libremidi_handle_error(this->configuration, "no MIDI output sources found!");
return make_error_code(std::errc::no_such_device); return make_error_code(std::errc::no_such_device);
@@ -109,11 +110,6 @@ public:
return stdx::error{}; return stdx::error{};
} }
stdx::error set_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
stdx::error set_port_name(std::string_view portName) override stdx::error set_port_name(std::string_view portName) override
{ {
return alsa_data::set_port_name(portName); return alsa_data::set_port_name(portName);
@@ -122,9 +118,9 @@ public:
stdx::error send_message(const unsigned char* message, std::size_t size) override stdx::error send_message(const unsigned char* message, std::size_t size) override
{ {
int64_t result{}; 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); result = snd.midi.event_resize_buffer(this->coder, size);
if (result != 0) if (result != 0)
{ {
@@ -146,8 +142,8 @@ public:
// FIXME direct is set but snd_seq_event_output_direct is not used... // FIXME direct is set but snd_seq_event_output_direct is not used...
snd_seq_ev_set_direct(&ev); snd_seq_ev_set_direct(&ev);
const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below 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)(nBytes - offset), &ev); result = snd.midi.event_encode(this->coder, message + offset, (long)(n_bytes - offset), &ev);
if (result < 0) if (result < 0)
{ {
libremidi_handle_warning(this->configuration, "event parsing error!"); libremidi_handle_warning(this->configuration, "event parsing error!");
@@ -174,6 +170,6 @@ public:
} }
private: private:
uint64_t bufferSize{32}; uint64_t m_bufferSize{32};
}; };
} }
+223 -74
View File
@@ -7,20 +7,32 @@
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <bitset> #if LIBREMIDI_HAS_UDEV
#include <libremidi/backends/linux/udev.hpp>
#endif
#include <map> #include <map>
namespace libremidi::alsa_seq NAMESPACE_LIBREMIDI::alsa_seq
{ {
struct client_info
{
std::string client_name;
int client_id{};
std::optional<int> card{};
};
struct port_info struct port_info
{ {
std::string client_name; std::string client_name;
std::string port_name; std::string port_name;
int client{}; int client{};
int port{}; int port{};
bool isInput{}; bool is_input{};
bool isOutput{}; bool is_output{};
std::optional<int> card{};
libremidi::transport_type type{};
}; };
template <typename ConfigurationImpl> template <typename ConfigurationImpl>
@@ -88,12 +100,69 @@ public:
} }
} }
std::optional<port_info> get_info(int client, int port) const noexcept std::optional<client_info>
get_client_info(int client, snd_seq_client_info_t& cinfo) const noexcept
{ {
client_info p;
p.client_id = client;
if (auto name = snd.seq.client_info_get_name(&cinfo))
p.client_name = name;
if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0)
p.card = card;
return p;
}
std::optional<port_info> get_info(
int client, int port, snd_seq_client_info_t& cinfo,
snd_seq_port_info_t& pinfo) const noexcept
{
const auto tp = snd.seq.port_info_get_type(&pinfo);
const auto cap = snd.seq.port_info_get_capability(&pinfo);
if ((cap & SND_SEQ_PORT_CAP_NO_EXPORT) != 0)
return std::nullopt;
port_info p; port_info p;
p.client = client; p.client = client;
p.port = port; p.port = port;
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::transport_type::hardware;
ok = true;
}
else if ((tp & virtual_port) && this->configuration.track_virtual)
{
p.type = libremidi::transport_type::software;
ok = true;
}
if (!ok)
return {};
if (auto name = snd.seq.client_info_get_name(&cinfo))
p.client_name = name;
if (auto name = snd.seq.port_info_get_name(&pinfo))
p.port_name = name;
if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0)
p.card = card;
p.is_input = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ);
p.is_output = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE);
return p;
}
std::optional<port_info> get_info(int client, int port) const noexcept
{
snd_seq_client_info_t* cinfo; snd_seq_client_info_t* cinfo;
snd_seq_client_info_alloca(&cinfo); snd_seq_client_info_alloca(&cinfo);
if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0) if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0)
@@ -104,55 +173,57 @@ public:
if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0) if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0)
return std::nullopt; return std::nullopt;
const auto tp = snd.seq.port_info_get_type(pinfo); return get_info(client, port, *cinfo, *pinfo);
bool ok = this->configuration.track_any;
static constexpr auto virtual_port
= SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER;
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
ok = true;
else if ((tp & virtual_port) && this->configuration.track_virtual)
ok = true;
if (!ok)
return {};
if (auto name = snd.seq.client_info_get_name(cinfo))
p.client_name = name;
if (auto name = snd.seq.port_info_get_name(pinfo))
p.port_name = name;
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);
return p;
} }
template <bool Input> 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> -> std::conditional_t<Input, input_port, output_port>
{ {
static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle)); static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle));
static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle)); static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle));
container_identifier container{};
device_identifier device{};
libremidi::transport_type type = p.type;
std::string manufacturer;
std::string product;
std::string serial;
#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;
manufacturer = res.vendor;
product = res.product;
serial = res.serial;
}
#endif
return { return {
{.client = std::uintptr_t(this->seq), {.api = get_current_api(),
.client = std::uintptr_t(this->seq),
.container = container,
.device = device,
.port = alsa_seq::seq_to_port_handle(p.client, p.port), .port = alsa_seq::seq_to_port_handle(p.client, p.port),
.manufacturer = "", .manufacturer = manufacturer,
.product = product,
.serial = serial,
.device_name = p.client_name, .device_name = p.client_name,
.port_name = p.port_name, .port_name = p.port_name,
.display_name = p.port_name}}; .display_name = p.port_name,
.type = type}};
} }
void init_all_ports() void init_all_ports()
{ {
alsa_seq::for_all_ports( alsa_seq::for_all_ports(
snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client); int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port); int pt = snd.seq.port_info_get_port(&port);
register_port(clt, pt); register_port(clt, pt);
}); });
} }
libremidi::API get_current_api() const noexcept override libremidi::API get_current_api() const noexcept override
@@ -168,12 +239,12 @@ public:
std::vector<libremidi::input_port> ret; std::vector<libremidi::input_port> ret;
alsa_seq::for_all_ports( alsa_seq::for_all_ports(
snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client); int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port); int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt)) if (auto p = get_info(clt, pt, client, port))
if (p->isInput) if (p->is_input)
ret.push_back(to_port_info<true>(*p)); ret.push_back(to_port_info<true>(*p));
}); });
return ret; return ret;
} }
@@ -182,12 +253,12 @@ public:
std::vector<libremidi::output_port> ret; std::vector<libremidi::output_port> ret;
alsa_seq::for_all_ports( alsa_seq::for_all_ports(
snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client); int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port); int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt)) if (auto p = get_info(clt, pt, client, port))
if (p->isOutput) if (p->is_output)
ret.push_back(to_port_info<false>(*p)); ret.push_back(to_port_info<false>(*p));
}); });
return ret; return ret;
} }
@@ -200,13 +271,13 @@ public:
if (p.client == snd.seq.client_id(seq)) if (p.client == snd.seq.client_id(seq))
return; return;
knownClients_[{p.client, p.port}] = p; m_knownClients[{p.client, p.port}] = p;
if (p.isInput && configuration.input_added) if (p.is_input && configuration.input_added)
{ {
configuration.input_added(to_port_info<true>(p)); configuration.input_added(to_port_info<true>(p));
} }
if (p.isOutput && configuration.output_added) if (p.is_output && configuration.output_added)
{ {
configuration.output_added(to_port_info<false>(p)); configuration.output_added(to_port_info<false>(p));
} }
@@ -214,37 +285,61 @@ public:
void unregister_port(int client, int port) void unregister_port(int client, int port)
{ {
auto it = knownClients_.find({client, port}); auto it = m_knownClients.find({client, port});
if (it != knownClients_.end()) if (it != m_knownClients.end())
{ {
auto p = it->second; auto p = it->second;
knownClients_.erase(it); m_knownClients.erase(it);
if (p.isInput && configuration.input_removed) if (p.is_input && configuration.input_removed)
{ {
configuration.input_removed(to_port_info<true>(p)); configuration.input_removed(to_port_info<true>(p));
} }
if (p.isOutput && configuration.output_removed) if (p.is_output && configuration.output_removed)
{ {
configuration.output_removed(to_port_info<false>(p)); configuration.output_removed(to_port_info<false>(p));
} }
} }
} }
void handle_event(const snd_seq_event_t& ev) void handle_event_direct(const snd_seq_event_t& ev)
{ {
switch (ev.type) switch (ev.type)
{ {
case SND_SEQ_EVENT_CLIENT_START: {
// TODO
break;
}
case SND_SEQ_EVENT_CLIENT_EXIT: {
// TODO
break;
}
case SND_SEQ_EVENT_CLIENT_CHANGE: {
// TODO
break;
}
#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS
case SND_SEQ_EVENT_UMP_EP_CHANGE: {
// TODO
break;
}
case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: {
// TODO
break;
}
#endif
case SND_SEQ_EVENT_PORT_START: { case SND_SEQ_EVENT_PORT_START: {
register_port(ev.data.addr.client, ev.data.addr.port); this->register_port(ev.data.addr.client, ev.data.addr.port);
break; break;
} }
case SND_SEQ_EVENT_PORT_EXIT: { case SND_SEQ_EVENT_PORT_EXIT: {
unregister_port(ev.data.addr.client, ev.data.addr.port); this->unregister_port(ev.data.addr.client, ev.data.addr.port);
break; break;
} }
case SND_SEQ_EVENT_PORT_CHANGE: case SND_SEQ_EVENT_PORT_CHANGE:
// TODO
break;
default: default:
break; break;
} }
@@ -263,7 +358,11 @@ public:
} }
private: 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> template <typename ConfigurationImpl>
@@ -275,38 +374,87 @@ public:
{ {
// Create relevant descriptors // Create relevant descriptors
auto& snd = alsa_data::snd; auto& snd = alsa_data::snd;
const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN);
descriptors_.resize(N + 1); // 1. Descriptor count
snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN); const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN);
descriptors_.back() = this->termination_event; int total_descriptors = n;
total_descriptors++; // eventfd for terminating the thread
// 2. Create storage
descriptors.resize(total_descriptors);
// 3. Store descriptors
snd.seq.poll_descriptors(this->seq, descriptors.data(), n, POLLIN);
descriptors[n] = this->termination_event;
// Start the listening thread // Start the listening thread
thread = std::thread{[this] { thread = std::thread{[this, n] {
auto& snd = alsa_data::snd; auto& snd = alsa_data::snd;
const auto period const auto period
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period) = std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
.count(); .count();
for (;;) for (;;)
{ {
int err = poll(descriptors_.data(), descriptors_.size(), period); int err = poll(descriptors.data(), descriptors.size(), static_cast<int32_t>(period));
if (err >= 0) if (err >= 0)
{ {
// We got our stop-thread signal // We got our stop-thread signal
if (descriptors_.back().revents & POLLIN) if (descriptors[n].revents & POLLIN)
break; break;
// Put ALSA event in our queue
snd_seq_event_t* ev{}; snd_seq_event_t* ev{};
event_handle handle{snd}; event_handle handle{snd};
while (snd.seq.event_input(this->seq, &ev) >= 0) while (snd.seq.event_input(this->seq, &ev) >= 0)
{ {
handle.reset(ev); handle.reset(ev);
this->handle_event(*ev); this->handle_event_delayed(*ev);
}
// Process the events in a deferred way.
// This is because udev takes some milliseconds to populate its field after a
// port was added
auto tm = std::chrono::steady_clock::now();
for (auto it = queued_events.begin(); it != queued_events.end();)
{
if ((tm - it->second) >= this->configuration.poll_period)
{
this->handle_event_direct(it->first);
it = queued_events.erase(it);
}
else
{
break;
}
} }
} }
} }
}}; }};
} }
void handle_event_delayed(const snd_seq_event_t& ev)
{
switch (ev.type)
{
case SND_SEQ_EVENT_CLIENT_START:
case SND_SEQ_EVENT_CLIENT_EXIT:
case SND_SEQ_EVENT_CLIENT_CHANGE:
#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS
case SND_SEQ_EVENT_UMP_EP_CHANGE:
case SND_SEQ_EVENT_UMP_BLOCK_CHANGE:
#endif
case SND_SEQ_EVENT_PORT_START:
case SND_SEQ_EVENT_PORT_EXIT:
case SND_SEQ_EVENT_PORT_CHANGE:
queued_events.emplace_back(ev, std::chrono::steady_clock::now());
break;
default:
break;
}
}
~observer_threaded() ~observer_threaded()
{ {
termination_event.notify(); termination_event.notify();
@@ -317,7 +465,8 @@ public:
eventfd_notifier termination_event{}; eventfd_notifier termination_event{};
std::thread thread; std::thread thread;
std::vector<pollfd> descriptors_; std::vector<pollfd> descriptors;
std::vector<std::pair<snd_seq_event_t, std::chrono::steady_clock::time_point>> queued_events;
}; };
template <typename ConfigurationImpl> template <typename ConfigurationImpl>
@@ -329,16 +478,16 @@ public:
{ {
this->configuration.manual_poll( this->configuration.manual_poll(
poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) { poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) {
this->handle_event(v); this->handle_event_direct(v);
return 0; return 0;
}}); }});
} }
~observer_manual() { this->configuration.stop_poll(this->vaddr); } ~observer_manual() { this->configuration.stop_poll(this->vaddr); }
}; };
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
template <> template <>
inline std::unique_ptr<observer_api> inline std::unique_ptr<observer_api>
@@ -9,7 +9,7 @@
#include <variant> #include <variant>
namespace libremidi::alsa_seq NAMESPACE_LIBREMIDI::alsa_seq
{ {
struct shared_handler : public libremidi::shared_context struct shared_handler : public libremidi::shared_context
@@ -61,7 +61,7 @@ struct shared_handler : public libremidi::shared_context
{ {
auto clt = std::make_shared<shared_handler>(client_name); 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()) if (auto clt = client.lock())
{ {
clt->events.push( 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) { auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) {
if (auto clt = client.lock()) if (auto clt = client.lock())
{ {
clt->events.push( clt->events.push({.type = shared_handler::event_type::callback_removed, .payload = id});
{.type = shared_handler::event_type::callback_removed, .payload = std::move(id)});
clt->queue_event.notify(); clt->queue_event.notify();
} }
return true; return true;
@@ -128,13 +127,13 @@ struct shared_handler : public libremidi::shared_context
{ {
case callback_added: { case callback_added: {
auto [addr, cb] auto [addr, cb]
= std::move(*std::get_if<libremidi::alsa_seq::poll_parameters>(&ev.payload)); = std::move(*get_if<libremidi::alsa_seq::poll_parameters>(&ev.payload));
addresses.push_back(addr); addresses.push_back(addr);
callbacks.push_back(std::move(cb)); callbacks.push_back(std::move(cb));
break; break;
} }
case callback_removed: case callback_removed:
auto addr = std::move(*std::get_if<snd_seq_addr_t>(&ev.payload)); auto addr = *get_if<snd_seq_addr_t>(&ev.payload);
if (auto index = index_of_address(addr); index >= 0) if (auto index = index_of_address(addr); index >= 0)
{ {
addresses.erase(addresses.begin() + index); addresses.erase(addresses.begin() + index);
@@ -146,15 +145,14 @@ struct shared_handler : public libremidi::shared_context
} }
// Look for who's ready // 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) if (fds[i].revents & POLLIN)
{ {
// Read alsa event // Read alsa event
snd_seq_event_t* ev{}; snd_seq_event_t* ev{};
event_handle handle{snd}; event_handle handle{snd};
int result = 0; while (snd.seq.event_input(client, &ev) > 0)
while ((result = snd.seq.event_input(client, &ev)) > 0)
{ {
handle.reset(ev); handle.reset(ev);
@@ -181,7 +179,7 @@ struct shared_handler : public libremidi::shared_context
struct event struct event
{ {
event_type type; event_type type;
std::variant<libremidi::alsa_seq::poll_parameters, snd_seq_addr_t> payload; libremidi_variant_alias::variant<libremidi::alsa_seq::poll_parameters, snd_seq_addr_t> payload;
}; };
snd_seq_t* client{}; snd_seq_t* client{};
+12 -5
View File
@@ -5,7 +5,11 @@
#include <libremidi/backends/alsa_seq_ump/config.hpp> #include <libremidi/backends/alsa_seq_ump/config.hpp>
#include <libremidi/backends/alsa_seq_ump/midi_out.hpp> #include <libremidi/backends/alsa_seq_ump/midi_out.hpp>
namespace libremidi #include <unistd.h>
#include <string_view>
NAMESPACE_LIBREMIDI
{ {
template <> template <>
inline std::unique_ptr<observer_api> inline std::unique_ptr<observer_api>
@@ -38,7 +42,7 @@ inline std::unique_ptr<midi_in_api> make<
} }
namespace libremidi::alsa_seq_ump NAMESPACE_LIBREMIDI::alsa_seq_ump
{ {
struct backend struct backend
{ {
@@ -50,13 +54,16 @@ struct backend
using midi_out_configuration = alsa_seq_ump::output_configuration; using midi_out_configuration = alsa_seq_ump::output_configuration;
using midi_observer_configuration = alsa_seq_ump::observer_configuration; using midi_observer_configuration = alsa_seq_ump::observer_configuration;
static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP; static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP;
static const constexpr auto name = "alsa_seq_ump"; static const constexpr std::string_view name = "alsa_seq_ump";
static const constexpr auto display_name = "ALSA (sequencer, UMP)"; static const constexpr std::string_view display_name = "ALSA (sequencer, UMP)";
static inline bool available() noexcept static inline bool available() noexcept
{ {
static const libasound& snd = libasound::instance(); 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 | R_OK | W_OK) == 0;
} }
}; };
} }
@@ -11,7 +11,7 @@ extern "C" {
typedef struct snd_seq_ump_event snd_seq_ump_event_t; typedef struct snd_seq_ump_event snd_seq_ump_event_t;
} }
namespace libremidi::alsa_seq_ump NAMESPACE_LIBREMIDI::alsa_seq_ump
{ {
struct poll_parameters struct poll_parameters
@@ -5,7 +5,7 @@
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/ump_stream.hpp> #include <libremidi/detail/ump_stream.hpp>
namespace libremidi::alsa_seq_ump NAMESPACE_LIBREMIDI::alsa_seq_ump
{ {
class midi_out_impl final class midi_out_impl final
@@ -64,8 +64,9 @@ public:
stdx::error 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); unsigned int n_src
if (nSrc < 1) = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (n_src < 1)
{ {
libremidi_handle_error(this->configuration, "no MIDI output sources found!"); libremidi_handle_error(this->configuration, "no MIDI output sources found!");
return make_error_code(std::errc::no_such_device); return make_error_code(std::errc::no_such_device);
@@ -105,11 +106,6 @@ public:
return stdx::error{}; return stdx::error{};
} }
stdx::error set_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
stdx::error set_port_name(std::string_view portName) override stdx::error set_port_name(std::string_view portName) override
{ {
return alsa_data::set_port_name(portName); return alsa_data::set_port_name(portName);
@@ -133,9 +129,10 @@ public:
libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
return static_cast<std::errc>(-ret); return static_cast<std::errc>(-ret);
} }
return std::errc{0}; static_assert(std::errc{0} == std::errc{});
return std::errc{};
}; };
segment_ump_stream(ump_stream, count, write_func, []() {}); segment_ump_stream(ump_stream, count, write_func, []() { });
snd.seq.drain_output(this->seq); snd.seq.drain_output(this->seq);
return stdx::error{}; return stdx::error{};
@@ -0,0 +1,33 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/midi_in.hpp>
#include <libremidi/backends/android/midi_out.hpp>
#include <libremidi/backends/android/observer.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
struct backend
{
using midi_in = android::midi_in;
using midi_out = android::midi_out;
using midi_observer = android::observer;
using midi_in_configuration = android::input_configuration;
using midi_out_configuration = android::output_configuration;
using midi_observer_configuration = android::observer_configuration;
static const constexpr auto API = libremidi::API::ANDROID_AMIDI;
static const constexpr std::string_view name = "android";
static const constexpr std::string_view display_name = "Android MIDI API";
static bool available() noexcept
{
#if defined(__ANDROID__)
return true;
#else
return false;
#endif
}
};
}
}
@@ -0,0 +1,23 @@
#pragma once
#include <libremidi/config.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
struct input_configuration
{
std::string_view client_name = "libremidi client";
};
struct output_configuration
{
std::string_view client_name = "libremidi client";
};
struct observer_configuration
{
std::string_view client_name = "libremidi client";
};
}
}
@@ -0,0 +1,239 @@
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/backends/android/midi_in.hpp>
#include <libremidi/backends/android/midi_out.hpp>
NAMESPACE_LIBREMIDI::android
{
// Main source of knowledge is RtMidi implementation
// which was done by Yellow Labrador
JNIEnv* context::get_thread_env()
{
JNIEnv* env;
JavaVM* jvm;
jsize count;
if (JNI_GetCreatedJavaVMs(&jvm, 1, &count) != JNI_OK || count < 1)
{
LOGE("No JVM found");
return nullptr;
}
if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED)
{
if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK)
{
LOGE("Failed to attach thread");
return nullptr;
}
}
return env;
}
jobject context::get_context(JNIEnv* env)
{
auto activity_thread = env->FindClass("android/app/ActivityThread");
auto current_activity_thread = env->GetStaticMethodID(
activity_thread, "currentActivityThread", "()Landroid/app/ActivityThread;");
auto at = env->CallStaticObjectMethod(activity_thread, current_activity_thread);
if (!at)
{
LOGE("Failed to get ActivityThread");
return nullptr;
}
auto get_application
= env->GetMethodID(activity_thread, "getApplication", "()Landroid/app/Application;");
auto app = env->CallObjectMethod(at, get_application);
if (!app)
{
LOGE("Failed to get Application");
return nullptr;
}
return app;
}
jobject context::get_midi_manager(JNIEnv* env, jobject ctx)
{
auto context_class = env->FindClass("android/content/Context");
auto get_system_service = env->GetMethodID(
context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
auto midi_service_str = env->NewStringUTF("midi");
auto service = env->CallObjectMethod(ctx, get_system_service, midi_service_str);
env->DeleteLocalRef(midi_service_str);
return service;
}
void context::refresh_midi_devices(JNIEnv* env, jobject ctx, bool is_output)
{
cleanup_devices(env);
auto midi_service = get_midi_manager(env, ctx);
if (!midi_service)
return;
auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager");
auto get_devices_method
= env->GetMethodID(midi_mgr_class, "getDevices", "()[Landroid/media/midi/MidiDeviceInfo;");
auto device_array = (jobjectArray)env->CallObjectMethod(midi_service, get_devices_method);
auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo");
auto get_input_count = env->GetMethodID(device_info_class, "getInputPortCount", "()I");
auto get_output_count = env->GetMethodID(device_info_class, "getOutputPortCount", "()I");
jsize count = env->GetArrayLength(device_array);
for (jsize i = 0; i < count; ++i)
{
auto device_info = env->GetObjectArrayElement(device_array, i);
int port_count = is_output ? env->CallIntMethod(device_info, get_input_count)
: env->CallIntMethod(device_info, get_output_count);
if (port_count > 0)
{
midi_devices.push_back(env->NewGlobalRef(device_info));
}
env->DeleteLocalRef(device_info);
}
env->DeleteLocalRef(device_array);
env->DeleteLocalRef(midi_service);
}
std::string context::port_name(JNIEnv* env, unsigned int port_number)
{
if (port_number >= midi_devices.size())
{
LOGE("Invalid port number");
return "";
}
auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo");
auto get_props_method
= env->GetMethodID(device_info_class, "getProperties", "()Landroid/os/Bundle;");
auto bundle = env->CallObjectMethod(midi_devices[port_number], get_props_method);
auto bundle_class = env->FindClass("android/os/Bundle");
auto get_string_method
= env->GetMethodID(bundle_class, "getString", "(Ljava/lang/String;)Ljava/lang/String;");
auto key = env->NewStringUTF("name");
auto name_str = (jstring)env->CallObjectMethod(bundle, get_string_method, key);
std::string result;
if (name_str)
{
const char* name_chars = env->GetStringUTFChars(name_str, nullptr);
result = name_chars;
env->ReleaseStringUTFChars(name_str, name_chars);
env->DeleteLocalRef(name_str);
}
env->DeleteLocalRef(key);
env->DeleteLocalRef(bundle);
return result;
}
void context::cleanup_devices(JNIEnv* env)
{
for (jobject device : midi_devices)
{
env->DeleteGlobalRef(device);
}
midi_devices.clear();
}
void context::open_device(jobject device_info, void* target, bool is_output)
{
auto env = get_thread_env();
if (!env)
return;
auto ctx = get_context(env);
if (!ctx)
return;
auto midi_mgr = get_midi_manager(env, ctx);
if (!midi_mgr)
return;
// Check if we're on the main thread
auto looper_class = env->FindClass("android/os/Looper");
auto get_my_looper_method
= env->GetStaticMethodID(looper_class, "myLooper", "()Landroid/os/Looper;");
auto my_looper = env->CallStaticObjectMethod(looper_class, get_my_looper_method);
jobject handler = nullptr;
if (!my_looper)
{
LOGI("Not on a Looper thread, using main looper");
auto get_main_looper_method
= env->GetStaticMethodID(looper_class, "getMainLooper", "()Landroid/os/Looper;");
auto main_looper = env->CallStaticObjectMethod(looper_class, get_main_looper_method);
auto handler_class = env->FindClass("android/os/Handler");
auto handler_ctor = env->GetMethodID(handler_class, "<init>", "(Landroid/os/Looper;)V");
handler = env->NewObject(handler_class, handler_ctor, main_looper);
env->DeleteLocalRef(main_looper);
}
// Create the callback listener
auto callback_class = env->FindClass("dev/celtera/libremidi/MidiDeviceCallback");
if (!callback_class)
{
LOGE("MidiDeviceCallback class not found - ensure the Java class is loaded");
if (handler)
env->DeleteLocalRef(handler);
return;
}
auto callback_ctor = env->GetMethodID(callback_class, "<init>", "(JZ)V");
auto callback
= env->NewObject(callback_class, callback_ctor, (jlong)target, (jboolean)is_output);
// Open the device
auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager");
auto open_device_method = env->GetMethodID(
midi_mgr_class, "openDevice",
"(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/"
"MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V");
env->CallVoidMethod(midi_mgr, open_device_method, device_info, callback, handler);
env->DeleteLocalRef(callback);
if (handler)
env->DeleteLocalRef(handler);
}
extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened(
JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output)
{
if (!midi_device || target_ptr == 0)
{
LOGE("Invalid device or target pointer in callback");
return;
}
AMidiDevice* amidi_device = nullptr;
AMidiDevice_fromJava(env, midi_device, &amidi_device);
if (!amidi_device)
{
LOGE("Failed to convert Java MIDI device to AMidiDevice");
return;
}
if (is_output)
{
midi_out::open_callback(reinterpret_cast<midi_out*>(target_ptr), amidi_device);
}
else
{
midi_in::open_callback(reinterpret_cast<midi_in*>(target_ptr), amidi_device);
}
}
}
@@ -0,0 +1,40 @@
#pragma once
#include <libremidi/error.hpp>
#include <amidi/AMidi.h>
#include <android/log.h>
#include <jni.h>
#include <pthread.h>
#include <string>
#include <vector>
#define LOG_TAG "libremidi"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
NAMESPACE_LIBREMIDI
{
namespace android
{
struct context
{
static inline std::string client_name;
static inline std::vector<jobject> midi_devices;
static JNIEnv* get_thread_env();
static jobject get_context(JNIEnv* env);
static jobject get_midi_manager(JNIEnv* env, jobject context);
static void refresh_midi_devices(JNIEnv* env, jobject context, bool is_output);
static void open_device(jobject device_info, void* target, bool is_output);
static std::string port_name(JNIEnv* env, unsigned int port_number);
static void cleanup_devices(JNIEnv* env);
};
extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened(
JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output);
}
}
@@ -0,0 +1,165 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <atomic>
#include <chrono>
#include <thread>
NAMESPACE_LIBREMIDI
{
namespace android
{
class midi_in
: public midi1::in_api
, public error_handler
{
public:
explicit midi_in(
libremidi::input_configuration&& conf, libremidi::android::input_configuration aconf)
: configuration{std::move(conf)}
{
if (!context::client_name.empty() && context::client_name != aconf.client_name)
{
libremidi_handle_error(
configuration, "Android backend only supports one client name per process");
return;
}
context::client_name = std::string(aconf.client_name);
client_open_ = stdx::error{};
}
~midi_in() override { close_port(); }
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::ANDROID_AMIDI;
}
stdx::error open_port(const input_port& port, std::string_view /*name*/) override
{
if (is_port_open())
{
libremidi_handle_error(configuration, "midi_in::open_port: a port is already open");
return std::errc{};
}
const unsigned int port_number = port.port;
auto env = context::get_thread_env();
if (!env)
{
libremidi_handle_error(configuration, "midi_in::open_port: failed to get JNI environment");
return std::errc{};
}
auto ctx = context::get_context(env);
if (!ctx)
{
libremidi_handle_error(configuration, "midi_in::open_port: failed to get Android context");
return std::errc{};
}
context::refresh_midi_devices(env, ctx, false);
if (port_number >= context::midi_devices.size())
{
libremidi_handle_error(configuration, "midi_in::open_port: invalid port number");
return std::errc{};
}
port_open = true;
running = true;
context::open_device(context::midi_devices[port_number], this, false);
return stdx::error{};
}
stdx::error close_port() override
{
if (!is_port_open())
return std::errc{};
running = false;
if (poll_thread.joinable())
{
poll_thread.join();
}
if (midi_output_port)
{
AMidiOutputPort_close(midi_output_port);
midi_output_port = nullptr;
}
if (receive_device)
{
AMidiDevice_release(receive_device);
receive_device = nullptr;
}
port_open = false;
return stdx::error{};
}
bool is_port_open() const noexcept { return port_open; }
void start_midi_thread() { poll_thread = std::thread(&midi_in::poll_midi, this); }
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
void poll_midi()
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = false,
.absolute_is_monotonic = false,
.has_samples = false,
};
uint8_t buffer[1024];
int32_t op_code;
int64_t timestamp;
size_t num_bytes;
while (running)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
ssize_t num_messages = AMidiOutputPort_receive(
midi_output_port, &op_code, buffer, sizeof(buffer), &num_bytes, &timestamp);
if (num_messages < 0)
{
LOGE("Error receiving MIDI data: %d", (int)num_messages);
continue;
}
if (num_messages > 0 && num_bytes > 0)
{
const auto to_ns = [=] { return timestamp; };
m_processing.on_bytes_multi(
{buffer, buffer + num_bytes},
m_processing.timestamp<timestamp_info>(to_ns, timestamp));
}
}
}
static void open_callback(midi_in* self, AMidiDevice* device)
{
self->receive_device = device;
AMidiOutputPort_open(device, 0, &self->midi_output_port);
self->start_midi_thread();
}
private:
libremidi::input_configuration configuration;
AMidiDevice* receive_device = nullptr;
AMidiOutputPort* midi_output_port = nullptr;
std::thread poll_thread;
std::atomic<bool> port_open{false};
std::atomic<bool> running{false};
midi1::input_state_machine m_processing{this->configuration};
};
}
}
@@ -0,0 +1,126 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
class midi_out final
: public midi1::out_api
, public error_handler
{
public:
explicit midi_out(
libremidi::output_configuration&& conf, libremidi::android::output_configuration aconf)
: configuration{std::move(conf)}
{
if (!context::client_name.empty() && context::client_name != aconf.client_name)
{
libremidi_handle_error(
configuration, "Android backend only supports one client name per process");
return;
}
context::client_name = std::string(aconf.client_name);
client_open_ = stdx::error{};
}
~midi_out() override { close_port(); }
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::ANDROID_AMIDI;
}
stdx::error open_port(const output_port& port, std::string_view /*name*/) override
{
if (is_port_open())
{
libremidi_handle_error(configuration, "midi_out::open_port: a port is already open");
return std::errc{};
}
const unsigned int port_number = port.port;
auto env = context::get_thread_env();
if (!env)
{
libremidi_handle_error(configuration, "midi_out::open_port: failed to get JNI environment");
return std::errc{};
}
auto ctx = context::get_context(env);
if (!ctx)
{
libremidi_handle_error(configuration, "midi_out::open_port: failed to get Android context");
return std::errc{};
}
context::refresh_midi_devices(env, ctx, true);
if (port_number >= context::midi_devices.size())
{
libremidi_handle_error(configuration, "midi_out::open_port: invalid port number");
return std::errc{};
}
port_open = true;
context::open_device(context::midi_devices[port_number], this, true);
return stdx::error{};
}
stdx::error close_port() override
{
if (!is_port_open())
return std::errc{};
if (midi_input_port)
{
AMidiInputPort_close(midi_input_port);
midi_input_port = nullptr;
}
if (send_device)
{
AMidiDevice_release(send_device);
send_device = nullptr;
}
port_open = false;
return stdx::error{};
}
bool is_port_open() const noexcept { return port_open; }
stdx::error send_message(const unsigned char* message, size_t size) override
{
if (!is_port_open() || !midi_input_port)
{
libremidi_handle_error(configuration, "midi_out::send_message: port is not open");
return std::errc{};
}
ssize_t result = AMidiInputPort_send(midi_input_port, message, size);
if (result < 0)
{
libremidi_handle_error(configuration, "midi_out::send_message: failed to send MIDI data");
return std::errc{};
}
return stdx::error{};
}
static void open_callback(midi_out* self, AMidiDevice* device)
{
self->send_device = device;
AMidiInputPort_open(device, 0, &self->midi_input_port);
}
private:
libremidi::output_configuration configuration;
AMidiDevice* send_device = nullptr;
AMidiInputPort* midi_input_port = nullptr;
bool port_open = false;
};
}
}
@@ -0,0 +1,93 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/detail/observer.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
class observer final
: public libremidi::observer_api
, public error_handler
{
public:
explicit observer(
libremidi::observer_configuration&& conf, libremidi::android::observer_configuration aconf)
: configuration{std::move(conf)}
{
if (!context::client_name.empty() && context::client_name != aconf.client_name)
{
LOGW("Android backend only supports one client name per process");
}
context::client_name = std::string(aconf.client_name);
}
~observer()
{
auto env = context::get_thread_env();
if (env)
{
context::cleanup_devices(env);
}
}
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::ANDROID_AMIDI;
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
auto env = context::get_thread_env();
if (!env)
return {};
auto ctx = context::get_context(env);
if (!ctx)
return {};
context::refresh_midi_devices(env, ctx, false);
std::vector<libremidi::input_port> ports;
for (size_t i = 0; i < context::midi_devices.size(); ++i)
{
libremidi::input_port port;
port.api = libremidi::API::ANDROID_AMIDI;
port.port_name = context::port_name(env, i);
port.port = i;
ports.push_back(std::move(port));
}
return ports;
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
auto env = context::get_thread_env();
if (!env)
return {};
auto ctx = context::get_context(env);
if (!ctx)
return {};
context::refresh_midi_devices(env, ctx, true);
std::vector<libremidi::output_port> ports;
for (size_t i = 0; i < context::midi_devices.size(); ++i)
{
libremidi::output_port port;
port.port_name = context::port_name(env, i);
port.port = i;
ports.push_back(std::move(port));
}
return ports;
}
private:
libremidi::observer_configuration configuration;
};
}
}
+5 -3
View File
@@ -3,7 +3,9 @@
#include <libremidi/backends/coremidi/midi_out.hpp> #include <libremidi/backends/coremidi/midi_out.hpp>
#include <libremidi/backends/coremidi/observer.hpp> #include <libremidi/backends/coremidi/observer.hpp>
namespace libremidi #include <string_view>
NAMESPACE_LIBREMIDI
{ {
struct core_backend struct core_backend
{ {
@@ -14,8 +16,8 @@ struct core_backend
using midi_out_configuration = coremidi_output_configuration; using midi_out_configuration = coremidi_output_configuration;
using midi_observer_configuration = coremidi_observer_configuration; using midi_observer_configuration = coremidi_observer_configuration;
static const constexpr auto API = libremidi::API::COREMIDI; static const constexpr auto API = libremidi::API::COREMIDI;
static const constexpr auto name = "core"; static const constexpr std::string_view name = "core";
static const constexpr auto display_name = "CoreMIDI"; static const constexpr std::string_view display_name = "CoreMIDI";
static constexpr inline bool available() noexcept { return true; } static constexpr inline bool available() noexcept { return true; }
}; };
@@ -18,7 +18,7 @@ typedef MIDIObjectRef MIDIClientRef;
using MIDIClientRef = uint32_t; using MIDIClientRef = uint32_t;
using MIDIObjectRef = uint32_t; using MIDIObjectRef = uint32_t;
#endif #endif
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct coremidi_input_configuration struct coremidi_input_configuration
@@ -3,7 +3,7 @@
#include <CoreMIDI/CoreMIDI.h> #include <CoreMIDI/CoreMIDI.h>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct coremidi_error_domain : public stdx::error_domain struct coremidi_error_domain : public stdx::error_domain
{ {
+19 -21
View File
@@ -1,9 +1,9 @@
#pragma once #pragma once
#include <libremidi/backends/coremidi/error_domain.hpp>
#include <libremidi/detail/memory.hpp> #include <libremidi/detail/memory.hpp>
#include <libremidi/detail/midi_api.hpp> #include <libremidi/detail/midi_api.hpp>
#include <libremidi/error_handler.hpp> #include <libremidi/error_handler.hpp>
#include <libremidi/input_configuration.hpp> #include <libremidi/input_configuration.hpp>
#include <libremidi/backends/coremidi/error_domain.hpp>
#include <CoreMIDI/CoreMIDI.h> #include <CoreMIDI/CoreMIDI.h>
#include <CoreServices/CoreServices.h> #include <CoreServices/CoreServices.h>
@@ -21,13 +21,12 @@
#define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime
#endif #endif
namespace libremidi NAMESPACE_LIBREMIDI
{ {
using CFString_handle = unique_handle<const __CFString, CFRelease>; using CFString_handle = unique_handle<const __CFString, CFRelease>;
using CFStringMutable_handle = unique_handle<__CFString, CFRelease>; using CFStringMutable_handle = unique_handle<__CFString, CFRelease>;
namespace
{ LIBREMIDI_STATIC std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept
static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept
{ {
CFStringRef res; CFStringRef res;
if (MIDIObjectGetStringProperty(object, property, &res) || !res) if (MIDIObjectGetStringProperty(object, property, &res) || !res)
@@ -39,14 +38,14 @@ static inline std::string get_string_property(MIDIObjectRef object, CFStringRef
return name; return name;
} }
static inline int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept LIBREMIDI_STATIC int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept
{ {
SInt32 res; SInt32 res;
MIDIObjectGetIntegerProperty(object, property, &res); MIDIObjectGetIntegerProperty(object, property, &res);
return res; return res;
} }
static inline CFString_handle toCFString(std::string_view str) noexcept LIBREMIDI_STATIC CFString_handle toCFString(std::string_view str) noexcept
{ {
return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)}; return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)};
} }
@@ -71,20 +70,22 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)
return static_cast<uint64_t>(res); return static_cast<uint64_t>(res);
} }
#endif #endif
LIBREMIDI_STATIC 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 // This function was submitted by Douglas Casey Tucker and apparently
// derived largely from PortMidi. // derived largely from PortMidi.
inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
{ {
CFMutableStringRef result = CFStringCreateMutable(nullptr, 0); 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. // 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()); CFStringAppend(result, endpoint_name.get());
} }
@@ -101,7 +102,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
if (CFStringGetLength(result) == 0) if (CFStringGetLength(result) == 0)
{ {
// endpoint name has zero length -- try the entity // 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()); CFStringAppend(result, entity_name.get());
} }
@@ -112,7 +113,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
if (device == 0) if (device == 0)
goto finish; 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()); const auto dev_strlen = CFStringGetLength(dev_name.get());
@@ -218,22 +219,19 @@ locate_object(auto& self, const port_information& info, MIDIObjectType requested
auto ret = MIDIObjectFindByUniqueID(uid, &object, &type); auto ret = MIDIObjectFindByUniqueID(uid, &object, &type);
if (ret != noErr) if (ret != noErr)
{ {
self.libremidi_handle_error( self.libremidi_handle_error(self.configuration, "cannot find port: " + info.port_name);
self.configuration, "cannot find port: " + info.port_name);
return 0; return 0;
} }
if (type != requested_type || object == 0) if (type != requested_type || object == 0)
{ {
self.libremidi_handle_error( self.libremidi_handle_error(
self.configuration, "invalid object: " + info.port_name + " : " self.configuration, "invalid object: " + info.port_name + " : " + std::to_string(object));
+ std::to_string(object));
return 0; return 0;
} }
return object; return object;
} }
}
// A structure to hold variables related to the CoreMIDI API // A structure to hold variables related to the CoreMIDI API
// implementation. // implementation.
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp> #include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class midi_in_core final class midi_in_core final
: public midi1::in_api : public midi1::in_api
@@ -25,8 +25,7 @@ public:
if (auto result = init_client(configuration); result != noErr) if (auto result = init_client(configuration); result != noErr)
{ {
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration, "error creating MIDI client object: " + std::to_string(result));
"error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result); client_open_ = from_osstatus(result);
return; return;
} }
@@ -64,8 +63,7 @@ public:
{ {
close_client(*this); close_client(*this);
libremidi_handle_error( libremidi_handle_error(
this->configuration, "error creating macOS MIDI input port: " this->configuration, "error creating macOS MIDI input port: " + std::to_string(result));
+ std::to_string(result));
return from_osstatus(result); return from_osstatus(result);
} }
@@ -74,8 +72,7 @@ public:
{ {
MIDIPortDispose(port); MIDIPortDispose(port);
close_client(*this); close_client(*this);
libremidi_handle_error( libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port.");
this->configuration, "error connecting macOS MIDI input port.");
return from_osstatus(result); return from_osstatus(result);
} }
@@ -105,10 +102,7 @@ public:
return stdx::error{}; return stdx::error{};
} }
stdx::error close_port() override stdx::error close_port() override { return coremidi_data::close_port(); }
{
return coremidi_data::close_port();
}
timestamp absolute_timestamp() const noexcept override timestamp absolute_timestamp() const noexcept override
{ {
@@ -3,7 +3,7 @@
#include <libremidi/backends/coremidi/helpers.hpp> #include <libremidi/backends/coremidi/helpers.hpp>
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class midi_out_core final class midi_out_core final
: public midi1::out_api : public midi1::out_api
@@ -24,8 +24,7 @@ public:
if (auto result = init_client(configuration); result != noErr) if (auto result = init_client(configuration); result != noErr)
{ {
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration, "error creating MIDI client object: " + std::to_string(result));
"error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result); client_open_ = from_osstatus(result);
return; return;
} }
@@ -60,8 +59,7 @@ public:
if (result != noErr) if (result != noErr)
{ {
close_client(*this); close_client(*this);
libremidi_handle_error( libremidi_handle_error(this->configuration, "error creating macOS MIDI output port.");
this->configuration, "error creating macOS MIDI output port.");
return from_osstatus(result); return from_osstatus(result);
} }
@@ -80,9 +78,7 @@ public:
if (result != noErr) if (result != noErr)
{ {
libremidi_handle_error( libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source.");
this->configuration,
"error creating macOS virtual MIDI source.");
return from_osstatus(result); return from_osstatus(result);
} }
@@ -92,10 +88,7 @@ public:
return stdx::error{}; return stdx::error{};
} }
stdx::error close_port() override stdx::error close_port() override { return coremidi_data::close_port(); }
{
return coremidi_data::close_port();
}
stdx::error send_message(const unsigned char* message, size_t size) override stdx::error send_message(const unsigned char* message, size_t size) override
{ {
@@ -118,8 +111,8 @@ public:
const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME(); const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes; const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes;
Byte buffer[bufsize + 16]; // pad for other struct members Byte* buffer = (Byte*)alloca(bufsize + 16); // pad for other struct members
ByteCount listSize = sizeof(buffer); ByteCount listSize = bufsize + 16;
MIDIPacketList* packetList = (MIDIPacketList*)buffer; MIDIPacketList* packetList = (MIDIPacketList*)buffer;
ByteCount remainingBytes = nBytes; ByteCount remainingBytes = nBytes;
@@ -137,8 +130,7 @@ public:
if (!packet) if (!packet)
{ {
libremidi_handle_error( libremidi_handle_error(this->configuration, "could not allocate packet list");
this->configuration, "could not allocate packet list");
return std::errc::message_size; return std::errc::message_size;
} }
@@ -163,9 +155,7 @@ public:
auto result = MIDISend(this->port, this->destinationId, packetList); auto result = MIDISend(this->port, this->destinationId, packetList);
if (result != noErr) if (result != noErr)
{ {
libremidi_handle_warning( libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
this->configuration,
"error sending MIDI message to port.");
return std::errc::io_error; return std::errc::io_error;
} }
} }
@@ -7,7 +7,7 @@
#include <bit> #include <bit>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class observer_core class observer_core
: public observer_api : public observer_api
@@ -22,6 +22,10 @@ public:
explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf) explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{
}
void finish_init()
{ {
if (configuration.client_name.empty()) if (configuration.client_name.empty())
configuration.client_name = "libremidi observer"; configuration.client_name = "libremidi observer";
@@ -32,15 +36,13 @@ public:
auto result = MIDIClientCreate( auto result = MIDIClientCreate(
toCFString(configuration.client_name).get(), toCFString(configuration.client_name).get(),
+[](const MIDINotification* message, void* ctx) { +[](const MIDINotification* message, void* ctx) {
((observer_core*)ctx)->notify(message); ((observer_core*)ctx)->notify(message);
}, }, this, &client);
this, &client);
if (result != noErr) if (result != noErr)
{ {
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration, "error creating MIDI client object: " + std::to_string(result));
"error creating MIDI client object: " + std::to_string(result));
return; return;
} }
@@ -78,13 +80,56 @@ public:
if (!ok) if (!ok)
return {}; return {};
// Get the MIDI device from the entity
libremidi::transport_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::transport_type;
SInt32 devid_res{};
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr)
{
type = (libremidi::transport_type)(hardware | usb);
usb_location_id = (uint64_t)devid_res;
}
SInt32 vendor_res{};
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res)
== noErr)
{
type = (libremidi::transport_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::transport_type)(hardware | usb);
if (driver == "com.apple.AppleMIDIBluetoothDriver")
type = (libremidi::transport_type)(hardware | bluetooth);
if (driver == "com.apple.AppleMIDIIACDriver")
type = (libremidi::transport_type)(software);
if (driver == "com.apple.AppleMIDIRTPDriver")
type = (libremidi::transport_type)(network);
}
}
}
return std::conditional_t<Input, input_port, output_port>{ return std::conditional_t<Input, input_port, output_port>{
{.client = (std::uintptr_t)this->client, {.api = get_current_api(),
.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)), .port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),
.manufacturer = get_string_property(obj, kMIDIPropertyManufacturer), .manufacturer = get_string_property(obj, kMIDIPropertyManufacturer),
.device_name = get_string_property(obj, kMIDIPropertyModel), .device_name = get_string_property(obj, kMIDIPropertyModel),
.port_name = get_string_property(obj, kMIDIPropertyName), .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 std::vector<libremidi::input_port> get_input_ports() const noexcept override
@@ -173,3 +218,17 @@ private:
MIDIClientRef client{}; MIDIClientRef client{};
}; };
} }
NAMESPACE_LIBREMIDI::coremidi
{
struct observer_impl : observer_core
{
observer_impl(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)
: observer_core{std::move(conf), std::move(apiconf)}
{
finish_init();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
};
}
+5 -3
View File
@@ -3,7 +3,9 @@
#include <libremidi/backends/coremidi_ump/midi_out.hpp> #include <libremidi/backends/coremidi_ump/midi_out.hpp>
#include <libremidi/backends/coremidi_ump/observer.hpp> #include <libremidi/backends/coremidi_ump/observer.hpp>
namespace libremidi::coremidi_ump #include <string_view>
NAMESPACE_LIBREMIDI::coremidi_ump
{ {
struct backend struct backend
{ {
@@ -14,8 +16,8 @@ struct backend
using midi_out_configuration = coremidi_ump::output_configuration; using midi_out_configuration = coremidi_ump::output_configuration;
using midi_observer_configuration = coremidi_ump::observer_configuration; using midi_observer_configuration = coremidi_ump::observer_configuration;
static const constexpr auto API = libremidi::API::COREMIDI_UMP; static const constexpr auto API = libremidi::API::COREMIDI_UMP;
static const constexpr auto name = "core_ump"; static const constexpr std::string_view name = "core_ump";
static const constexpr auto display_name = "CoreMIDI UMP"; static const constexpr std::string_view display_name = "CoreMIDI UMP";
static constexpr inline bool available() noexcept { return true; /* todo? */ } static constexpr inline bool available() noexcept { return true; /* todo? */ }
}; };
@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <libremidi/backends/coremidi/config.hpp> #include <libremidi/backends/coremidi/config.hpp>
namespace libremidi::coremidi_ump NAMESPACE_LIBREMIDI::coremidi_ump
{ {
struct input_configuration struct input_configuration
{ {
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp> #include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi::coremidi_ump NAMESPACE_LIBREMIDI::coremidi_ump
{ {
class midi_in_impl final class midi_in_impl final
@@ -26,8 +26,7 @@ public:
if (auto result = init_client(configuration); result != noErr) if (auto result = init_client(configuration); result != noErr)
{ {
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration, "error creating MIDI client object: " + std::to_string(result));
"error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result); client_open_ = from_osstatus(result);
return; return;
} }
@@ -69,8 +68,7 @@ public:
{ {
close_client(*this); close_client(*this);
libremidi_handle_error( libremidi_handle_error(
this->configuration, "error creating macOS MIDI input port: " this->configuration, "error creating macOS MIDI input port: " + std::to_string(result));
+ std::to_string(result));
return from_osstatus(result); return from_osstatus(result);
} }
@@ -79,8 +77,7 @@ public:
{ {
MIDIPortDispose(port); MIDIPortDispose(port);
close_client(*this); close_client(*this);
libremidi_handle_error( libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port.");
this->configuration, "error connecting macOS MIDI input port.");
return from_osstatus(result); return from_osstatus(result);
} }
@@ -113,10 +110,7 @@ public:
return stdx::error{}; return stdx::error{};
} }
stdx::error close_port() override stdx::error close_port() override { return coremidi_data::close_port(); }
{
return coremidi_data::close_port();
}
timestamp absolute_timestamp() const noexcept override timestamp absolute_timestamp() const noexcept override
{ {
@@ -5,7 +5,7 @@
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/ump_stream.hpp> #include <libremidi/detail/ump_stream.hpp>
namespace libremidi::coremidi_ump NAMESPACE_LIBREMIDI::coremidi_ump
{ {
class midi_out_impl final class midi_out_impl final
: public midi2::out_api : public midi2::out_api
@@ -27,8 +27,7 @@ public:
if (auto result = init_client(configuration); result != noErr) if (auto result = init_client(configuration); result != noErr)
{ {
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration, "error creating MIDI client object: " + std::to_string(result));
"error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result); client_open_ = from_osstatus(result);
return; return;
} }
@@ -63,8 +62,7 @@ public:
if (result != noErr) if (result != noErr)
{ {
close_client(*this); close_client(*this);
libremidi_handle_error( libremidi_handle_error(this->configuration, "error creating macOS MIDI output port.");
this->configuration, "error creating macOS MIDI output port.");
return from_osstatus(result); return from_osstatus(result);
} }
@@ -84,9 +82,7 @@ public:
if (result != noErr) if (result != noErr)
{ {
this->endpoint = 0; this->endpoint = 0;
libremidi_handle_error( libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source.");
this->configuration,
"error creating macOS virtual MIDI source.");
return from_osstatus(result); return from_osstatus(result);
} }
@@ -94,10 +90,7 @@ public:
return stdx::error{}; return stdx::error{};
} }
stdx::error close_port() override stdx::error close_port() override { return coremidi_data::close_port(); }
{
return coremidi_data::close_port();
}
stdx::error 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
{ {
@@ -142,9 +135,7 @@ public:
auto result = MIDISendEventList(this->port, this->destinationId, eventList); auto result = MIDISendEventList(this->port, this->destinationId, eventList);
if (result != noErr) if (result != noErr)
{ {
libremidi_handle_warning( libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
this->configuration,
"error sending MIDI message to port.");
return std::errc::io_error; return std::errc::io_error;
} }
} }
@@ -1,17 +1,20 @@
#pragma once #pragma once
#include <libremidi/backends/coremidi_ump/config.hpp>
#include <libremidi/backends/coremidi/observer.hpp> #include <libremidi/backends/coremidi/observer.hpp>
#include <libremidi/backends/coremidi_ump/config.hpp>
namespace libremidi::coremidi_ump NAMESPACE_LIBREMIDI::coremidi_ump
{ {
class observer_impl final : public libremidi::observer_core class observer_impl final : public libremidi::observer_core
{ {
public: public:
explicit observer_impl(libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) explicit observer_impl(
: observer_core{std::move(conf), coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf)
: observer_core{
std::move(conf),
coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}}
{ {
finish_init();
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
+14 -30
View File
@@ -5,7 +5,9 @@
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/observer.hpp> #include <libremidi/detail/observer.hpp>
namespace libremidi #include <string_view>
NAMESPACE_LIBREMIDI
{ {
class observer_dummy : public observer_api class observer_dummy : public observer_api
{ {
@@ -23,11 +25,12 @@ class midi_in_dummy final
, public error_handler , public error_handler
{ {
public: public:
explicit midi_in_dummy(const input_configuration& configuration, std::any) explicit midi_in_dummy(const input_configuration& configuration, const input_api_configuration&)
{ {
libremidi_handle_warning(configuration, "This class provides no functionality."); libremidi_handle_warning(configuration, "This class provides no functionality.");
} }
explicit midi_in_dummy(const ump_input_configuration& configuration, std::any) explicit midi_in_dummy(
const ump_input_configuration& configuration, const input_api_configuration&)
{ {
libremidi_handle_warning(configuration, "This class provides no functionality."); libremidi_handle_warning(configuration, "This class provides no functionality.");
} }
@@ -37,19 +40,9 @@ public:
{ {
return stdx::error{}; return stdx::error{};
} }
stdx::error open_virtual_port(std::string_view /*portName*/) override stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; }
{
return stdx::error{};
}
stdx::error close_port() override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; }
stdx::error set_client_name(std::string_view /*clientName*/) override stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; }
{
return stdx::error{};
}
stdx::error set_port_name(std::string_view /*portName*/) override
{
return stdx::error{};
}
timestamp absolute_timestamp() const noexcept override { return 0; } timestamp absolute_timestamp() const noexcept override { return 0; }
}; };
@@ -58,7 +51,8 @@ class midi_out_dummy final
, public error_handler , public error_handler
{ {
public: public:
explicit midi_out_dummy(const output_configuration& configuration, std::any) explicit midi_out_dummy(
const output_configuration& configuration, const output_api_configuration&)
{ {
libremidi_handle_warning(configuration, "This class provides no functionality."); libremidi_handle_warning(configuration, "This class provides no functionality.");
} }
@@ -68,20 +62,10 @@ public:
{ {
return stdx::error{}; return stdx::error{};
} }
stdx::error open_virtual_port(std::string_view /*portName*/) override stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; }
{
return stdx::error{};
}
stdx::error close_port() override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; }
stdx::error set_client_name(std::string_view /*clientName*/) override stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; }
{
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 stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override
{ {
return stdx::error{}; return stdx::error{};
@@ -97,8 +81,8 @@ struct dummy_backend
using midi_out_configuration = dummy_configuration; using midi_out_configuration = dummy_configuration;
using midi_observer_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration;
static const constexpr auto API = libremidi::API::DUMMY; static const constexpr auto API = libremidi::API::DUMMY;
static const constexpr auto name = "dummy"; static const constexpr std::string_view name = "dummy";
static const constexpr auto display_name = "Dummy"; static const constexpr std::string_view display_name = "Dummy";
static constexpr inline bool available() noexcept { return true; } static constexpr inline bool available() noexcept { return true; }
}; };
+5 -3
View File
@@ -7,7 +7,9 @@
#include <libremidi/backends/emscripten/midi_out.hpp> #include <libremidi/backends/emscripten/midi_out.hpp>
#include <libremidi/backends/emscripten/observer.hpp> #include <libremidi/backends/emscripten/observer.hpp>
namespace libremidi #include <string_view>
NAMESPACE_LIBREMIDI
{ {
struct emscripten_backend struct emscripten_backend
@@ -19,8 +21,8 @@ struct emscripten_backend
using midi_out_configuration = emscripten_output_configuration; using midi_out_configuration = emscripten_output_configuration;
using midi_observer_configuration = emscripten_observer_configuration; using midi_observer_configuration = emscripten_observer_configuration;
static const constexpr auto API = libremidi::API::WEBMIDI; static const constexpr auto API = libremidi::API::WEBMIDI;
static const constexpr auto name = "webmidi"; static const constexpr std::string_view name = "webmidi";
static const constexpr auto display_name = "WebMIDI"; static const constexpr std::string_view display_name = "WebMIDI";
static constexpr inline bool available() noexcept { return true; } static constexpr inline bool available() noexcept { return true; }
}; };
@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct emscripten_input_configuration struct emscripten_input_configuration
@@ -3,7 +3,7 @@
#include <string> #include <string>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
namespace webmidi_helpers namespace webmidi_helpers
{ {
@@ -16,7 +16,7 @@ EMSCRIPTEN_KEEPALIVE
void libremidi_devices_input(int port, double timestamp, int len, char* bytes); void libremidi_devices_input(int port, double timestamp, int len, char* bytes);
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
namespace webmidi_helpers namespace webmidi_helpers
{ {
@@ -37,16 +37,25 @@ public:
int input_count() const noexcept int input_count() const noexcept
{ {
if (!available())
return 0;
return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;); return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;);
} }
int output_count() const noexcept int output_count() const noexcept
{ {
if (!available())
return 0;
return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;); return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;);
} }
void load_current_infos() noexcept void load_current_infos() noexcept
{ {
if (!available())
return;
#define get_js_string(variable_to_read, ...) \ #define get_js_string(variable_to_read, ...) \
(char*)EM_ASM_INT( \ (char*)EM_ASM_INT( \
{ \ { \
@@ -206,6 +215,9 @@ public:
stdx::error send_message(int port_index, const char* bytes, int len) stdx::error send_message(int port_index, const char* bytes, int len)
{ {
if (!available())
return std::errc::operation_not_supported;
const auto& id = m_current_outputs[port_index].id; const auto& id = m_current_outputs[port_index].id;
EM_ASM( EM_ASM(
{ {
@@ -216,6 +228,8 @@ public:
output.send(Array.from(bytes)); output.send(Array.from(bytes));
}, },
bytes, len, id.c_str()); bytes, len, id.c_str());
return stdx::error{};
} }
const std::vector<device_information>& inputs() const noexcept { return m_current_inputs; } const std::vector<device_information>& inputs() const noexcept { return m_current_inputs; }
@@ -248,6 +262,9 @@ private:
void start_stream(int port_index) void start_stream(int port_index)
{ {
if (!available())
return;
// Isn't life great... // Isn't life great...
// https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays // https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays
const auto& id = m_current_inputs[port_index].id; const auto& id = m_current_inputs[port_index].id;
@@ -282,6 +299,9 @@ private:
void stop_stream(int port_index) void stop_stream(int port_index)
{ {
if (!available())
return;
const auto& id = m_current_inputs[port_index].id; const auto& id = m_current_inputs[port_index].id;
EM_ASM(const id = UTF8ToString($1); EM_ASM(const id = UTF8ToString($1);
@@ -5,7 +5,7 @@
#include <chrono> #include <chrono>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten( LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten(
input_configuration&& conf, emscripten_input_configuration&& apiconf) input_configuration&& conf, emscripten_input_configuration&& apiconf)
@@ -32,13 +32,12 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std::
if (portNumber < 0 || portNumber >= midi.input_count()) if (portNumber < 0 || portNumber >= midi.input_count())
{ {
libremidi_handle_error( libremidi_handle_error(this->configuration, "no MIDI output sources found.");
this->configuration, "no MIDI output sources found.");
return std::errc::invalid_argument; return std::errc::invalid_argument;
} }
midi.open_input(portNumber, *this); midi.open_input(portNumber, *this);
portNumber_ = portNumber; m_portNumber = portNumber;
return stdx::error{}; return stdx::error{};
} }
@@ -52,7 +51,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port()
{ {
auto& midi = webmidi_helpers::midi_access_emscripten::instance(); auto& midi = webmidi_helpers::midi_access_emscripten::instance();
midi.close_input(portNumber_, *this); midi.close_input(m_portNumber, *this);
return stdx::error{}; return stdx::error{};
} }
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp> #include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class midi_in_emscripten final class midi_in_emscripten final
: public midi1::in_api : public midi1::in_api
@@ -31,7 +31,7 @@ public:
void on_input(double ts, unsigned char* begin, unsigned char* end); void on_input(double ts, unsigned char* begin, unsigned char* end);
private: private:
int portNumber_{}; int m_portNumber{};
midi1::input_state_machine m_processing{this->configuration}; midi1::input_state_machine m_processing{this->configuration};
}; };
@@ -2,7 +2,7 @@
#include <libremidi/backends/emscripten/midi_access.hpp> #include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/midi_out.hpp> #include <libremidi/backends/emscripten/midi_out.hpp>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten( LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten(
output_configuration&& conf, emscripten_output_configuration&& apiconf) output_configuration&& conf, emscripten_output_configuration&& apiconf)
@@ -29,34 +29,36 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std:
if (portNumber >= midi.output_count()) if (portNumber >= midi.output_count())
{ {
libremidi_handle_error( libremidi_handle_error(this->configuration, "no MIDI output sources found.");
this->configuration, "no MIDI output sources found.");
return std::errc::invalid_argument; return std::errc::invalid_argument;
} }
portNumber_ = portNumber; m_portNumber = portNumber;
return stdx::error{}; return stdx::error{};
} }
LIBREMIDI_INLINE stdx::error 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); return open_port(p.port, nm);
} }
LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() { LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port()
{
return stdx::error{}; return stdx::error{};
} }
LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size) LIBREMIDI_INLINE stdx::error
midi_out_emscripten::send_message(const unsigned char* message, size_t size)
{ {
if (portNumber_ < 0) if (m_portNumber < 0)
libremidi_handle_error( libremidi_handle_error(
this->configuration, this->configuration,
"trying to send a message without an open " "trying to send a message without an open "
"port."); "port.");
webmidi_helpers::midi_access_emscripten::instance().send_message( 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{}; return stdx::error{};
} }
} }
@@ -3,7 +3,7 @@
#include <libremidi/backends/emscripten/helpers.hpp> #include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class midi_out_emscripten final class midi_out_emscripten final
: public midi1::out_api : public midi1::out_api
@@ -28,6 +28,6 @@ public:
stdx::error send_message(const unsigned char* message, size_t size) override; stdx::error send_message(const unsigned char* message, size_t size) override;
private: private:
int portNumber_{-1}; int m_portNumber{-1};
}; };
} }
@@ -4,7 +4,7 @@
#include <cassert> #include <cassert>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
LIBREMIDI_INLINE observer_emscripten::observer_emscripten( LIBREMIDI_INLINE observer_emscripten::observer_emscripten(
observer_configuration&& conf, emscripten_observer_configuration&& apiconf) observer_configuration&& conf, emscripten_observer_configuration&& apiconf)
@@ -42,7 +42,8 @@ static auto to_port_info(int index, const webmidi_helpers::device_information& d
-> std::conditional_t<Input, input_port, output_port> -> std::conditional_t<Input, input_port, output_port>
{ {
return { return {
{.client = 0, {.api = libremidi::API::WEBMIDI,
.client = 0,
.port = (uint64_t)index, .port = (uint64_t)index,
.manufacturer = "", .manufacturer = "",
.device_name = "", .device_name = "",
@@ -5,7 +5,7 @@
#include <vector> #include <vector>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class observer_emscripten final : public observer_api class observer_emscripten final : public observer_api
{ {
+5 -3
View File
@@ -3,6 +3,8 @@
#include <libremidi/backends/jack/midi_out.hpp> #include <libremidi/backends/jack/midi_out.hpp>
#include <libremidi/backends/jack/observer.hpp> #include <libremidi/backends/jack/observer.hpp>
#include <string_view>
//*********************************************************************// //*********************************************************************//
// API: UNIX JACK // API: UNIX JACK
// //
@@ -11,7 +13,7 @@
// //
// *********************************************************************// // *********************************************************************//
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct jack_backend struct jack_backend
{ {
@@ -22,8 +24,8 @@ struct jack_backend
using midi_out_configuration = jack_output_configuration; using midi_out_configuration = jack_output_configuration;
using midi_observer_configuration = jack_observer_configuration; using midi_observer_configuration = jack_observer_configuration;
static const constexpr auto API = libremidi::API::JACK_MIDI; static const constexpr auto API = libremidi::API::JACK_MIDI;
static const constexpr auto name = "jack"; static const constexpr std::string_view name = "jack";
static const constexpr auto display_name = "JACK"; static const constexpr std::string_view display_name = "JACK";
static inline bool available() noexcept static inline bool available() noexcept
{ {
+1 -1
View File
@@ -11,7 +11,7 @@ typedef uint32_t jack_nframes_t;
typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg); typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg);
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
using jack_callback_function = std::function<void(int nframes)>; using jack_callback_function = std::function<void(int nframes)>;
struct jack_callback struct jack_callback
@@ -11,7 +11,7 @@
#include <jack/ringbuffer.h> #include <jack/ringbuffer.h>
#endif #endif
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct jack_error_domain : public stdx::error_domain struct jack_error_domain : public stdx::error_domain
{ {
@@ -34,36 +34,36 @@ public:
stdx::string_ref message(const stdx::error& e) const noexcept override stdx::string_ref message(const stdx::error& e) const noexcept override
{ {
const auto status = error_cast<jack_status_t>(e); const auto status = error_cast<jack_status_t>(e);
if(status == jack_status_t{}) if (status == jack_status_t{})
return "Success"; return "Success";
if(status & JackInvalidOption) if (status & JackInvalidOption)
return "The operation contained an invalid or unsupported option"; return "The operation contained an invalid or unsupported option";
if(status & JackServerFailed) if (status & JackServerFailed)
return "Unable to connect to the JACK server"; return "Unable to connect to the JACK server";
if(status & JackServerError) if (status & JackServerError)
return "Communication error with the JACK server"; return "Communication error with the JACK server";
if(status & JackNoSuchClient) if (status & JackNoSuchClient)
return "Requested client does not exist"; return "Requested client does not exist";
if(status & JackLoadFailure) if (status & JackLoadFailure)
return "Unable to load internal client"; return "Unable to load internal client";
if(status & JackInitFailure) if (status & JackInitFailure)
return "Unable to initialize client"; return "Unable to initialize client";
if(status & JackShmFailure) if (status & JackShmFailure)
return "Unable to access shared memory"; return "Unable to access shared memory";
if(status & JackVersionError) if (status & JackVersionError)
return "Client's protocol version does not match"; return "Client's protocol version does not match";
if(status & JackBackendError) if (status & JackBackendError)
return "Backend error"; return "Backend error";
if(status & JackClientZombie) if (status & JackClientZombie)
return "Client zombified failure"; return "Client zombified failure";
if(status & JackFailure) if (status & JackFailure)
return "Failure"; return "Failure";
if(status & JackNameNotUnique) if (status & JackNameNotUnique)
return "The desired client name was not unique"; return "The desired client name was not unique";
// Can't happen in libremidi as we set JackNoStartServer // Can't happen in libremidi as we set JackNoStartServer
if(status & JackServerStarted) if (status & JackServerStarted)
return "Server was started"; return "Server was started";
return "Unknown JACK status code"; return "Unknown JACK status code";
+114 -37
View File
@@ -5,9 +5,11 @@
#include <libremidi/detail/semaphore.hpp> #include <libremidi/detail/semaphore.hpp>
#include <atomic> #include <atomic>
#include <semaphore> #include <cstdint>
#include <cstring>
#include <memory>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct jack_client struct jack_client
{ {
@@ -39,31 +41,32 @@ struct jack_client
} }
} }
template <bool Input> template <bool Input, libremidi::API Api>
static auto to_port_info(jack_client_t* client, jack_port_t* port) static auto to_port_info(jack_client_t* client, jack_port_t* port)
-> std::conditional_t<Input, input_port, output_port> -> std::conditional_t<Input, input_port, output_port>
{ {
return {{ return {{
.api = Api,
.client = reinterpret_cast<std::uintptr_t>(client), .client = reinterpret_cast<std::uintptr_t>(client),
.port = 0, .port = 0,
.manufacturer = "", .manufacturer = "",
.device_name = "", .device_name = jack_get_client_name(client),
.port_name = jack_port_name(port), .port_name = jack_port_name(port),
.display_name = get_port_display_name(port), .display_name = get_port_display_name(port),
}}; }};
} }
template <bool Input> template <bool Input, libremidi::API Api>
static auto static auto get_ports(
get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags,
-> std::vector<std::conditional_t<Input, input_port, output_port>> bool midi2) noexcept -> std::vector<std::conditional_t<Input, input_port, output_port>>
{ {
std::vector<std::conditional_t<Input, input_port, output_port>> ret; std::vector<std::conditional_t<Input, input_port, output_port>> ret;
if (!client) if (!client)
return {}; 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) if (ports == nullptr)
return {}; return {};
@@ -73,7 +76,11 @@ struct jack_client
{ {
// FIXME this does not take into account filtering sw / hw ports // FIXME this does not take into account filtering sw / hw ports
auto port = jack_port_by_name(client, ports[i]); 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, Api>(client, port));
}
i++; i++;
} }
@@ -131,13 +138,13 @@ struct jack_helpers : jack_client
configuration.set_process_func( configuration.set_process_func(
{.token = this_instance, {.token = this_instance,
.callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int { .callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {
if (auto pt = p.lock()) if (auto pt = p.lock())
if (auto ppt = pt->load()) if (pt->load())
self.process(nf); self.process(nf);
self.thread_lock.check_client_released(); self.thread_lock.check_client_released();
return 0; return 0;
}}); }});
this->client = configuration.context; this->client = configuration.context;
return jack_status_t{}; return jack_status_t{};
@@ -149,26 +156,25 @@ struct jack_helpers : jack_client
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
if (this->client != nullptr) if (this->client != nullptr)
{ {
if(status & JackNameNotUnique) { if (status & JackNameNotUnique)
self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed."); {
self.libremidi_handle_warning(
self.configuration, "JACK client with the same name already exists, renamed.");
} }
jack_set_process_callback( jack_set_process_callback(this->client, +[](jack_nframes_t nf, void* ctx) -> int {
this->client, auto& self = *static_cast<Self*>(ctx);
+[](jack_nframes_t nf, void* ctx) -> int { jack_port_t* port = self.port;
auto& self = *static_cast<Self*>(ctx);
jack_port_t* port = self.port;
// Is port created? // Is port created?
if (port == nullptr) if (port == nullptr)
return 0; return 0;
self.process(nf); self.process(nf);
self.thread_lock.check_client_released(); self.thread_lock.check_client_released();
return 0; return 0;
}, }, &self);
&self);
jack_activate(this->client); jack_activate(this->client);
} }
return status; return status;
@@ -192,8 +198,8 @@ struct jack_helpers : jack_client
self.client_open_ = std::errc::not_connected; self.client_open_ = std::errc::not_connected;
} }
stdx::error stdx::error create_local_port(
create_local_port(const auto& self, std::string_view portName, JackPortFlags flags) const auto& self, std::string_view portName, const char* type, JackPortFlags flags)
{ {
// full name: "client_name:port_name\0" // full name: "client_name:port_name\0"
if (portName.empty()) if (portName.empty())
@@ -202,15 +208,13 @@ struct jack_helpers : jack_client
if (self.configuration.client_name.size() + portName.size() + 2u if (self.configuration.client_name.size() + portName.size() + 2u
>= static_cast<size_t>(jack_port_name_size())) >= static_cast<size_t>(jack_port_name_size()))
{ {
self.libremidi_handle_error( self.libremidi_handle_error(self.configuration, "port name length limit exceeded");
self.configuration, "port name length limit exceeded");
return std::errc::invalid_argument; return std::errc::invalid_argument;
} }
if (!this->port) if (!this->port)
{ {
this->port this->port = jack_port_register(this->client, portName.data(), type, flags, 0);
= jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0);
} }
if (!this->port) if (!this->port)
@@ -238,4 +242,77 @@ struct jack_helpers : jack_client
return from_errc(err); 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";
};
} }
+9 -9
View File
@@ -4,13 +4,12 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp> #include <libremidi/detail/midi_stream_decoder.hpp>
#include <chrono> NAMESPACE_LIBREMIDI
namespace libremidi
{ {
class midi_in_jack final class midi_in_jack final
: public midi1::in_api : public midi1::in_api
, public jack_helpers , public jack_helpers
, public jack_midi1
, public error_handler , public error_handler
{ {
public: public:
@@ -46,15 +45,16 @@ public:
stdx::error 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 (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{}) if (auto err = create_local_port(*this, portName, port_type, JackPortIsInput);
err != stdx::error{})
return err; return err;
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
err != 0 && err != EEXIST) err != 0 && err != EEXIST)
{ {
libremidi_handle_error( libremidi_handle_error(
configuration, "could not connect to port: " + port.port_name + " -> " configuration,
+ jack_port_name(this->port)); "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port));
return from_errc(err); return from_errc(err);
} }
return stdx::error{}; return stdx::error{};
@@ -62,7 +62,7 @@ public:
stdx::error 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);
} }
stdx::error close_port() override { return do_close_port(); } stdx::error close_port() override { return do_close_port(); }
@@ -96,8 +96,8 @@ public:
this->client, &current_frames, &current_usecs, &next_usecs, &period_usecs); this->client, &current_frames, &current_usecs, &next_usecs, &period_usecs);
// We have midi events in buffer // We have midi events in buffer
uint32_t evCount = jack_midi_get_event_count(buff); uint32_t ev_count = jack_midi_get_event_count(buff);
for (uint32_t j = 0; j < evCount; j++) for (uint32_t j = 0; j < ev_count; j++)
{ {
jack_midi_event_t event{}; jack_midi_event_t event{};
jack_midi_event_get(&event, buff, j); jack_midi_event_get(&event, buff, j);
+11 -75
View File
@@ -3,76 +3,12 @@
#include <libremidi/backends/jack/helpers.hpp> #include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
#include <semaphore> NAMESPACE_LIBREMIDI
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);
}
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
};
class midi_out_jack class midi_out_jack
: public midi1::out_api : public midi1::out_api
, public jack_helpers , public jack_helpers
, public jack_midi1
, public error_handler , public error_handler
{ {
public: public:
@@ -94,15 +30,15 @@ public:
stdx::error 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 (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{}) if (auto err = create_local_port(*this, portName, port_type, JackPortIsOutput);
err != stdx::error{})
return err; return err;
// Connecting to the output // Connecting to the output
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
err != 0 && err != EEXIST) err != 0 && err != EEXIST)
{ {
libremidi_handle_error( libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
configuration, "could not connect to port" + port.port_name);
return from_errc(err); return from_errc(err);
} }
@@ -111,7 +47,7 @@ public:
stdx::error 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);
} }
stdx::error close_port() override { return do_close_port(); } stdx::error close_port() override { return do_close_port(); }
@@ -128,7 +64,7 @@ class midi_out_jack_queued final : public midi_out_jack
public: public:
midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf) midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf)
: midi_out_jack{std::move(conf), std::move(apiconf)} : midi_out_jack{std::move(conf), std::move(apiconf)}
, queue{configuration.ringbuffer_size} , m_queue{configuration.ringbuffer_size}
{ {
auto status = connect(*this); auto status = connect(*this);
if (!this->client) if (!this->client)
@@ -150,7 +86,7 @@ public:
stdx::error send_message(const unsigned char* message, std::size_t size) override stdx::error send_message(const unsigned char* message, std::size_t size) override
{ {
return queue.write(message, size); return m_queue.write(message, size);
} }
int process(jack_nframes_t nframes) int process(jack_nframes_t nframes)
@@ -158,13 +94,13 @@ public:
void* buff = jack_port_get_buffer(this->port, nframes); void* buff = jack_port_get_buffer(this->port, nframes);
jack_midi_clear_buffer(buff); jack_midi_clear_buffer(buff);
this->queue.read(buff); this->m_queue.read(buff);
return 0; return 0;
} }
private: private:
jack_queue queue; jack_queue m_queue;
}; };
class midi_out_jack_direct final : public midi_out_jack class midi_out_jack_direct final : public midi_out_jack
@@ -230,7 +166,7 @@ public:
}; };
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
template <> template <>
inline std::unique_ptr<midi_out_api> make<midi_out_jack>( inline std::unique_ptr<midi_out_api> make<midi_out_jack>(
+33 -30
View File
@@ -5,11 +5,12 @@
#include <unordered_set> #include <unordered_set>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class observer_jack final class observer_jack final
: public observer_api : public observer_api
, private jack_client , private jack_client
, public jack_midi1
, private error_handler , private error_handler
{ {
public: public:
@@ -48,8 +49,7 @@ public:
void initial_callback() void initial_callback()
{ {
{ {
const char** ports const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);
if (ports != nullptr) if (ports != nullptr)
{ {
@@ -70,7 +70,8 @@ public:
{ {
seen_input_ports.insert(ports[i]); seen_input_ports.insert(ports[i]);
if (this->configuration.input_added && configuration.notify_in_constructor) if (this->configuration.input_added && configuration.notify_in_constructor)
this->configuration.input_added(to_port_info<true>(client, port)); this->configuration.input_added(
to_port_info<true, libremidi::API::JACK_MIDI>(client, port));
} }
i++; i++;
} }
@@ -80,8 +81,7 @@ public:
} }
{ {
const char** ports const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
if (ports != nullptr) if (ports != nullptr)
{ {
@@ -102,7 +102,8 @@ public:
{ {
seen_output_ports.insert(ports[i]); seen_output_ports.insert(ports[i]);
if (this->configuration.output_added && configuration.notify_in_constructor) if (this->configuration.output_added && configuration.notify_in_constructor)
this->configuration.output_added(to_port_info<false>(client, port)); this->configuration.output_added(
to_port_info<false, libremidi::API::JACK_MIDI>(client, port));
} }
i++; i++;
} }
@@ -119,7 +120,7 @@ public:
if (reg) if (reg)
{ {
std::string_view type = jack_port_type(port); std::string_view type = jack_port_type(port);
if (type != JACK_DEFAULT_MIDI_TYPE) if (type != port_type)
return; return;
bool physical = flags & JackPortIsPhysical; bool physical = flags & JackPortIsPhysical;
@@ -139,13 +140,15 @@ public:
{ {
seen_input_ports.insert(name); seen_input_ports.insert(name);
if (this->configuration.input_added) if (this->configuration.input_added)
this->configuration.input_added(to_port_info<true>(client, port)); this->configuration.input_added(
to_port_info<true, libremidi::API::JACK_MIDI>(client, port));
} }
else if (flags & JackPortIsInput) else if (flags & JackPortIsInput)
{ {
seen_output_ports.insert(name); seen_output_ports.insert(name);
if (this->configuration.output_added) if (this->configuration.output_added)
this->configuration.output_added(to_port_info<false>(client, port)); this->configuration.output_added(
to_port_info<false, libremidi::API::JACK_MIDI>(client, port));
} }
} }
else else
@@ -153,13 +156,15 @@ public:
if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())
{ {
if (this->configuration.input_removed) if (this->configuration.input_removed)
this->configuration.input_removed(to_port_info<true>(client, port)); this->configuration.input_removed(
to_port_info<true, libremidi::API::JACK_MIDI>(client, port));
seen_input_ports.erase(it); seen_input_ports.erase(it);
} }
if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) if (auto it = seen_output_ports.find(name); it != seen_output_ports.end())
{ {
if (this->configuration.output_removed) if (this->configuration.output_removed)
this->configuration.output_removed(to_port_info<false>(client, port)); this->configuration.output_removed(
to_port_info<false, libremidi::API::JACK_MIDI>(client, port));
seen_output_ports.erase(it); seen_output_ports.erase(it);
} }
} }
@@ -172,39 +177,37 @@ public:
if (!configuration.has_callbacks()) if (!configuration.has_callbacks())
return; return;
jack_set_port_registration_callback( jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) {
this->client, auto& self = *(observer_jack*)arg;
+[](jack_port_id_t p, int r, void* arg) { if (auto port = jack_port_by_id(self.client, p))
auto& self = *(observer_jack*)arg; {
if (auto port = jack_port_by_id(self.client, p)) self.on_port_callback(port, r != 0);
{ }
self.on_port_callback(port, r != 0); }, this);
}
},
this);
jack_set_port_rename_callback( jack_set_port_rename_callback(
this->client, this->client,
+[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) { +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) {
const auto& self = *static_cast<observer_jack*>(arg); const auto& self = *static_cast<observer_jack*>(arg);
auto port = jack_port_by_id(self.client, p); auto port = jack_port_by_id(self.client, p);
if (!port) if (!port)
return; return;
}, }, this);
this);
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; } libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override std::vector<libremidi::input_port> get_input_ports() const noexcept override
{ {
return get_ports<true>(this->client, nullptr, JackPortIsOutput); return get_ports<true, libremidi::API::JACK_MIDI>(
this->client, nullptr, port_type, JackPortIsOutput, false);
} }
std::vector<libremidi::output_port> get_output_ports() const noexcept override std::vector<libremidi::output_port> get_output_ports() const noexcept override
{ {
return get_ports<false>(this->client, nullptr, JackPortIsInput); return get_ports<false, libremidi::API::JACK_MIDI>(
this->client, nullptr, port_type, JackPortIsInput, false);
} }
~observer_jack() ~observer_jack()
@@ -8,7 +8,7 @@
#include <variant> #include <variant>
namespace libremidi::jack NAMESPACE_LIBREMIDI::jack
{ {
// Create a JACK client which will be shared across objects // Create a JACK client which will be shared across objects
@@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context
client = jack_client_open(v.data(), JackNoStartServer, &status); client = jack_client_open(v.data(), JackNoStartServer, &status);
assert(client); assert(client);
assert(status == 0); assert(status == 0);
jack_set_process_callback( jack_set_process_callback(client, +[](jack_nframes_t cnt, void* ctx) -> int {
client, ((shared_handler*)ctx)->jack_callback(cnt);
+[](jack_nframes_t cnt, void* ctx) -> int { return 0;
((shared_handler*)ctx)->jack_callback(cnt); }, this);
return 0;
},
this);
} }
virtual void start_processing() override { jack_activate(client); } virtual void start_processing() override { jack_activate(client); }
@@ -73,11 +70,10 @@ struct shared_handler : public libremidi::shared_context
switch (ev.type) switch (ev.type)
{ {
case in_callback_added: case in_callback_added:
midiin_callbacks.push_back( midiin_callbacks.push_back(std::move(*get_if<libremidi::jack_callback>(&ev.payload)));
std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));
break; break;
case in_callback_removed: { case in_callback_removed: {
auto idx = *std::get_if<int64_t>(&ev.payload); auto idx = *get_if<int64_t>(&ev.payload);
for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();) for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)
{ {
if (it->token == idx) if (it->token == idx)
@@ -93,11 +89,10 @@ struct shared_handler : public libremidi::shared_context
break; break;
} }
case out_callback_added: case out_callback_added:
midiout_callbacks.push_back( midiout_callbacks.push_back(std::move(*get_if<libremidi::jack_callback>(&ev.payload)));
std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));
break; break;
case out_callback_removed: case out_callback_removed:
auto idx = *std::get_if<int64_t>(&ev.payload); auto idx = *get_if<int64_t>(&ev.payload);
for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();) for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)
{ {
if (it->token == idx) if (it->token == idx)
@@ -141,7 +136,7 @@ struct shared_handler : public libremidi::shared_context
struct event struct event
{ {
event_type type; event_type type;
std::variant<libremidi::jack_callback, int64_t> payload; libremidi_variant_alias::variant<libremidi::jack_callback, int64_t> payload;
}; };
boost::lockfree::spsc_queue<event> events{16}; boost::lockfree::spsc_queue<event> events{16};
+34
View File
@@ -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, &micro, &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, &current_frames, &current_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,243 @@
#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, libremidi::API::JACK_UMP>(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, libremidi::API::JACK_UMP>(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, libremidi::API::JACK_UMP>(client, port));
}
else if (flags & JackPortIsInput)
{
seen_output_ports.insert(name);
if (this->configuration.output_added)
this->configuration.output_added(
to_port_info<false, libremidi::API::JACK_UMP>(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, libremidi::API::JACK_UMP>(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, libremidi::API::JACK_UMP>(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, libremidi::API::JACK_UMP>(
this->client, nullptr, port_type, JackPortIsOutput, true);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<false, libremidi::API::JACK_UMP>(
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;
};
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <libremidi/backends/kdmapi/midi_in.hpp>
#include <libremidi/backends/kdmapi/midi_out.hpp>
#include <libremidi/backends/kdmapi/observer.hpp>
#include <string_view>
//*********************************************************************//
// API: OmniMIDI KDMAPI (Keppy's Direct MIDI API)
//*********************************************************************//
// OmniMIDI is a low-latency MIDI synthesizer driver for Windows.
// KDMAPI provides a direct interface to OmniMIDI, bypassing the
// Windows Multimedia API for lower latency.
//
// Note: KDMAPI is output-only. The midi_in class is a placeholder
// that always fails - use a different backend for MIDI input.
//
// https://github.com/KeppySoftware/OmniMIDI
namespace libremidi
{
struct kdmapi_backend
{
using midi_in = kdmapi::midi_in;
using midi_out = kdmapi::midi_out;
using midi_observer = kdmapi::observer;
using midi_in_configuration = kdmapi::input_configuration;
using midi_out_configuration = kdmapi::output_configuration;
using midi_observer_configuration = kdmapi::observer_configuration;
static const constexpr auto API = libremidi::API::KDMAPI;
static const constexpr std::string_view name = "kdmapi";
static const constexpr std::string_view display_name = "OmniMIDI (KDMAPI)";
static inline bool available() noexcept
{
return kdmapi::kdmapi_loader::instance().is_available();
}
};
}
@@ -0,0 +1,23 @@
#pragma once
#include <libremidi/config.hpp>
namespace libremidi::kdmapi
{
// KDMAPI does not support MIDI input
struct input_configuration
{
};
struct output_configuration
{
// Use the no-buffer variant of SendDirectData for lowest latency
// This bypasses OmniMIDI's internal buffer
bool use_no_buffer = false;
};
struct observer_configuration
{
};
}
@@ -0,0 +1,178 @@
#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/detail/midi_api.hpp>
#include <atomic>
#include <mutex>
namespace libremidi::kdmapi
{
using IsKDMAPIAvailable_t = BOOL(WINAPI*)();
using InitializeKDMAPIStream_t = BOOL(WINAPI*)();
using TerminateKDMAPIStream_t = BOOL(WINAPI*)();
using ResetKDMAPIStream_t = VOID(WINAPI*)();
using SendDirectData_t = VOID(WINAPI*)(DWORD);
using SendDirectDataNoBuf_t = VOID(WINAPI*)(DWORD);
using SendDirectLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
using SendDirectLongDataNoBuf_t = UINT(WINAPI*)(LPSTR, DWORD);
using PrepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
using UnprepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
using ReturnKDMAPIVer_t = BOOL(WINAPI*)(LPDWORD, LPDWORD, LPDWORD, LPDWORD);
class kdmapi_loader
{
public:
static kdmapi_loader& instance()
{
static kdmapi_loader loader;
return loader;
}
bool is_available() const noexcept { return m_available; }
HMODULE handle() const noexcept { return m_handle; }
// KDMAPI function pointers
IsKDMAPIAvailable_t IsKDMAPIAvailable{};
InitializeKDMAPIStream_t InitializeKDMAPIStream{};
TerminateKDMAPIStream_t TerminateKDMAPIStream{};
ResetKDMAPIStream_t ResetKDMAPIStream{};
SendDirectData_t SendDirectData{};
SendDirectDataNoBuf_t SendDirectDataNoBuf{};
SendDirectLongData_t SendDirectLongData{};
SendDirectLongDataNoBuf_t SendDirectLongDataNoBuf{};
PrepareLongData_t PrepareLongData{};
UnprepareLongData_t UnprepareLongData{};
ReturnKDMAPIVer_t ReturnKDMAPIVer{};
private:
kdmapi_loader()
{
// Try to get OmniMIDI if it's already loaded
m_handle = GetModuleHandleW(L"OmniMIDI");
if (!m_handle)
{
// Try to load it explicitly
m_handle = LoadLibraryW(L"OmniMIDI.dll");
}
if (!m_handle)
{
m_available = false;
return;
}
#if !defined(_MSC_VER)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
// Load all function pointers
IsKDMAPIAvailable
= reinterpret_cast<IsKDMAPIAvailable_t>(GetProcAddress(m_handle, "IsKDMAPIAvailable"));
InitializeKDMAPIStream = reinterpret_cast<InitializeKDMAPIStream_t>(
GetProcAddress(m_handle, "InitializeKDMAPIStream"));
TerminateKDMAPIStream = reinterpret_cast<TerminateKDMAPIStream_t>(
GetProcAddress(m_handle, "TerminateKDMAPIStream"));
ResetKDMAPIStream
= reinterpret_cast<ResetKDMAPIStream_t>(GetProcAddress(m_handle, "ResetKDMAPIStream"));
SendDirectData
= reinterpret_cast<SendDirectData_t>(GetProcAddress(m_handle, "SendDirectData"));
SendDirectDataNoBuf
= reinterpret_cast<SendDirectDataNoBuf_t>(GetProcAddress(m_handle, "SendDirectDataNoBuf"));
SendDirectLongData
= reinterpret_cast<SendDirectLongData_t>(GetProcAddress(m_handle, "SendDirectLongData"));
SendDirectLongDataNoBuf = reinterpret_cast<SendDirectLongDataNoBuf_t>(
GetProcAddress(m_handle, "SendDirectLongDataNoBuf"));
PrepareLongData
= reinterpret_cast<PrepareLongData_t>(GetProcAddress(m_handle, "PrepareLongData"));
UnprepareLongData
= reinterpret_cast<UnprepareLongData_t>(GetProcAddress(m_handle, "UnprepareLongData"));
ReturnKDMAPIVer
= reinterpret_cast<ReturnKDMAPIVer_t>(GetProcAddress(m_handle, "ReturnKDMAPIVer"));
#if !defined(_MSC_VER)
#pragma GCC diagnostic pop
#endif
// Check if the minimum required functions are available
if (!IsKDMAPIAvailable || !InitializeKDMAPIStream || !TerminateKDMAPIStream || !SendDirectData)
{
m_available = false;
return;
}
// Call IsKDMAPIAvailable to activate KDMAPI mode in OmniMIDI
m_available = IsKDMAPIAvailable() != FALSE;
}
~kdmapi_loader()
{
}
kdmapi_loader(const kdmapi_loader&) = delete;
kdmapi_loader& operator=(const kdmapi_loader&) = delete;
HMODULE m_handle{};
bool m_available{false};
};
class kdmapi_stream_manager
{
public:
static kdmapi_stream_manager& instance()
{
static kdmapi_stream_manager mgr;
return mgr;
}
bool acquire()
{
std::lock_guard lock{m_mutex};
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
return false;
if (m_rc == 0)
{
if (!loader.InitializeKDMAPIStream())
return false;
}
++m_rc;
return true;
}
void release()
{
std::lock_guard lock{m_mutex};
if (m_rc == 0)
return;
--m_rc;
if (m_rc == 0)
{
auto& loader = kdmapi_loader::instance();
if (loader.TerminateKDMAPIStream)
loader.TerminateKDMAPIStream();
}
}
private:
kdmapi_stream_manager() = default;
~kdmapi_stream_manager() = default;
kdmapi_stream_manager(const kdmapi_stream_manager&) = delete;
kdmapi_stream_manager& operator=(const kdmapi_stream_manager&) = delete;
std::mutex m_mutex;
std::size_t m_rc{0};
};
}
@@ -0,0 +1,11 @@
#pragma once
#include <libremidi/backends/kdmapi/config.hpp>
#include <libremidi/backends/dummy.hpp>
#include <libremidi/detail/midi_in.hpp>
namespace libremidi::kdmapi
{
using midi_in = libremidi::midi_in_dummy;
}
@@ -0,0 +1,173 @@
#pragma once
#include <libremidi/backends/kdmapi/config.hpp>
#include <libremidi/backends/kdmapi/helpers.hpp>
#include <libremidi/backends/kdmapi/observer.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi::kdmapi
{
class midi_out final
: public midi1::out_api
, public error_handler
{
public:
struct
: libremidi::output_configuration
, kdmapi::output_configuration
{
} configuration;
midi_out(libremidi::output_configuration&& conf, kdmapi::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
{
libremidi_handle_error(
configuration, "KDMAPI is not available. Is OmniMIDI installed?");
this->client_open_ = std::errc::no_such_device;
return;
}
this->client_open_ = stdx::error{};
}
~midi_out() override
{
midi_out::close_port();
this->client_open_ = std::errc::not_connected;
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; }
stdx::error open_port(const output_port& port, std::string_view) override
{
if (port.api != libremidi::API::KDMAPI)
{
libremidi_handle_error(configuration, "port is not a KDMAPI port");
return std::errc::invalid_argument;
}
return do_open();
}
stdx::error do_open()
{
auto& mgr = kdmapi_stream_manager::instance();
if (!mgr.acquire())
{
libremidi_handle_error(configuration, "failed to initialize KDMAPI stream");
return std::errc::io_error;
}
connected_ = true;
return stdx::error{};
}
stdx::error close_port() override
{
if (connected_)
{
auto& mgr = kdmapi_stream_manager::instance();
mgr.release();
connected_ = false;
}
return stdx::error{};
}
stdx::error send_message(const unsigned char* message, size_t size) override
{
if (!connected_)
return std::errc::not_connected;
if (size == 0)
{
libremidi_handle_warning(configuration, "message argument is empty!");
return std::errc::invalid_argument;
}
auto& loader = kdmapi_loader::instance();
if (message[0] == 0xF0)
{
// SysEx message
return send_sysex(message, size);
}
else
{
// Channel or system message
if (size > 3)
{
libremidi_handle_warning(
configuration, "message size is greater than 3 bytes (and not sysex)!");
return std::errc::message_size;
}
// Pack MIDI bytes into DWORD
DWORD packet = 0;
std::copy_n(message, size, reinterpret_cast<unsigned char*>(&packet));
// Send the message
if (configuration.use_no_buffer && loader.SendDirectDataNoBuf)
loader.SendDirectDataNoBuf(packet);
else
loader.SendDirectData(packet);
}
return stdx::error{};
}
private:
stdx::error send_sysex(const unsigned char* message, size_t size)
{
auto& loader = kdmapi_loader::instance();
if (configuration.use_no_buffer && loader.SendDirectLongDataNoBuf)
{
// Use the no-buffer variant - it takes raw data directly
loader.SendDirectLongDataNoBuf(
const_cast<LPSTR>(reinterpret_cast<const char*>(message)), static_cast<DWORD>(size));
return stdx::error{};
}
// Standard path with MIDIHDR
buffer_.assign(message, message + size);
MIDIHDR sysex{};
sysex.lpData = reinterpret_cast<LPSTR>(buffer_.data());
sysex.dwBufferLength = static_cast<DWORD>(size);
sysex.dwFlags = 0;
if (loader.PrepareLongData)
{
UINT result = loader.PrepareLongData(&sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(configuration, "error preparing sysex header");
return std::errc::io_error;
}
}
if (loader.SendDirectLongData)
{
UINT result = loader.SendDirectLongData(&sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(configuration, "error sending sysex message");
// Still try to unprepare
}
}
if (loader.UnprepareLongData)
{
loader.UnprepareLongData(&sysex, sizeof(MIDIHDR));
}
return stdx::error{};
}
std::vector<char> buffer_;
};
}
@@ -0,0 +1,86 @@
#pragma once
#include <libremidi/backends/kdmapi/config.hpp>
#include <libremidi/backends/kdmapi/helpers.hpp>
#include <libremidi/detail/observer.hpp>
namespace libremidi::kdmapi
{
class observer final : public observer_api
{
public:
struct
: libremidi::observer_configuration
, kdmapi::observer_configuration
{
} configuration;
explicit observer(
libremidi::observer_configuration&& conf, kdmapi::observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (!configuration.has_callbacks())
return;
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
return;
// KDMAPI provides a single virtual output port
if (configuration.notify_in_constructor)
{
if (configuration.output_added)
{
auto ports = get_output_ports();
for (const auto& port : ports)
configuration.output_added(port);
}
}
}
~observer() = default;
libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
// KDMAPI does not provide input ports
return {};
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
return {};
// Get KDMAPI version for display name
std::string display_name = "OmniMIDI";
if (loader.ReturnKDMAPIVer)
{
DWORD major{}, minor{}, build{}, rev{};
if (loader.ReturnKDMAPIVer(&major, &minor, &build, &rev))
{
display_name += " (KDMAPI ";
display_name += std::to_string(major);
display_name += ".";
display_name += std::to_string(minor);
display_name += ".";
display_name += std::to_string(build);
display_name += ")";
}
}
return {libremidi::output_port{
{.api = libremidi::API::KDMAPI,
.client = 0,
.port = 0,
.manufacturer = "KeppySoftware",
.device_name = "OmniMIDI",
.port_name = "OmniMIDI",
.display_name = std::move(display_name)},
}};
}
};
}
+5 -3
View File
@@ -2,7 +2,9 @@
#include <libremidi/backends/dummy.hpp> #include <libremidi/backends/dummy.hpp>
#include <libremidi/backends/keyboard/midi_in.hpp> #include <libremidi/backends/keyboard/midi_in.hpp>
namespace libremidi #include <string_view>
NAMESPACE_LIBREMIDI
{ {
struct kbd_backend struct kbd_backend
{ {
@@ -13,8 +15,8 @@ struct kbd_backend
using midi_out_configuration = dummy_configuration; using midi_out_configuration = dummy_configuration;
using midi_observer_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration;
static const constexpr auto API = libremidi::API::KEYBOARD; static const constexpr auto API = libremidi::API::KEYBOARD;
static const constexpr auto name = "keyboard"; static const constexpr std::string_view name = "keyboard";
static const constexpr auto display_name = "Computer keyboard"; static const constexpr std::string_view display_name = "Computer keyboard";
static inline bool available() noexcept { return true; } static inline bool available() noexcept { return true; }
}; };
+134 -72
View File
@@ -4,8 +4,130 @@
#include <functional> #include <functional>
#include <map> #include <map>
namespace libremidi 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
LIBREMIDI_STATIC 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
LIBREMIDI_STATIC 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. * Used to set up keyboard input.
* Your app should pass a function that will give you a callback * Your app should pass a function that will give you a callback
@@ -13,80 +135,20 @@ namespace libremidi
*/ */
struct kbd_input_configuration 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)>; using scancode_callback = std::function<void(int)>;
// First argument is on key press, second on key release // First argument is on key press, second on key release
std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks
= [](scancode_callback, scancode_callback) {}; = [](const scancode_callback&, const scancode_callback&) { };
enum kbd_event std::map<int, int> scancode_map
{ #if defined(__APPLE__)
NOTE_0 = 0x0, // C = scancode_map_macos()
VEL_0 = NOTE_0 + 128, // Set velocity to 0 #else
OCT_0 = VEL_0 + 128, // Set octave to 0 = scancode_map_linux()
OCTAVE_PLUS = OCT_0 + 128, #endif
OCTAVE_MINUS, ;
VELOCITY_PLUS,
VELOCITY_MINUS,
};
// Default map
//
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
// | ctrl | | alt | |altgr | | ctrl |
// '------' '-----'--------------------------'------' '------'
// clang-format off
// Use: https://kbdlayout.info/KBDUSX/scancodes
std::map<int, int> scancode_map{
{ 0x1E, NOTE_0}, // C0
{ 0x11, NOTE_0 + 1},
{ 0x1F, NOTE_0 + 2},
{ 0x12, NOTE_0 + 3},
{ 0x20, NOTE_0 + 4},
{ 0x21, NOTE_0 + 5},
{ 0x14, NOTE_0 + 6},
{ 0x22, NOTE_0 + 7},
{ 0x15, NOTE_0 + 8},
{ 0x23, NOTE_0 + 9},
{ 0x16, NOTE_0 + 10},
{ 0x24, NOTE_0 + 11},
{ 0x25, NOTE_0 + 12}, // C1
{ 0x18, NOTE_0 + 13},
{ 0x26, NOTE_0 + 14},
{ 0x19, NOTE_0 + 15},
{ 0x27, NOTE_0 + 16},
{ 0x28, NOTE_0 + 17},
{ 0x1B, NOTE_0 + 18},
{ 0x2B, NOTE_0 + 19},
{ 0x29, VEL_0 + int(0)},
{ 0x02, VEL_0 + int(1 * 127 / 12)},
{ 0x03, VEL_0 + int(2 * 127 / 12)},
{ 0x04, VEL_0 + int(3 * 127 / 12)},
{ 0x05, VEL_0 + int(4 * 127 / 12)},
{ 0x06, VEL_0 + int(5 * 127 / 12)},
{ 0x07, VEL_0 + int(6 * 127 / 12)},
{ 0x08, VEL_0 + int(7 * 127 / 12)},
{ 0x09, VEL_0 + int(8 * 127 / 12)},
{ 0x0A, VEL_0 + int(9 * 127 / 12)},
{ 0x0B, VEL_0 + int(10 * 127 / 12)},
{ 0x0C, VEL_0 + int(11 * 127 / 12)},
{ 0x0D, VEL_0 + int(127)},
{ 0x2C, OCTAVE_MINUS },
{ 0x2D, OCTAVE_PLUS },
{ 0x2E, VELOCITY_MINUS },
{ 0x2F, VELOCITY_PLUS},
};
// clang-format on
}; };
} }
@@ -5,7 +5,7 @@
#include <chrono> #include <chrono>
#include <unordered_map> #include <unordered_map>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class midi_in_kbd final class midi_in_kbd final
: public midi1::in_api : public midi1::in_api
@@ -22,22 +22,22 @@ public:
explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf) explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{ {
configuration.set_input_scancode_callbacks(
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
} }
~midi_in_kbd() override { } ~midi_in_kbd() override { }
libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; } libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }
stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; } stdx::error open_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); });
stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; } return stdx::error{};
}
stdx::error close_port() override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; }
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override timestamp absolute_timestamp() const noexcept override
{ {
return std::chrono::steady_clock::now().time_since_epoch().count(); return std::chrono::steady_clock::now().time_since_epoch().count();
@@ -45,7 +45,7 @@ public:
void on_keypress(int scancode) void on_keypress(int scancode)
{ {
using kevent = kbd_input_configuration::kbd_event; using kevent = libremidi::kbd_event;
auto it = configuration.scancode_map.find(scancode); auto it = configuration.scancode_map.find(scancode);
if (it == configuration.scancode_map.end()) if (it == configuration.scancode_map.end())
@@ -88,7 +88,7 @@ public:
void on_keyrelease(int scancode) void on_keyrelease(int scancode)
{ {
using kevent = kbd_input_configuration::kbd_event; using kevent = libremidi::kbd_event;
auto it = configuration.scancode_map.find(scancode); auto it = configuration.scancode_map.find(scancode);
if (it == configuration.scancode_map.end()) if (it == configuration.scancode_map.end())
+100 -20
View File
@@ -2,7 +2,7 @@
#include <libremidi/backends/linux/dylib_loader.hpp> #include <libremidi/backends/linux/dylib_loader.hpp>
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h> // IWYU pragma: export
#if defined(SND_LIB_VERSION) #if defined(SND_LIB_VERSION)
#if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6) #if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)
@@ -13,9 +13,13 @@
#if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10) #if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10)
#define LIBREMIDI_ALSA_HAS_UMP 1 #define LIBREMIDI_ALSA_HAS_UMP 1
#endif #endif
#if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 14)
#define LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS 1
#endif
#endif #endif
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct libasound struct libasound
@@ -241,6 +245,7 @@ struct libasound
LIBREMIDI_SYMBOL_INIT(snd_seq, client_id) LIBREMIDI_SYMBOL_INIT(snd_seq, client_id)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client) 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_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_set_client)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, close) LIBREMIDI_SYMBOL_INIT(snd_seq, close)
@@ -277,6 +282,7 @@ struct libasound
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest) 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_sender)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)
@@ -291,6 +297,12 @@ struct libasound
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo) LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo)
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port) LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port)
#if LIBREMIDI_ALSA_HAS_UMP
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version)
LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_endpoint_info)
LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_block_info)
#endif
} }
bool available{true}; bool available{true};
@@ -298,6 +310,7 @@ struct libasound
LIBREMIDI_SYMBOL_DEF(snd_seq, client_id) LIBREMIDI_SYMBOL_DEF(snd_seq, client_id)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client) 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_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_set_client)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, close) LIBREMIDI_SYMBOL_DEF(snd_seq, close)
@@ -334,6 +347,7 @@ struct libasound
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest) 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_sender)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real)
@@ -350,6 +364,12 @@ struct libasound
LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port)
LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port) LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port)
#if LIBREMIDI_ALSA_HAS_UMP
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version)
LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_endpoint_info)
LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_block_info)
#endif
#if LIBREMIDI_ALSA_HAS_UMP #if LIBREMIDI_ALSA_HAS_UMP
struct ump_t struct ump_t
{ {
@@ -360,7 +380,6 @@ struct libasound
available = false; available = false;
return; 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_input)
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output)
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct)
@@ -368,10 +387,10 @@ struct libasound
bool available{true}; 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_input)
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output)
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct)
} ump; } ump;
#endif #endif
} seq{library}; } seq{library};
@@ -387,39 +406,98 @@ struct libasound
return; return;
} }
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name) // Core UMP functions
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, open)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors) LIBREMIDI_SYMBOL_INIT(snd_ump, close)
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)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current)
LIBREMIDI_SYMBOL_INIT(snd_ump, read) LIBREMIDI_SYMBOL_INIT(snd_ump, read)
LIBREMIDI_SYMBOL_INIT(snd_ump, tread) LIBREMIDI_SYMBOL_INIT(snd_ump, tread)
LIBREMIDI_SYMBOL_INIT(snd_ump, write) LIBREMIDI_SYMBOL_INIT(snd_ump, write)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents)
// Endpoint info functions
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_card)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_device)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_flags)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol_caps)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_num_blocks)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_version)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_manufacturer_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_family_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_model_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_sw_revision)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_product_id)
// Block info functions
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_set_block_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_block_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_active)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_flags)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_direction)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_first_group)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_num_groups)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_midi_ci_version)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_sysex8_streams)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_ui_hint)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name)
} }
bool available{true}; bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof) // Core UMP functions
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, open)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors) LIBREMIDI_SYMBOL_DEF(snd_ump, close)
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)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current)
LIBREMIDI_SYMBOL_DEF(snd_ump, read) LIBREMIDI_SYMBOL_DEF(snd_ump, read)
LIBREMIDI_SYMBOL_DEF(snd_ump, tread) LIBREMIDI_SYMBOL_DEF(snd_ump, tread)
LIBREMIDI_SYMBOL_DEF(snd_ump, write) LIBREMIDI_SYMBOL_DEF(snd_ump, write)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents)
// Endpoint info functions
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_card)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_device)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_flags)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol_caps)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_num_blocks)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_version)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_manufacturer_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_family_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_model_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_sw_revision)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_product_id)
// Block info functions
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_set_block_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_block_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_active)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_flags)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_direction)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_first_group)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_num_groups)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_midi_ci_version)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_sysex8_streams)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_ui_hint)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name)
} ump{library}; } ump{library};
#endif #endif
}; };
@@ -447,6 +525,8 @@ struct libasound
#define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info) #define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info)
#undef snd_seq_port_info_alloca #undef snd_seq_port_info_alloca
#define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info) #define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info)
#undef snd_seq_port_subscribe_alloca
#define snd_seq_port_subscribe_alloca(ptr) snd_dylib_alloca(ptr, seq, port_subscribe)
#undef snd_seq_queue_tempo_alloca #undef snd_seq_queue_tempo_alloca
#define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo) #define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo)
@@ -1,10 +1,11 @@
#pragma once #pragma once
#include <libremidi/config.hpp>
#if __has_include(<dlfcn.h>) #if __has_include(<dlfcn.h>)
#include <dlfcn.h> #include <dlfcn.h>
#include <cassert> #include <cassert>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class dylib_loader class dylib_loader
{ {
+1 -1
View File
@@ -7,7 +7,7 @@
#include <iostream> #include <iostream>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct eventfd_notifier struct eventfd_notifier
{ {
+44 -35
View File
@@ -4,7 +4,7 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -12,6 +12,8 @@ namespace libremidi
class libpipewire class libpipewire
{ {
public: public:
decltype(&::pw_get_library_version) get_library_version{};
decltype(&::pw_init) init{}; decltype(&::pw_init) init{};
decltype(&::pw_deinit) deinit{}; decltype(&::pw_deinit) deinit{};
@@ -67,12 +69,12 @@ public:
bool available{true}; bool available{true};
private: private:
dylib_loader library; dylib_loader m_library;
libpipewire() libpipewire()
: library("libpipewire-0.3.so.0") : m_library("libpipewire-0.3.so.0")
{ {
if (!library) if (!m_library)
{ {
available = false; available = false;
return; return;
@@ -81,52 +83,59 @@ private:
// in terms of regex: // in terms of regex:
// decltype\(&::([a-z_]+)\) [a-z_]+{}; // decltype\(&::([a-z_]+)\) [a-z_]+{};
// \1 = library.symbol<decltype(&::\1)>("\1"); // \1 = library.symbol<decltype(&::\1)>("\1");
init = library.symbol<decltype(&::pw_init)>("pw_init"); get_library_version
deinit = library.symbol<decltype(&::pw_deinit)>("pw_deinit"); = m_library.symbol<decltype(&::pw_get_library_version)>("pw_get_library_version");
context_new = library.symbol<decltype(&::pw_context_new)>("pw_context_new"); init = m_library.symbol<decltype(&::pw_init)>("pw_init");
context_connect = library.symbol<decltype(&::pw_context_connect)>("pw_context_connect"); deinit = m_library.symbol<decltype(&::pw_deinit)>("pw_deinit");
context_destroy = library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
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 proxy_add_listener
= library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener"); = m_library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy"); 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_new = m_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_destroy
main_loop_quit = library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit"); = m_library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");
main_loop_run = library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run"); 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 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_new = m_library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
properties_free = library.symbol<decltype(&::pw_properties_free)>("pw_properties_free"); properties_free = m_library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
properties_get = library.symbol<decltype(&::pw_properties_get)>("pw_properties_get"); 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 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 filter_get_properties
= library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties"); = m_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"); filter_add_port = m_library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");
filter_remove_port filter_remove_port
= library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port"); = m_library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");
filter_update_properties filter_update_properties = m_library.symbol<decltype(&::pw_filter_update_properties)>(
= library.symbol<decltype(&::pw_filter_update_properties)>("pw_filter_update_properties"); "pw_filter_update_properties");
filter_update_params filter_update_params
= library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params"); = m_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_get_time = m_library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");
filter_destroy = library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy"); filter_destroy = m_library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");
filter_connect = library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect"); filter_connect = m_library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");
filter_get_dsp_buffer 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 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 filter_queue_buffer
= library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer"); = m_library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");
filter_flush = library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush"); filter_flush = m_library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");
assert(get_library_version);
assert(init); assert(init);
assert(deinit); assert(deinit);
+138 -1
View File
@@ -1,12 +1,14 @@
#pragma once #pragma once
#include <libremidi/backends/linux/dylib_loader.hpp> #include <libremidi/backends/linux/dylib_loader.hpp>
#include <libremidi/backends/linux/helpers.hpp> #include <libremidi/backends/linux/helpers.hpp>
#include <libremidi/observer_configuration.hpp>
#include <libudev.h> #include <libudev.h>
#include <cassert> #include <cassert>
#include <optional>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
struct libudev struct libudev
@@ -25,12 +27,28 @@ struct libudev
LIBREMIDI_SYMBOL_INIT(udev, device_get_action) LIBREMIDI_SYMBOL_INIT(udev, device_get_action)
LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem) 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, device_unref)
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd) LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device) LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref) 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_INIT2(udev, new, create)
LIBREMIDI_SYMBOL_INIT(udev, unref) LIBREMIDI_SYMBOL_INIT(udev, unref)
} }
@@ -46,12 +64,28 @@ struct libudev
LIBREMIDI_SYMBOL_DEF(udev, device_get_action) LIBREMIDI_SYMBOL_DEF(udev, device_get_action)
LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem) 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, device_unref)
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd) LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device) LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref) 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_DEF2(udev, new, create)
LIBREMIDI_SYMBOL_DEF(udev, unref) LIBREMIDI_SYMBOL_DEF(udev, unref)
}; };
@@ -89,4 +123,107 @@ struct udev_helper
udev_monitor* monitor{}; 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;
transport_type type{};
std::string vendor;
std::string product;
std::string serial;
};
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");
const auto pci_device = udev.device_get_parent_with_subsystem_devtype(dev, "pci", nullptr);
transport_type type = transport_type::hardware;
if (usb_device)
type = (transport_type)(type | transport_type::usb);
else if (pci_device)
type = (transport_type)(type | transport_type::pci);
auto id_path = udev.device_get_property_value(dev, "ID_PATH");
if (!id_path)
id_path = "";
auto read_property = [&](const char* property) {
auto res = udev.device_get_property_value(dev, property);
if (!res)
res = "";
if (strlen(res) == 0)
{
if (usb_device)
{
res = udev.device_get_property_value(usb_device, property);
if (!res)
res = "";
}
else if (pci_device)
{
res = udev.device_get_property_value(pci_device, property);
if (!res)
res = "";
}
}
return res;
};
auto id_vendor = read_property("ID_VENDOR_FROM_DATABASE");
auto id_product = read_property("ID_MODEL_FROM_DATABASE");
auto id_serial = read_property("ID_SERIAL");
auto ret = udev_soundcard_info{
.container = id_path,
.path = path,
.type = type,
.vendor = id_vendor,
.product = id_product,
.serial = id_serial};
udev.device_unref(dev);
return ret;
}
return {};
}
} }
+5 -2
View File
@@ -2,12 +2,15 @@
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <string> #include <string>
#if !defined(BOOST_ASIO_IO_CONTEXT_HPP)
namespace boost::asio namespace boost::asio
{ {
struct io_context; struct io_context;
} }
#endif
namespace libremidi::net NAMESPACE_LIBREMIDI::net
{ {
enum class protocol enum class protocol
{ {
@@ -46,7 +49,7 @@ struct dgram_observer_configuration
} }
namespace libremidi::net_ump NAMESPACE_LIBREMIDI::net_ump
{ {
enum class protocol enum class protocol
{ {
+16 -13
View File
@@ -35,38 +35,41 @@ public:
*/ */
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
template <typename T> template <typename T>
struct optionally_owned struct optionally_owned
{ {
public: public:
explicit optionally_owned(T* maybe_existing) explicit optionally_owned(T* maybe_existing)
: storage{.ref = maybe_existing} : m_storage{.ref = maybe_existing}
{ {
if (storage.ref) if (m_storage.ref)
{ {
ownership = unowned; m_ownership = unowned;
return; return;
} }
else else
{ {
std::destroy_at(&storage.ref); std::destroy_at(&m_storage.ref);
std::construct_at<T>(reinterpret_cast<T*>(storage.object)); std::construct_at<T>(reinterpret_cast<T*>(m_storage.object));
ownership = owned; m_ownership = owned;
} }
} }
~optionally_owned() ~optionally_owned()
{ {
if (is_owned()) if (is_owned())
std::destroy_at(reinterpret_cast<T*>(storage.object)); std::destroy_at(reinterpret_cast<T*>(m_storage.object));
} }
T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); } T& get() noexcept
{
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
}
const T& get() const noexcept const T& get() const noexcept
{ {
return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
} }
optionally_owned(const optionally_owned&) = delete; optionally_owned(const optionally_owned&) = delete;
@@ -74,18 +77,18 @@ public:
optionally_owned& operator=(const optionally_owned&) = delete; optionally_owned& operator=(const optionally_owned&) = delete;
optionally_owned& operator=(optionally_owned&&) noexcept = delete; optionally_owned& operator=(optionally_owned&&) noexcept = delete;
bool is_owned() const noexcept { return ownership == owned; } bool is_owned() const noexcept { return m_ownership == owned; }
private: private:
union union
{ {
alignas(T) unsigned char object[sizeof(T)]; alignas(T) unsigned char object[sizeof(T)];
T* ref; T* ref;
} storage; } m_storage;
enum enum
{ {
owned, owned,
unowned unowned
} ownership{}; } m_ownership{};
}; };
} }
+4 -7
View File
@@ -4,7 +4,8 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp> #include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi #include <thread>
NAMESPACE_LIBREMIDI
{ {
template <typename Impl, typename F> template <typename Impl, typename F>
struct osc_parser struct osc_parser
@@ -114,7 +115,7 @@ struct osc_parser
} }
namespace libremidi::net NAMESPACE_LIBREMIDI::net
{ {
struct osc_parser_midi1 struct osc_parser_midi1
@@ -219,8 +220,6 @@ public:
return {}; return {};
} }
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override timestamp absolute_timestamp() const noexcept override
{ {
return std::chrono::steady_clock::now().time_since_epoch().count(); return std::chrono::steady_clock::now().time_since_epoch().count();
@@ -265,7 +264,7 @@ public:
}; };
} }
namespace libremidi::net_ump NAMESPACE_LIBREMIDI::net_ump
{ {
struct osc_parser_midi2 struct osc_parser_midi2
{ {
@@ -394,8 +393,6 @@ public:
return {}; return {};
} }
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override timestamp absolute_timestamp() const noexcept override
{ {
return std::chrono::steady_clock::now().time_since_epoch().count(); return std::chrono::steady_clock::now().time_since_epoch().count();
+2 -2
View File
@@ -8,7 +8,7 @@
#include <boost/asio/ip/udp.hpp> #include <boost/asio/ip/udp.hpp>
#include <boost/endian.hpp> #include <boost/endian.hpp>
namespace libremidi::net NAMESPACE_LIBREMIDI::net
{ {
struct osc_midi1_packet struct osc_midi1_packet
@@ -146,7 +146,7 @@ public:
} }
namespace libremidi::net_ump NAMESPACE_LIBREMIDI::net_ump
{ {
struct osc_midi2_packet struct osc_midi2_packet
{ {
+2 -2
View File
@@ -1,13 +1,13 @@
#pragma once #pragma once
#include <libremidi/backends/dummy.hpp> #include <libremidi/backends/dummy.hpp>
namespace libremidi::net NAMESPACE_LIBREMIDI::net
{ {
using net_observer_configuration = libremidi::dummy_configuration; using net_observer_configuration = libremidi::dummy_configuration;
using observer = libremidi::observer_dummy; using observer = libremidi::observer_dummy;
} }
namespace libremidi::net_ump NAMESPACE_LIBREMIDI::net_ump
{ {
using net_observer_configuration = libremidi::dummy_configuration; using net_observer_configuration = libremidi::dummy_configuration;
using observer = libremidi::observer_dummy; using observer = libremidi::observer_dummy;
+5 -3
View File
@@ -5,7 +5,9 @@
#include <libremidi/backends/net/midi_out.hpp> #include <libremidi/backends/net/midi_out.hpp>
#include <libremidi/backends/net/observer.hpp> #include <libremidi/backends/net/observer.hpp>
namespace libremidi::net #include <string_view>
NAMESPACE_LIBREMIDI::net
{ {
struct backend struct backend
{ {
@@ -16,8 +18,8 @@ struct backend
using midi_out_configuration = libremidi::net::dgram_output_configuration; using midi_out_configuration = libremidi::net::dgram_output_configuration;
using midi_observer_configuration = libremidi::net::net_observer_configuration; using midi_observer_configuration = libremidi::net::net_observer_configuration;
static const constexpr auto API = libremidi::API::NETWORK; static const constexpr auto API = libremidi::API::NETWORK;
static const constexpr auto name = "network"; static const constexpr std::string_view name = "network";
static const constexpr auto display_name = "Network"; static const constexpr std::string_view display_name = "Network";
static inline bool available() noexcept { return true; } static inline bool available() noexcept { return true; }
}; };
+5 -3
View File
@@ -4,7 +4,9 @@
#include <libremidi/backends/net/midi_out.hpp> #include <libremidi/backends/net/midi_out.hpp>
#include <libremidi/backends/net/observer.hpp> #include <libremidi/backends/net/observer.hpp>
namespace libremidi::net_ump #include <string_view>
NAMESPACE_LIBREMIDI::net_ump
{ {
struct backend struct backend
{ {
@@ -15,8 +17,8 @@ struct backend
using midi_out_configuration = libremidi::net_ump::dgram_output_configuration; using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;
using midi_observer_configuration = libremidi::net_ump::net_observer_configuration; using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;
static const constexpr auto API = libremidi::API::NETWORK_UMP; static const constexpr auto API = libremidi::API::NETWORK_UMP;
static const constexpr auto name = "network (UMP)"; static const constexpr std::string_view name = "network (UMP)";
static const constexpr auto display_name = "Network (UMP)"; static const constexpr std::string_view display_name = "Network (UMP)";
static inline bool available() noexcept { return true; } static inline bool available() noexcept { return true; }
}; };
+5 -3
View File
@@ -7,7 +7,9 @@
#include <libremidi/backends/pipewire/midi_out.hpp> #include <libremidi/backends/pipewire/midi_out.hpp>
#include <libremidi/backends/pipewire/observer.hpp> #include <libremidi/backends/pipewire/observer.hpp>
namespace libremidi::pipewire #include <string_view>
NAMESPACE_LIBREMIDI::pipewire
{ {
struct backend struct backend
{ {
@@ -18,8 +20,8 @@ struct backend
using midi_out_configuration = pipewire_output_configuration; using midi_out_configuration = pipewire_output_configuration;
using midi_observer_configuration = pipewire_observer_configuration; using midi_observer_configuration = pipewire_observer_configuration;
static const constexpr auto API = libremidi::API::PIPEWIRE; static const constexpr auto API = libremidi::API::PIPEWIRE;
static const constexpr auto name = "pipewire"; static const constexpr std::string_view name = "pipewire";
static const constexpr auto display_name = "PipeWire"; static const constexpr std::string_view display_name = "PipeWire";
static inline bool available() noexcept static inline bool available() noexcept
{ {
@@ -12,7 +12,7 @@ struct pw_filter;
struct spa_io_position; struct spa_io_position;
} }
namespace libremidi NAMESPACE_LIBREMIDI
{ {
using pipewire_callback_function = std::function<void(spa_io_position*)>; using pipewire_callback_function = std::function<void(spa_io_position*)>;
struct pipewire_callback struct pipewire_callback
+27 -21
View File
@@ -18,13 +18,14 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <utility>
#include <vector> #include <vector>
#include <spa/param/audio/format-utils.h> #include <spa/param/audio/format-utils.h>
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers" #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
namespace libremidi NAMESPACE_LIBREMIDI
{ {
template <typename K, typename V> template <typename K, typename V>
using hash_map = std::unordered_map<K, V>; using hash_map = std::unordered_map<K, V>;
@@ -79,11 +80,11 @@ struct pipewire_context
struct graph struct graph
{ {
mutable std::mutex mtx; mutable std::mutex mtx;
libremidi::hash_map<uint32_t, node> physical_audio; libremidi::hash_map<uint64_t, node> physical_audio;
libremidi::hash_map<uint32_t, node> physical_midi; libremidi::hash_map<uint64_t, node> physical_midi;
libremidi::hash_map<uint32_t, node> software_audio; libremidi::hash_map<uint64_t, node> software_audio;
libremidi::hash_map<uint32_t, node> software_midi; libremidi::hash_map<uint64_t, node> software_midi;
libremidi::hash_map<uint32_t, port_info> port_cache; libremidi::hash_map<uint64_t, port_info> port_cache;
void for_each_port(auto func) void for_each_port(auto func)
{ {
@@ -123,7 +124,7 @@ struct pipewire_context
} }
explicit pipewire_context(std::shared_ptr<pipewire_instance> inst) explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)
: global_instance{inst} : global_instance{std::move(inst)}
, owns_main_loop{true} , owns_main_loop{true}
{ {
this->main_loop = pw.main_loop_new(nullptr); this->main_loop = pw.main_loop_new(nullptr);
@@ -187,13 +188,13 @@ struct pipewire_context
pipewire_context& self = *(pipewire_context*)object; pipewire_context& self = *(pipewire_context*)object;
if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
self.register_port(id, type); self.register_port(id, type);
}, },
.global_remove = .global_remove =
[](void* object, uint32_t id) { [](void* object, uint32_t id) {
pipewire_context& self = *(pipewire_context*)object; pipewire_context& self = *(pipewire_context*)object;
self.unregister_port(id); self.unregister_port(id);
}, },
}; };
// Start listening // Start listening
pw_registry_add_listener(this->registry, &this->registry_listener, &registry_events, this); pw_registry_add_listener(this->registry, &this->registry_listener, &registry_events, this);
@@ -244,8 +245,8 @@ struct pipewire_context
// Remove from the listeners // Remove from the listeners
auto it auto it
= std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) { = std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) {
return l.id == id; return l.id == id;
}); });
if (it != port_listener.end()) if (it != port_listener.end())
{ {
pw.proxy_destroy((pw_proxy*)it->port); pw.proxy_destroy((pw_proxy*)it->port);
@@ -266,14 +267,14 @@ struct pipewire_context
static constexpr struct pw_core_events core_events = { static constexpr struct pw_core_events core_events = {
.version = PW_VERSION_CORE_EVENTS, .version = PW_VERSION_CORE_EVENTS,
.done = .done =
[](void* object, uint32_t id, int seq) { [](void* object, uint32_t id, int seq) {
auto& self = *(pipewire_context*)object; auto& self = *(pipewire_context*)object;
if(id == PW_ID_CORE && seq == self.pending) if (id == PW_ID_CORE && seq == self.pending)
{ {
self.done = 1; self.done = 1;
libpipewire::instance().main_loop_quit(self.main_loop); libpipewire::instance().main_loop_quit(self.main_loop);
} }
}, },
}; };
spa_zero(core_listener); spa_zero(core_listener);
@@ -287,7 +288,7 @@ struct pipewire_context
spa_hook_remove(&core_listener); 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( auto props = pw.properties_new(
PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT, PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT,
@@ -361,6 +362,8 @@ struct pipewire_context
return &this->current_graph.physical_audio[nid]; return &this->current_graph.physical_audio[nid];
else if (p.format.find("midi") != p.format.npos) else if (p.format.find("midi") != p.format.npos)
return &this->current_graph.physical_midi[nid]; return &this->current_graph.physical_midi[nid];
else if (p.format.find("UMP") != p.format.npos)
return &this->current_graph.physical_midi[nid];
} }
else else
{ {
@@ -368,6 +371,8 @@ struct pipewire_context
return &this->current_graph.software_audio[nid]; return &this->current_graph.software_audio[nid];
else if (p.format.find("midi") != p.format.npos) else if (p.format.find("midi") != p.format.npos)
return &this->current_graph.software_midi[nid]; 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; return nullptr;
}; };
@@ -453,12 +458,12 @@ struct pipewire_filter
}* port{}; }* port{};
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop) 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) explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)
: loop{loop} : loop{std::move(loop)}
, filter{filter} , filter{filter}
{ {
} }
@@ -498,7 +503,8 @@ struct pipewire_filter
pw.filter_destroy(this->filter); pw.filter_destroy(this->filter);
} }
stdx::error 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 // clang-format off
this->port = (struct port*)pw.filter_add_port( this->port = (struct port*)pw.filter_add_port(
@@ -507,7 +513,7 @@ struct pipewire_filter
PW_FILTER_PORT_FLAG_MAP_BUFFERS, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(struct port), sizeof(struct port),
pw.properties_new( pw.properties_new(
PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_FORMAT_DSP, format,
PW_KEY_PORT_NAME, port_name.data(), PW_KEY_PORT_NAME, port_name.data(),
nullptr), nullptr),
nullptr, 0); nullptr, 0);
@@ -517,7 +523,7 @@ struct pipewire_filter
return stdx::error{}; return stdx::error{};
} }
void set_port_buffer(int bytes) void set_port_buffer(int64_t bytes)
{ {
uint8_t buffer[1024]; uint8_t buffer[1024];
struct spa_pod_builder builder; struct spa_pod_builder builder;
+42 -28
View File
@@ -9,11 +9,14 @@
#include <libremidi/detail/semaphore.hpp> #include <libremidi/detail/semaphore.hpp>
#include <atomic> #include <atomic>
#include <semaphore>
#include <stop_token>
#include <thread> #include <thread>
namespace libremidi #if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-compare"
#endif
NAMESPACE_LIBREMIDI
{ {
struct pipewire_helpers struct pipewire_helpers
{ {
@@ -66,14 +69,14 @@ struct pipewire_helpers
{ {
this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter); this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);
pipewire_callback cbs{ libremidi::pipewire_callback cbs{
.token = this_instance, .token = this_instance,
.callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void { .callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void {
if (auto pt = p.lock()) if (auto pt = p.lock())
self.process(nf); self.process(nf);
self.thread_lock.check_client_released(); self.thread_lock.check_client_released();
}}; }};
configuration.set_process_func(cbs); configuration.set_process_func(cbs);
} }
else else
@@ -84,10 +87,10 @@ struct pipewire_helpers
static constexpr struct pw_filter_events filter_events static constexpr struct pw_filter_events filter_events
= {.version = PW_VERSION_FILTER_EVENTS, = {.version = PW_VERSION_FILTER_EVENTS,
.process = +[](void* _data, struct spa_io_position* position) -> void { .process = +[](void* _data, struct spa_io_position* position) -> void {
// FIXME likely we need the thread_lock check here too // FIXME likely we need the thread_lock check here too
Self& self = *static_cast<Self*>(_data); Self& self = *static_cast<Self*>(_data);
self.process(position); self.process(position);
}}; }};
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
this->filter->create_filter(self.configuration.client_name, filter_events, &self); this->filter->create_filter(self.configuration.client_name, filter_events, &self);
@@ -197,7 +200,8 @@ struct pipewire_helpers
} }
template <typename Self> template <typename Self>
stdx::error 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->global_context);
assert(this->filter); assert(this->filter);
@@ -207,7 +211,7 @@ struct pipewire_helpers
if (!this->filter->port) if (!this->filter->port)
{ {
auto ret = this->filter->create_local_port(portName.data(), direction); auto ret = this->filter->create_local_port(portName.data(), direction, format);
if (ret != stdx::error{}) if (ret != stdx::error{})
{ {
self.libremidi_handle_error(self.configuration, "error creating port"); self.libremidi_handle_error(self.configuration, "error creating port");
@@ -218,11 +222,12 @@ struct pipewire_helpers
return stdx::error{}; return stdx::error{};
} }
void add_callbacks(const observer_configuration& conf) template <libremidi::API Api>
void add_callbacks(std::string format, const observer_configuration& conf)
{ {
assert(global_context); assert(global_context);
global_context->on_port_added = [&conf](const pipewire_context::port_info& port) { global_context->on_port_added = [format, &conf](const pipewire_context::port_info& port) {
if (port.format.find("midi") == std::string::npos) if (port.format.find(format) == std::string::npos)
return; return;
bool unfiltered = conf.track_any; bool unfiltered = conf.track_any;
@@ -233,18 +238,18 @@ struct pipewire_helpers
if (port.direction == SPA_DIRECTION_INPUT) if (port.direction == SPA_DIRECTION_INPUT)
{ {
if (conf.output_added) if (conf.output_added)
conf.output_added(to_port_info<SPA_DIRECTION_INPUT>(port)); conf.output_added(to_port_info<SPA_DIRECTION_INPUT, Api>(port));
} }
else else
{ {
if (conf.input_added) if (conf.input_added)
conf.input_added(to_port_info<SPA_DIRECTION_OUTPUT>(port)); conf.input_added(to_port_info<SPA_DIRECTION_OUTPUT, Api>(port));
} }
} }
}; };
global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) { global_context->on_port_removed = [format, &conf](const pipewire_context::port_info& port) {
if (port.format.find("midi") == std::string::npos) if (port.format.find(format) == std::string::npos)
return; return;
bool unfiltered = conf.track_any; bool unfiltered = conf.track_any;
@@ -255,12 +260,12 @@ struct pipewire_helpers
if (port.direction == SPA_DIRECTION_INPUT) if (port.direction == SPA_DIRECTION_INPUT)
{ {
if (conf.output_removed) if (conf.output_removed)
conf.output_removed(to_port_info<SPA_DIRECTION_INPUT>(port)); conf.output_removed(to_port_info<SPA_DIRECTION_INPUT, Api>(port));
} }
else else
{ {
if (conf.input_removed) if (conf.input_removed)
conf.input_removed(to_port_info<SPA_DIRECTION_OUTPUT>(port)); conf.input_removed(to_port_info<SPA_DIRECTION_OUTPUT, Api>(port));
} }
} }
}; };
@@ -408,7 +413,7 @@ struct pipewire_helpers
return stdx::error{}; return stdx::error{};
} }
template <spa_direction Direction> template <spa_direction Direction, libremidi::API Api>
static auto to_port_info(const pipewire_context::port_info& port) static auto to_port_info(const pipewire_context::port_info& port)
-> std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port> -> std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>
{ {
@@ -425,6 +430,7 @@ struct pipewire_helpers
} }
return {{ return {{
.api = Api,
.client = 0, .client = 0,
.port = port.id, .port = port.id,
.manufacturer = "", .manufacturer = "",
@@ -436,8 +442,10 @@ struct pipewire_helpers
// Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer) // 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 // is an "output" port from the point of view of pipewire as data will come out of it
template <spa_direction Direction> template <spa_direction Direction, libremidi::API Api>
static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept static auto get_ports(
std::string_view format, const observer_configuration& conf,
const pipewire_context& ctx) noexcept
-> std::vector< -> std::vector<
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>> std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
{ {
@@ -452,7 +460,8 @@ struct pipewire_helpers
for (auto& port : for (auto& port :
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{ {
ret.push_back(to_port_info<Direction>(port)); if (port.format.find(format) != std::string::npos)
ret.push_back(to_port_info<Direction, Api>(port));
} }
} }
@@ -462,7 +471,8 @@ struct pipewire_helpers
for (auto& port : for (auto& port :
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{ {
ret.push_back(to_port_info<Direction>(port)); if (port.format.find(format) != std::string::npos)
ret.push_back(to_port_info<Direction, Api>(port));
} }
} }
} }
@@ -471,3 +481,7 @@ struct pipewire_helpers
} }
}; };
} }
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp> #include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi NAMESPACE_LIBREMIDI
{ {
class midi_in_pipewire final class midi_in_pipewire final
: public midi1::in_api : public midi1::in_api
@@ -47,7 +47,8 @@ public:
stdx::error 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 (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi");
err != stdx::error{})
return err; return err;
if (auto err = link_ports(*this, in_port); err != stdx::error{}) if (auto err = link_ports(*this, in_port); err != stdx::error{})
@@ -59,7 +60,8 @@ public:
stdx::error open_virtual_port(std::string_view name) override stdx::error open_virtual_port(std::string_view name) override
{ {
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi");
err != stdx::error{})
return err; return err;
start_thread(); start_thread();
@@ -72,10 +74,7 @@ public:
return do_close_port(); return do_close_port();
} }
stdx::error set_port_name(std::string_view port_name) override stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); }
{
return rename_port(port_name);
}
timestamp absolute_timestamp() const noexcept override { return system_ns(); } timestamp absolute_timestamp() const noexcept override { return system_ns(); }
@@ -5,9 +5,7 @@
#include <readerwriterqueue.h> #include <readerwriterqueue.h>
#include <semaphore> NAMESPACE_LIBREMIDI
namespace libremidi
{ {
class midi_out_pipewire class midi_out_pipewire
: public midi1::out_api : public midi1::out_api
@@ -50,7 +48,8 @@ public:
stdx::error 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 (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi");
err != stdx::error{})
return err; return err;
this->filter->set_port_buffer(configuration.output_buffer_size); this->filter->set_port_buffer(configuration.output_buffer_size);
@@ -64,7 +63,8 @@ public:
stdx::error open_virtual_port(std::string_view name) override stdx::error open_virtual_port(std::string_view name) override
{ {
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi");
err != stdx::error{})
return err; return err;
this->filter->set_port_buffer(configuration.output_buffer_size); this->filter->set_port_buffer(configuration.output_buffer_size);
@@ -79,10 +79,7 @@ public:
return do_close_port(); return do_close_port();
} }
stdx::error set_port_name(std::string_view port_name) override stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); }
{
return rename_port(port_name);
}
int process(spa_io_position* pos) int process(spa_io_position* pos)
{ {
@@ -121,8 +118,9 @@ public:
continue; continue;
} }
spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi); spa_pod_builder_control(&build, static_cast<int32_t>(m.timestamp), SPA_CONTROL_Midi);
int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size()); int res
= spa_pod_builder_bytes(&build, m.bytes.data(), static_cast<uint32_t>(m.bytes.size()));
// Try again next buffer // Try again next buffer
if (res == -ENOSPC) if (res == -ENOSPC)

Some files were not shown because too many files have changed in this diff Show More