Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a1f5acae8 | |||
| 51cd271b6f | |||
| dc0003232a | |||
| cae4acfdef | |||
| b8438ade34 | |||
| 657c23702c | |||
| 5886052228 | |||
| d881ef43a3 | |||
| 77a227f40d | |||
| f086834da6 | |||
| e5f614ce06 | |||
| 011552c020 | |||
| 2725806749 | |||
| bf2f34c902 | |||
| 0e6f2c7256 | |||
| 9e9c32bff4 | |||
| 6aef199984 | |||
| c3543cffce | |||
| 9b75b36c41 | |||
| 996ed16c01 | |||
| 96c2bde0e1 | |||
| b2ed35a704 | |||
| 7f2d16932f | |||
| bc8c433e47 | |||
| 456528f230 | |||
| ddf9e640b1 | |||
| 3b08037f92 | |||
| 558829f619 | |||
| d9c97be326 | |||
| 3b0e7e986f | |||
| 36273ba7ac | |||
| ade33b0608 | |||
| 638df54ee6 | |||
| b6c2029386 | |||
| 036c6f4723 | |||
| 1fde652495 | |||
| 0040956801 | |||
| 722848d46b | |||
| f926b95092 | |||
| b6a049ea0d | |||
| c63ee3eabe | |||
| c7e76ae7ac | |||
| fcc5d74e18 | |||
| 635c94e3cf | |||
| 492971ed4f | |||
| 813958002b | |||
| 9c0aac8618 | |||
| f04838bffa | |||
| 62a5c53969 | |||
| b12f1d0f9d | |||
| a6b6ff1d16 | |||
| bfc14d5f6e | |||
| df91a1b107 | |||
| 5779c93339 | |||
| 22842f9deb | |||
| e9633ff19d | |||
| 55926a5021 | |||
| 96526f709a | |||
| 9cccbab703 | |||
| ab34081dbb | |||
| 2e103dc561 | |||
| 5d3b72d4ef | |||
| 300001d385 | |||
| e6f370ba4f | |||
| 1e0a131169 | |||
| 96082e1190 | |||
| 31a1a264d4 | |||
| 4f48aa2d5a | |||
| 71dc08d21f | |||
| cdc0fb7075 | |||
| 529106e815 | |||
| 96a19d55ef | |||
| e519b7c758 | |||
| 95620a5f07 | |||
| 9878b7b2a4 | |||
| 4a346350f4 | |||
| dc7eae1931 | |||
| 0c797807dd | |||
| 374efbe47c | |||
| bfd8bb192e | |||
| 7bdd68bdcc | |||
| a4ea20b2b3 | |||
| 522e9d456a | |||
| 70d0a186a4 | |||
| 41cad27806 | |||
| a10335ed47 | |||
| 19b2c9549f | |||
| c0eb7c98ba | |||
| f7a13e48b7 | |||
| ba5874837c | |||
| c0f5138765 | |||
| ad7b0e4524 | |||
| c63eeed1b4 | |||
| fc48c2e7ed | |||
| 1e3755148c | |||
| 06cb6d8319 | |||
| 58766cc373 | |||
| bd17288ab0 | |||
| cb08f69e3b | |||
| 53289bb9e2 | |||
| d747f80d11 |
@@ -0,0 +1,3 @@
|
|||||||
|
CompileFlags:
|
||||||
|
Add: [-I/home/sebastian/code/viseq/include, -std=c++23]
|
||||||
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
build/**
|
build/**
|
||||||
.cache
|
.cache
|
||||||
viseq
|
viseq
|
||||||
|
viseq.exe
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
*.json
|
||||||
|
|||||||
@@ -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/*
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
CXX = g++
|
||||||
|
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_WINUWP=1
|
||||||
|
CXXINC = -Iinclude
|
||||||
|
|
||||||
|
LDLIBS = -lraylibdll -pthread -lwindowsapp
|
||||||
|
LDFLAGS = -L./lib -L/usr/lib -L/usr/local/lib
|
||||||
|
|
||||||
|
hdr_files = ${wildcard src/*.h}
|
||||||
|
|
||||||
|
src_files = ${wildcard src/*.cpp}
|
||||||
|
src_files += ${wildcard include/libremidi/*.cpp}
|
||||||
|
|
||||||
|
all: viseq
|
||||||
|
|
||||||
|
viseq: $(src_files) $(hdr_files)
|
||||||
|
mkdir -p build
|
||||||
|
$(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
|
$(src_files): %.cpp : %_src
|
||||||
|
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
|
||||||
|
|
||||||
|
$(hdr_files): %.h : %_hdr
|
||||||
|
@echo "G++ >---------- $(*F).h -----------<"
|
||||||
|
|
||||||
|
%_src:
|
||||||
|
@echo "G++ >---------- $(*F).cpp -----------<"
|
||||||
|
|
||||||
|
%_hdr:
|
||||||
|
@echo "G++ >---------- $(*F).h -----------<"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f build/*
|
||||||
|
rm -f viseq
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef LIBREMIDI_API_C_H
|
||||||
|
#define LIBREMIDI_API_C_H
|
||||||
|
|
||||||
|
#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//! MIDI API specifier arguments.
|
||||||
|
//! To get information on which feature is supported by each back-end, check their backend file
|
||||||
|
//! in e.g. backends/winmm.hpp, etc.
|
||||||
|
enum libremidi_api
|
||||||
|
{
|
||||||
|
UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */
|
||||||
|
|
||||||
|
// MIDI 1.0 APIs
|
||||||
|
COREMIDI = 0x1, /*!< macOS CoreMidi API. */
|
||||||
|
ALSA_SEQ, /*!< Linux ALSA Sequencer API. */
|
||||||
|
ALSA_RAW, /*!< Linux Raw ALSA API. */
|
||||||
|
JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */
|
||||||
|
WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */
|
||||||
|
WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */
|
||||||
|
WEBMIDI, /*!< Web MIDI API through Emscripten */
|
||||||
|
PIPEWIRE, /*!< PipeWire */
|
||||||
|
KEYBOARD, /*!< Computer keyboard input */
|
||||||
|
NETWORK, /*!< MIDI over IP */
|
||||||
|
ANDROID_AMIDI, /*!< Android AMidi API */
|
||||||
|
KDMAPI, /*!< OmniMIDI KDMAPI (Windows) */
|
||||||
|
|
||||||
|
// MIDI 2.0 APIs
|
||||||
|
ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */
|
||||||
|
ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */
|
||||||
|
COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */
|
||||||
|
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
|
||||||
|
KEYBOARD_UMP, /*!< Computer keyboard input */
|
||||||
|
NETWORK_UMP, /*!< MIDI2 over IP */
|
||||||
|
JACK_UMP, /*!< MIDI2 over JACK, type "32 bit raw UMP". Requires PipeWire v1.4+. */
|
||||||
|
PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */
|
||||||
|
|
||||||
|
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum libremidi_api libremidi_api;
|
||||||
|
|
||||||
|
#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
+18
-25
@@ -1,36 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <libremidi/api-c.h>
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#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
|
||||||
//! in e.g. backends/winmm.hpp, etc.
|
//! in e.g. backends/winmm.hpp, etc.
|
||||||
enum class API
|
using API = libremidi_api;
|
||||||
{
|
|
||||||
UNSPECIFIED, /*!< Search for a working compiled API. */
|
|
||||||
|
|
||||||
// MIDI 1.0 APIs
|
|
||||||
COREMIDI, /*!< macOS CoreMidi API. */
|
|
||||||
ALSA_SEQ, /*!< Linux ALSA Sequencer API. */
|
|
||||||
ALSA_RAW, /*!< Linux Raw ALSA API. */
|
|
||||||
JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */
|
|
||||||
WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */
|
|
||||||
WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */
|
|
||||||
WEBMIDI, /*!< Web MIDI API through Emscripten */
|
|
||||||
PIPEWIRE, /*!< PipeWire */
|
|
||||||
|
|
||||||
// MIDI 2.0 APIs
|
|
||||||
ALSA_RAW_UMP, /*!< Raw ALSA API for MIDI 2.0 */
|
|
||||||
ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */
|
|
||||||
COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */
|
|
||||||
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
|
|
||||||
|
|
||||||
DUMMY /*!< A compilable but non-functional API. */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A function to determine the available compiled MIDI 1.0 APIs.
|
* \brief A function to determine the available compiled MIDI 1.0 APIs.
|
||||||
@@ -60,6 +40,17 @@ LIBREMIDI_EXPORT std::string_view get_api_display_name(libremidi::API api);
|
|||||||
//! Look-up an API through its name
|
//! Look-up an API through its name
|
||||||
LIBREMIDI_EXPORT libremidi::API get_compiled_api_by_name(std::string_view api);
|
LIBREMIDI_EXPORT libremidi::API get_compiled_api_by_name(std::string_view api);
|
||||||
|
|
||||||
|
inline constexpr bool is_midi1(libremidi::API api)
|
||||||
|
{
|
||||||
|
return (static_cast<int>(api) >= 0x1 && static_cast<int>(api) < 0x1000)
|
||||||
|
|| api == libremidi::API::DUMMY;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr bool is_midi2(libremidi::API api)
|
||||||
|
{
|
||||||
|
return static_cast<int>(api) >= 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
namespace midi1
|
namespace midi1
|
||||||
{
|
{
|
||||||
//! Returns the default MIDI 1.0 backend to use for the target OS.
|
//! Returns the default MIDI 1.0 backend to use for the target OS.
|
||||||
@@ -69,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
|
||||||
@@ -88,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;
|
||||||
|
|||||||
+103
-11
@@ -1,4 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#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>)
|
||||||
@@ -27,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)
|
||||||
@@ -42,11 +50,16 @@
|
|||||||
#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>
|
||||||
#if __has_include(<winrt/Microsoft.Devices.Midi2.h>)
|
|
||||||
#include <libremidi/backends/winmidi.hpp>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(LIBREMIDI_WINMIDI)
|
||||||
|
#include <libremidi/backends/winmidi.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(LIBREMIDI_EMSCRIPTEN)
|
#if defined(LIBREMIDI_EMSCRIPTEN)
|
||||||
@@ -55,7 +68,20 @@
|
|||||||
|
|
||||||
#include <libremidi/backends/dummy.hpp>
|
#include <libremidi/backends/dummy.hpp>
|
||||||
|
|
||||||
namespace libremidi
|
#if defined(LIBREMIDI_KEYBOARD)
|
||||||
|
#include <libremidi/backends/keyboard.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(LIBREMIDI_NETWORK)
|
||||||
|
#include <libremidi/backends/network.hpp>
|
||||||
|
#include <libremidi/backends/network_ump.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#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.
|
||||||
@@ -67,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)
|
||||||
,
|
,
|
||||||
@@ -85,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{}
|
||||||
@@ -100,6 +130,18 @@ static constexpr auto available_backends = make_tl(
|
|||||||
#if defined(LIBREMIDI_PIPEWIRE)
|
#if defined(LIBREMIDI_PIPEWIRE)
|
||||||
,
|
,
|
||||||
pipewire::backend{}
|
pipewire::backend{}
|
||||||
|
#endif
|
||||||
|
#if defined(LIBREMIDI_KEYBOARD)
|
||||||
|
,
|
||||||
|
kbd_backend{}
|
||||||
|
#endif
|
||||||
|
#if defined(LIBREMIDI_NETWORK)
|
||||||
|
,
|
||||||
|
net::backend{}
|
||||||
|
#endif
|
||||||
|
#if defined(LIBREMIDI_ANDROID)
|
||||||
|
,
|
||||||
|
android::backend{}
|
||||||
#endif
|
#endif
|
||||||
,
|
,
|
||||||
dummy_backend{});
|
dummy_backend{});
|
||||||
@@ -125,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
|
||||||
,
|
,
|
||||||
@@ -135,15 +177,21 @@ static constexpr auto available_backends = make_tl(
|
|||||||
,
|
,
|
||||||
coremidi_ump::backend{}
|
coremidi_ump::backend{}
|
||||||
#endif
|
#endif
|
||||||
#if defined(LIBREMIDI_JACK)
|
#if defined(LIBREMIDI_WINMIDI)
|
||||||
#endif
|
|
||||||
#if defined(LIBREMIDI_WINUWP)
|
|
||||||
#if __has_include(<winrt/Microsoft.Devices.Midi2.h>)
|
|
||||||
,
|
,
|
||||||
winmidi::backend{}
|
winmidi::backend{}
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(LIBREMIDI_NETWORK)
|
||||||
|
,
|
||||||
|
net_ump::backend{}
|
||||||
#endif
|
#endif
|
||||||
#if defined(LIBREMIDI_EMSCRIPTEN)
|
#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{});
|
||||||
@@ -154,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>
|
||||||
@@ -183,5 +231,49 @@ auto for_backend(libremidi::API api, F&& f)
|
|||||||
midi2::for_backend(api, f);
|
midi2::for_backend(api, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf)
|
||||||
|
{
|
||||||
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
|
if (auto conf = get_if<typename T::midi_in_configuration>(&api_conf))
|
||||||
|
{
|
||||||
|
f(*conf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
|
||||||
|
return;
|
||||||
|
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||||
|
}
|
||||||
|
|
||||||
|
void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf)
|
||||||
|
{
|
||||||
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
|
if (auto conf = get_if<typename T::midi_out_configuration>(&api_conf))
|
||||||
|
{
|
||||||
|
f(*conf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
|
||||||
|
return;
|
||||||
|
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||||
|
}
|
||||||
|
|
||||||
|
void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf)
|
||||||
|
{
|
||||||
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
|
if (auto conf = get_if<typename T::midi_observer_configuration>(&api_conf))
|
||||||
|
{
|
||||||
|
f(*conf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
|
||||||
|
return;
|
||||||
|
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,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
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +62,7 @@ struct manual_poll_parameters
|
|||||||
struct alsa_raw_input_configuration
|
struct alsa_raw_input_configuration
|
||||||
{
|
{
|
||||||
std::function<bool(const manual_poll_parameters&)> manual_poll;
|
std::function<bool(const manual_poll_parameters&)> manual_poll;
|
||||||
|
std::chrono::milliseconds poll_period{2};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct alsa_raw_output_configuration
|
struct alsa_raw_output_configuration
|
||||||
|
|||||||
@@ -2,15 +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 <chrono>
|
#include <alsa/asoundlib.h>
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
#include <cerrno>
|
||||||
#include <optional>
|
#include <cstdint>
|
||||||
#include <span>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <thread>
|
#include <system_error>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Credits: greatly inspired from
|
// Credits: greatly inspired from
|
||||||
@@ -18,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
|
||||||
{
|
{
|
||||||
@@ -32,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;
|
||||||
@@ -87,39 +87,15 @@ struct snd_ctl_wrapper
|
|||||||
struct enumerator
|
struct enumerator
|
||||||
{
|
{
|
||||||
const libasound& snd = libasound::instance();
|
const libasound& snd = libasound::instance();
|
||||||
|
const error_handler& handler;
|
||||||
|
const observer_configuration& configuration;
|
||||||
std::vector<alsa_raw_port_info> inputs;
|
std::vector<alsa_raw_port_info> inputs;
|
||||||
std::vector<alsa_raw_port_info> outputs;
|
std::vector<alsa_raw_port_info> outputs;
|
||||||
|
|
||||||
std::function<void(std::string_view)> error_callback;
|
explicit enumerator(const auto& self)
|
||||||
std::function<void(std::string_view)> warn_callback;
|
: handler{self}
|
||||||
|
, configuration{self.configuration}
|
||||||
template <typename... Args>
|
|
||||||
void warning(Args&&... args)
|
|
||||||
{
|
{
|
||||||
std::string s;
|
|
||||||
((s += args), ...);
|
|
||||||
if (warn_callback)
|
|
||||||
{
|
|
||||||
warn_callback(std::move(s));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cerr << s << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template <typename... Args>
|
|
||||||
void error(Args&&... args)
|
|
||||||
{
|
|
||||||
std::string s;
|
|
||||||
((s += args), ...);
|
|
||||||
if (error_callback)
|
|
||||||
{
|
|
||||||
error_callback(std::move(s));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw std::runtime_error(s.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1: is an input / output
|
// 1: is an input / output
|
||||||
@@ -141,9 +117,10 @@ struct enumerator
|
|||||||
}
|
}
|
||||||
else if (status < 0 && status != -ENXIO)
|
else if (status < 0 && status != -ENXIO)
|
||||||
{
|
{
|
||||||
error(
|
handler.libremidi_handle_error(
|
||||||
"alsa_raw_helpers::enumerator::is: cannot get rawmidi information:", 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
|
||||||
@@ -185,26 +162,23 @@ struct enumerator
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void enumerate_cards()
|
stdx::error enumerate_cards()
|
||||||
{
|
{
|
||||||
|
using namespace std::literals;
|
||||||
int card = -1;
|
int card = -1;
|
||||||
|
|
||||||
int status = snd.card.next(&card);
|
int status = snd.card.next(&card);
|
||||||
if (status < 0)
|
if (status < 0)
|
||||||
{
|
{
|
||||||
error(
|
handler.libremidi_handle_error(
|
||||||
"alsa_raw_helpers::enumerator::enumerate_cards: "
|
configuration, "Cannot determine card number: "s + snd.strerror(status));
|
||||||
"cannot determine card number: ",
|
return from_errc(status);
|
||||||
snd.strerror(status));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card < 0)
|
if (card < 0)
|
||||||
{
|
{
|
||||||
error(
|
handler.libremidi_handle_error(configuration, "No sound cards found");
|
||||||
"alsa_raw_helpers::enumerator::enumerate_cards: "
|
return std::errc::no_such_device;
|
||||||
"no sound cards found");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (card >= 0)
|
while (card >= 0)
|
||||||
@@ -213,13 +187,12 @@ struct enumerator
|
|||||||
|
|
||||||
if ((status = snd.card.next(&card)) < 0)
|
if ((status = snd.card.next(&card)) < 0)
|
||||||
{
|
{
|
||||||
error(
|
handler.libremidi_handle_error(
|
||||||
"alsa_raw_helpers::enumerator::enumerate_cards: "
|
configuration, "cannot determine card number: "s + snd.strerror(status));
|
||||||
"cannot determine card number: ",
|
return std::errc::no_such_device;
|
||||||
snd.strerror(status));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void enumerate_devices(int card) = 0;
|
virtual void enumerate_devices(int card) = 0;
|
||||||
@@ -228,20 +201,23 @@ struct enumerator
|
|||||||
inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)
|
inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)
|
||||||
: snd{self.snd}
|
: snd{self.snd}
|
||||||
{
|
{
|
||||||
|
using namespace std::literals;
|
||||||
int status = snd.ctl.open(&ctl, name, 0);
|
int status = snd.ctl.open(&ctl, name, 0);
|
||||||
if (status < 0)
|
if (status < 0)
|
||||||
{
|
{
|
||||||
self.error(
|
self.handler.libremidi_handle_error(
|
||||||
"alsa_raw_helpers::enumerator::snd_ctl_wrapper: "
|
self.configuration,
|
||||||
"cannot open control for card",
|
"cannot open control for card"s + name + " : " + snd.strerror(status));
|
||||||
name, snd.strerror(status));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct midi1_enumerator : enumerator
|
struct midi1_enumerator : enumerator
|
||||||
{
|
{
|
||||||
|
using alsa_raw::enumerator::enumerator;
|
||||||
|
|
||||||
void enumerate_devices(int card) override
|
void enumerate_devices(int card) override
|
||||||
{
|
{
|
||||||
|
using namespace std::literals;
|
||||||
char name[128];
|
char name[128];
|
||||||
|
|
||||||
sprintf(name, "hw:%d", card);
|
sprintf(name, "hw:%d", card);
|
||||||
@@ -261,10 +237,8 @@ struct midi1_enumerator : enumerator
|
|||||||
|
|
||||||
if (status < 0)
|
if (status < 0)
|
||||||
{
|
{
|
||||||
error(
|
handler.libremidi_handle_error(
|
||||||
"alsa_raw::midi1_enumerator::enumerate_devices: "
|
configuration, "Cannot determine device number: "s + snd.strerror(status));
|
||||||
"cannot determine device number: ",
|
|
||||||
snd.strerror(status));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -33,83 +33,72 @@ public:
|
|||||||
|
|
||||||
~midi_in_impl() override { }
|
~midi_in_impl() override { }
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_alsa_raw: open_virtual_port unsupported");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_alsa_raw: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_alsa_raw: set_port_name unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
||||||
|
|
||||||
// Must be a string such as: "hw:2,4,1"
|
// Must be a string such as: "hw:2,4,1"
|
||||||
[[nodiscard]] int do_init_port(const char* portname)
|
[[nodiscard]] stdx::error do_init_port(const char* portname)
|
||||||
{
|
{
|
||||||
constexpr int mode = SND_RAWMIDI_NONBLOCK;
|
constexpr int mode = SND_RAWMIDI_NONBLOCK;
|
||||||
if (const int err = snd.rawmidi.open(&midiport_, nullptr, portname, mode); err < 0)
|
if (const int err = snd.rawmidi.open(&midiport_, nullptr, portname, mode); err < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(this->configuration, "midi_in_alsa_raw::open_port: cannot open device.");
|
libremidi_handle_error(this->configuration, "cannot open device.");
|
||||||
return err;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_rawmidi_params_t* params{};
|
snd_rawmidi_params_t* params{};
|
||||||
snd_rawmidi_params_alloca(¶ms);
|
snd_rawmidi_params_alloca(¶ms);
|
||||||
|
|
||||||
if (const int err = snd.rawmidi.params_current(midiport_, params); err < 0)
|
if (const int err = snd.rawmidi.params_current(midiport_, params); err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
if (const int err = snd.rawmidi.params_set_no_active_sensing(midiport_, params, 1); err < 0)
|
if (const int err = snd.rawmidi.params_set_no_active_sensing(midiport_, params, 1); err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD
|
#if LIBREMIDI_ALSA_HAS_RAWMIDI_TREAD
|
||||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||||
{
|
{
|
||||||
if (const int err
|
if (const int err
|
||||||
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_STANDARD);
|
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_STANDARD);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
if (const int err
|
if (const int err
|
||||||
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_NONE);
|
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_NONE);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (const int err
|
if (const int err
|
||||||
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_TSTAMP);
|
= snd.rawmidi.params_set_read_mode(midiport_, params, SND_RAWMIDI_READ_TSTAMP);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
if (const int err
|
if (const int err
|
||||||
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_MONOTONIC);
|
= snd.rawmidi.params_set_clock_type(midiport_, params, SND_RAWMIDI_CLOCK_MONOTONIC);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (const int err = snd.rawmidi.params(midiport_, params); err < 0)
|
if (const int err = snd.rawmidi.params(midiport_, params); err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
|
|
||||||
return init_pollfd();
|
return init_pollfd();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int init_port(const port_information& p)
|
[[nodiscard]] stdx::error init_port(const port_information& p)
|
||||||
{
|
{
|
||||||
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
|
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int init_pollfd()
|
[[nodiscard]] stdx::error init_pollfd()
|
||||||
{
|
{
|
||||||
const int num_fds = snd.rawmidi.poll_descriptors_count(this->midiport_);
|
const int num_fds = snd.rawmidi.poll_descriptors_count(this->midiport_);
|
||||||
|
|
||||||
this->fds_.clear();
|
this->fds_.clear();
|
||||||
this->fds_.resize(num_fds);
|
this->fds_.resize(num_fds);
|
||||||
|
|
||||||
return snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
int ret = snd.rawmidi.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
||||||
|
if (ret < 0)
|
||||||
|
return from_errc(ret);
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
|
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
|
||||||
@@ -155,7 +144,8 @@ public:
|
|||||||
while ((err = snd.rawmidi.read(this->midiport_, bytes, nbytes)) > 0)
|
while ((err = snd.rawmidi.read(this->midiport_, bytes, nbytes)) > 0)
|
||||||
{
|
{
|
||||||
const auto to_ns = [this] { return absolute_timestamp(); };
|
const auto to_ns = [this] { return absolute_timestamp(); };
|
||||||
decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp<timestamp_info>(to_ns, 0));
|
m_processing.on_bytes(
|
||||||
|
{bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
}
|
}
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -180,7 +170,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);
|
||||||
};
|
};
|
||||||
decoder_.on_bytes({bytes, bytes + err}, decoder_.timestamp<timestamp_info>(to_ns, 0));
|
m_processing.on_bytes(
|
||||||
|
{bytes, bytes + err}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
}
|
}
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -188,18 +179,19 @@ public:
|
|||||||
ssize_t read_input_buffer_with_timestamps() { return read_input_buffer(); }
|
ssize_t read_input_buffer_with_timestamps() { return read_input_buffer(); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
if (midiport_)
|
if (midiport_)
|
||||||
snd.rawmidi.close(midiport_);
|
snd.rawmidi.close(midiport_);
|
||||||
midiport_ = nullptr;
|
midiport_ = nullptr;
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept final override { return system_ns(); }
|
timestamp absolute_timestamp() const noexcept final override { return system_ns(); }
|
||||||
|
|
||||||
snd_rawmidi_t* midiport_{};
|
snd_rawmidi_t* midiport_{};
|
||||||
std::vector<pollfd> fds_;
|
std::vector<pollfd> fds_;
|
||||||
midi1::input_state_machine decoder_{this->configuration};
|
midi1::input_state_machine m_processing{this->configuration};
|
||||||
};
|
};
|
||||||
|
|
||||||
class midi_in_alsa_raw_threaded : public midi_in_impl
|
class midi_in_alsa_raw_threaded : public midi_in_impl
|
||||||
@@ -208,33 +200,40 @@ public:
|
|||||||
midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
|
midi_in_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)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||||
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_in_alsa_raw_threaded() override
|
~midi_in_alsa_raw_threaded() override
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
this->midi_in_alsa_raw_threaded::close_port();
|
this->midi_in_alsa_raw_threaded::close_port();
|
||||||
|
|
||||||
|
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
|
||||||
|
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||||
|
.count();
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
// Poll
|
// Poll
|
||||||
ssize_t err = poll(fds_.data(), fds_.size(), -1);
|
ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
|
||||||
if (err == -EAGAIN)
|
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});
|
||||||
@@ -245,64 +244,70 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] ssize_t start_thread()
|
[[nodiscard]] stdx::error start_thread()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
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{};
|
||||||
}
|
}
|
||||||
catch (const std::system_error& e)
|
catch (const std::system_error& e)
|
||||||
{
|
{
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
error<thread_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error starting MIDI input thread: "s + e.what());
|
||||||
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
|
return e.code();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const input_port& port, std::string_view /*name*/) override
|
stdx::error open_port(const input_port& port, std::string_view /*name*/) override
|
||||||
{
|
{
|
||||||
if (const int err = midi_in_impl::init_port(port); err < 0)
|
if (auto err = midi_in_impl::init_port(port); err != stdx::error{})
|
||||||
return false;
|
return err;
|
||||||
if (!start_thread())
|
if (auto err = start_thread(); err != stdx::error{})
|
||||||
return false;
|
return err;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void 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
|
||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using midi_in_impl::midi_in_impl;
|
midi_in_alsa_raw_manual(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
|
||||||
|
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
~midi_in_alsa_raw_manual()
|
~midi_in_alsa_raw_manual()
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
this->close_port();
|
this->close_port();
|
||||||
|
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -310,7 +315,8 @@ private:
|
|||||||
{
|
{
|
||||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||||
{
|
{
|
||||||
configuration.manual_poll(manual_poll_parameters{
|
configuration.manual_poll(
|
||||||
|
manual_poll_parameters{
|
||||||
.fds = {this->fds_.data(), this->fds_.size()},
|
.fds = {this->fds_.data(), this->fds_.size()},
|
||||||
.callback = [this](std::span<pollfd> fds) {
|
.callback = [this](std::span<pollfd> fds) {
|
||||||
return do_read_events(&midi_in_impl::read_input_buffer, fds);
|
return do_read_events(&midi_in_impl::read_input_buffer, fds);
|
||||||
@@ -318,7 +324,8 @@ private:
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
configuration.manual_poll(manual_poll_parameters{
|
configuration.manual_poll(
|
||||||
|
manual_poll_parameters{
|
||||||
.fds = {this->fds_.data(), this->fds_.size()},
|
.fds = {this->fds_.data(), this->fds_.size()},
|
||||||
.callback = [this](std::span<pollfd> fds) {
|
.callback = [this](std::span<pollfd> fds) {
|
||||||
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
|
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
|
||||||
@@ -326,17 +333,17 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const input_port& p, std::string_view /*name*/) override
|
stdx::error open_port(const input_port& p, std::string_view /*name*/) override
|
||||||
{
|
{
|
||||||
if (midi_in_impl::init_port(p) < 0)
|
if (auto err = midi_in_impl::init_port(p); err != stdx::error{})
|
||||||
return false;
|
return err;
|
||||||
send_poll_callback();
|
send_poll_callback();
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -26,83 +26,71 @@ public:
|
|||||||
midi_out_impl(output_configuration&& conf, alsa_raw_output_configuration&& apiconf)
|
midi_out_impl(output_configuration&& conf, alsa_raw_output_configuration&& apiconf)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_out_impl() override
|
~midi_out_impl() override
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
midi_out_impl::close_port();
|
midi_out_impl::close_port();
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view) override
|
stdx::error connect_port(const char* portname)
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_alsa_raw: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_alsa_raw: set_port_name unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
int connect_port(const char* portname)
|
|
||||||
{
|
{
|
||||||
constexpr int mode = SND_RAWMIDI_SYNC;
|
constexpr int mode = SND_RAWMIDI_SYNC;
|
||||||
int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);
|
int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);
|
||||||
if (status < 0)
|
if (status < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "cannot open device.");
|
||||||
this->configuration, "midi_out_alsa_raw::open_port: cannot open device.");
|
return from_errc(status);
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
return status;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const output_port& p, std::string_view) override
|
stdx::error open_port(const output_port& p, std::string_view) override
|
||||||
{
|
{
|
||||||
return connect_port(raw_from_port_handle(p.port).to_string().c_str()) == 0;
|
return connect_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
if (midiport_)
|
if (midiport_)
|
||||||
snd.rawmidi.close(midiport_);
|
snd.rawmidi.close(midiport_);
|
||||||
midiport_ = nullptr;
|
midiport_ = nullptr;
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_message(const unsigned char* message, size_t size) override
|
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||||
{
|
{
|
||||||
if (!midiport_)
|
if (!midiport_)
|
||||||
error<invalid_use_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_out_alsa_raw::send_message: trying to send a message without an open "
|
"trying to send a message without an open "
|
||||||
"port.");
|
"port.");
|
||||||
|
|
||||||
if (!this->configuration.chunking)
|
if (!this->configuration.chunking)
|
||||||
{
|
{
|
||||||
write(message, size);
|
return write(message, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_chunked(message, size);
|
return write_chunked(message, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool write(const unsigned char* message, size_t size)
|
stdx::error write(const unsigned char* message, size_t size)
|
||||||
{
|
{
|
||||||
if (snd.rawmidi.write(midiport_, message, size) < 0)
|
if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "cannot write message.");
|
||||||
this->configuration, "midi_out_alsa_raw::send_message: cannot write message.");
|
return from_errc(err);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t get_chunk_size() const noexcept
|
std::size_t get_chunk_size() const noexcept
|
||||||
@@ -125,7 +113,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// inspired from ALSA amidi.c source code
|
// inspired from ALSA amidi.c source code
|
||||||
void write_chunked(const unsigned char* const begin, size_t size)
|
stdx::error write_chunked(const unsigned char* const begin, size_t size)
|
||||||
{
|
{
|
||||||
const unsigned char* data = begin;
|
const unsigned char* data = begin;
|
||||||
const unsigned char* end = begin + size;
|
const unsigned char* end = begin + size;
|
||||||
@@ -135,8 +123,8 @@ public:
|
|||||||
// Send the first buffer
|
// Send the first buffer
|
||||||
std::size_t len = chunk_size;
|
std::size_t len = chunk_size;
|
||||||
|
|
||||||
if (!write(data, len))
|
if (auto err = write(data, len); err != stdx::error{})
|
||||||
return;
|
return err;
|
||||||
|
|
||||||
data += len;
|
data += len;
|
||||||
|
|
||||||
@@ -149,11 +137,11 @@ public:
|
|||||||
{
|
{
|
||||||
if (!configuration.chunking->wait(
|
if (!configuration.chunking->wait(
|
||||||
std::chrono::microseconds((chunk_size - available) * 320), written_bytes))
|
std::chrono::microseconds((chunk_size - available) * 320), written_bytes))
|
||||||
return;
|
return std::errc::protocol_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes))
|
if (!configuration.chunking->wait(configuration.chunking->interval, written_bytes))
|
||||||
return;
|
return std::errc::protocol_error;
|
||||||
|
|
||||||
// Write more data
|
// Write more data
|
||||||
len = end - data;
|
len = end - data;
|
||||||
@@ -165,11 +153,13 @@ public:
|
|||||||
if (len > chunk_size)
|
if (len > chunk_size)
|
||||||
len = chunk_size;
|
len = chunk_size;
|
||||||
|
|
||||||
if (!write(data, len))
|
if (auto err = write(data, len); err != stdx::error{})
|
||||||
return;
|
return err;
|
||||||
|
|
||||||
data += len;
|
data += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_rawmidi_t* midiport_{};
|
snd_rawmidi_t* midiport_{};
|
||||||
|
|||||||
@@ -6,14 +6,15 @@
|
|||||||
#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>
|
||||||
|
#endif
|
||||||
#include <libremidi/detail/observer.hpp>
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
#include <stdexcept>
|
NAMESPACE_LIBREMIDI::alsa_raw
|
||||||
|
|
||||||
namespace libremidi::alsa_raw
|
|
||||||
{
|
{
|
||||||
template <typename Enumerator>
|
template <typename Enumerator>
|
||||||
class observer_impl_base : public observer_api
|
class observer_impl_base
|
||||||
|
: public observer_api
|
||||||
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct
|
struct
|
||||||
@@ -27,34 +28,45 @@ 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
|
||||||
{
|
{
|
||||||
std::vector<libremidi::input_port> ret;
|
std::vector<libremidi::input_port> ret;
|
||||||
Enumerator new_devs;
|
Enumerator new_devs{*this};
|
||||||
|
|
||||||
new_devs.enumerate_cards();
|
new_devs.enumerate_cards();
|
||||||
for (auto& d : new_devs.inputs)
|
for (auto& d : new_devs.inputs)
|
||||||
@@ -67,7 +79,7 @@ public:
|
|||||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||||
{
|
{
|
||||||
std::vector<libremidi::output_port> ret;
|
std::vector<libremidi::output_port> ret;
|
||||||
Enumerator new_devs;
|
Enumerator new_devs{*this};
|
||||||
|
|
||||||
new_devs.enumerate_cards();
|
new_devs.enumerate_cards();
|
||||||
for (auto& d : new_devs.outputs)
|
for (auto& d : new_devs.outputs)
|
||||||
@@ -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,68 +102,96 @@ 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()
|
||||||
{
|
{
|
||||||
Enumerator new_devs;
|
Enumerator new_devs{*this};
|
||||||
|
|
||||||
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; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,11 +1,15 @@
|
|||||||
#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
|
||||||
{
|
{
|
||||||
std::function<bool(const manual_poll_parameters&)> manual_poll;
|
std::function<bool(const manual_poll_parameters&)> manual_poll;
|
||||||
|
std::chrono::milliseconds poll_period{2};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct output_configuration
|
struct output_configuration
|
||||||
@@ -17,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,12 +3,16 @@
|
|||||||
#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
|
||||||
{
|
{
|
||||||
|
using alsa_raw::enumerator::enumerator;
|
||||||
void enumerate_devices(int card) override
|
void enumerate_devices(int card) override
|
||||||
{
|
{
|
||||||
|
using namespace std::literals;
|
||||||
char name[128];
|
char name[128];
|
||||||
|
|
||||||
sprintf(name, "hw:%d", card);
|
sprintf(name, "hw:%d", card);
|
||||||
@@ -28,10 +32,8 @@ struct midi2_enumerator : alsa_raw::enumerator
|
|||||||
|
|
||||||
if (status < 0)
|
if (status < 0)
|
||||||
{
|
{
|
||||||
error(
|
handler.libremidi_handle_error(
|
||||||
"alsa_raw_ump::midi2_enumerator::enumerate_devices: "
|
configuration, "Cannot determine device number: "s + snd.strerror(status));
|
||||||
"cannot determine device number: ",
|
|
||||||
snd.strerror(status));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -35,32 +35,18 @@ public:
|
|||||||
|
|
||||||
~midi_in_impl() override { }
|
~midi_in_impl() override { }
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "alsa_raw_ump::ump: open_virtual_port unsupported");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "alsa_raw_ump::ump: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "alsa_raw_ump::ump: set_port_name unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
|
||||||
|
|
||||||
// Must be a string such as: "hw:2,4,1"
|
// Must be a string such as: "hw:2,4,1"
|
||||||
[[nodiscard]] int do_init_port(const char* portname)
|
[[nodiscard]] stdx::error do_init_port(const char* portname)
|
||||||
{
|
{
|
||||||
constexpr int mode = 0;
|
constexpr int mode = 0;
|
||||||
SND_RAWMIDI_NONBLOCK;
|
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)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
|
this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
|
||||||
return err;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_rawmidi_params_t* params{};
|
snd_rawmidi_params_t* params{};
|
||||||
@@ -69,49 +55,52 @@ public:
|
|||||||
auto rawmidi = snd.ump.rawmidi(midiport_);
|
auto rawmidi = snd.ump.rawmidi(midiport_);
|
||||||
|
|
||||||
if (int err = snd.ump.rawmidi_params_current(midiport_, params); err < 0)
|
if (int err = snd.ump.rawmidi_params_current(midiport_, params); err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
if (int err = snd.rawmidi.params_set_no_active_sensing(rawmidi, params, 1); err < 0)
|
if (int err = snd.rawmidi.params_set_no_active_sensing(rawmidi, params, 1); err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
|
|
||||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||||
{
|
{
|
||||||
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_STANDARD);
|
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_STANDARD);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_NONE);
|
if (int err = snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_NONE);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_TSTAMP);
|
if (int err = snd.rawmidi.params_set_read_mode(rawmidi, params, SND_RAWMIDI_READ_TSTAMP);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
if (int err
|
if (int err
|
||||||
= snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_MONOTONIC);
|
= snd.rawmidi.params_set_clock_type(rawmidi, params, SND_RAWMIDI_CLOCK_MONOTONIC);
|
||||||
err < 0)
|
err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (int err = snd.ump.rawmidi_params(midiport_, params); err < 0)
|
if (int err = snd.ump.rawmidi_params(midiport_, params); err < 0)
|
||||||
return err;
|
return from_errc(err);
|
||||||
|
|
||||||
return init_pollfd();
|
return init_pollfd();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int init_port(const port_information& p)
|
[[nodiscard]] stdx::error init_port(const port_information& p)
|
||||||
{
|
{
|
||||||
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
|
return do_init_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int init_pollfd()
|
[[nodiscard]] stdx::error init_pollfd()
|
||||||
{
|
{
|
||||||
int num_fds = snd.ump.poll_descriptors_count(this->midiport_);
|
int num_fds = snd.ump.poll_descriptors_count(this->midiport_);
|
||||||
|
|
||||||
this->fds_.clear();
|
this->fds_.clear();
|
||||||
this->fds_.resize(num_fds);
|
this->fds_.resize(num_fds);
|
||||||
|
|
||||||
return snd.ump.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
int ret = snd.ump.poll_descriptors(this->midiport_, fds_.data(), num_fds);
|
||||||
|
if (ret < 0)
|
||||||
|
return from_errc(ret);
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
|
ssize_t do_read_events(auto parse_func, std::span<pollfd> fds)
|
||||||
@@ -143,95 +132,62 @@ public:
|
|||||||
|
|
||||||
ssize_t read_input_buffer()
|
ssize_t read_input_buffer()
|
||||||
{
|
{
|
||||||
static const constexpr int nbytes = 1024;
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = false,
|
||||||
unsigned char bytes[nbytes];
|
.absolute_is_monotonic = false,
|
||||||
|
.has_samples = false,
|
||||||
|
};
|
||||||
|
static const constexpr int nwords = 64;
|
||||||
|
uint32_t words[nwords];
|
||||||
|
|
||||||
ssize_t err = 0;
|
ssize_t err = 0;
|
||||||
while ((err = snd.ump.read(this->midiport_, bytes, nbytes)) > 0)
|
while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0)
|
||||||
{
|
{
|
||||||
std::cerr << "We read: " << nbytes << "bytes !!!!";
|
const auto to_ns = [this] { return absolute_timestamp(); };
|
||||||
return 1;
|
m_processing.on_bytes(
|
||||||
// err is the amount of bytes read
|
{words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
// decoder_.add_bytes(bytes, err);
|
|
||||||
}
|
}
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_timestamp(struct timespec ts, timestamp& res)
|
|
||||||
{
|
|
||||||
static constexpr int64_t nanos = 1e9;
|
|
||||||
switch (configuration.timestamps)
|
|
||||||
{
|
|
||||||
// Unneeded here
|
|
||||||
case timestamp_mode::NoTimestamp:
|
|
||||||
break;
|
|
||||||
case timestamp_mode::Relative: {
|
|
||||||
const auto t = static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec);
|
|
||||||
if (firstMessage == true)
|
|
||||||
{
|
|
||||||
firstMessage = false;
|
|
||||||
res = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
res = t - last_time;
|
|
||||||
}
|
|
||||||
last_time = t;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case timestamp_mode::Absolute:
|
|
||||||
case timestamp_mode::SystemMonotonic:
|
|
||||||
res = static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec);
|
|
||||||
break;
|
|
||||||
case timestamp_mode::Custom:
|
|
||||||
res = configuration.get_timestamp(
|
|
||||||
static_cast<int64_t>(ts.tv_sec) * nanos + static_cast<int64_t>(ts.tv_nsec));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t read_input_buffer_with_timestamps()
|
ssize_t read_input_buffer_with_timestamps()
|
||||||
{
|
{
|
||||||
static const constexpr int nbytes = 1024;
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = true,
|
||||||
|
.absolute_is_monotonic = true,
|
||||||
|
.has_samples = false,
|
||||||
|
};
|
||||||
|
|
||||||
unsigned char bytes[nbytes];
|
static const constexpr int nwords = 64;
|
||||||
|
uint32_t words[nwords];
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
|
|
||||||
ssize_t err = 0;
|
ssize_t err = 0;
|
||||||
while ((err = snd.ump.tread(this->midiport_, &ts, bytes, nbytes)) > 0)
|
while ((err = snd.ump.tread(this->midiport_, &ts, words, nwords * 4)) > 0)
|
||||||
{
|
{
|
||||||
// err is the amount of bytes read
|
const auto to_ns = [ts] {
|
||||||
int64_t ns{};
|
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
|
||||||
set_timestamp(ts, ns);
|
};
|
||||||
std::cerr << "We read: " << nbytes << "bytes !!!!";
|
m_processing.on_bytes(
|
||||||
return 1;
|
{words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
// decoder_.add_bytes(bytes, err, ns);
|
|
||||||
}
|
}
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
if (midiport_)
|
if (midiport_)
|
||||||
snd.ump.close(midiport_);
|
snd.ump.close(midiport_);
|
||||||
midiport_ = nullptr;
|
midiport_ = nullptr;
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
|
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
|
||||||
|
|
||||||
midi2_enumerator get_device_enumerator() const noexcept
|
|
||||||
{
|
|
||||||
midi2_enumerator device_list;
|
|
||||||
device_list.error_callback
|
|
||||||
= [this](std::string_view text) { this->error<driver_error>(this->configuration, text); };
|
|
||||||
return device_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
snd_ump_t* midiport_{};
|
snd_ump_t* midiport_{};
|
||||||
std::vector<pollfd> fds_;
|
std::vector<pollfd> fds_;
|
||||||
// midi_stream_decoder decoder_{this->configuration.on_message};
|
midi2::input_state_machine m_processing{this->configuration};
|
||||||
int64_t last_time{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class midi_in_impl_threaded : public midi_in_impl
|
class midi_in_impl_threaded : public midi_in_impl
|
||||||
@@ -241,33 +197,39 @@ 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)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||||
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||||
|
.count();
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
// Poll
|
// Poll
|
||||||
ssize_t err = poll(fds_.data(), fds_.size(), -1);
|
ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
|
||||||
if (err == -EAGAIN)
|
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});
|
||||||
@@ -278,64 +240,70 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int start_thread()
|
[[nodiscard]] stdx::error start_thread()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
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{};
|
||||||
}
|
}
|
||||||
catch (const std::system_error& e)
|
catch (const std::system_error& e)
|
||||||
{
|
{
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
error<thread_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error starting MIDI input thread: "s + e.what());
|
||||||
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
|
return e.code();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const input_port& port, [[maybe_unused]] std::string_view name) override
|
stdx::error open_port(const input_port& port, [[maybe_unused]] std::string_view name) override
|
||||||
{
|
{
|
||||||
if (int err = midi_in_impl::init_port(port); err < 0)
|
if (auto err = midi_in_impl::init_port(port); err != stdx::error{})
|
||||||
return false;
|
return err;
|
||||||
if (!start_thread())
|
if (auto err = start_thread(); err != stdx::error{})
|
||||||
return false;
|
return err;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void 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
|
||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using midi_in_impl::midi_in_impl;
|
midi_in_impl_manual(ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
|
||||||
|
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
~midi_in_impl_manual()
|
~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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -343,7 +311,8 @@ private:
|
|||||||
{
|
{
|
||||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||||
{
|
{
|
||||||
configuration.manual_poll(manual_poll_parameters{
|
configuration.manual_poll(
|
||||||
|
manual_poll_parameters{
|
||||||
.fds = {this->fds_.data(), this->fds_.size()},
|
.fds = {this->fds_.data(), this->fds_.size()},
|
||||||
.callback = [this](std::span<pollfd> fds) {
|
.callback = [this](std::span<pollfd> fds) {
|
||||||
return do_read_events(&midi_in_impl::read_input_buffer, fds);
|
return do_read_events(&midi_in_impl::read_input_buffer, fds);
|
||||||
@@ -351,7 +320,8 @@ private:
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
configuration.manual_poll(manual_poll_parameters{
|
configuration.manual_poll(
|
||||||
|
manual_poll_parameters{
|
||||||
.fds = {this->fds_.data(), this->fds_.size()},
|
.fds = {this->fds_.data(), this->fds_.size()},
|
||||||
.callback = [this](std::span<pollfd> fds) {
|
.callback = [this](std::span<pollfd> fds) {
|
||||||
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
|
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
|
||||||
@@ -359,17 +329,17 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const input_port& p, [[maybe_unused]] std::string_view name) override
|
stdx::error open_port(const input_port& p, [[maybe_unused]] std::string_view name) override
|
||||||
{
|
{
|
||||||
if (midi_in_impl::init_port(p) < 0)
|
if (auto err = midi_in_impl::init_port(p); err != stdx::error{})
|
||||||
return false;
|
return err;
|
||||||
send_poll_callback();
|
send_poll_callback();
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -29,76 +34,63 @@ public:
|
|||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
assert(snd.ump.available);
|
assert(snd.ump.available);
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_out_impl() override
|
~midi_out_impl() override
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
midi_out_impl::close_port();
|
midi_out_impl::close_port();
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view) override
|
stdx::error connect_port(const char* portname)
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_alsa_raw: open_virtual_port unsupported");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_alsa_raw: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_alsa_raw: set_port_name unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
int connect_port(const char* portname)
|
|
||||||
{
|
{
|
||||||
constexpr int mode = SND_RAWMIDI_SYNC;
|
constexpr int mode = SND_RAWMIDI_SYNC;
|
||||||
int status = snd.ump.open(NULL, &midiport_, portname, mode);
|
int ret = snd.ump.open(NULL, &midiport_, portname, mode);
|
||||||
if (status < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "cannot open device.");
|
||||||
this->configuration, "midi_out_alsa_raw::open_port: cannot open device.");
|
return from_errc(ret);
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
return status;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const output_port& p, std::string_view) override
|
stdx::error open_port(const output_port& p, std::string_view) override
|
||||||
{
|
{
|
||||||
return connect_port(raw_from_port_handle(p.port).to_string().c_str()) == 0;
|
return connect_port(raw_from_port_handle(p.port).to_string().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
if (midiport_)
|
if (midiport_)
|
||||||
snd.ump.close(midiport_);
|
snd.ump.close(midiport_);
|
||||||
midiport_ = nullptr;
|
midiport_ = nullptr;
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_ump(const uint32_t* ump_stream, std::size_t count) override
|
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||||
{
|
{
|
||||||
if (!midiport_)
|
if (!midiport_)
|
||||||
error<invalid_use_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_out_alsa_raw::send_message: trying to send a message without an open "
|
"trying to send a message without an open "
|
||||||
"port.");
|
"port.");
|
||||||
|
|
||||||
write(ump_stream, count * sizeof(uint32_t));
|
return write(ump_stream, count * sizeof(uint32_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool write(const uint32_t* ump_stream, size_t bytes)
|
stdx::error write(const uint32_t* ump_stream, size_t bytes)
|
||||||
{
|
{
|
||||||
if (snd.ump.write(midiport_, ump_stream, bytes) < 0)
|
if (auto err = snd.ump.write(midiport_, ump_stream, bytes); err < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "cannot write message.");
|
||||||
this->configuration, "midi_out_alsa_raw::send_message: cannot write message.");
|
return from_errc(err);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_ump_t* midiport_{};
|
snd_ump_t* midiport_{};
|
||||||
|
|||||||
@@ -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; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -38,6 +38,7 @@ struct input_configuration
|
|||||||
snd_seq_t* context{};
|
snd_seq_t* context{};
|
||||||
std::function<bool(const poll_parameters&)> manual_poll;
|
std::function<bool(const poll_parameters&)> manual_poll;
|
||||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||||
|
std::chrono::milliseconds poll_period{2};
|
||||||
|
|
||||||
static constexpr int midi_version = 1;
|
static constexpr int midi_version = 1;
|
||||||
};
|
};
|
||||||
@@ -56,6 +57,7 @@ struct observer_configuration
|
|||||||
snd_seq_t* context{};
|
snd_seq_t* context{};
|
||||||
std::function<bool(const poll_parameters&)> manual_poll;
|
std::function<bool(const poll_parameters&)> manual_poll;
|
||||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||||
|
std::chrono::milliseconds poll_period{100};
|
||||||
|
|
||||||
static constexpr int midi_version = 1;
|
static constexpr int midi_version = 1;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,18 +207,20 @@ struct alsa_data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_client_name(std::string_view clientName)
|
stdx::error set_client_name(std::string_view clientName)
|
||||||
{
|
{
|
||||||
snd.seq.set_client_name(seq, clientName.data());
|
int ret = snd.seq.set_client_name(seq, clientName.data());
|
||||||
|
return from_errc(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_port_name(std::string_view portName)
|
stdx::error set_port_name(std::string_view portName)
|
||||||
{
|
{
|
||||||
snd_seq_port_info_t* pinfo;
|
snd_seq_port_info_t* pinfo;
|
||||||
snd_seq_port_info_alloca(&pinfo);
|
snd_seq_port_info_alloca(&pinfo);
|
||||||
snd.seq.get_port_info(seq, vport, pinfo);
|
snd.seq.get_port_info(seq, vport, pinfo);
|
||||||
snd.seq.port_info_set_name(pinfo, portName.data());
|
snd.seq.port_info_set_name(pinfo, portName.data());
|
||||||
snd.seq.set_port_info(seq, vport, pinfo);
|
int ret = snd.seq.set_port_info(seq, vport, pinfo);
|
||||||
|
return from_errc(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int get_port_count(int caps) const
|
unsigned int get_port_count(int caps) const
|
||||||
@@ -209,9 +241,9 @@ struct alsa_data
|
|||||||
// snd.seq.port_info_set_port(src_pinfo, port);
|
// snd.seq.port_info_set_port(src_pinfo, port);
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// self.template error<invalid_parameter_error>(
|
// self.libremidi_handle_error(
|
||||||
// self.configuration,
|
// self.configuration,
|
||||||
// "alsa::get_port_info: invalid 'portNumber' argument: " + std::to_string(portNumber));
|
// "invalid 'portNumber' argument: " + std::to_string(portNumber));
|
||||||
// return {};
|
// return {};
|
||||||
// }
|
// }
|
||||||
snd_seq_addr_t addr;
|
snd_seq_addr_t addr;
|
||||||
@@ -270,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.template error<driver_error>(
|
self.libremidi_handle_error(self.configuration, "ALSA error allocation port subscription.");
|
||||||
self.configuration, "create_connection: ALSA error allocation port subscription.");
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -17,7 +17,7 @@ using midi_in_base
|
|||||||
= std::conditional_t<ConfigurationImpl::midi_version == 1, midi1::in_api, midi2::in_api>;
|
= std::conditional_t<ConfigurationImpl::midi_version == 1, midi1::in_api, midi2::in_api>;
|
||||||
template <typename ConfigurationImpl>
|
template <typename ConfigurationImpl>
|
||||||
using midi_in_processing = std::conditional_t<
|
using midi_in_processing = std::conditional_t<
|
||||||
ConfigurationImpl::midi_version == 1, midi1::input_state_machine, dummy_processing>;
|
ConfigurationImpl::midi_version == 1, midi1::input_state_machine, midi2::input_state_machine>;
|
||||||
|
|
||||||
template <typename ConfigurationBase, typename ConfigurationImpl>
|
template <typename ConfigurationBase, typename ConfigurationImpl>
|
||||||
class midi_in_impl
|
class midi_in_impl
|
||||||
@@ -55,9 +55,9 @@ public:
|
|||||||
{
|
{
|
||||||
if (init_client(configuration) < 0)
|
if (init_client(configuration) < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_in_alsa::initialize: error creating ALSA sequencer client "
|
"error creating ALSA sequencer client "
|
||||||
"object.");
|
"object.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -80,8 +80,7 @@ public:
|
|||||||
int result = snd.midi.event_new(0, &coder);
|
int result = snd.midi.event_new(0, &coder);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error during snd_midi_event_new.");
|
||||||
this->configuration, "midi_in_alsa::initialize: error during snd_midi_event_new.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
snd.midi.event_init(coder);
|
snd.midi.event_init(coder);
|
||||||
@@ -105,7 +104,13 @@ public:
|
|||||||
snd.seq.close(this->seq);
|
snd.seq.close(this->seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
|
libremidi::API get_current_api() const noexcept override
|
||||||
|
{
|
||||||
|
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||||
|
return libremidi::API::ALSA_SEQ;
|
||||||
|
else
|
||||||
|
return libremidi::API::ALSA_SEQ_UMP;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] int create_port(std::string_view portName)
|
[[nodiscard]] int create_port(std::string_view portName)
|
||||||
{
|
{
|
||||||
@@ -157,14 +162,13 @@ public:
|
|||||||
|
|
||||||
if (int ret = create_port(portName); ret < 0)
|
if (int ret = create_port(portName); ret < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(configuration, "midi_in_alsa::create_port: ALSA error creating port.");
|
libremidi_handle_error(configuration, "ALSA error creating port.");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (int ret = connect_port(*source); ret < 0)
|
if (int ret = connect_port(*source); ret < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(configuration, "ALSA error making port connection.");
|
||||||
configuration, "midi_in_alsa::create_port: ALSA error making port connection.");
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,63 +188,16 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
stop_queue();
|
stop_queue();
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_client_name(std::string_view clientName) override
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
{
|
{
|
||||||
alsa_data::set_client_name(clientName);
|
return alsa_data::set_port_name(portName);
|
||||||
}
|
|
||||||
|
|
||||||
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void set_timestamp(const auto& ev, auto& msg) noexcept
|
|
||||||
{
|
|
||||||
static constexpr int64_t nanos = 1e9;
|
|
||||||
switch (configuration.timestamps)
|
|
||||||
{
|
|
||||||
case timestamp_mode::NoTimestamp:
|
|
||||||
msg.timestamp = 0;
|
|
||||||
return;
|
|
||||||
case timestamp_mode::Relative: {
|
|
||||||
const auto t1 = static_cast<int64_t>(ev.time.time.tv_sec) * nanos
|
|
||||||
+ static_cast<int64_t>(ev.time.time.tv_nsec);
|
|
||||||
const auto t0 = static_cast<int64_t>(last_time.tv_sec) * nanos
|
|
||||||
+ static_cast<int64_t>(last_time.tv_nsec);
|
|
||||||
const auto time = t1 - t0;
|
|
||||||
|
|
||||||
last_time = ev.time.time;
|
|
||||||
|
|
||||||
if (this->firstMessage == true)
|
|
||||||
{
|
|
||||||
this->firstMessage = false;
|
|
||||||
msg.timestamp = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
msg.timestamp = time;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case timestamp_mode::Absolute: {
|
|
||||||
msg.timestamp = ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case timestamp_mode::Custom: {
|
|
||||||
msg.timestamp
|
|
||||||
= configuration.get_timestamp(ev.time.time.tv_sec * nanos + ev.time.time.tv_nsec);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case timestamp_mode::SystemMonotonic: {
|
|
||||||
namespace clk = std::chrono;
|
|
||||||
msg.timestamp = system_ns();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override
|
timestamp absolute_timestamp() const noexcept override
|
||||||
@@ -250,7 +207,7 @@ protected:
|
|||||||
.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)
|
||||||
{
|
{
|
||||||
@@ -296,19 +253,21 @@ protected:
|
|||||||
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;
|
||||||
while ((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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -346,11 +305,17 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MIDI 2 : no decoder, we can just send the UMP data directly, yay
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
libremidi::ump ump;
|
.has_absolute_timestamps = true,
|
||||||
std::memcpy(ump.data, ev.ump, sizeof(ev.ump));
|
.absolute_is_monotonic = false,
|
||||||
set_timestamp(ev, ump);
|
.has_samples = false,
|
||||||
configuration.on_message(std::move(ump));
|
};
|
||||||
|
const auto to_ns = [ts = ev.time.time] {
|
||||||
|
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
|
||||||
|
};
|
||||||
|
|
||||||
|
m_processing.on_bytes_multi(
|
||||||
|
{ev.ump, ev.ump + 4}, m_processing.template timestamp<timestamp_info>(to_ns, 0));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +335,6 @@ protected:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int queue_id{}; // an input queue is needed to get timestamped events
|
int queue_id{}; // an input queue is needed to get timestamped events
|
||||||
snd_seq_real_time_t last_time{};
|
|
||||||
|
|
||||||
// Only needed for midi 1
|
// Only needed for midi 1
|
||||||
std::vector<unsigned char> decoding_buffer = std::vector<unsigned char>(4096);
|
std::vector<unsigned char> decoding_buffer = std::vector<unsigned char>(4096);
|
||||||
@@ -384,89 +348,96 @@ 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->template error<driver_error>(
|
this->libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||||
this->configuration, "midi_in_alsa::initialize: error creating eventfd.");
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_in_alsa_threaded() { this->close_port(); }
|
this->client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_in_alsa_threaded()
|
||||||
|
{
|
||||||
|
midi_in_alsa_threaded::close_port();
|
||||||
|
this->client_open_ = std::errc::not_connected;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool open_port(const input_port& pt, std::string_view local_port_name) override
|
stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
|
||||||
{
|
{
|
||||||
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
|
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
|
|
||||||
if (!start_thread())
|
return this->start_thread();
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (int err = this->init_virtual_port(portName); err < 0)
|
if (int err = this->init_virtual_port(portName); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
|
|
||||||
if (!this->start_thread())
|
return this->start_thread();
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
// FIXME shouldn't we always close the thread
|
||||||
|
auto err = midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
||||||
|
|
||||||
stop_thread();
|
stop_thread();
|
||||||
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int start_thread()
|
[[nodiscard]] stdx::error start_thread()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this->thread = std::thread([this] { thread_handler(); });
|
this->m_thread = std::thread([this] { thread_handler(); });
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
catch (const std::system_error& e)
|
catch (const std::system_error& e)
|
||||||
{
|
{
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
this->unsubscribe();
|
this->unsubscribe();
|
||||||
|
|
||||||
this->template error<thread_error>(
|
this->libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error starting MIDI input thread: "s + e.what());
|
||||||
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what());
|
return e.code();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void 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{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void thread_handler()
|
void thread_handler()
|
||||||
{
|
{
|
||||||
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
|
||||||
|
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||||
|
.count();
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
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, -1) >= 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;
|
||||||
}
|
}
|
||||||
@@ -474,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();
|
||||||
@@ -486,17 +457,13 @@ private:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
(void)res;
|
|
||||||
#if defined(__LIBREMIDI_DEBUG__)
|
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
std::cerr << "midi_in_alsa::thread_handler: MIDI input error: " << snd.strerror(res)
|
LIBREMIDI_LOG("MIDI input error: ", this->snd.strerror(res));
|
||||||
<< "\n";
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
@@ -525,38 +492,40 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_in_alsa_manual() { this->close_port(); }
|
~midi_in_alsa_manual() { midi_in_alsa_manual::close_port(); }
|
||||||
|
|
||||||
bool open_port(const input_port& pt, std::string_view local_port_name) override
|
stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
|
||||||
{
|
{
|
||||||
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
|
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
|
|
||||||
if (int err = init_callback(); err < 0)
|
if (int err = init_callback(); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
return true;
|
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view name) override
|
stdx::error open_virtual_port(std::string_view name) override
|
||||||
{
|
{
|
||||||
if (int err = this->init_virtual_port(name); err < 0)
|
if (int err = this->init_virtual_port(name); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
|
|
||||||
if (int err = init_callback(); err < 0)
|
if (int err = init_callback(); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
return true;
|
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
this->configuration.stop_poll(this->vaddr);
|
this->configuration.stop_poll(this->vaddr);
|
||||||
|
|
||||||
midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
return midi_in_impl<ConfigurationBase, ConfigurationImpl>::close_port();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -23,22 +23,21 @@ public:
|
|||||||
{
|
{
|
||||||
if (init_client(configuration) < 0)
|
if (init_client(configuration) < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_in_alsa::initialize: error creating ALSA sequencer client "
|
"error creating ALSA sequencer client "
|
||||||
"object.");
|
"object.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
|
if (snd.midi.event_new(this->m_bufferSize, &this->coder) < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");
|
||||||
this->configuration,
|
|
||||||
"midi_out_alsa::initialize: error initializing MIDI event "
|
|
||||||
"parser.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
snd.midi.event_init(this->coder);
|
snd.midi.event_init(this->coder);
|
||||||
|
|
||||||
|
this->client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_out_impl() override
|
~midi_out_impl() override
|
||||||
@@ -54,6 +53,8 @@ public:
|
|||||||
|
|
||||||
if (!configuration.context)
|
if (!configuration.context)
|
||||||
snd.seq.close(this->seq);
|
snd.seq.close(this->seq);
|
||||||
|
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
|
||||||
@@ -65,68 +66,69 @@ public:
|
|||||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
|
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const output_port& p, std::string_view portName) override
|
stdx::error open_port(const output_port& p, std::string_view portName) override
|
||||||
{
|
{
|
||||||
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
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)
|
||||||
{
|
{
|
||||||
error<no_devices_found_error>(
|
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
||||||
this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!");
|
return make_error_code(std::errc::no_such_device);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sink = get_port_info(p);
|
auto sink = get_port_info(p);
|
||||||
if (!sink)
|
if (!sink)
|
||||||
return false;
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
if (int err = create_port(portName); err < 0)
|
if (int err = create_port(portName); err < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(configuration, "midi_out_alsa::create_port: ALSA error creating port.");
|
libremidi_handle_error(configuration, "ALSA error creating port.");
|
||||||
return false;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_seq_addr_t source{
|
snd_seq_addr_t source{
|
||||||
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
|
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
|
||||||
if (int err = create_connection(*this, source, *sink, true); err < 0)
|
if (int err = create_connection(*this, source, *sink, true); err < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(configuration, "ALSA error making port connection.");
|
||||||
configuration, "midi_out_alsa::create_port: ALSA error making port connection.");
|
return from_errc(err);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (int err = create_port(portName); err < 0)
|
if (int err = create_port(portName); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override { unsubscribe(); }
|
stdx::error close_port() override
|
||||||
|
|
||||||
void set_client_name(std::string_view clientName) override
|
|
||||||
{
|
{
|
||||||
alsa_data::set_client_name(clientName);
|
unsubscribe();
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
|
{
|
||||||
|
return alsa_data::set_port_name(portName);
|
||||||
|
}
|
||||||
|
|
||||||
void send_message(const unsigned char* message, std::size_t size) override
|
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
||||||
{
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_out_alsa::send_message: ALSA error resizing MIDI event "
|
"ALSA error resizing MIDI event "
|
||||||
"buffer.");
|
"buffer.");
|
||||||
return;
|
return std::errc::no_buffer_space;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,18 +142,18 @@ 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)
|
||||||
{
|
{
|
||||||
warning(this->configuration, "midi_out_alsa::send_message: event parsing error!");
|
libremidi_handle_warning(this->configuration, "event parsing error!");
|
||||||
return;
|
return std::errc::bad_message;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.type == SND_SEQ_EVENT_NONE)
|
if (ev.type == SND_SEQ_EVENT_NONE)
|
||||||
{
|
{
|
||||||
warning(this->configuration, "midi_out_alsa::send_message: incomplete message!");
|
libremidi_handle_warning(this->configuration, "incomplete message!");
|
||||||
return;
|
return std::errc::message_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += result;
|
offset += result;
|
||||||
@@ -159,16 +161,15 @@ public:
|
|||||||
result = snd.seq.event_output(this->seq, &ev);
|
result = snd.seq.event_output(this->seq, &ev);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
{
|
{
|
||||||
warning(
|
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
||||||
this->configuration,
|
return std::errc::io_error;
|
||||||
"midi_out_alsa::send_message: error sending MIDI message to port.");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
snd.seq.drain_output(this->seq);
|
snd.seq.drain_output(this->seq);
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t bufferSize{32};
|
uint64_t m_bufferSize{32};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,30 +2,44 @@
|
|||||||
#include <libremidi/backends/alsa_seq/config.hpp>
|
#include <libremidi/backends/alsa_seq/config.hpp>
|
||||||
#include <libremidi/backends/alsa_seq/helpers.hpp>
|
#include <libremidi/backends/alsa_seq/helpers.hpp>
|
||||||
#include <libremidi/backends/linux/helpers.hpp>
|
#include <libremidi/backends/linux/helpers.hpp>
|
||||||
|
#include <libremidi/detail/midi_api.hpp>
|
||||||
#include <libremidi/detail/observer.hpp>
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
#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>
|
||||||
class observer_impl
|
class observer_impl
|
||||||
: public observer_api
|
: public observer_api
|
||||||
, public alsa_data
|
, public alsa_data
|
||||||
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct
|
struct
|
||||||
@@ -40,7 +54,11 @@ public:
|
|||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
if (int err = init_client(configuration); err < 0)
|
if (int err = init_client(configuration); err < 0)
|
||||||
{
|
{
|
||||||
throw driver_error("observer_alsa: snd_seq_open failed");
|
libremidi_handle_error(
|
||||||
|
this->configuration,
|
||||||
|
"error creating ALSA sequencer client "
|
||||||
|
"object.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!configuration.has_callbacks())
|
if (!configuration.has_callbacks())
|
||||||
@@ -65,7 +83,8 @@ public:
|
|||||||
*this, "libremidi-observe", caps, SND_SEQ_PORT_TYPE_APPLICATION, false);
|
*this, "libremidi-observe", caps, SND_SEQ_PORT_TYPE_APPLICATION, false);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
{
|
{
|
||||||
throw driver_error("observer: ALSA error creating port.");
|
libremidi_handle_error(this->configuration, "error creating ALSA sequencer port.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,17 +94,75 @@ public:
|
|||||||
= snd.seq.connect_from(seq, vport, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
|
= snd.seq.connect_from(seq, vport, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
{
|
{
|
||||||
throw driver_error("observer_alsa: snd_seq_connect_from failed");
|
libremidi_handle_error(this->configuration, "error connecting to ALSA sequencer.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -96,41 +173,47 @@ 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;
|
|
||||||
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
|
|
||||||
ok = true;
|
|
||||||
else if ((tp & SND_SEQ_PORT_TYPE_SOFTWARE) && this->configuration.track_virtual)
|
|
||||||
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()
|
||||||
@@ -158,8 +241,8 @@ public:
|
|||||||
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;
|
||||||
@@ -172,8 +255,8 @@ public:
|
|||||||
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;
|
||||||
@@ -188,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));
|
||||||
}
|
}
|
||||||
@@ -202,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;
|
||||||
}
|
}
|
||||||
@@ -251,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>
|
||||||
@@ -263,35 +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
|
||||||
|
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||||
|
.count();
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
int err = poll(descriptors_.data(), descriptors_.size(), -1);
|
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();
|
||||||
@@ -302,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>
|
||||||
@@ -314,7 +478,7 @@ 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;
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
@@ -323,7 +487,7 @@ public:
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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{};
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -28,6 +28,7 @@ struct input_configuration
|
|||||||
snd_seq_t* context{};
|
snd_seq_t* context{};
|
||||||
std::function<bool(const poll_parameters&)> manual_poll;
|
std::function<bool(const poll_parameters&)> manual_poll;
|
||||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||||
|
std::chrono::milliseconds poll_period{2};
|
||||||
|
|
||||||
static constexpr int midi_version = 2;
|
static constexpr int midi_version = 2;
|
||||||
};
|
};
|
||||||
@@ -46,6 +47,7 @@ struct observer_configuration
|
|||||||
snd_seq_t* context{};
|
snd_seq_t* context{};
|
||||||
std::function<bool(const libremidi::alsa_seq::poll_parameters&)> manual_poll;
|
std::function<bool(const libremidi::alsa_seq::poll_parameters&)> manual_poll;
|
||||||
std::function<bool(snd_seq_addr_t)> stop_poll;
|
std::function<bool(snd_seq_addr_t)> stop_poll;
|
||||||
|
std::chrono::milliseconds poll_period{100};
|
||||||
|
|
||||||
static constexpr int midi_version = 2;
|
static constexpr int midi_version = 2;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -27,22 +27,14 @@ public:
|
|||||||
assert(snd.seq.ump.available);
|
assert(snd.seq.ump.available);
|
||||||
if (init_client(configuration) < 0)
|
if (init_client(configuration) < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_in_alsa::initialize: error creating ALSA sequencer client "
|
"error creating ALSA sequencer client "
|
||||||
"object.");
|
"object.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
|
this->client_open_ = stdx::error{};
|
||||||
{
|
|
||||||
error<driver_error>(
|
|
||||||
this->configuration,
|
|
||||||
"midi_out_alsa::initialize: error initializing MIDI event "
|
|
||||||
"parser.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
snd.midi.event_init(this->coder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_out_impl() override
|
~midi_out_impl() override
|
||||||
@@ -53,14 +45,14 @@ public:
|
|||||||
// Cleanup.
|
// Cleanup.
|
||||||
if (this->vport >= 0)
|
if (this->vport >= 0)
|
||||||
snd.seq.delete_port(this->seq, this->vport);
|
snd.seq.delete_port(this->seq, this->vport);
|
||||||
if (this->coder)
|
|
||||||
snd.midi.event_free(this->coder);
|
|
||||||
|
|
||||||
if (!configuration.context)
|
if (!configuration.context)
|
||||||
snd.seq.close(this->seq);
|
snd.seq.close(this->seq);
|
||||||
|
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_SEQ_UMP; }
|
||||||
|
|
||||||
[[nodiscard]] int create_port(std::string_view portName)
|
[[nodiscard]] int create_port(std::string_view portName)
|
||||||
{
|
{
|
||||||
@@ -70,55 +62,56 @@ public:
|
|||||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
|
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_port(const output_port& p, std::string_view portName) override
|
stdx::error open_port(const output_port& p, std::string_view portName) override
|
||||||
{
|
{
|
||||||
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
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)
|
||||||
{
|
{
|
||||||
error<no_devices_found_error>(
|
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
||||||
this->configuration, "midi_out_alsa::open_port: no MIDI output sources found!");
|
return make_error_code(std::errc::no_such_device);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sink = get_port_info(p);
|
auto sink = get_port_info(p);
|
||||||
if (!sink)
|
if (!sink)
|
||||||
return false;
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
if (int err = create_port(portName); err < 0)
|
if (int err = create_port(portName); err < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(configuration, "midi_out_alsa::create_port: ALSA error creating port.");
|
libremidi_handle_error(configuration, "ALSA error creating port.");
|
||||||
return false;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_seq_addr_t source{
|
snd_seq_addr_t source{
|
||||||
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
|
.client = (unsigned char)snd.seq.client_id(this->seq), .port = (unsigned char)this->vport};
|
||||||
if (int err = create_connection(*this, source, *sink, true); err < 0)
|
if (int err = create_connection(*this, source, *sink, true); err < 0)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(configuration, "ALSA error making port connection.");
|
||||||
configuration, "midi_out_alsa::create_port: ALSA error making port connection.");
|
return from_errc(err);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (int err = create_port(portName); err < 0)
|
if (int err = create_port(portName); err < 0)
|
||||||
return false;
|
return from_errc(err);
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override { unsubscribe(); }
|
stdx::error close_port() override
|
||||||
|
|
||||||
void set_client_name(std::string_view clientName) override
|
|
||||||
{
|
{
|
||||||
alsa_data::set_client_name(clientName);
|
unsubscribe();
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); }
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
|
{
|
||||||
|
return alsa_data::set_port_name(portName);
|
||||||
|
}
|
||||||
|
|
||||||
void send_ump(const uint32_t* ump_stream, std::size_t count) override
|
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||||
{
|
{
|
||||||
snd_seq_ump_event_t ev;
|
snd_seq_ump_event_t ev;
|
||||||
|
|
||||||
@@ -128,24 +121,21 @@ public:
|
|||||||
snd_seq_ev_set_subs(&ev);
|
snd_seq_ev_set_subs(&ev);
|
||||||
snd_seq_ev_set_direct(&ev);
|
snd_seq_ev_set_direct(&ev);
|
||||||
|
|
||||||
auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) {
|
auto write_func = [this, &ev](const uint32_t* ump, int64_t bytes) -> std::errc {
|
||||||
std::memcpy(ev.ump, ump, bytes);
|
std::memcpy(ev.ump, ump, bytes);
|
||||||
const int result = snd.seq.ump.event_output_direct(this->seq, &ev);
|
const int ret = snd.seq.ump.event_output_direct(this->seq, &ev);
|
||||||
if (result < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
warning(
|
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
||||||
this->configuration,
|
return static_cast<std::errc>(-ret);
|
||||||
"midi_out_alsa::send_message: error sending MIDI message to port.");
|
|
||||||
return libremidi::segmentation_error::other;
|
|
||||||
}
|
}
|
||||||
return libremidi::segmentation_error::no_error;
|
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{};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned int bufferSize{32};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, ×tamp);
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ typedef UInt32 MIDIObjectRef;
|
|||||||
typedef MIDIObjectRef MIDIClientRef;
|
typedef MIDIObjectRef MIDIClientRef;
|
||||||
#else
|
#else
|
||||||
using MIDIClientRef = uint32_t;
|
using MIDIClientRef = uint32_t;
|
||||||
|
using MIDIObjectRef = uint32_t;
|
||||||
#endif
|
#endif
|
||||||
namespace libremidi
|
NAMESPACE_LIBREMIDI
|
||||||
{
|
{
|
||||||
|
|
||||||
struct coremidi_input_configuration
|
struct coremidi_input_configuration
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/error.hpp>
|
||||||
|
|
||||||
|
#include <CoreMIDI/CoreMIDI.h>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
struct coremidi_error_domain : public stdx::error_domain
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
constexpr coremidi_error_domain() noexcept
|
||||||
|
: error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::string_ref name() const noexcept override { return "coremidi"; }
|
||||||
|
|
||||||
|
bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override
|
||||||
|
{
|
||||||
|
if (lhs.domain() == rhs.domain())
|
||||||
|
return error_cast<std::errc>(lhs) == error_cast<std::errc>(rhs);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::string_ref message(const stdx::error& e) const noexcept override
|
||||||
|
{
|
||||||
|
switch (error_cast<OSStatus>(e))
|
||||||
|
{
|
||||||
|
case kMIDIInvalidClient:
|
||||||
|
return "Invalid Client";
|
||||||
|
case kMIDIInvalidPort:
|
||||||
|
return "Invalid Port";
|
||||||
|
case kMIDIWrongEndpointType:
|
||||||
|
return "Wrong EndpointT ype";
|
||||||
|
case kMIDINoConnection:
|
||||||
|
return "No Connection";
|
||||||
|
case kMIDIUnknownEndpoint:
|
||||||
|
return "Unknown Endpoint";
|
||||||
|
case kMIDIUnknownProperty:
|
||||||
|
return "Unknown Property";
|
||||||
|
case kMIDIWrongPropertyType:
|
||||||
|
return "Wrong Property Type";
|
||||||
|
case kMIDINoCurrentSetup:
|
||||||
|
return "No Current Setup";
|
||||||
|
case kMIDIMessageSendErr:
|
||||||
|
return "Message Send Error";
|
||||||
|
case kMIDIServerStartErr:
|
||||||
|
return "Server Start Error";
|
||||||
|
case kMIDISetupFormatErr:
|
||||||
|
return "Setup Format Error";
|
||||||
|
case kMIDIWrongThread:
|
||||||
|
return "Wrong Thread";
|
||||||
|
case kMIDIObjectNotFound:
|
||||||
|
return "Object Not Found";
|
||||||
|
case kMIDIIDNotUnique:
|
||||||
|
return "ID Not Unique";
|
||||||
|
case kMIDINotPermitted:
|
||||||
|
return "Not Permitted";
|
||||||
|
case kMIDIUnknownError:
|
||||||
|
return "Unknown Error";
|
||||||
|
}
|
||||||
|
return "Unknown error code";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline stdx::error from_osstatus(OSStatus ret) noexcept
|
||||||
|
{
|
||||||
|
static constexpr coremidi_error_domain domain{};
|
||||||
|
return {ret, domain};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#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/input_configuration.hpp>
|
#include <libremidi/input_configuration.hpp>
|
||||||
|
|
||||||
#include <CoreMIDI/CoreMIDI.h>
|
#include <CoreMIDI/CoreMIDI.h>
|
||||||
@@ -19,16 +21,16 @@
|
|||||||
#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;
|
||||||
MIDIObjectGetStringProperty(object, property, &res);
|
if (MIDIObjectGetStringProperty(object, property, &res) || !res)
|
||||||
|
return {};
|
||||||
|
|
||||||
char name[256];
|
char name[256];
|
||||||
CFStringGetCString(res, name, sizeof(name), kCFStringEncodingUTF8);
|
CFStringGetCString(res, name, sizeof(name), kCFStringEncodingUTF8);
|
||||||
@@ -36,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)};
|
||||||
}
|
}
|
||||||
@@ -68,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());
|
||||||
}
|
}
|
||||||
@@ -98,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());
|
||||||
}
|
}
|
||||||
@@ -109,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());
|
||||||
|
|
||||||
@@ -215,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.template error<invalid_parameter_error>(
|
self.libremidi_handle_error(self.configuration, "cannot find port: " + info.port_name);
|
||||||
self.configuration, "coremidi::locate_object: cannot find port: " + info.port_name);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != requested_type || object == 0)
|
if (type != requested_type || object == 0)
|
||||||
{
|
{
|
||||||
self.template error<invalid_parameter_error>(
|
self.libremidi_handle_error(
|
||||||
self.configuration, "coremidi::locate_object: 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.
|
||||||
@@ -255,6 +256,13 @@ struct coremidi_data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void close_client(auto& self)
|
||||||
|
{
|
||||||
|
self.client_open_ = std::errc::not_connected;
|
||||||
|
if (!self.configuration.context)
|
||||||
|
MIDIClientDispose(self.client);
|
||||||
|
}
|
||||||
|
|
||||||
static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept
|
static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept
|
||||||
{
|
{
|
||||||
if (tp == 0)
|
if (tp == 0)
|
||||||
@@ -319,6 +327,23 @@ struct coremidi_data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdx::error close_port()
|
||||||
|
{
|
||||||
|
if (this->endpoint)
|
||||||
|
{
|
||||||
|
MIDIEndpointDispose(this->endpoint);
|
||||||
|
this->endpoint = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->port)
|
||||||
|
{
|
||||||
|
MIDIPortDispose(this->port);
|
||||||
|
this->port = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
#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
|
||||||
, private coremidi_data
|
, public coremidi_data
|
||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
struct
|
struct
|
||||||
: input_configuration
|
: input_configuration
|
||||||
, coremidi_input_configuration
|
, coremidi_input_configuration
|
||||||
@@ -23,11 +24,13 @@ public:
|
|||||||
{
|
{
|
||||||
if (auto result = init_client(configuration); result != noErr)
|
if (auto result = init_client(configuration); result != noErr)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error creating MIDI client object: " + std::to_string(result));
|
||||||
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
|
client_open_ = from_osstatus(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_in_core() override
|
~midi_in_core() override
|
||||||
@@ -38,33 +41,18 @@ public:
|
|||||||
if (this->endpoint)
|
if (this->endpoint)
|
||||||
MIDIEndpointDispose(this->endpoint);
|
MIDIEndpointDispose(this->endpoint);
|
||||||
|
|
||||||
close_client();
|
close_client(*this);
|
||||||
}
|
|
||||||
|
|
||||||
void close_client()
|
|
||||||
{
|
|
||||||
if (!configuration.context)
|
|
||||||
MIDIClientDispose(this->client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_core: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_core: set_port_name unsupported");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
||||||
|
|
||||||
bool open_port(const input_port& info, std::string_view portName) override
|
stdx::error open_port(const input_port& info, std::string_view portName) override
|
||||||
{
|
{
|
||||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||||
|
|
||||||
auto source = locate_object(*this, info, kMIDIObjectType_Source);
|
auto source = locate_object(*this, info, kMIDIObjectType_Source);
|
||||||
if (source == 0)
|
if (source == 0)
|
||||||
return false;
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
// Create our local sink
|
// Create our local sink
|
||||||
MIDIPortRef port;
|
MIDIPortRef port;
|
||||||
@@ -73,29 +61,27 @@ public:
|
|||||||
|
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
close_client();
|
close_client(*this);
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration, "midi_in_core::open_port: 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 false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the connection.
|
// Make the connection.
|
||||||
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
|
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
|
||||||
{
|
{
|
||||||
MIDIPortDispose(port);
|
MIDIPortDispose(port);
|
||||||
close_client();
|
close_client(*this);
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port.");
|
||||||
this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port.");
|
return from_osstatus(result);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our api-specific port information.
|
// Save our api-specific port information.
|
||||||
this->port = port;
|
this->port = port;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
// Create a virtual MIDI input destination.
|
// Create a virtual MIDI input destination.
|
||||||
MIDIEndpointRef endpoint;
|
MIDIEndpointRef endpoint;
|
||||||
@@ -104,32 +90,19 @@ public:
|
|||||||
|
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_in_core::open_virtual_port: error creating virtual macOS MIDI "
|
"error creating virtual macOS MIDI "
|
||||||
"destination.");
|
"destination.");
|
||||||
return false;
|
return from_osstatus(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our api-specific connection information.
|
// Save our api-specific connection information.
|
||||||
this->endpoint = endpoint;
|
this->endpoint = endpoint;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override { return coremidi_data::close_port(); }
|
||||||
{
|
|
||||||
if (this->endpoint)
|
|
||||||
{
|
|
||||||
MIDIEndpointDispose(this->endpoint);
|
|
||||||
this->endpoint = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->port)
|
|
||||||
{
|
|
||||||
MIDIPortDispose(this->port);
|
|
||||||
this->port = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override
|
timestamp absolute_timestamp() const noexcept override
|
||||||
{
|
{
|
||||||
@@ -158,16 +131,13 @@ public:
|
|||||||
// MIDIPacketLists, they must be handled by multiple calls to this
|
// MIDIPacketLists, they must be handled by multiple calls to this
|
||||||
// function.
|
// function.
|
||||||
|
|
||||||
if (packet->length == 0)
|
if (packet->length > 0)
|
||||||
{
|
{
|
||||||
packet = MIDIPacketNext(packet);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };
|
auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };
|
||||||
self.m_processing.on_bytes_multi(
|
self.m_processing.on_bytes_multi(
|
||||||
{packet->data, packet->data + packet->length},
|
{packet->data, packet->data + packet->length},
|
||||||
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
|
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
|
}
|
||||||
|
|
||||||
packet = MIDIPacketNext(packet);
|
packet = MIDIPacketNext(packet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
#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
|
||||||
, private coremidi_data
|
, public coremidi_data
|
||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
struct
|
struct
|
||||||
: output_configuration
|
: output_configuration
|
||||||
, coremidi_output_configuration
|
, coremidi_output_configuration
|
||||||
@@ -22,11 +23,13 @@ public:
|
|||||||
{
|
{
|
||||||
if (auto result = init_client(configuration); result != noErr)
|
if (auto result = init_client(configuration); result != noErr)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error creating MIDI client object: " + std::to_string(result));
|
||||||
"midi_out_core: error creating MIDI client object: " + std::to_string(result));
|
client_open_ = from_osstatus(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_out_core()
|
~midi_out_core()
|
||||||
@@ -36,119 +39,80 @@ public:
|
|||||||
if (this->endpoint)
|
if (this->endpoint)
|
||||||
MIDIEndpointDispose(this->endpoint);
|
MIDIEndpointDispose(this->endpoint);
|
||||||
|
|
||||||
close_client();
|
close_client(*this);
|
||||||
}
|
|
||||||
|
|
||||||
void close_client()
|
|
||||||
{
|
|
||||||
if (!configuration.context)
|
|
||||||
MIDIClientDispose(this->client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_core: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_core: set_port_name unsupported");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
||||||
|
|
||||||
bool open_port(const output_port& info, std::string_view portName) override
|
stdx::error open_port(const output_port& info, std::string_view portName) override
|
||||||
{
|
{
|
||||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||||
|
|
||||||
// Find where we want to send
|
// Find where we want to send
|
||||||
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
|
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
|
||||||
if (destination == 0)
|
if (destination == 0)
|
||||||
return false;
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
// Create our local source
|
// Create our local source
|
||||||
MIDIPortRef port;
|
MIDIPortRef port;
|
||||||
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
|
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
close_client();
|
close_client(*this);
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error creating macOS MIDI output port.");
|
||||||
this->configuration, "midi_out_core::open_port: error creating macOS MIDI output port.");
|
return from_osstatus(result);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our api-specific connection information.
|
// Save our api-specific connection information.
|
||||||
this->port = port;
|
this->port = port;
|
||||||
this->destinationId = destination;
|
this->destinationId = destination;
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (this->endpoint)
|
|
||||||
{
|
|
||||||
warning(
|
|
||||||
configuration,
|
|
||||||
"midi_out_core::open_virtual_port: a virtual output port already "
|
|
||||||
"exists!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a virtual MIDI output source.
|
// Create a virtual MIDI output source.
|
||||||
MIDIEndpointRef endpoint;
|
MIDIEndpointRef endpoint;
|
||||||
OSStatus result = MIDISourceCreate(this->client, toCFString(portName).get(), &endpoint);
|
OSStatus result = MIDISourceCreate(this->client, toCFString(portName).get(), &endpoint);
|
||||||
|
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source.");
|
||||||
this->configuration,
|
|
||||||
"midi_out_core::initialize: error creating macOS virtual MIDI source.");
|
return from_osstatus(result);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our api-specific connection information.
|
// Save our api-specific connection information.
|
||||||
this->endpoint = endpoint;
|
this->endpoint = endpoint;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override { return coremidi_data::close_port(); }
|
||||||
{
|
|
||||||
if (this->endpoint)
|
|
||||||
{
|
|
||||||
MIDIEndpointDispose(this->endpoint);
|
|
||||||
this->endpoint = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->port)
|
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||||
{
|
|
||||||
MIDIPortDispose(this->port);
|
|
||||||
this->port = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_message(const unsigned char* message, size_t size) override
|
|
||||||
{
|
{
|
||||||
unsigned int nBytes = static_cast<unsigned int>(size);
|
unsigned int nBytes = static_cast<unsigned int>(size);
|
||||||
if (nBytes == 0)
|
if (nBytes == 0)
|
||||||
{
|
{
|
||||||
warning(configuration, "midi_out_core::send_message: no data in message argument!");
|
libremidi_handle_warning(configuration, "no data in message argument!");
|
||||||
return;
|
return std::errc::invalid_argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message[0] != 0xF0 && nBytes > 3)
|
if (message[0] != 0xF0 && nBytes > 3)
|
||||||
{
|
{
|
||||||
warning(
|
libremidi_handle_warning(
|
||||||
configuration,
|
configuration,
|
||||||
"midi_out_core::send_message: message format problem ... not sysex but "
|
"message format problem ... not sysex but "
|
||||||
"> 3 bytes?");
|
"> 3 bytes?");
|
||||||
return;
|
return std::errc::bad_message;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -166,9 +130,9 @@ public:
|
|||||||
|
|
||||||
if (!packet)
|
if (!packet)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "could not allocate packet list");
|
||||||
this->configuration, "midi_out_core::send_message: could not allocate packet list");
|
|
||||||
return;
|
return std::errc::message_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to any destinations that may have connected to us.
|
// Send to any destinations that may have connected to us.
|
||||||
@@ -177,10 +141,11 @@ public:
|
|||||||
auto result = MIDIReceived(this->endpoint, packetList);
|
auto result = MIDIReceived(this->endpoint, packetList);
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
warning(
|
libremidi_handle_warning(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_out_core::send_message: error sending MIDI to virtual "
|
"error sending MIDI to virtual "
|
||||||
"destinations.");
|
"destinations.");
|
||||||
|
return std::errc::io_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,12 +155,12 @@ public:
|
|||||||
auto result = MIDISend(this->port, this->destinationId, packetList);
|
auto result = MIDISend(this->port, this->destinationId, packetList);
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
warning(
|
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
||||||
this->configuration,
|
return std::errc::io_error;
|
||||||
"midi_out_core::send_message: error sending MIDI message to port.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
MIDIEndpointRef destinationId{};
|
MIDIEndpointRef destinationId{};
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -33,14 +37,12 @@ public:
|
|||||||
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)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error creating MIDI client object: " + std::to_string(result));
|
||||||
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,22 +71,65 @@ public:
|
|||||||
MIDIEndpointGetEntity(obj, &e);
|
MIDIEndpointGetEntity(obj, &e);
|
||||||
bool physical = bool(e);
|
bool physical = bool(e);
|
||||||
|
|
||||||
bool ok = false;
|
bool ok = this->configuration.track_any;
|
||||||
if (physical && this->configuration.track_hardware)
|
if (physical && this->configuration.track_hardware)
|
||||||
ok = true;
|
ok |= true;
|
||||||
else if ((!physical) && this->configuration.track_virtual)
|
else if ((!physical) && this->configuration.track_virtual)
|
||||||
ok = true;
|
ok |= true;
|
||||||
|
|
||||||
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; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,19 +3,21 @@
|
|||||||
#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
|
||||||
{
|
{
|
||||||
using midi_in = midi_in_impl;
|
using midi_in = midi_in_impl;
|
||||||
using midi_out = midi_out_impl;
|
using midi_out = midi_out_impl;
|
||||||
using midi_observer = observer_impl;
|
using midi_observer = observer_impl;
|
||||||
using midi_in_configuration = coremidi_input_configuration;
|
using midi_in_configuration = coremidi_ump::input_configuration;
|
||||||
using midi_out_configuration = coremidi_output_configuration;
|
using midi_out_configuration = coremidi_ump::output_configuration;
|
||||||
using midi_observer_configuration = coremidi_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,9 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/backends/coremidi/config.hpp>
|
#include <libremidi/backends/coremidi/config.hpp>
|
||||||
|
|
||||||
namespace libremidi::coremidi_ump
|
NAMESPACE_LIBREMIDI::coremidi_ump
|
||||||
{
|
{
|
||||||
using input_configuration = coremidi_input_configuration;
|
struct input_configuration
|
||||||
using output_configuration = coremidi_output_configuration;
|
{
|
||||||
using observer_configuration = coremidi_observer_configuration;
|
std::string client_name = "libremidi client";
|
||||||
|
std::optional<MIDIClientRef> context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct output_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
std::optional<MIDIClientRef> context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct observer_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
std::function<void(MIDIClientRef)> on_create_context{};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,36 @@
|
|||||||
#include <libremidi/backends/coremidi_ump/config.hpp>
|
#include <libremidi/backends/coremidi_ump/config.hpp>
|
||||||
#include <libremidi/backends/coremidi_ump/helpers.hpp>
|
#include <libremidi/backends/coremidi_ump/helpers.hpp>
|
||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_in.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
|
||||||
: public midi2::in_api
|
: public midi2::in_api
|
||||||
, private coremidi_data
|
, public coremidi_data
|
||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
struct
|
struct
|
||||||
: ump_input_configuration
|
: ump_input_configuration
|
||||||
, coremidi_ump::input_configuration
|
, coremidi_ump::input_configuration
|
||||||
{
|
{
|
||||||
} configuration;
|
} configuration;
|
||||||
|
|
||||||
midi_in_impl(ump_input_configuration&& conf, coremidi_input_configuration&& apiconf)
|
midi_in_impl(ump_input_configuration&& conf, coremidi_ump::input_configuration&& apiconf)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
if (auto result = init_client(configuration); result != noErr)
|
if (auto result = init_client(configuration); result != noErr)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error creating MIDI client object: " + std::to_string(result));
|
||||||
"midi_in_core: error creating MIDI client object: " + std::to_string(result));
|
client_open_ = from_osstatus(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_in_impl() override
|
~midi_in_impl() override
|
||||||
@@ -38,33 +42,18 @@ public:
|
|||||||
if (this->endpoint)
|
if (this->endpoint)
|
||||||
MIDIEndpointDispose(this->endpoint);
|
MIDIEndpointDispose(this->endpoint);
|
||||||
|
|
||||||
close_client();
|
close_client(*this);
|
||||||
}
|
|
||||||
|
|
||||||
void close_client()
|
|
||||||
{
|
|
||||||
if (!configuration.context)
|
|
||||||
MIDIClientDispose(this->client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_core: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_core: set_port_name unsupported");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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; }
|
||||||
|
|
||||||
bool open_port(const input_port& info, std::string_view portName) override
|
stdx::error open_port(const input_port& info, std::string_view portName) override
|
||||||
{
|
{
|
||||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||||
|
|
||||||
auto source = locate_object(*this, info, kMIDIObjectType_Source);
|
auto source = locate_object(*this, info, kMIDIObjectType_Source);
|
||||||
if (source == 0)
|
if (source == 0)
|
||||||
return false;
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
// Create our local sink
|
// Create our local sink
|
||||||
MIDIPortRef port;
|
MIDIPortRef port;
|
||||||
@@ -77,29 +66,27 @@ public:
|
|||||||
|
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
close_client();
|
close_client(*this);
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration, "midi_in_core::open_port: 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 false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the connection.
|
// Make the connection.
|
||||||
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
|
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
|
||||||
{
|
{
|
||||||
MIDIPortDispose(port);
|
MIDIPortDispose(port);
|
||||||
close_client();
|
close_client(*this);
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port.");
|
||||||
this->configuration, "midi_in_core::open_port: error connecting macOS MIDI input port.");
|
return from_osstatus(result);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our api-specific port information.
|
// Save our api-specific port information.
|
||||||
this->port = port;
|
this->port = port;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
// Create a virtual MIDI input destination.
|
// Create a virtual MIDI input destination.
|
||||||
MIDIEndpointRef endpoint;
|
MIDIEndpointRef endpoint;
|
||||||
@@ -111,38 +98,19 @@ public:
|
|||||||
|
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_in_core::open_virtual_port: error creating virtual macOS MIDI "
|
"error creating virtual macOS MIDI "
|
||||||
"destination.");
|
"destination.");
|
||||||
return false;
|
return from_osstatus(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our api-specific connection information.
|
// Save our api-specific connection information.
|
||||||
this->endpoint = endpoint;
|
this->endpoint = endpoint;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override { return coremidi_data::close_port(); }
|
||||||
{
|
|
||||||
if (this->endpoint)
|
|
||||||
{
|
|
||||||
MIDIEndpointDispose(this->endpoint);
|
|
||||||
this->endpoint = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->port)
|
|
||||||
{
|
|
||||||
MIDIPortDispose(this->port);
|
|
||||||
this->port = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_timestamp(
|
|
||||||
[[maybe_unused]] const MIDIEventPacket& packet,
|
|
||||||
[[maybe_unused]] libremidi::ump& msg) noexcept
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override
|
timestamp absolute_timestamp() const noexcept override
|
||||||
{
|
{
|
||||||
@@ -151,33 +119,28 @@ public:
|
|||||||
|
|
||||||
void midiInputCallback(const MIDIEventList* list, void* /*srcRef*/)
|
void midiInputCallback(const MIDIEventList* list, void* /*srcRef*/)
|
||||||
{
|
{
|
||||||
unsigned short nBytes{};
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = true,
|
||||||
|
.absolute_is_monotonic = false,
|
||||||
|
.has_samples = false,
|
||||||
|
};
|
||||||
|
|
||||||
const MIDIEventPacket* packet = &list->packet[0];
|
const MIDIEventPacket* packet = &list->packet[0];
|
||||||
for (unsigned int i = 0; i < list->numPackets; ++i)
|
for (unsigned int i = 0; i < list->numPackets; ++i)
|
||||||
{
|
{
|
||||||
nBytes = packet->wordCount;
|
if (packet->wordCount > 0)
|
||||||
if (nBytes == 0)
|
|
||||||
{
|
{
|
||||||
packet = MIDIEventPacketNext(packet);
|
auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };
|
||||||
continue;
|
m_processing.on_bytes_multi(
|
||||||
|
{packet->words, packet->words + packet->wordCount},
|
||||||
|
m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::ump msg;
|
|
||||||
coremidi_data::set_timestamp(*this, packet->timeStamp, msg.timestamp);
|
|
||||||
|
|
||||||
if (packet->wordCount <= 4)
|
|
||||||
{
|
|
||||||
std::copy_n(packet->words, packet->wordCount, msg.data);
|
|
||||||
configuration.on_message(std::move(msg));
|
|
||||||
}
|
|
||||||
last_time = time_in_nanos(packet->timeStamp);
|
|
||||||
|
|
||||||
packet = MIDIEventPacketNext(packet);
|
packet = MIDIEventPacketNext(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long long last_time{};
|
midi2::input_state_machine m_processing{this->configuration};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,15 @@
|
|||||||
#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
|
||||||
, private coremidi_data
|
, public coremidi_data
|
||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
struct
|
struct
|
||||||
: libremidi::output_configuration
|
: libremidi::output_configuration
|
||||||
, coremidi_ump::output_configuration
|
, coremidi_ump::output_configuration
|
||||||
@@ -25,11 +26,13 @@ public:
|
|||||||
{
|
{
|
||||||
if (auto result = init_client(configuration); result != noErr)
|
if (auto result = init_client(configuration); result != noErr)
|
||||||
{
|
{
|
||||||
error<driver_error>(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration, "error creating MIDI client object: " + std::to_string(result));
|
||||||
"midi_out_impl: error creating MIDI client object: " + std::to_string(result));
|
client_open_ = from_osstatus(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_out_impl()
|
~midi_out_impl()
|
||||||
@@ -39,64 +42,39 @@ public:
|
|||||||
if (this->endpoint)
|
if (this->endpoint)
|
||||||
MIDIEndpointDispose(this->endpoint);
|
MIDIEndpointDispose(this->endpoint);
|
||||||
|
|
||||||
close_client();
|
close_client(*this);
|
||||||
}
|
|
||||||
|
|
||||||
void close_client()
|
|
||||||
{
|
|
||||||
if (!configuration.context)
|
|
||||||
MIDIClientDispose(this->client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_impl: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
void set_port_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_impl: set_port_name unsupported");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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; }
|
||||||
|
|
||||||
bool open_port(const output_port& info, std::string_view portName) override
|
stdx::error open_port(const output_port& info, std::string_view portName) override
|
||||||
{
|
{
|
||||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||||
|
|
||||||
// Find where we want to send
|
// Find where we want to send
|
||||||
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
|
auto destination = locate_object(*this, info, kMIDIObjectType_Destination);
|
||||||
if (destination == 0)
|
if (destination == 0)
|
||||||
return false;
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
// Create our local source
|
// Create our local source
|
||||||
MIDIPortRef port;
|
MIDIPortRef port;
|
||||||
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
|
OSStatus result = MIDIOutputPortCreate(this->client, toCFString(portName).get(), &port);
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
close_client();
|
close_client(*this);
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error creating macOS MIDI output port.");
|
||||||
this->configuration, "midi_out_impl::open_port: error creating macOS MIDI output port.");
|
return from_osstatus(result);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our api-specific connection information.
|
// Save our api-specific connection information.
|
||||||
this->port = port;
|
this->port = port;
|
||||||
this->destinationId = destination;
|
this->destinationId = destination;
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (this->endpoint)
|
|
||||||
{
|
|
||||||
warning(
|
|
||||||
configuration,
|
|
||||||
"midi_out_impl::open_virtual_port: a virtual output port already "
|
|
||||||
"exists!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a virtual MIDI output source.
|
// Create a virtual MIDI output source.
|
||||||
OSStatus result = MIDISourceCreateWithProtocol(
|
OSStatus result = MIDISourceCreateWithProtocol(
|
||||||
this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &this->endpoint);
|
this->client, toCFString(portName).get(), kMIDIProtocol_2_0, &this->endpoint);
|
||||||
@@ -104,42 +82,28 @@ public:
|
|||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
this->endpoint = 0;
|
this->endpoint = 0;
|
||||||
error<driver_error>(
|
libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source.");
|
||||||
this->configuration,
|
|
||||||
"midi_out_impl::initialize: error creating macOS virtual MIDI source.");
|
return from_osstatus(result);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override
|
stdx::error close_port() override { return coremidi_data::close_port(); }
|
||||||
{
|
|
||||||
if (this->endpoint)
|
|
||||||
{
|
|
||||||
MIDIEndpointDispose(this->endpoint);
|
|
||||||
this->endpoint = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->port)
|
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
|
||||||
{
|
|
||||||
MIDIPortDispose(this->port);
|
|
||||||
this->port = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_ump(const uint32_t* ump_stream, std::size_t count) override
|
|
||||||
{
|
{
|
||||||
MIDIEventList* eventList = reinterpret_cast<MIDIEventList*>(m_eventListBuffer);
|
MIDIEventList* eventList = reinterpret_cast<MIDIEventList*>(m_eventListBuffer);
|
||||||
MIDIEventPacket* packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);
|
MIDIEventPacket* packet = MIDIEventListInit(eventList, kMIDIProtocol_2_0);
|
||||||
const MIDITimeStamp ts = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
|
const MIDITimeStamp ts = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
|
||||||
|
|
||||||
auto write_fun = [ts, &packet, &eventList](const uint32_t* ump, int bytes) {
|
auto write_fun = [ts, &packet, &eventList](const uint32_t* ump, int bytes) -> std::errc {
|
||||||
packet = MIDIEventListAdd(eventList, event_list_max_size, packet, ts, bytes / 4, ump);
|
packet = MIDIEventListAdd(eventList, event_list_max_size, packet, ts, bytes / 4, ump);
|
||||||
if (packet)
|
if (packet)
|
||||||
return segmentation_error::no_error;
|
return std::errc{0};
|
||||||
else
|
else
|
||||||
return segmentation_error::need_space;
|
return std::errc::not_enough_memory;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto realloc_fun = [this, &packet, &eventList] {
|
auto realloc_fun = [this, &packet, &eventList] {
|
||||||
@@ -148,20 +112,21 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
segment_ump_stream(ump_stream, count, write_fun, realloc_fun);
|
segment_ump_stream(ump_stream, count, write_fun, realloc_fun);
|
||||||
push_event_list(eventList);
|
return push_event_list(eventList);
|
||||||
}
|
}
|
||||||
|
|
||||||
void push_event_list(MIDIEventList* eventList)
|
stdx::error push_event_list(MIDIEventList* eventList)
|
||||||
{
|
{
|
||||||
if (this->endpoint)
|
if (this->endpoint)
|
||||||
{
|
{
|
||||||
auto result = MIDIReceivedEventList(this->endpoint, eventList);
|
auto result = MIDIReceivedEventList(this->endpoint, eventList);
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
warning(
|
libremidi_handle_warning(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_out_core::send_message: error sending MIDI to virtual "
|
"error sending MIDI to virtual "
|
||||||
"destinations.");
|
"destinations.");
|
||||||
|
return std::errc::io_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,11 +135,11 @@ public:
|
|||||||
auto result = MIDISendEventList(this->port, this->destinationId, eventList);
|
auto result = MIDISendEventList(this->port, this->destinationId, eventList);
|
||||||
if (result != noErr)
|
if (result != noErr)
|
||||||
{
|
{
|
||||||
warning(
|
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
||||||
this->configuration,
|
return std::errc::io_error;
|
||||||
"midi_out_core::send_message: error sending MIDI message to port.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
MIDIEndpointRef destinationId{};
|
MIDIEndpointRef destinationId{};
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#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
|
||||||
{
|
{
|
||||||
using observer_core::observer_core;
|
public:
|
||||||
|
explicit observer_impl(
|
||||||
|
libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf)
|
||||||
|
: observer_core{
|
||||||
|
std::move(conf),
|
||||||
|
coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}}
|
||||||
|
{
|
||||||
|
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; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,20 +25,24 @@ class midi_in_dummy final
|
|||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit midi_in_dummy(const auto& configuration, const auto&)
|
explicit midi_in_dummy(const input_configuration& configuration, const input_api_configuration&)
|
||||||
{
|
{
|
||||||
warning(configuration, "midi_in_dummy: This class provides no functionality.");
|
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||||
|
}
|
||||||
|
explicit midi_in_dummy(
|
||||||
|
const ump_input_configuration& configuration, const input_api_configuration&)
|
||||||
|
{
|
||||||
|
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
|
||||||
bool open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override
|
stdx::error open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override
|
||||||
{
|
{
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
|
stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; }
|
||||||
void close_port() override { }
|
stdx::error close_port() override { return stdx::error{}; }
|
||||||
void set_client_name(std::string_view /*clientName*/) override { }
|
stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; }
|
||||||
void set_port_name(std::string_view /*portName*/) override { }
|
|
||||||
timestamp absolute_timestamp() const noexcept override { return 0; }
|
timestamp absolute_timestamp() const noexcept override { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,22 +51,25 @@ class midi_out_dummy final
|
|||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit midi_out_dummy(const auto& configuration, const auto&)
|
explicit midi_out_dummy(
|
||||||
|
const output_configuration& configuration, const output_api_configuration&)
|
||||||
{
|
{
|
||||||
warning(configuration, "midi_out_dummy: This class provides no functionality.");
|
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
|
||||||
bool open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override
|
stdx::error open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override
|
||||||
{
|
{
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
|
stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; }
|
||||||
|
|
||||||
void close_port() override { }
|
stdx::error close_port() override { return stdx::error{}; }
|
||||||
void set_client_name(std::string_view /*clientName*/) override { }
|
stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; }
|
||||||
void set_port_name(std::string_view /*portName*/) override { }
|
stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override
|
||||||
void send_message(const unsigned char* /*message*/, size_t /*size*/) override { }
|
{
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dummy_backend
|
struct dummy_backend
|
||||||
@@ -72,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; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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( \
|
||||||
{ \
|
{ \
|
||||||
@@ -204,8 +213,11 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void 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,18 +5,20 @@
|
|||||||
|
|
||||||
#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)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_in_emscripten::~midi_in_emscripten()
|
LIBREMIDI_INLINE midi_in_emscripten::~midi_in_emscripten()
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
midi_in_emscripten::close_port();
|
midi_in_emscripten::close_port();
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noexcept
|
LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noexcept
|
||||||
@@ -24,49 +26,34 @@ LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noex
|
|||||||
return libremidi::API::WEBMIDI;
|
return libremidi::API::WEBMIDI;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE bool midi_in_emscripten::open_port(int portNumber, std::string_view)
|
LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std::string_view)
|
||||||
{
|
{
|
||||||
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
||||||
|
|
||||||
if (portNumber < 0 || portNumber >= midi.input_count())
|
if (portNumber < 0 || portNumber >= midi.input_count())
|
||||||
{
|
{
|
||||||
error<no_devices_found_error>(
|
libremidi_handle_error(this->configuration, "no MIDI output sources found.");
|
||||||
this->configuration, "midi_in_emscripten::open_port: no MIDI output sources found.");
|
return std::errc::invalid_argument;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
midi.open_input(portNumber, *this);
|
midi.open_input(portNumber, *this);
|
||||||
portNumber_ = portNumber;
|
m_portNumber = portNumber;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE bool
|
LIBREMIDI_INLINE stdx::error
|
||||||
midi_in_emscripten::open_port(const libremidi::input_port& p, std::string_view nm)
|
midi_in_emscripten::open_port(const libremidi::input_port& p, std::string_view nm)
|
||||||
{
|
{
|
||||||
return open_port(p.port, nm);
|
return open_port(p.port, nm);
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE bool midi_in_emscripten::open_virtual_port(std::string_view)
|
LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port()
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LIBREMIDI_INLINE void 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);
|
||||||
}
|
|
||||||
|
|
||||||
LIBREMIDI_INLINE void midi_in_emscripten::set_client_name(std::string_view)
|
return stdx::error{};
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_emscripten::set_client_name: unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
LIBREMIDI_INLINE void midi_in_emscripten::set_port_name(std::string_view)
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_emscripten::set_port_name: unsupported.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE int64_t midi_in_emscripten::absolute_timestamp() const noexcept
|
LIBREMIDI_INLINE int64_t midi_in_emscripten::absolute_timestamp() const noexcept
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -22,19 +22,16 @@ public:
|
|||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override;
|
libremidi::API get_current_api() const noexcept override;
|
||||||
|
|
||||||
bool open_port(int portNumber, std::string_view);
|
stdx::error open_port(int portNumber, std::string_view);
|
||||||
bool open_port(const input_port& p, std::string_view) override;
|
stdx::error open_port(const input_port& p, std::string_view) override;
|
||||||
bool open_virtual_port(std::string_view) override;
|
stdx::error close_port() override;
|
||||||
void close_port() override;
|
|
||||||
|
|
||||||
void set_client_name(std::string_view clientName) override;
|
|
||||||
void set_port_name(std::string_view portName) override;
|
|
||||||
timestamp absolute_timestamp() const noexcept override;
|
timestamp absolute_timestamp() const noexcept override;
|
||||||
|
|
||||||
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,18 +2,20 @@
|
|||||||
#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)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_out_emscripten::~midi_out_emscripten()
|
LIBREMIDI_INLINE midi_out_emscripten::~midi_out_emscripten()
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
midi_out_emscripten::close_port();
|
midi_out_emscripten::close_port();
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noexcept
|
LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noexcept
|
||||||
@@ -21,54 +23,43 @@ LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noe
|
|||||||
return libremidi::API::WEBMIDI;
|
return libremidi::API::WEBMIDI;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(unsigned int portNumber, std::string_view)
|
LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std::string_view)
|
||||||
{
|
{
|
||||||
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
||||||
|
|
||||||
if (portNumber >= midi.output_count())
|
if (portNumber >= midi.output_count())
|
||||||
{
|
{
|
||||||
error<no_devices_found_error>(
|
libremidi_handle_error(this->configuration, "no MIDI output sources found.");
|
||||||
this->configuration, "midi_out_emscripten::open_port: no MIDI output sources found.");
|
return std::errc::invalid_argument;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
portNumber_ = portNumber;
|
m_portNumber = portNumber;
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
|
LIBREMIDI_INLINE stdx::error
|
||||||
|
midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
|
||||||
{
|
{
|
||||||
return open_port(p.port, nm);
|
return open_port(p.port, nm);
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE void midi_out_emscripten::close_port() { }
|
LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port()
|
||||||
|
|
||||||
LIBREMIDI_INLINE void midi_out_emscripten::set_client_name(std::string_view clientName)
|
|
||||||
{
|
{
|
||||||
warning(configuration, "midi_out_emscripten::set_client_name: unsupported.");
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE void midi_out_emscripten::set_port_name(std::string_view portName)
|
LIBREMIDI_INLINE stdx::error
|
||||||
|
midi_out_emscripten::send_message(const unsigned char* message, size_t size)
|
||||||
{
|
{
|
||||||
warning(configuration, "midi_out_emscripten::set_port_name: unsupported.");
|
if (m_portNumber < 0)
|
||||||
}
|
libremidi_handle_error(
|
||||||
|
|
||||||
LIBREMIDI_INLINE bool midi_out_emscripten::open_virtual_port(std::string_view)
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LIBREMIDI_INLINE void midi_out_emscripten::send_message(const unsigned char* message, size_t size)
|
|
||||||
{
|
|
||||||
if (portNumber_ < 0)
|
|
||||||
error<invalid_use_error>(
|
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"midi_out_emscripten::send_message: 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{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -21,17 +21,13 @@ public:
|
|||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override;
|
libremidi::API get_current_api() const noexcept override;
|
||||||
|
|
||||||
bool open_port(unsigned int portNumber, std::string_view);
|
stdx::error open_port(int portNumber, std::string_view);
|
||||||
bool open_port(const output_port& p, std::string_view) override;
|
stdx::error open_port(const output_port& p, std::string_view) override;
|
||||||
bool open_virtual_port(std::string_view) override;
|
stdx::error close_port() override;
|
||||||
void close_port() override;
|
|
||||||
|
|
||||||
void set_client_name(std::string_view clientName) override;
|
stdx::error send_message(const unsigned char* message, size_t size) override;
|
||||||
void set_port_name(std::string_view portName) override;
|
|
||||||
|
|
||||||
void 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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -12,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
|
||||||
@@ -25,8 +24,8 @@ struct jack_input_configuration
|
|||||||
std::string client_name = "libremidi client";
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
jack_client_t* context{};
|
jack_client_t* context{};
|
||||||
std::function<void(jack_callback)> set_process_func;
|
std::function<void(jack_callback)> set_process_func{};
|
||||||
std::function<void(int64_t)> clear_process_func;
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct jack_output_configuration
|
struct jack_output_configuration
|
||||||
@@ -34,8 +33,8 @@ struct jack_output_configuration
|
|||||||
std::string client_name = "libremidi client";
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
jack_client_t* context{};
|
jack_client_t* context{};
|
||||||
std::function<void(jack_callback)> set_process_func;
|
std::function<void(jack_callback)> set_process_func{};
|
||||||
std::function<void(int64_t)> clear_process_func;
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
|
||||||
int32_t ringbuffer_size = 16384;
|
int32_t ringbuffer_size = 16384;
|
||||||
bool direct = false;
|
bool direct = false;
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/error.hpp>
|
||||||
|
|
||||||
|
#if __has_include(<weakjack/weak_libjack.h>)
|
||||||
|
#include <weakjack/weak_libjack.h>
|
||||||
|
#elif __has_include(<weak_libjack.h>)
|
||||||
|
#include <weak_libjack.h>
|
||||||
|
#elif __has_include(<jack/jack.h> )
|
||||||
|
#include <jack/jack.h>
|
||||||
|
#include <jack/midiport.h>
|
||||||
|
#include <jack/ringbuffer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
struct jack_error_domain : public stdx::error_domain
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
constexpr jack_error_domain() noexcept
|
||||||
|
: error_domain{{0xc714ea32b705080aULL, 0xe409a437daf5f921ULL}}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::string_ref name() const noexcept override { return "jack"; }
|
||||||
|
|
||||||
|
bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override
|
||||||
|
{
|
||||||
|
if (lhs.domain() == rhs.domain())
|
||||||
|
return error_cast<jack_status_t>(lhs) == error_cast<jack_status_t>(rhs);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::string_ref message(const stdx::error& e) const noexcept override
|
||||||
|
{
|
||||||
|
const auto status = error_cast<jack_status_t>(e);
|
||||||
|
if (status == jack_status_t{})
|
||||||
|
return "Success";
|
||||||
|
|
||||||
|
if (status & JackInvalidOption)
|
||||||
|
return "The operation contained an invalid or unsupported option";
|
||||||
|
if (status & JackServerFailed)
|
||||||
|
return "Unable to connect to the JACK server";
|
||||||
|
if (status & JackServerError)
|
||||||
|
return "Communication error with the JACK server";
|
||||||
|
if (status & JackNoSuchClient)
|
||||||
|
return "Requested client does not exist";
|
||||||
|
if (status & JackLoadFailure)
|
||||||
|
return "Unable to load internal client";
|
||||||
|
if (status & JackInitFailure)
|
||||||
|
return "Unable to initialize client";
|
||||||
|
if (status & JackShmFailure)
|
||||||
|
return "Unable to access shared memory";
|
||||||
|
if (status & JackVersionError)
|
||||||
|
return "Client's protocol version does not match";
|
||||||
|
if (status & JackBackendError)
|
||||||
|
return "Backend error";
|
||||||
|
if (status & JackClientZombie)
|
||||||
|
return "Client zombified failure";
|
||||||
|
if (status & JackFailure)
|
||||||
|
return "Failure";
|
||||||
|
if (status & JackNameNotUnique)
|
||||||
|
return "The desired client name was not unique";
|
||||||
|
|
||||||
|
// Can't happen in libremidi as we set JackNoStartServer
|
||||||
|
if (status & JackServerStarted)
|
||||||
|
return "Server was started";
|
||||||
|
|
||||||
|
return "Unknown JACK status code";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline stdx::error from_jack_status(jack_status_t ret) noexcept
|
||||||
|
{
|
||||||
|
static constexpr jack_error_domain domain{};
|
||||||
|
return {ret, domain};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if __has_include(<weakjack/weak_libjack.h>)
|
#include <libremidi/backends/jack/error_domain.hpp>
|
||||||
#include <weakjack/weak_libjack.h>
|
|
||||||
#elif __has_include(<weak_libjack.h>)
|
|
||||||
#include <weak_libjack.h>
|
|
||||||
#elif __has_include(<jack/jack.h> )
|
|
||||||
#include <jack/jack.h>
|
|
||||||
#include <jack/midiport.h>
|
|
||||||
#include <jack/ringbuffer.h>
|
|
||||||
#endif
|
|
||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
#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
|
||||||
{
|
{
|
||||||
@@ -47,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 {};
|
||||||
@@ -81,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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +139,7 @@ struct jack_helpers : jack_client
|
|||||||
{.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();
|
||||||
@@ -157,9 +156,13 @@ 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)
|
||||||
{
|
{
|
||||||
jack_set_process_callback(
|
if (status & JackNameNotUnique)
|
||||||
this->client,
|
{
|
||||||
+[](jack_nframes_t nf, void* ctx) -> int {
|
self.libremidi_handle_warning(
|
||||||
|
self.configuration, "JACK client with the same name already exists, renamed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_set_process_callback(this->client, +[](jack_nframes_t nf, void* ctx) -> int {
|
||||||
auto& self = *static_cast<Self*>(ctx);
|
auto& self = *static_cast<Self*>(ctx);
|
||||||
jack_port_t* port = self.port;
|
jack_port_t* port = self.port;
|
||||||
|
|
||||||
@@ -171,8 +174,7 @@ struct jack_helpers : jack_client
|
|||||||
|
|
||||||
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,9 +194,12 @@ struct jack_helpers : jack_client
|
|||||||
|
|
||||||
if (this->client && !self.configuration.context)
|
if (this->client && !self.configuration.context)
|
||||||
jack_client_close(this->client);
|
jack_client_close(this->client);
|
||||||
|
|
||||||
|
self.client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)
|
stdx::error create_local_port(
|
||||||
|
const auto& self, std::string_view portName, const char* type, JackPortFlags flags)
|
||||||
{
|
{
|
||||||
// full name: "client_name:port_name\0"
|
// full name: "client_name:port_name\0"
|
||||||
if (portName.empty())
|
if (portName.empty())
|
||||||
@@ -203,29 +208,27 @@ 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.template error<invalid_use_error>(
|
self.libremidi_handle_error(self.configuration, "port name length limit exceeded");
|
||||||
self.configuration, "JACK: port name length limit exceeded");
|
return std::errc::invalid_argument;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
self.template error<driver_error>(self.configuration, "JACK: error creating port");
|
self.libremidi_handle_error(self.configuration, "error creating port");
|
||||||
return false;
|
return std::errc::operation_not_supported;
|
||||||
}
|
}
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void do_close_port()
|
stdx::error do_close_port()
|
||||||
{
|
{
|
||||||
if (this->port == nullptr)
|
if (this->port == nullptr)
|
||||||
return;
|
return stdx::error{};
|
||||||
|
|
||||||
// 1. Ensure that the next time the cycle runs it sees the port as nullptr
|
// 1. Ensure that the next time the cycle runs it sees the port as nullptr
|
||||||
jack_port_t* port_ptr = this->port.impl->load();
|
jack_port_t* port_ptr = this->port.impl->load();
|
||||||
@@ -235,7 +238,81 @@ struct jack_helpers : jack_client
|
|||||||
this->thread_lock.prepare_release_client();
|
this->thread_lock.prepare_release_client();
|
||||||
|
|
||||||
// 3. Now we are sure that the client is not going to use the port anymore
|
// 3. Now we are sure that the client is not going to use the port anymore
|
||||||
jack_port_unregister(this->client, port_ptr);
|
int err = jack_port_unregister(this->client, port_ptr);
|
||||||
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct jack_queue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr auto size_sz = sizeof(int32_t);
|
||||||
|
|
||||||
|
jack_queue() = default;
|
||||||
|
jack_queue(const jack_queue&) = delete;
|
||||||
|
jack_queue(jack_queue&&) = delete;
|
||||||
|
jack_queue& operator=(const jack_queue&) = delete;
|
||||||
|
|
||||||
|
jack_queue& operator=(jack_queue&& other) noexcept
|
||||||
|
{
|
||||||
|
ringbuffer = other.ringbuffer;
|
||||||
|
ringbuffer_space = other.ringbuffer_space;
|
||||||
|
other.ringbuffer = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit jack_queue(int64_t sz) noexcept
|
||||||
|
{
|
||||||
|
ringbuffer = jack_ringbuffer_create(sz);
|
||||||
|
ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
~jack_queue() noexcept
|
||||||
|
{
|
||||||
|
if (ringbuffer)
|
||||||
|
jack_ringbuffer_free(ringbuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error write(const unsigned char* data, int64_t sz) const noexcept
|
||||||
|
{
|
||||||
|
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
|
||||||
|
return std::errc::no_buffer_space;
|
||||||
|
|
||||||
|
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
|
||||||
|
sched_yield();
|
||||||
|
|
||||||
|
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
|
||||||
|
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void read(void* jack_events) const noexcept
|
||||||
|
{
|
||||||
|
int32_t sz;
|
||||||
|
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
|
||||||
|
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
|
||||||
|
{
|
||||||
|
jack_ringbuffer_read_advance(ringbuffer, size_sz);
|
||||||
|
|
||||||
|
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
|
||||||
|
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
|
||||||
|
else
|
||||||
|
jack_ringbuffer_read_advance(ringbuffer, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_ringbuffer_t* ringbuffer{};
|
||||||
|
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
|
||||||
|
};
|
||||||
|
|
||||||
|
struct jack_midi1
|
||||||
|
{
|
||||||
|
static constexpr const char* port_type = "8 bit raw midi";
|
||||||
|
};
|
||||||
|
|
||||||
|
struct jack_midi2
|
||||||
|
{
|
||||||
|
static constexpr const char* port_type = "32 bit raw UMP";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@
|
|||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_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:
|
||||||
|
using midi_api::client_open_;
|
||||||
struct
|
struct
|
||||||
: input_configuration
|
: input_configuration
|
||||||
, jack_input_configuration
|
, jack_input_configuration
|
||||||
@@ -24,8 +24,14 @@ public:
|
|||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
auto status = connect(*this);
|
auto status = connect(*this);
|
||||||
if (status != jack_status_t{})
|
if (!this->client)
|
||||||
warning(configuration, "midi_in_jack: " + std::to_string((int)jack_status_t{}));
|
{
|
||||||
|
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() override
|
||||||
@@ -35,39 +41,36 @@ public:
|
|||||||
disconnect(*this);
|
disconnect(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_in_jack: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
|
||||||
|
|
||||||
bool open_port(const input_port& port, std::string_view portName) override
|
stdx::error open_port(const input_port& port, std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (!create_local_port(*this, portName, JackPortIsInput))
|
if (auto err = create_local_port(*this, portName, port_type, JackPortIsInput);
|
||||||
return false;
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
if (auto ret = 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));
|
||||||
ret != 0)
|
err != 0 && err != EEXIST)
|
||||||
{
|
{
|
||||||
error<invalid_parameter_error>(
|
libremidi_handle_error(
|
||||||
configuration, "JACK: 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 false;
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
return create_local_port(*this, portName, JackPortIsInput);
|
return create_local_port(*this, portName, port_type, JackPortIsInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override { return do_close_port(); }
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
|
|
||||||
void set_port_name(std::string_view portName) override
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
{
|
{
|
||||||
jack_port_rename(this->client, this->port, portName.data());
|
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||||
|
return from_errc(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override
|
timestamp absolute_timestamp() const noexcept override
|
||||||
@@ -93,8 +96,8 @@ public:
|
|||||||
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
this->client, ¤t_frames, ¤t_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);
|
||||||
|
|||||||
@@ -3,77 +3,16 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write(const unsigned char* data, int64_t sz) const noexcept
|
|
||||||
{
|
|
||||||
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
|
|
||||||
sched_yield();
|
|
||||||
|
|
||||||
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
|
|
||||||
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
|
|
||||||
}
|
|
||||||
|
|
||||||
void read(void* jack_events) const noexcept
|
|
||||||
{
|
|
||||||
int32_t sz;
|
|
||||||
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
|
|
||||||
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
|
|
||||||
{
|
|
||||||
jack_ringbuffer_read_advance(ringbuffer, size_sz);
|
|
||||||
|
|
||||||
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
|
|
||||||
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
|
|
||||||
else
|
|
||||||
jack_ringbuffer_read_advance(ringbuffer, sz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_ringbuffer_t* ringbuffer{};
|
|
||||||
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
|
|
||||||
};
|
|
||||||
|
|
||||||
class midi_out_jack
|
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:
|
||||||
|
using midi_api::client_open_;
|
||||||
struct
|
struct
|
||||||
: output_configuration
|
: output_configuration
|
||||||
, jack_output_configuration
|
, jack_output_configuration
|
||||||
@@ -87,39 +26,36 @@ public:
|
|||||||
|
|
||||||
~midi_out_jack() override { }
|
~midi_out_jack() override { }
|
||||||
|
|
||||||
void set_client_name(std::string_view) override
|
|
||||||
{
|
|
||||||
warning(configuration, "midi_out_jack: set_client_name unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
|
||||||
|
|
||||||
bool open_port(const output_port& port, std::string_view portName) override
|
stdx::error open_port(const output_port& port, std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (!create_local_port(*this, portName, JackPortIsOutput))
|
if (auto err = create_local_port(*this, portName, port_type, JackPortIsOutput);
|
||||||
return false;
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
// Connecting to the output
|
// Connecting to the output
|
||||||
if (jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()) != 0)
|
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
|
||||||
|
err != 0 && err != EEXIST)
|
||||||
{
|
{
|
||||||
error<invalid_parameter_error>(
|
libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
|
||||||
configuration, "JACK: could not connect to port" + port.port_name);
|
return from_errc(err);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
return create_local_port(*this, portName, JackPortIsOutput);
|
return create_local_port(*this, portName, port_type, JackPortIsOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_port() override { return do_close_port(); }
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
|
|
||||||
void set_port_name(std::string_view portName) override
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
{
|
{
|
||||||
jack_port_rename(this->client, this->port, portName.data());
|
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||||
|
return from_errc(ret);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,11 +64,17 @@ 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 (status != jack_status_t{})
|
if (!this->client)
|
||||||
warning(configuration, "midi_out_jack_queued: " + std::to_string((int)jack_status_t{}));
|
{
|
||||||
|
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_queued() override
|
||||||
@@ -142,9 +84,9 @@ public:
|
|||||||
disconnect(*this);
|
disconnect(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_message(const unsigned char* message, std::size_t size) override
|
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
||||||
{
|
{
|
||||||
queue.write(message, size);
|
return m_queue.write(message, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
int process(jack_nframes_t nframes)
|
int process(jack_nframes_t nframes)
|
||||||
@@ -152,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
|
||||||
@@ -168,10 +110,15 @@ public:
|
|||||||
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
auto status = connect(*this);
|
auto status = connect(*this);
|
||||||
if (status != jack_status_t{})
|
if (!this->client)
|
||||||
warning(configuration, "midi_out_jack_direct: " + std::to_string((int)jack_status_t{}));
|
{
|
||||||
|
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||||
|
client_open_ = from_jack_status(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
buffer_size = jack_get_buffer_size(this->client);
|
buffer_size = jack_get_buffer_size(this->client);
|
||||||
|
client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_out_jack_direct() override
|
~midi_out_jack_direct() override
|
||||||
@@ -188,10 +135,11 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_message(const unsigned char* message, size_t size) override
|
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||||
{
|
{
|
||||||
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||||
jack_midi_event_write(buff, 0, message, size);
|
int ret = jack_midi_event_write(buff, 0, message, size);
|
||||||
|
return from_errc(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
int convert_timestamp(int64_t user) const noexcept
|
int convert_timestamp(int64_t user) const noexcept
|
||||||
@@ -207,17 +155,18 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void schedule_message(int64_t ts, const unsigned char* message, size_t size) override
|
stdx::error schedule_message(int64_t ts, const unsigned char* message, size_t size) override
|
||||||
{
|
{
|
||||||
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||||
jack_midi_event_write(buff, convert_timestamp(ts), message, size);
|
int ret = jack_midi_event_write(buff, convert_timestamp(ts), message, size);
|
||||||
|
return from_errc(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
int buffer_size{};
|
int buffer_size{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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>(
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -34,7 +35,7 @@ public:
|
|||||||
this->client
|
this->client
|
||||||
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
|
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
|
||||||
if (status != jack_status_t{})
|
if (status != jack_status_t{})
|
||||||
warning(configuration, "observer_jack: " + std::to_string((int)jack_status_t{}));
|
libremidi_handle_error(configuration, std::to_string((int)status));
|
||||||
|
|
||||||
if (this->client != nullptr)
|
if (this->client != nullptr)
|
||||||
{
|
{
|
||||||
@@ -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,16 +177,13 @@ 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,
|
|
||||||
+[](jack_port_id_t p, int r, void* arg) {
|
|
||||||
auto& self = *(observer_jack*)arg;
|
auto& self = *(observer_jack*)arg;
|
||||||
if (auto port = jack_port_by_id(self.client, p))
|
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,
|
||||||
@@ -191,20 +193,21 @@ public:
|
|||||||
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,
|
|
||||||
+[](jack_nframes_t cnt, void* ctx) -> int {
|
|
||||||
((shared_handler*)ctx)->jack_callback(cnt);
|
((shared_handler*)ctx)->jack_callback(cnt);
|
||||||
return 0;
|
return 0;
|
||||||
},
|
}, this);
|
||||||
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};
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack_ump/midi_in.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/midi_out.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::jack_ump
|
||||||
|
{
|
||||||
|
struct backend
|
||||||
|
{
|
||||||
|
using midi_in = midi_in_jack;
|
||||||
|
using midi_out = midi_out_jack;
|
||||||
|
using midi_observer = observer_jack;
|
||||||
|
using midi_in_configuration = jack_ump::input_configuration;
|
||||||
|
using midi_out_configuration = jack_ump::output_configuration;
|
||||||
|
using midi_observer_configuration = jack_ump::observer_configuration;
|
||||||
|
static const constexpr auto API = libremidi::API::JACK_UMP;
|
||||||
|
static const constexpr std::string_view name = "jack_ump";
|
||||||
|
static const constexpr std::string_view display_name = "JACK (UMP)";
|
||||||
|
|
||||||
|
static inline bool available() noexcept
|
||||||
|
{
|
||||||
|
#if LIBREMIDI_WEAKJACK
|
||||||
|
if (WeakJack::instance().available() != 0)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int major, minor, micro, patch;
|
||||||
|
jack_get_version(&major, &minor, µ, &patch);
|
||||||
|
return (major >= 3 && minor >= 1 && micro >= 4);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack/config.hpp>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::jack_ump
|
||||||
|
{
|
||||||
|
struct input_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
jack_client_t* context{};
|
||||||
|
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||||
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct output_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
jack_client_t* context{};
|
||||||
|
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||||
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
|
||||||
|
int32_t ringbuffer_size = 16384;
|
||||||
|
bool direct = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct observer_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
jack_client_t* context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack/helpers.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::jack_ump
|
||||||
|
{
|
||||||
|
class midi_in_jack final
|
||||||
|
: public midi2::in_api
|
||||||
|
, public jack_helpers
|
||||||
|
, private jack_midi1
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
|
struct
|
||||||
|
: libremidi::ump_input_configuration
|
||||||
|
, jack_ump::input_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit midi_in_jack(
|
||||||
|
libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
auto status = connect(*this);
|
||||||
|
if (!this->client)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||||
|
client_open_ = from_jack_status(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_in_jack() override
|
||||||
|
{
|
||||||
|
midi_in_jack::close_port();
|
||||||
|
|
||||||
|
disconnect(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const input_port& port, std::string_view portName) override
|
||||||
|
{
|
||||||
|
if (auto err
|
||||||
|
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
||||||
|
err != 0 && err != EEXIST)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(
|
||||||
|
configuration,
|
||||||
|
"could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port));
|
||||||
|
return from_errc(err);
|
||||||
|
}
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
|
{
|
||||||
|
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
|
|
||||||
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
|
{
|
||||||
|
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp absolute_timestamp() const noexcept override
|
||||||
|
{
|
||||||
|
return 1000 * jack_frames_to_time(client, jack_frame_time(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(jack_nframes_t nframes)
|
||||||
|
{
|
||||||
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = true,
|
||||||
|
.absolute_is_monotonic = true,
|
||||||
|
.has_samples = true,
|
||||||
|
};
|
||||||
|
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
jack_nframes_t current_frames{};
|
||||||
|
jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC
|
||||||
|
jack_time_t next_usecs{};
|
||||||
|
float period_usecs{};
|
||||||
|
jack_get_cycle_times(
|
||||||
|
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
||||||
|
|
||||||
|
// We have midi events in buffer
|
||||||
|
uint32_t ev_count = jack_midi_get_event_count(buff);
|
||||||
|
for (uint32_t j = 0; j < ev_count; j++)
|
||||||
|
{
|
||||||
|
jack_midi_event_t event{};
|
||||||
|
jack_midi_event_get(&event, buff, j);
|
||||||
|
const auto to_ns
|
||||||
|
= [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); };
|
||||||
|
|
||||||
|
m_processing.on_bytes(
|
||||||
|
{(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)},
|
||||||
|
m_processing.timestamp<timestamp_info>(to_ns, event.time));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
midi2::input_state_machine m_processing{this->configuration};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack/helpers.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/midi_out.hpp>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::jack_ump
|
||||||
|
{
|
||||||
|
class midi_out_jack
|
||||||
|
: public midi2::out_api
|
||||||
|
, public jack_helpers
|
||||||
|
, private jack_midi1
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
|
struct
|
||||||
|
: libremidi::output_configuration
|
||||||
|
, jack_ump::output_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out_jack() override { }
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const output_port& port, std::string_view portName) override
|
||||||
|
{
|
||||||
|
if (auto err
|
||||||
|
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Connecting to the output
|
||||||
|
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
|
||||||
|
err != 0 && err != EEXIST)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
|
||||||
|
return from_errc(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
|
{
|
||||||
|
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
|
|
||||||
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
|
{
|
||||||
|
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_out_jack_queued final : public midi_out_jack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
midi_out_jack_queued(
|
||||||
|
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||||
|
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||||
|
, m_queue{configuration.ringbuffer_size}
|
||||||
|
{
|
||||||
|
auto status = connect(*this);
|
||||||
|
if (!this->client)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||||
|
client_open_ = from_jack_status(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out_jack_queued() override
|
||||||
|
{
|
||||||
|
midi_out_jack::close_port();
|
||||||
|
|
||||||
|
disconnect(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||||
|
{
|
||||||
|
return m_queue.write((unsigned char*)message, size * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(jack_nframes_t nframes)
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||||
|
jack_midi_clear_buffer(buff);
|
||||||
|
|
||||||
|
this->m_queue.read(buff);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
libremidi::jack_queue m_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_out_jack_direct final : public midi_out_jack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
midi_out_jack_direct(
|
||||||
|
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||||
|
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
auto status = connect(*this);
|
||||||
|
if (!this->client)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||||
|
client_open_ = from_jack_status(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_size = jack_get_buffer_size(this->client);
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out_jack_direct() override
|
||||||
|
{
|
||||||
|
midi_out_jack::close_port();
|
||||||
|
|
||||||
|
disconnect(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(jack_nframes_t nframes)
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||||
|
jack_midi_clear_buffer(buff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||||
|
int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t));
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int convert_timestamp(int64_t user) const noexcept
|
||||||
|
{
|
||||||
|
switch (configuration.timestamps)
|
||||||
|
{
|
||||||
|
case timestamp_mode::AudioFrame:
|
||||||
|
return static_cast<int>(user);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// TODO
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||||
|
int ret = jack_midi_event_write(
|
||||||
|
buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t));
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int buffer_size{};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
template <>
|
||||||
|
inline std::unique_ptr<midi_out_api>
|
||||||
|
make<jack_ump::midi_out_jack>(output_configuration&& conf, jack_ump::output_configuration&& api)
|
||||||
|
{
|
||||||
|
if (api.direct)
|
||||||
|
return std::make_unique<jack_ump::midi_out_jack_direct>(std::move(conf), std::move(api));
|
||||||
|
else
|
||||||
|
return std::make_unique<jack_ump::midi_out_jack_queued>(std::move(conf), std::move(api));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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)},
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/dummy.hpp>
|
||||||
|
#include <libremidi/backends/keyboard/midi_in.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
struct kbd_backend
|
||||||
|
{
|
||||||
|
using midi_in = midi_in_kbd;
|
||||||
|
using midi_in_configuration = kbd_input_configuration;
|
||||||
|
using midi_out = midi_out_dummy;
|
||||||
|
using midi_observer = observer_dummy;
|
||||||
|
using midi_out_configuration = dummy_configuration;
|
||||||
|
using midi_observer_configuration = dummy_configuration;
|
||||||
|
static const constexpr auto API = libremidi::API::KEYBOARD;
|
||||||
|
static const constexpr std::string_view name = "keyboard";
|
||||||
|
static const constexpr std::string_view display_name = "Computer keyboard";
|
||||||
|
|
||||||
|
static inline bool available() noexcept { return true; }
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
enum kbd_event
|
||||||
|
{
|
||||||
|
NOTE_0 = 0x0, // C
|
||||||
|
VEL_0 = NOTE_0 + 128, // Set velocity to 0
|
||||||
|
OCT_0 = VEL_0 + 128, // Set octave to 0
|
||||||
|
OCTAVE_PLUS = OCT_0 + 128,
|
||||||
|
OCTAVE_MINUS,
|
||||||
|
VELOCITY_PLUS,
|
||||||
|
VELOCITY_MINUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default map
|
||||||
|
//
|
||||||
|
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
|
||||||
|
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
|
||||||
|
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
|
||||||
|
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
|
||||||
|
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
|
||||||
|
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
|
||||||
|
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
|
||||||
|
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
|
||||||
|
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
|
||||||
|
// | ctrl | | alt | |altgr | | ctrl |
|
||||||
|
// '------' '-----'--------------------------'------' '------'
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
// actually based on macOS virtual codes as it seems
|
||||||
|
// impossible to get raw scan codes.
|
||||||
|
// https://eastmanreference.com/complete-list-of-applescript-key-codes
|
||||||
|
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.
|
||||||
|
* Your app should pass a function that will give you a callback
|
||||||
|
* that you should call whenever you are getting a key input (press or release).
|
||||||
|
*/
|
||||||
|
struct kbd_input_configuration
|
||||||
|
{
|
||||||
|
// Note that on Linux with X11 scancodes should be offset by 8 (an X11 legacy quirk)
|
||||||
|
// ex.: pass QKeyEvent::nativeScanCode() - 8 to this API
|
||||||
|
using scancode_callback = std::function<void(int)>;
|
||||||
|
|
||||||
|
// First argument is on key press, second on key release
|
||||||
|
std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks
|
||||||
|
= [](const scancode_callback&, const scancode_callback&) { };
|
||||||
|
|
||||||
|
std::map<int, int> scancode_map
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
= scancode_map_macos()
|
||||||
|
#else
|
||||||
|
= scancode_map_linux()
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/keyboard/config.hpp>
|
||||||
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
class midi_in_kbd final
|
||||||
|
: public midi1::in_api
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
|
struct
|
||||||
|
: input_configuration
|
||||||
|
, kbd_input_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_in_kbd() override { }
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }
|
||||||
|
|
||||||
|
stdx::error open_port(const input_port&, std::string_view) override
|
||||||
|
{
|
||||||
|
configuration.set_input_scancode_callbacks(
|
||||||
|
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override { return stdx::error{}; }
|
||||||
|
|
||||||
|
timestamp absolute_timestamp() const noexcept override
|
||||||
|
{
|
||||||
|
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_keypress(int scancode)
|
||||||
|
{
|
||||||
|
using kevent = libremidi::kbd_event;
|
||||||
|
|
||||||
|
auto it = configuration.scancode_map.find(scancode);
|
||||||
|
if (it == configuration.scancode_map.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))
|
||||||
|
{
|
||||||
|
int note = it->second - kevent::NOTE_0 + 12 * m_current_octave;
|
||||||
|
this->configuration.on_message(
|
||||||
|
libremidi::channel_events::note_on(0, note, m_current_velocity));
|
||||||
|
m_current_notes_scancodes[scancode] = note;
|
||||||
|
}
|
||||||
|
else if (it->second >= kevent::VEL_0 && it->second < (kevent::VEL_0 + 128))
|
||||||
|
{
|
||||||
|
m_current_velocity = it->second - kevent::VEL_0;
|
||||||
|
}
|
||||||
|
else if (it->second >= kevent::OCT_0 && it->second < (kevent::OCT_0 + 128))
|
||||||
|
{
|
||||||
|
m_current_octave = it->second - kevent::OCT_0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (it->second)
|
||||||
|
{
|
||||||
|
case kevent::VELOCITY_MINUS:
|
||||||
|
m_current_velocity = std::clamp(m_current_velocity - 10, 0, 127);
|
||||||
|
break;
|
||||||
|
case kevent::VELOCITY_PLUS:
|
||||||
|
m_current_velocity = std::clamp(m_current_velocity + 10, 0, 127);
|
||||||
|
break;
|
||||||
|
case kevent::OCTAVE_MINUS:
|
||||||
|
m_current_octave = std::clamp(m_current_octave - 1, 0, 127);
|
||||||
|
break;
|
||||||
|
case kevent::OCTAVE_PLUS:
|
||||||
|
m_current_octave = std::clamp(m_current_octave + 1, 0, 127);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_keyrelease(int scancode)
|
||||||
|
{
|
||||||
|
using kevent = libremidi::kbd_event;
|
||||||
|
|
||||||
|
auto it = configuration.scancode_map.find(scancode);
|
||||||
|
if (it == configuration.scancode_map.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))
|
||||||
|
{
|
||||||
|
if (auto note_it = m_current_notes_scancodes.find(scancode);
|
||||||
|
note_it != m_current_notes_scancodes.end())
|
||||||
|
{
|
||||||
|
this->configuration.on_message(libremidi::channel_events::note_off(0, note_it->second, 0));
|
||||||
|
m_current_notes_scancodes.erase(note_it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int m_current_octave{3};
|
||||||
|
int m_current_velocity{80};
|
||||||
|
std::unordered_map<int, int> m_current_notes_scancodes;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <libremidi/backends/linux/dylib_loader.hpp>
|
#include <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
|
||||||
@@ -57,13 +61,13 @@ struct libasound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_card, get_name);
|
LIBREMIDI_SYMBOL_INIT(snd_card, get_name)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_card, next);
|
LIBREMIDI_SYMBOL_INIT(snd_card, next)
|
||||||
}
|
}
|
||||||
bool available{true};
|
bool available{true};
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_card, get_name);
|
LIBREMIDI_SYMBOL_DEF(snd_card, get_name)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_card, next);
|
LIBREMIDI_SYMBOL_DEF(snd_card, next)
|
||||||
} card{library};
|
} card{library};
|
||||||
|
|
||||||
struct ctl_t
|
struct ctl_t
|
||||||
@@ -80,13 +84,13 @@ struct libasound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ctl, close);
|
LIBREMIDI_SYMBOL_INIT(snd_ctl, close)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ctl, open);
|
LIBREMIDI_SYMBOL_INIT(snd_ctl, open)
|
||||||
}
|
}
|
||||||
bool available{true};
|
bool available{true};
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ctl, close);
|
LIBREMIDI_SYMBOL_DEF(snd_ctl, close)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ctl, open);
|
LIBREMIDI_SYMBOL_DEF(snd_ctl, open)
|
||||||
|
|
||||||
struct rawmidi_t
|
struct rawmidi_t
|
||||||
{
|
{
|
||||||
@@ -98,12 +102,12 @@ struct libasound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info);
|
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, info)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device);
|
LIBREMIDI_SYMBOL_INIT(snd_ctl_rawmidi, next_device)
|
||||||
}
|
}
|
||||||
bool available{true};
|
bool available{true};
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info);
|
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, info)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device);
|
LIBREMIDI_SYMBOL_DEF(snd_ctl_rawmidi, next_device)
|
||||||
} rawmidi;
|
} rawmidi;
|
||||||
|
|
||||||
#if LIBREMIDI_ALSA_HAS_UMP
|
#if LIBREMIDI_ALSA_HAS_UMP
|
||||||
@@ -117,14 +121,14 @@ struct libasound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, block_info);
|
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, block_info)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, endpoint_info);
|
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, endpoint_info)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, next_device);
|
LIBREMIDI_SYMBOL_INIT(snd_ctl_ump, next_device)
|
||||||
}
|
}
|
||||||
bool available{true};
|
bool available{true};
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, block_info);
|
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, block_info)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, endpoint_info);
|
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, endpoint_info)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, next_device);
|
LIBREMIDI_SYMBOL_DEF(snd_ctl_ump, next_device)
|
||||||
} ump;
|
} ump;
|
||||||
#endif
|
#endif
|
||||||
} ctl{library};
|
} ctl{library};
|
||||||
@@ -139,23 +143,23 @@ struct libasound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_decode);
|
LIBREMIDI_SYMBOL_INIT(snd_midi, event_decode)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_encode);
|
LIBREMIDI_SYMBOL_INIT(snd_midi, event_encode)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_free);
|
LIBREMIDI_SYMBOL_INIT(snd_midi, event_free)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_init);
|
LIBREMIDI_SYMBOL_INIT(snd_midi, event_init)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_new);
|
LIBREMIDI_SYMBOL_INIT(snd_midi, event_new)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_no_status);
|
LIBREMIDI_SYMBOL_INIT(snd_midi, event_no_status)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_midi, event_resize_buffer);
|
LIBREMIDI_SYMBOL_INIT(snd_midi, event_resize_buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool available{true};
|
bool available{true};
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_decode);
|
LIBREMIDI_SYMBOL_DEF(snd_midi, event_decode)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_encode);
|
LIBREMIDI_SYMBOL_DEF(snd_midi, event_encode)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_free);
|
LIBREMIDI_SYMBOL_DEF(snd_midi, event_free)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_init);
|
LIBREMIDI_SYMBOL_DEF(snd_midi, event_init)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_new);
|
LIBREMIDI_SYMBOL_DEF(snd_midi, event_new)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_no_status);
|
LIBREMIDI_SYMBOL_DEF(snd_midi, event_no_status)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_midi, event_resize_buffer);
|
LIBREMIDI_SYMBOL_DEF(snd_midi, event_resize_buffer)
|
||||||
} midi{library};
|
} midi{library};
|
||||||
|
|
||||||
#if LIBREMIDI_ALSA_HAS_RAMWIDI
|
#if LIBREMIDI_ALSA_HAS_RAMWIDI
|
||||||
@@ -168,59 +172,59 @@ struct libasound
|
|||||||
available = false;
|
available = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, close);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, close)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_name);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_name)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevice_name);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevice_name)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevices_count);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_get_subdevices_count)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_device);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_device)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_stream);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_stream)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_subdevice);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_set_subdevice)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_sizeof);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, open);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, open)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_current);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_current)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_get_buffer_size);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_get_buffer_size)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_clock_type);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_clock_type)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_no_active_sensing);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_no_active_sensing)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_read_mode);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_set_read_mode)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_sizeof);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, params_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_count);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_count)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_revents);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, poll_descriptors_revents)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, read);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, read)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_get_avail);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_get_avail)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_sizeof);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, status_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, tread);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, tread)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, write);
|
LIBREMIDI_SYMBOL_INIT(snd_rawmidi, write)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool available{true};
|
bool available{true};
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, close);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, close)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_name);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_name)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevice_name);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevice_name)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevices_count);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_get_subdevices_count)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_device);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_device)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_stream);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_stream)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_subdevice);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_set_subdevice)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_sizeof);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, open);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, open)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_current);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_current)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_get_buffer_size);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_get_buffer_size)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_clock_type);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_clock_type)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_no_active_sensing);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_no_active_sensing)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_read_mode);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_set_read_mode)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_sizeof);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, params_sizeof)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_count);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_count)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_revents);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, poll_descriptors_revents)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, read);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, read)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_get_avail);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_get_avail)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_sizeof);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, status_sizeof)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, tread);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, tread)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, write);
|
LIBREMIDI_SYMBOL_DEF(snd_rawmidi, write)
|
||||||
} rawmidi{library};
|
} rawmidi{library};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -237,118 +241,134 @@ struct libasound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, alloc_queue);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, alloc_queue)
|
||||||
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_set_client);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_card)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, close);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, close)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, create_port);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, create_port)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, event_output);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, free_event);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, event_output)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, free_event)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, open);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, open)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port);
|
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_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};
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, alloc_queue);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, alloc_queue)
|
||||||
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_set_client);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_card)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, close);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, close)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, create_port);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, create_port)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, event_output);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, free_event);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, event_output)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, free_event)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, open);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, open)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_sizeof)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof)
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_name);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_name)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_port_info);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, set_port_info)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, set_queue_tempo);
|
LIBREMIDI_SYMBOL_DEF(snd_seq, set_queue_tempo)
|
||||||
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,18 +380,17 @@ 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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, open)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, close);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, close)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, open);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, read)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, tread)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, write)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current);
|
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, read);
|
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, tread);
|
// Endpoint info functions
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_ump, write);
|
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, open)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, close)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, open);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, read)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, tread)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, write)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, read);
|
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, tread);
|
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_ump, write);
|
// 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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace libremidi
|
NAMESPACE_LIBREMIDI
|
||||||
{
|
{
|
||||||
struct eventfd_notifier
|
struct eventfd_notifier
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -23,16 +25,32 @@ struct libudev
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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_unref);
|
LIBREMIDI_SYMBOL_INIT(udev, device_get_property_value)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving);
|
LIBREMIDI_SYMBOL_INIT(udev, device_new_from_syspath)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd);
|
LIBREMIDI_SYMBOL_INIT(udev, device_get_parent_with_subsystem_devtype)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink);
|
LIBREMIDI_SYMBOL_INIT(udev, device_unref)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device);
|
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref);
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)
|
||||||
LIBREMIDI_SYMBOL_INIT2(udev, new, create);
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, unref);
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_new)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_unref)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_is_initialized)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_subsystem)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_sysname)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_scan_devices)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_get_list_entry)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_name)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_next)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_INIT2(udev, new, create)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, unref)
|
||||||
}
|
}
|
||||||
|
|
||||||
static const libudev& instance()
|
static const libudev& instance()
|
||||||
@@ -44,16 +62,32 @@ struct libudev
|
|||||||
dylib_loader library;
|
dylib_loader library;
|
||||||
bool available{true};
|
bool available{true};
|
||||||
|
|
||||||
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_unref);
|
LIBREMIDI_SYMBOL_DEF(udev, device_new_from_syspath)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving);
|
LIBREMIDI_SYMBOL_DEF(udev, device_get_property_value)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd);
|
LIBREMIDI_SYMBOL_DEF(udev, device_get_parent_with_subsystem_devtype)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink);
|
LIBREMIDI_SYMBOL_DEF(udev, device_unref)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device);
|
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref);
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)
|
||||||
LIBREMIDI_SYMBOL_DEF2(udev, new, create);
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, unref);
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_new)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_unref)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_is_initialized)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_subsystem)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_sysname)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_scan_devices)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_get_list_entry)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_name)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_next)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_DEF2(udev, new, create)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, unref)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct udev_helper
|
struct udev_helper
|
||||||
@@ -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 {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if !defined(BOOST_ASIO_IO_CONTEXT_HPP)
|
||||||
|
namespace boost::asio
|
||||||
|
{
|
||||||
|
struct io_context;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net
|
||||||
|
{
|
||||||
|
enum class protocol
|
||||||
|
{
|
||||||
|
OSC_MIDI,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dgram_input_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
enum protocol protocol = protocol::OSC_MIDI;
|
||||||
|
std::string accept = "0.0.0.0";
|
||||||
|
int port{};
|
||||||
|
|
||||||
|
boost::asio::io_context* io_context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dgram_output_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
enum protocol protocol = protocol::OSC_MIDI;
|
||||||
|
std::string host = "127.0.0.1";
|
||||||
|
int port{};
|
||||||
|
bool broadcast{};
|
||||||
|
|
||||||
|
boost::asio::io_context* io_context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dgram_observer_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
boost::asio::io_context* io_context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net_ump
|
||||||
|
{
|
||||||
|
enum class protocol
|
||||||
|
{
|
||||||
|
OSC_MIDI2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dgram_input_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
enum protocol protocol = protocol::OSC_MIDI2;
|
||||||
|
std::string accept = "0.0.0.0";
|
||||||
|
int port{};
|
||||||
|
|
||||||
|
boost::asio::io_context* io_context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dgram_output_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
enum protocol protocol = protocol::OSC_MIDI2;
|
||||||
|
std::string host = "127.0.0.1";
|
||||||
|
int port{};
|
||||||
|
bool broadcast{};
|
||||||
|
|
||||||
|
boost::asio::io_context* io_context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dgram_observer_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
boost::asio::io_context* io_context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio/ip/udp.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
namespace stdx
|
||||||
|
{ /*
|
||||||
|
|
||||||
|
class boost_system_error_domain : public error_domain
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
constexpr boost_system_error_domain() noexcept
|
||||||
|
: error_domain{
|
||||||
|
{1 - 0x3c223c0aa3cf45e5ULL, 1 - 0x80dac24345cfb9fcULL},
|
||||||
|
default_error_resource_management_t<detail::exception_ptr_wrapper>{}}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
string_ref name() const noexcept override { return "boost::system_error domain"; }
|
||||||
|
|
||||||
|
bool equivalent(const error& lhs, const error& rhs) const noexcept override { return false; }
|
||||||
|
|
||||||
|
string_ref message(const error& e) const noexcept override {
|
||||||
|
auto err = e.m_value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void throw_exception(const error& e) const override
|
||||||
|
{
|
||||||
|
assert(e.domain() == *this);
|
||||||
|
std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
template <typename T>
|
||||||
|
struct optionally_owned
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit optionally_owned(T* maybe_existing)
|
||||||
|
: m_storage{.ref = maybe_existing}
|
||||||
|
{
|
||||||
|
if (m_storage.ref)
|
||||||
|
{
|
||||||
|
m_ownership = unowned;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::destroy_at(&m_storage.ref);
|
||||||
|
std::construct_at<T>(reinterpret_cast<T*>(m_storage.object));
|
||||||
|
m_ownership = owned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~optionally_owned()
|
||||||
|
{
|
||||||
|
if (is_owned())
|
||||||
|
std::destroy_at(reinterpret_cast<T*>(m_storage.object));
|
||||||
|
}
|
||||||
|
|
||||||
|
T& get() noexcept
|
||||||
|
{
|
||||||
|
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
|
||||||
|
}
|
||||||
|
const T& get() const noexcept
|
||||||
|
{
|
||||||
|
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
optionally_owned(const optionally_owned&) = delete;
|
||||||
|
optionally_owned(optionally_owned&&) noexcept = delete;
|
||||||
|
optionally_owned& operator=(const optionally_owned&) = delete;
|
||||||
|
optionally_owned& operator=(optionally_owned&&) noexcept = delete;
|
||||||
|
|
||||||
|
bool is_owned() const noexcept { return m_ownership == owned; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
union
|
||||||
|
{
|
||||||
|
alignas(T) unsigned char object[sizeof(T)];
|
||||||
|
T* ref;
|
||||||
|
} m_storage;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
owned,
|
||||||
|
unowned
|
||||||
|
} m_ownership{};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/net/config.hpp>
|
||||||
|
#include <libremidi/backends/net/helpers.hpp>
|
||||||
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
NAMESPACE_LIBREMIDI
|
||||||
|
{
|
||||||
|
template <typename Impl, typename F>
|
||||||
|
struct osc_parser
|
||||||
|
{
|
||||||
|
[[no_unique_address]]
|
||||||
|
Impl impl;
|
||||||
|
F on_message;
|
||||||
|
std::string_view port_name;
|
||||||
|
|
||||||
|
stdx::error parse_bundle(const char* data, std::size_t sz)
|
||||||
|
{
|
||||||
|
std::string_view txt(data, sz);
|
||||||
|
if (!txt.starts_with("#bundle"))
|
||||||
|
return std::errc::bad_message;
|
||||||
|
return std::errc::protocol_not_supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error parse_message(const char* data, std::size_t sz)
|
||||||
|
{
|
||||||
|
const auto begin = data;
|
||||||
|
const auto end = data + sz;
|
||||||
|
std::size_t pattern_len = strnlen(data, sz);
|
||||||
|
if (pattern_len == sz)
|
||||||
|
return std::errc::bad_message;
|
||||||
|
|
||||||
|
if (auto pat = std::string_view(data, pattern_len); pat != port_name)
|
||||||
|
return std::errc::bad_address;
|
||||||
|
|
||||||
|
data += pattern_len;
|
||||||
|
for (; data < end; ++data)
|
||||||
|
if (*data == 0)
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Now we shold reach the typetag beginning, ","
|
||||||
|
if (data == end)
|
||||||
|
return std::errc::bad_message;
|
||||||
|
if ((data - begin) % 4 != 0)
|
||||||
|
return std::errc::bad_message;
|
||||||
|
if (*data++ != ',')
|
||||||
|
return std::errc::bad_message;
|
||||||
|
|
||||||
|
// Count the ,mmmmm arguments... yummy
|
||||||
|
int num_msgs = 0;
|
||||||
|
for (; data < end; ++data)
|
||||||
|
if (*data == Impl::typetag)
|
||||||
|
num_msgs++;
|
||||||
|
else if (*data == 0)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
return std::errc::bad_message;
|
||||||
|
|
||||||
|
if (num_msgs == 0)
|
||||||
|
return std::errc::no_message;
|
||||||
|
|
||||||
|
// By now there's at least 5 bytes left as the ,mmm has to be round-up to 4 and padded with zeros:
|
||||||
|
if (end - data < 4)
|
||||||
|
return std::errc::bad_message;
|
||||||
|
|
||||||
|
switch ((1 + num_msgs) % 4)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
data += 4;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
data += 3;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
data += 2;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
data += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data starts
|
||||||
|
return impl.process_midi_bytes(on_message, num_msgs, data, end - data);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error parse_int_message(const char* /* data */, std::size_t /* sz */)
|
||||||
|
{
|
||||||
|
return std::errc::protocol_not_supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error parse_packet(const char* data, std::size_t sz)
|
||||||
|
{
|
||||||
|
if (sz == 0)
|
||||||
|
return std::errc::no_message;
|
||||||
|
|
||||||
|
switch (data[0])
|
||||||
|
{
|
||||||
|
case '#':
|
||||||
|
return parse_bundle(data, sz);
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
return parse_message(data, sz);
|
||||||
|
break;
|
||||||
|
case '\0':
|
||||||
|
return parse_int_message(data, sz);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return std::errc::bad_message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net
|
||||||
|
{
|
||||||
|
|
||||||
|
struct osc_parser_midi1
|
||||||
|
{
|
||||||
|
static constexpr char typetag = 'm';
|
||||||
|
|
||||||
|
stdx::error
|
||||||
|
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t sz)
|
||||||
|
{
|
||||||
|
if (sz != num_msgs * 4)
|
||||||
|
return std::errc::bad_message;
|
||||||
|
|
||||||
|
auto end = data + sz;
|
||||||
|
for (; data < end; data += 4)
|
||||||
|
on_message(data + 1);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_in final
|
||||||
|
: public midi1::in_api
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
|
struct
|
||||||
|
: input_configuration
|
||||||
|
, dgram_input_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit midi_in(input_configuration&& conf, dgram_input_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
, m_ctx{configuration.io_context}
|
||||||
|
, m_socket{m_ctx.get()}
|
||||||
|
{
|
||||||
|
m_socket.open(boost::asio::ip::udp::v4());
|
||||||
|
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||||
|
|
||||||
|
m_endpoint
|
||||||
|
= {boost::asio::ip::make_address(this->configuration.accept),
|
||||||
|
static_cast<unsigned short>(this->configuration.port)};
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
m_socket.bind(m_endpoint, ec);
|
||||||
|
if (ec != boost::system::error_code{})
|
||||||
|
{
|
||||||
|
client_open_ = std::errc::address_in_use;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_in() override { close_port(); }
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }
|
||||||
|
|
||||||
|
stdx::error open_port(const input_port&, std::string_view port) override
|
||||||
|
{
|
||||||
|
return open_virtual_port(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view port) override
|
||||||
|
{
|
||||||
|
m_portname = std::string(port);
|
||||||
|
|
||||||
|
receive();
|
||||||
|
|
||||||
|
if (m_ctx.is_owned())
|
||||||
|
{
|
||||||
|
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
|
||||||
|
auto wg = boost::asio::make_work_guard(m_ctx);
|
||||||
|
ctx.run();
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive()
|
||||||
|
{
|
||||||
|
m_socket.async_receive_from(
|
||||||
|
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
|
||||||
|
[this](auto ec, std::size_t sz) {
|
||||||
|
if (ec == boost::asio::error::operation_aborted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ec && sz > 0)
|
||||||
|
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
|
||||||
|
|
||||||
|
this->receive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override
|
||||||
|
{
|
||||||
|
// FIXME async close
|
||||||
|
if (m_socket.is_open())
|
||||||
|
m_socket.close();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp absolute_timestamp() const noexcept override
|
||||||
|
{
|
||||||
|
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_bytes(const char* data, std::size_t size)
|
||||||
|
{
|
||||||
|
const auto on_msg = [this](const char* bytes) { this->on_message(bytes); };
|
||||||
|
osc_parser<osc_parser_midi1, decltype(on_msg)> parser{{}, on_msg, m_portname};
|
||||||
|
parser.parse_packet(data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_message(const char* data)
|
||||||
|
{
|
||||||
|
// OSC enforces always 3 bytes
|
||||||
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = false,
|
||||||
|
.absolute_is_monotonic = false,
|
||||||
|
.has_samples = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr auto to_ns = []() {
|
||||||
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch())
|
||||||
|
.count();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ptr = reinterpret_cast<const uint8_t*>(data);
|
||||||
|
m_processing.on_bytes({ptr, ptr + 3}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
midi1::input_state_machine m_processing{this->configuration};
|
||||||
|
|
||||||
|
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
|
||||||
|
boost::asio::ip::udp::endpoint m_endpoint;
|
||||||
|
boost::asio::ip::udp::socket m_socket;
|
||||||
|
|
||||||
|
std::jthread m_thread;
|
||||||
|
|
||||||
|
std::string m_portname;
|
||||||
|
alignas(16) unsigned char m_data[65535];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net_ump
|
||||||
|
{
|
||||||
|
struct osc_parser_midi2
|
||||||
|
{
|
||||||
|
static constexpr char typetag = 'M';
|
||||||
|
|
||||||
|
stdx::error
|
||||||
|
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t byte_sz)
|
||||||
|
{
|
||||||
|
if (byte_sz % 4 != 0)
|
||||||
|
return std::errc::bad_message;
|
||||||
|
|
||||||
|
auto sz = byte_sz / 4;
|
||||||
|
|
||||||
|
auto begin = reinterpret_cast<const uint32_t*>(data);
|
||||||
|
auto end = begin + sz;
|
||||||
|
std::size_t accounted = 0;
|
||||||
|
for (auto it = begin; it < end && accounted < num_msgs;)
|
||||||
|
{
|
||||||
|
const auto N = cmidi2_ump_get_message_size_bytes(it) / 4;
|
||||||
|
switch (N)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
if (it + N <= end)
|
||||||
|
{
|
||||||
|
on_message(it, N);
|
||||||
|
accounted++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return std::errc::bad_message;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return std::errc::bad_message;
|
||||||
|
}
|
||||||
|
it += N;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_in final
|
||||||
|
: public midi2::in_api
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
|
struct
|
||||||
|
: ump_input_configuration
|
||||||
|
, dgram_input_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit midi_in(ump_input_configuration&& conf, dgram_input_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
, m_ctx{configuration.io_context}
|
||||||
|
, m_socket{m_ctx.get()}
|
||||||
|
{
|
||||||
|
m_socket.open(boost::asio::ip::udp::v4());
|
||||||
|
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||||
|
|
||||||
|
m_endpoint
|
||||||
|
= {boost::asio::ip::make_address(this->configuration.accept),
|
||||||
|
static_cast<unsigned short>(this->configuration.port)};
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
m_socket.bind(m_endpoint, ec);
|
||||||
|
if (ec != boost::system::error_code{})
|
||||||
|
{
|
||||||
|
client_open_ = std::errc::address_in_use;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_in() override { close_port(); }
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const input_port&, std::string_view port) override
|
||||||
|
{
|
||||||
|
return open_virtual_port(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view port) override
|
||||||
|
{
|
||||||
|
m_portname = std::string(port);
|
||||||
|
|
||||||
|
receive();
|
||||||
|
|
||||||
|
if (m_ctx.is_owned())
|
||||||
|
{
|
||||||
|
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
|
||||||
|
auto wg = boost::asio::make_work_guard(m_ctx);
|
||||||
|
ctx.run();
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive()
|
||||||
|
{
|
||||||
|
m_socket.async_receive_from(
|
||||||
|
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
|
||||||
|
[this](auto ec, std::size_t sz) {
|
||||||
|
if (ec == boost::asio::error::operation_aborted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ec && sz > 0)
|
||||||
|
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
|
||||||
|
|
||||||
|
this->receive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override
|
||||||
|
{
|
||||||
|
// FIXME async close
|
||||||
|
if (m_ctx.is_owned())
|
||||||
|
m_ctx.get().stop();
|
||||||
|
|
||||||
|
if (m_socket.is_open())
|
||||||
|
m_socket.close();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp absolute_timestamp() const noexcept override
|
||||||
|
{
|
||||||
|
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_bytes(const char* data, std::size_t size)
|
||||||
|
{
|
||||||
|
const auto on_msg
|
||||||
|
= [this](const uint32_t* bytes, std::size_t N) { this->on_message(bytes, N); };
|
||||||
|
osc_parser<osc_parser_midi2, decltype(on_msg)> parser{{}, on_msg, m_portname};
|
||||||
|
parser.parse_packet(data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_message(const uint32_t* ump, std::size_t sz)
|
||||||
|
{
|
||||||
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = false,
|
||||||
|
.absolute_is_monotonic = false,
|
||||||
|
.has_samples = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr auto to_ns = []() {
|
||||||
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch())
|
||||||
|
.count();
|
||||||
|
};
|
||||||
|
|
||||||
|
m_processing.on_bytes({ump, sz}, m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
midi2::input_state_machine m_processing{this->configuration};
|
||||||
|
|
||||||
|
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
|
||||||
|
boost::asio::ip::udp::endpoint m_endpoint;
|
||||||
|
boost::asio::ip::udp::socket m_socket;
|
||||||
|
|
||||||
|
std::jthread m_thread;
|
||||||
|
|
||||||
|
std::string m_portname;
|
||||||
|
alignas(16) unsigned char m_data[65535];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/net/config.hpp>
|
||||||
|
#include <libremidi/backends/net/helpers.hpp>
|
||||||
|
#include <libremidi/cmidi2.hpp>
|
||||||
|
#include <libremidi/detail/midi_out.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio/io_context.hpp>
|
||||||
|
#include <boost/asio/ip/udp.hpp>
|
||||||
|
#include <boost/endian.hpp>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net
|
||||||
|
{
|
||||||
|
|
||||||
|
struct osc_midi1_packet
|
||||||
|
{
|
||||||
|
stdx::error init_packet(std::string_view osc_pattern)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (; i < std::ssize(osc_pattern); i++)
|
||||||
|
if (osc_pattern.data()[i] != 0)
|
||||||
|
bytes[i] = osc_pattern.data()[i];
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
bytes[i] = 0;
|
||||||
|
while (i % 4 != 3)
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
bytes[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes[++i] = ',';
|
||||||
|
bytes[++i] = 'm';
|
||||||
|
bytes[++i] = 0;
|
||||||
|
bytes[++i] = 0;
|
||||||
|
|
||||||
|
// MIDI message (m) spec: port n°, status byte, data 1, data 2
|
||||||
|
bytes[++i] = 0;
|
||||||
|
message_size = ++i + 3;
|
||||||
|
|
||||||
|
return stdx::error{}; // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error deinit()
|
||||||
|
{
|
||||||
|
message_size = 0;
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error set_packet_content(const unsigned char* message, size_t size)
|
||||||
|
{
|
||||||
|
if (message_size == 0)
|
||||||
|
return std::errc::not_connected;
|
||||||
|
|
||||||
|
if (size != 3)
|
||||||
|
return std::errc::message_size;
|
||||||
|
|
||||||
|
std::memcpy(this->bytes + message_size - 3, message, 3);
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const char> get_data() { return std::span(this->bytes, this->message_size); }
|
||||||
|
char bytes[512 + 8 + 8];
|
||||||
|
int message_size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_out final
|
||||||
|
: public midi1::out_api
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct
|
||||||
|
: output_configuration
|
||||||
|
, dgram_output_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
, ctx{configuration.io_context}
|
||||||
|
, m_socket{ctx.get()}
|
||||||
|
{
|
||||||
|
m_socket.open(boost::asio::ip::udp::v4());
|
||||||
|
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||||
|
m_socket.set_option(boost::asio::socket_base::broadcast(true));
|
||||||
|
|
||||||
|
m_endpoint
|
||||||
|
= {boost::asio::ip::make_address(this->configuration.host),
|
||||||
|
static_cast<unsigned short>(this->configuration.port)};
|
||||||
|
|
||||||
|
this->client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out() override { close_port(); }
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }
|
||||||
|
|
||||||
|
stdx::error open_port(const output_port& /* port */, std::string_view portName) override
|
||||||
|
{
|
||||||
|
return open_virtual_port(portName);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
|
{
|
||||||
|
// Random arbitrary limit to avoid abuse
|
||||||
|
if (portName.size() >= 512)
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
|
pkt.init_packet(portName);
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override
|
||||||
|
{
|
||||||
|
pkt.deinit();
|
||||||
|
// FIXME async close
|
||||||
|
if (m_socket.is_open())
|
||||||
|
m_socket.close();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||||
|
{
|
||||||
|
if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
auto dat = pkt.get_data();
|
||||||
|
boost::system::error_code ec;
|
||||||
|
m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);
|
||||||
|
return stdx::error{}; // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error schedule_message(int64_t, const unsigned char*, size_t) override
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::optionally_owned<boost::asio::io_context> ctx;
|
||||||
|
boost::asio::ip::udp::endpoint m_endpoint;
|
||||||
|
boost::asio::ip::udp::socket m_socket;
|
||||||
|
|
||||||
|
osc_midi1_packet pkt;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net_ump
|
||||||
|
{
|
||||||
|
struct osc_midi2_packet
|
||||||
|
{
|
||||||
|
stdx::error init_packet(std::string_view osc_pattern)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (; i < std::ssize(osc_pattern); i++)
|
||||||
|
if (osc_pattern.data()[i] != 0)
|
||||||
|
bytes[i] = osc_pattern.data()[i];
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
bytes[i] = 0;
|
||||||
|
while (i % 4 != 3)
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
bytes[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes[++i] = ',';
|
||||||
|
bytes[++i] = 'M';
|
||||||
|
bytes[++i] = 0;
|
||||||
|
bytes[++i] = 0;
|
||||||
|
|
||||||
|
// MIDI 2 message (M) spec: an UMP
|
||||||
|
header_size = ++i;
|
||||||
|
|
||||||
|
return stdx::error{}; // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error deinit()
|
||||||
|
{
|
||||||
|
header_size = 0;
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error set_packet_content(const uint32_t* message, size_t size)
|
||||||
|
{
|
||||||
|
if (header_size == 0)
|
||||||
|
return std::errc::not_connected;
|
||||||
|
|
||||||
|
if (size > 4)
|
||||||
|
return std::errc::message_size;
|
||||||
|
|
||||||
|
message_size = 4 * size;
|
||||||
|
|
||||||
|
std::memcpy(this->bytes + header_size, message, message_size);
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const char> get_data()
|
||||||
|
{
|
||||||
|
return std::span(this->bytes, this->header_size + this->message_size);
|
||||||
|
}
|
||||||
|
char bytes[512 + 8 + 64];
|
||||||
|
int header_size{};
|
||||||
|
int message_size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_out final
|
||||||
|
: public midi2::out_api
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct
|
||||||
|
: output_configuration
|
||||||
|
, dgram_output_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
midi_out(output_configuration&& conf, dgram_output_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
, ctx{configuration.io_context}
|
||||||
|
, m_socket{ctx.get()}
|
||||||
|
{
|
||||||
|
m_socket.open(boost::asio::ip::udp::v4());
|
||||||
|
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||||
|
m_socket.set_option(boost::asio::socket_base::broadcast(true));
|
||||||
|
|
||||||
|
m_endpoint
|
||||||
|
= {boost::asio::ip::make_address(this->configuration.host),
|
||||||
|
static_cast<unsigned short>(this->configuration.port)};
|
||||||
|
|
||||||
|
this->client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out() override { close_port(); }
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const output_port& /* port */, std::string_view portName) override
|
||||||
|
{
|
||||||
|
return open_virtual_port(portName);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
|
{
|
||||||
|
// Random arbitrary limit to avoid abuse
|
||||||
|
if (portName.size() >= 512)
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
|
pkt.init_packet(portName);
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override
|
||||||
|
{
|
||||||
|
pkt.deinit();
|
||||||
|
if (m_socket.is_open())
|
||||||
|
m_socket.close();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error send_ump(const uint32_t* message, size_t size) override
|
||||||
|
{
|
||||||
|
if (auto err = pkt.set_packet_content(message, size); err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
auto dat = pkt.get_data();
|
||||||
|
boost::system::error_code ec;
|
||||||
|
m_socket.send_to(boost::asio::const_buffer(dat.data(), dat.size()), m_endpoint, 0, ec);
|
||||||
|
return stdx::error{}; // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error schedule_ump(int64_t, const uint32_t*, size_t) override
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::optionally_owned<boost::asio::io_context> ctx;
|
||||||
|
boost::asio::ip::udp::endpoint m_endpoint;
|
||||||
|
boost::asio::ip::udp::socket m_socket;
|
||||||
|
|
||||||
|
osc_midi2_packet pkt;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/dummy.hpp>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net
|
||||||
|
{
|
||||||
|
using net_observer_configuration = libremidi::dummy_configuration;
|
||||||
|
using observer = libremidi::observer_dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net_ump
|
||||||
|
{
|
||||||
|
using net_observer_configuration = libremidi::dummy_configuration;
|
||||||
|
using observer = libremidi::observer_dummy;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/dummy.hpp>
|
||||||
|
#include <libremidi/backends/net/config.hpp>
|
||||||
|
#include <libremidi/backends/net/midi_in.hpp>
|
||||||
|
#include <libremidi/backends/net/midi_out.hpp>
|
||||||
|
#include <libremidi/backends/net/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net
|
||||||
|
{
|
||||||
|
struct backend
|
||||||
|
{
|
||||||
|
using midi_in = libremidi::net::midi_in;
|
||||||
|
using midi_out = libremidi::net::midi_out;
|
||||||
|
using midi_observer = libremidi::net::observer;
|
||||||
|
using midi_in_configuration = libremidi::net::dgram_input_configuration;
|
||||||
|
using midi_out_configuration = libremidi::net::dgram_output_configuration;
|
||||||
|
using midi_observer_configuration = libremidi::net::net_observer_configuration;
|
||||||
|
static const constexpr auto API = libremidi::API::NETWORK;
|
||||||
|
static const constexpr std::string_view name = "network";
|
||||||
|
static const constexpr std::string_view display_name = "Network";
|
||||||
|
|
||||||
|
static inline bool available() noexcept { return true; }
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/net/config.hpp>
|
||||||
|
#include <libremidi/backends/net/midi_in.hpp>
|
||||||
|
#include <libremidi/backends/net/midi_out.hpp>
|
||||||
|
#include <libremidi/backends/net/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
NAMESPACE_LIBREMIDI::net_ump
|
||||||
|
{
|
||||||
|
struct backend
|
||||||
|
{
|
||||||
|
using midi_in = libremidi::net_ump::midi_in;
|
||||||
|
using midi_out = libremidi::net_ump::midi_out;
|
||||||
|
using midi_observer = libremidi::net_ump::observer;
|
||||||
|
using midi_in_configuration = libremidi::net_ump::dgram_input_configuration;
|
||||||
|
using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;
|
||||||
|
using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;
|
||||||
|
static const constexpr auto API = libremidi::API::NETWORK_UMP;
|
||||||
|
static const constexpr std::string_view name = "network (UMP)";
|
||||||
|
static const constexpr std::string_view display_name = "Network (UMP)";
|
||||||
|
|
||||||
|
static inline bool available() noexcept { return true; }
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,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,8 +27,8 @@ struct pipewire_input_configuration
|
|||||||
|
|
||||||
pw_main_loop* context{};
|
pw_main_loop* context{};
|
||||||
pw_filter* filter{};
|
pw_filter* filter{};
|
||||||
std::function<void(pipewire_callback)> set_process_func;
|
std::function<void(pipewire_callback)> set_process_func{};
|
||||||
std::function<void(int64_t)> clear_process_func;
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct pipewire_output_configuration
|
struct pipewire_output_configuration
|
||||||
@@ -37,8 +37,8 @@ struct pipewire_output_configuration
|
|||||||
|
|
||||||
pw_main_loop* context{};
|
pw_main_loop* context{};
|
||||||
pw_filter* filter{};
|
pw_filter* filter{};
|
||||||
std::function<void(pipewire_callback)> set_process_func;
|
std::function<void(pipewire_callback)> set_process_func{};
|
||||||
std::function<void(int64_t)> clear_process_func;
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
|
||||||
int64_t output_buffer_size{65536};
|
int64_t output_buffer_size{65536};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/backends/linux/pipewire.hpp>
|
#include <libremidi/backends/linux/pipewire.hpp>
|
||||||
#include <libremidi/detail/memory.hpp>
|
#include <libremidi/detail/memory.hpp>
|
||||||
|
#include <libremidi/error.hpp>
|
||||||
|
|
||||||
#include <pipewire/filter.h>
|
#include <pipewire/filter.h>
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
@@ -17,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>;
|
||||||
@@ -78,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)
|
||||||
{
|
{
|
||||||
@@ -122,13 +124,13 @@ 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);
|
||||||
if (!this->main_loop)
|
if (!this->main_loop)
|
||||||
{
|
{
|
||||||
// libremidi::logger().error("PipeWire: main_loop_new failed!");
|
// libremidi::logger().libremidi_handle_error("main_loop_new failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
initialize();
|
initialize();
|
||||||
@@ -139,28 +141,28 @@ struct pipewire_context
|
|||||||
this->lp = pw.main_loop_get_loop(this->main_loop);
|
this->lp = pw.main_loop_get_loop(this->main_loop);
|
||||||
if (!lp)
|
if (!lp)
|
||||||
{
|
{
|
||||||
// libremidi::logger().error("PipeWire: main_loop_get_loop failed!");
|
// libremidi::logger().libremidi_handle_error("main_loop_get_loop failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->context = pw.context_new(lp, nullptr, 0);
|
this->context = pw.context_new(lp, nullptr, 0);
|
||||||
if (!this->context)
|
if (!this->context)
|
||||||
{
|
{
|
||||||
// libremidi::logger().error("PipeWire: context_new failed!");
|
// libremidi::logger().libremidi_handle_error("context_new failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->core = pw.context_connect(this->context, nullptr, 0);
|
this->core = pw.context_connect(this->context, nullptr, 0);
|
||||||
if (!this->core)
|
if (!this->core)
|
||||||
{
|
{
|
||||||
// libremidi::logger().error("PipeWire: context_connect failed!");
|
// libremidi::logger().libremidi_handle_error("context_connect failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);
|
this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);
|
||||||
if (!this->registry)
|
if (!this->registry)
|
||||||
{
|
{
|
||||||
// libremidi::logger().error("PipeWire: core_get_registry failed!");
|
// libremidi::logger().libremidi_handle_error("core_get_registry failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,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,
|
||||||
@@ -297,7 +299,6 @@ struct pipewire_context
|
|||||||
|
|
||||||
if (!proxy)
|
if (!proxy)
|
||||||
{
|
{
|
||||||
std::cerr << "PipeWire: could not allocate link\n";
|
|
||||||
pw.properties_free(props);
|
pw.properties_free(props);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -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}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -476,7 +481,7 @@ struct pipewire_filter
|
|||||||
PW_KEY_MEDIA_TYPE, "Midi",
|
PW_KEY_MEDIA_TYPE, "Midi",
|
||||||
PW_KEY_MEDIA_CATEGORY, "Filter",
|
PW_KEY_MEDIA_CATEGORY, "Filter",
|
||||||
PW_KEY_MEDIA_ROLE, "DSP",
|
PW_KEY_MEDIA_ROLE, "DSP",
|
||||||
PW_KEY_MEDIA_NAME, "libremidi",
|
PW_KEY_MEDIA_NAME, "",
|
||||||
#if defined(PW_KEY_NODE_LOCK_RATE)
|
#if defined(PW_KEY_NODE_LOCK_RATE)
|
||||||
PW_KEY_NODE_LOCK_RATE, "true",
|
PW_KEY_NODE_LOCK_RATE, "true",
|
||||||
#endif
|
#endif
|
||||||
@@ -498,7 +503,8 @@ struct pipewire_filter
|
|||||||
pw.filter_destroy(this->filter);
|
pw.filter_destroy(this->filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void create_local_port(std::string_view port_name, spa_direction direction)
|
stdx::error
|
||||||
|
create_local_port(std::string_view port_name, spa_direction direction, const char* format)
|
||||||
{
|
{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
this->port = (struct port*)pw.filter_add_port(
|
this->port = (struct port*)pw.filter_add_port(
|
||||||
@@ -507,15 +513,17 @@ 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);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
assert(port);
|
if (!port)
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_port_buffer(int bytes)
|
void set_port_buffer(int64_t bytes)
|
||||||
{
|
{
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
struct spa_pod_builder builder;
|
struct spa_pod_builder builder;
|
||||||
@@ -537,31 +545,41 @@ struct pipewire_filter
|
|||||||
pw.filter_update_params(this->filter, this->port, params, 1);
|
pw.filter_update_params(this->filter, this->port, params, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove_port()
|
stdx::error remove_port()
|
||||||
{
|
{
|
||||||
assert(this->port);
|
assert(this->port);
|
||||||
pw.filter_remove_port(this->port);
|
int ret = pw.filter_remove_port(this->port);
|
||||||
this->port = nullptr;
|
this->port = nullptr;
|
||||||
|
return from_errc(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
void rename_port(std::string_view port_name)
|
stdx::error rename_port(std::string_view port_name)
|
||||||
|
{
|
||||||
|
if (this->port)
|
||||||
{
|
{
|
||||||
assert(this->port);
|
|
||||||
spa_dict_item items[1] = {
|
spa_dict_item items[1] = {
|
||||||
SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name.data()),
|
SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name.data()),
|
||||||
};
|
};
|
||||||
|
|
||||||
auto properties = SPA_DICT_INIT(items, 1);
|
auto properties = SPA_DICT_INIT(items, 1);
|
||||||
pw.filter_update_properties(this->filter, this->port, &properties);
|
int ret = pw.filter_update_properties(this->filter, this->port, &properties);
|
||||||
this->port = nullptr;
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::errc::not_connected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_filter()
|
[[nodiscard]] stdx::error start_filter()
|
||||||
{
|
{
|
||||||
if (pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0)
|
if (int ret = pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0); ret < 0)
|
||||||
{
|
{
|
||||||
std::cerr << "can't connect\n";
|
return from_errc(ret);
|
||||||
return;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
#include <libremidi/backends/linux/helpers.hpp>
|
#include <libremidi/backends/linux/helpers.hpp>
|
||||||
#include <libremidi/backends/linux/pipewire.hpp>
|
#include <libremidi/backends/linux/pipewire.hpp>
|
||||||
|
#include <libremidi/backends/pipewire/config.hpp>
|
||||||
#include <libremidi/backends/pipewire/context.hpp>
|
#include <libremidi/backends/pipewire/context.hpp>
|
||||||
#include <libremidi/detail/memory.hpp>
|
#include <libremidi/detail/memory.hpp>
|
||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
#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
|
||||||
{
|
{
|
||||||
@@ -55,17 +59,17 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Self>
|
template <typename Self>
|
||||||
void create_filter(Self& self)
|
stdx::error create_filter(Self& self)
|
||||||
{
|
{
|
||||||
if (this->filter)
|
if (this->filter)
|
||||||
return;
|
return stdx::error{};
|
||||||
|
|
||||||
auto& configuration = self.configuration;
|
auto& configuration = self.configuration;
|
||||||
if (configuration.context && configuration.filter && configuration.set_process_func)
|
if (configuration.context && configuration.filter && configuration.set_process_func)
|
||||||
{
|
{
|
||||||
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())
|
||||||
@@ -90,8 +94,9 @@ struct pipewire_helpers
|
|||||||
#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);
|
||||||
this->filter->start_filter();
|
return this->filter->start_filter();
|
||||||
}
|
}
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Self>
|
template <typename Self>
|
||||||
@@ -117,10 +122,10 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Self>
|
template <typename Self>
|
||||||
int create_context(Self& self)
|
stdx::error create_context(Self& self)
|
||||||
{
|
{
|
||||||
if (this->global_context)
|
if (this->global_context)
|
||||||
return 0;
|
return stdx::error{};
|
||||||
|
|
||||||
// Initialize PipeWire client
|
// Initialize PipeWire client
|
||||||
auto& configuration = self.configuration;
|
auto& configuration = self.configuration;
|
||||||
@@ -133,7 +138,10 @@ struct pipewire_helpers
|
|||||||
this->global_instance = std::make_shared<pipewire_instance>();
|
this->global_instance = std::make_shared<pipewire_instance>();
|
||||||
this->global_context = std::make_shared<pipewire_context>(this->global_instance);
|
this->global_context = std::make_shared<pipewire_context>(this->global_instance);
|
||||||
}
|
}
|
||||||
return 0;
|
if (!this->global_context->main_loop)
|
||||||
|
return std::errc::connection_refused;
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy_context()
|
void destroy_context()
|
||||||
@@ -170,7 +178,9 @@ struct pipewire_helpers
|
|||||||
{
|
{
|
||||||
int result = pw_loop_iterate(lp, 0);
|
int result = pw_loop_iterate(lp, 0);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
std::cerr << "pw_loop_iterate: " << spa_strerror(result) << "\n";
|
{
|
||||||
|
LIBREMIDI_LOG(spa_strerror(result));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fds[0].revents = 0;
|
fds[0].revents = 0;
|
||||||
}
|
}
|
||||||
@@ -190,7 +200,8 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Self>
|
template <typename Self>
|
||||||
bool create_local_port(Self& self, std::string_view portName, spa_direction direction)
|
stdx::error create_local_port(
|
||||||
|
Self& self, std::string_view portName, spa_direction direction, const char* format)
|
||||||
{
|
{
|
||||||
assert(this->global_context);
|
assert(this->global_context);
|
||||||
assert(this->filter);
|
assert(this->filter);
|
||||||
@@ -200,22 +211,23 @@ struct pipewire_helpers
|
|||||||
|
|
||||||
if (!this->filter->port)
|
if (!this->filter->port)
|
||||||
{
|
{
|
||||||
this->filter->create_local_port(portName.data(), direction);
|
auto ret = this->filter->create_local_port(portName.data(), direction, format);
|
||||||
}
|
if (ret != stdx::error{})
|
||||||
|
|
||||||
if (!this->filter->port)
|
|
||||||
{
|
{
|
||||||
self.template error<driver_error>(self.configuration, "PipeWire: error creating port");
|
self.libremidi_handle_error(self.configuration, "error creating port");
|
||||||
return false;
|
return ret;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_callbacks(const observer_configuration& conf)
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -226,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;
|
||||||
@@ -248,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -293,12 +305,12 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void do_close_port()
|
stdx::error do_close_port()
|
||||||
{
|
{
|
||||||
if (!this->filter)
|
if (!this->filter)
|
||||||
return;
|
return stdx::error{};
|
||||||
if (!this->filter->port)
|
if (!this->filter->port)
|
||||||
return;
|
return stdx::error{};
|
||||||
|
|
||||||
if (!this->global_context->owns_main_loop)
|
if (!this->global_context->owns_main_loop)
|
||||||
{
|
{
|
||||||
@@ -307,13 +319,19 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
unlink_ports();
|
unlink_ports();
|
||||||
this->filter->remove_port();
|
return this->filter->remove_port();
|
||||||
}
|
}
|
||||||
|
|
||||||
void rename_port(std::string_view port_name)
|
stdx::error rename_port(std::string_view port_name)
|
||||||
{
|
{
|
||||||
if (this->filter)
|
if (this->filter)
|
||||||
this->filter->rename_port(port_name);
|
{
|
||||||
|
return this->filter->rename_port(port_name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::errc::not_connected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void unlink_ports()
|
void unlink_ports()
|
||||||
@@ -325,7 +343,7 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool link_ports(auto& self, const input_port& in_port)
|
stdx::error link_ports(auto& self, const input_port& in_port)
|
||||||
{
|
{
|
||||||
// Wait for the pipewire server to send us back our node's info
|
// Wait for the pipewire server to send us back our node's info
|
||||||
for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < 1000; i++)
|
||||||
@@ -335,19 +353,13 @@ struct pipewire_helpers
|
|||||||
auto& midi = this->global_context->current_graph.software_midi;
|
auto& midi = this->global_context->current_graph.software_midi;
|
||||||
auto node_it = midi.find(this_node);
|
auto node_it = midi.find(this_node);
|
||||||
if (node_it == midi.end())
|
if (node_it == midi.end())
|
||||||
{
|
return std::errc::invalid_argument;
|
||||||
std::cerr << "Node " << this_node << " not found! \n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the pipewire server to send us back our node's ports
|
// Wait for the pipewire server to send us back our node's ports
|
||||||
this->filter->synchronize_ports(node_it->second);
|
this->filter->synchronize_ports(node_it->second);
|
||||||
|
|
||||||
if (node_it->second.inputs.empty())
|
if (node_it->second.inputs.empty())
|
||||||
{
|
return std::errc::no_link;
|
||||||
std::cerr << "Node " << this_node << " has no ports! \n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link ports
|
// Link ports
|
||||||
const auto& p = node_it->second.inputs.front();
|
const auto& p = node_it->second.inputs.front();
|
||||||
@@ -355,16 +367,16 @@ struct pipewire_helpers
|
|||||||
pw_loop_iterate(this->global_context->lp, 1);
|
pw_loop_iterate(this->global_context->lp, 1);
|
||||||
if (!link)
|
if (!link)
|
||||||
{
|
{
|
||||||
self.template error<invalid_parameter_error>(
|
self.libremidi_handle_error(
|
||||||
self.configuration,
|
self.configuration,
|
||||||
"PipeWire: could not connect to port: " + in_port.port_name + " -> " + p.port_name);
|
"could not connect to port: " + in_port.port_name + " -> " + p.port_name);
|
||||||
return false;
|
return std::errc::no_link;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool link_ports(auto& self, const output_port& out_port)
|
stdx::error link_ports(auto& self, const output_port& out_port)
|
||||||
{
|
{
|
||||||
// Wait for the pipewire server to send us back our node's info
|
// Wait for the pipewire server to send us back our node's info
|
||||||
for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < 1000; i++)
|
||||||
@@ -375,8 +387,7 @@ struct pipewire_helpers
|
|||||||
auto node_it = midi.find(this_node);
|
auto node_it = midi.find(this_node);
|
||||||
if (node_it == midi.end())
|
if (node_it == midi.end())
|
||||||
{
|
{
|
||||||
std::cerr << "Node " << this_node << " not found! \n";
|
return std::errc::invalid_argument;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the pipewire server to send us back our node's ports
|
// Wait for the pipewire server to send us back our node's ports
|
||||||
@@ -384,8 +395,7 @@ struct pipewire_helpers
|
|||||||
|
|
||||||
if (node_it->second.outputs.empty())
|
if (node_it->second.outputs.empty())
|
||||||
{
|
{
|
||||||
std::cerr << "Node " << this_node << " has no ports! \n";
|
return std::errc::no_link;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link ports
|
// Link ports
|
||||||
@@ -394,16 +404,16 @@ struct pipewire_helpers
|
|||||||
pw_loop_iterate(this->global_context->lp, 1);
|
pw_loop_iterate(this->global_context->lp, 1);
|
||||||
if (!link)
|
if (!link)
|
||||||
{
|
{
|
||||||
self.template error<invalid_parameter_error>(
|
self.libremidi_handle_error(
|
||||||
self.configuration,
|
self.configuration,
|
||||||
"PipeWire: could not connect to port: " + p.port_name + " -> " + out_port.port_name);
|
"could not connect to port: " + p.port_name + " -> " + out_port.port_name);
|
||||||
return false;
|
return std::errc::no_link;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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>
|
||||||
{
|
{
|
||||||
@@ -420,6 +430,7 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {{
|
return {{
|
||||||
|
.api = Api,
|
||||||
.client = 0,
|
.client = 0,
|
||||||
.port = port.id,
|
.port = port.id,
|
||||||
.manufacturer = "",
|
.manufacturer = "",
|
||||||
@@ -431,8 +442,11 @@ 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 pipewire_context& ctx) noexcept -> std::vector<
|
static auto get_ports(
|
||||||
|
std::string_view format, const observer_configuration& conf,
|
||||||
|
const pipewire_context& ctx) noexcept
|
||||||
|
-> std::vector<
|
||||||
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
||||||
{
|
{
|
||||||
std::vector<std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
std::vector<std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
||||||
@@ -440,20 +454,25 @@ struct pipewire_helpers
|
|||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard _{ctx.current_graph.mtx};
|
std::lock_guard _{ctx.current_graph.mtx};
|
||||||
|
if (conf.track_any || conf.track_hardware)
|
||||||
for (auto& node : ctx.current_graph.physical_midi)
|
for (auto& node : ctx.current_graph.physical_midi)
|
||||||
{
|
{
|
||||||
for (auto& p :
|
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>(p));
|
if (port.format.find(format) != std::string::npos)
|
||||||
|
ret.push_back(to_port_info<Direction, Api>(port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conf.track_any || conf.track_virtual)
|
||||||
for (auto& node : ctx.current_graph.software_midi)
|
for (auto& node : ctx.current_graph.software_midi)
|
||||||
{
|
{
|
||||||
for (auto& p :
|
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>(p));
|
if (port.format.find(format) != std::string::npos)
|
||||||
|
ret.push_back(to_port_info<Direction, Api>(port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,3 +481,7 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user