85 Commits

Author SHA1 Message Date
Sebastian 6aef199984 Serialization of handles working! 2024-12-20 01:57:28 +01:00
Sebastian c3543cffce Workaround for vector_shared_ptr_templated serialization mess. 2024-12-19 01:00:38 +01:00
Sebastian 9b75b36c41 Serialization of NodeHandle<T> stays a mystery... 2024-12-16 02:10:58 +01:00
Sebastian 996ed16c01 Better centered text. Pass by reference for midi variable is not going to work with json serialization. 2024-12-15 03:17:10 +01:00
Sebastian 96c2bde0e1 Vector UI on its way! 2024-12-15 01:47:53 +01:00
Sebastian b2ed35a704 ControlNodes init. Feels like a good idea! 2024-12-13 01:15:55 +01:00
Sebastian 7f2d16932f Resize on hover taken out. ModNode introduced but needs a better ui approach. 2024-12-13 00:30:31 +01:00
Sebastian bc8c433e47 Fun with config nodes that modifiy other config nodes. Circular connections lead to a crash. 2024-12-11 01:16:06 +01:00
Sebastian 456528f230 Some tweaks, not much progress. 2024-12-10 00:00:53 +01:00
Sebastian ddf9e640b1 Good working protoype with config nodes and a experimental channel setting ui. 2024-12-09 01:40:13 +01:00
Sebastian 3b08037f92 Config nodes do what they should do now. UX is still off, modifier keys do different things on MidiNodes and ConfigNodes. 2024-12-08 14:21:46 +01:00
Sebastian 558829f619 Config nodes not working yet, but everything is prepared. 2024-12-08 02:25:57 +01:00
Sebastian d9c97be326 Inbetween states. Midi nodes and config nodes use same interface now. 2024-12-07 22:09:06 +01:00
Sebastian 3b0e7e986f Config nodes change midi notes. Better would be midi nodes read from config nodes. 2024-12-07 15:28:33 +01:00
Sebastian 36273ba7ac Working on config nodes. They exist now, but do nothing yet. 2024-12-07 03:44:03 +01:00
Sebastian ade33b0608 Context menu is back, but not quite yet. 2024-12-05 23:39:01 +01:00
Sebastian 638df54ee6 Connecting works again (almost). Connectors need to be copied when splitting edges. 2024-12-05 01:25:00 +01:00
Sebastian b6c2029386 Running again. Next step: making connections work again! 2024-12-05 00:01:42 +01:00
Sebastian 036c6f4723 Running again after weak_ptr refactoring. No connecting of nodes atm. Copy/Paste might not work, too. 2024-12-04 01:43:06 +01:00
Sebastian 1fde652495 More weak_ptr and clipboard shenanigans :) 2024-12-03 11:08:40 +01:00
Sebastian 0040956801 Rework to weak_ptr from lots of shared_ptr. 2024-12-03 01:25:49 +01:00
Sebastian 722848d46b Deleting Connectable from the connectables vector is resulting in segfault. 2024-12-02 00:28:44 +01:00
Sebastian f926b95092 Still the ownership problem. 5, why 5? It should be 3... 2024-12-01 01:45:36 +01:00
Sebastian b6a049ea0d Connector shared_ptr keeps one owner and i don't know where it is! 2024-12-01 00:55:59 +01:00
Sebastian c63ee3eabe Double connections fixed 2024-11-30 12:47:33 +01:00
Sebastian c7e76ae7ac Tidied up main 2024-11-30 03:01:26 +01:00
Sebastian fcc5d74e18 Node interactions working again, graph editing functions not complete yet 2024-11-30 02:54:40 +01:00
Sebastian 635c94e3cf On the road to connector -> connector with edges... 2024-11-28 08:49:35 +01:00
Sebastian 492971ed4f Dragging and selecting works. Copy / Paste does not copy connectors yet. 2024-11-28 01:08:04 +01:00
Sebastian 813958002b Drag selection and snap to grid ok. Deselect not working though... 2024-11-26 01:58:09 +01:00
Sebastian 9c0aac8618 Dragging and selecting works. Dragging the selection not yet. 2024-11-25 00:53:10 +01:00
Sebastian f04838bffa Super simple events added. Seems to be a good way for handling interaction. Modified a few base classes. 2024-11-24 03:08:06 +01:00
Sebastian 62a5c53969 Mini commit 2024-11-23 00:54:31 +01:00
Sebastian b12f1d0f9d Might be a better way to handle hovering, dragging and mouse interaction in general 2024-11-22 00:24:31 +01:00
Sebastian a6b6ff1d16 Introduction of mouse.h for z-index mouse events 2024-11-21 23:58:35 +01:00
Sebastian bfc14d5f6e copy/paste partially. z-index experiments, but not working yet. 2024-11-21 02:03:32 +01:00
Sebastian df91a1b107 Back to work! 2024-11-20 11:13:13 +01:00
Sebastian 5779c93339 Clipboard introduced, but not incorporated yet. Some defines for convenience and better readability. 2024-11-19 01:04:06 +01:00
Sebastian 22842f9deb Drag selection working again - not much progress 2024-11-18 01:57:10 +01:00
Sebastian e9633ff19d Some progress on selecting, hovering but still buggy and not a good design really. 2024-11-17 02:40:31 +01:00
Sebastian 55926a5021 Selection working partially. Needs global z-index hovering to work reliably. 2024-11-16 00:29:01 +01:00
Sebastian 96526f709a preparing for node->connector->edge->connector->node 2024-11-15 02:09:06 +01:00
Sebastian 9cccbab703 refactored from main to canvas.h - basic functionality working 2024-11-15 01:49:10 +01:00
Sebastian ab34081dbb raw_mouse changed to screen_mouse 2024-11-14 23:21:04 +01:00
Sebastian 2e103dc561 global mouse moved to state and removed some GetMousePosition()s. 2024-11-14 01:37:16 +01:00
Sebastian 5d3b72d4ef Major refactoring: State, mouse and more... 2024-11-14 00:55:09 +01:00
Sebastian 300001d385 Global snap to grid. 2024-11-13 15:35:39 +01:00
Sebastian e6f370ba4f Connectables working now. On to more refactoring! 2024-11-13 14:39:46 +01:00
Sebastian 1e0a131169 connecting works, but position is off. addConnector is the problem 2024-11-13 01:58:44 +01:00
Sebastian 96082e1190 connecting does weird things! 2024-11-12 01:42:05 +01:00
Sebastian 31a1a264d4 little progress on the connector idea 2024-11-11 02:11:54 +01:00
Sebastian 4f48aa2d5a dragging works. introducing connectors maybe 2024-11-10 01:56:33 +01:00
Sebastian 71dc08d21f interactable base class 2024-11-09 23:56:40 +01:00
Sebastian cdc0fb7075 working on node ui 2024-11-08 01:43:06 +01:00
Sebastian 529106e815 introduction of config nodes 2024-11-07 00:56:33 +01:00
Sebastian 96a19d55ef signal ui is next! 2024-11-07 00:01:54 +01:00
Sebastian e519b7c758 scale groups of nodes is so cool! 2024-11-06 15:35:25 +01:00
Sebastian 95620a5f07 two node snap for triangles 2024-11-06 02:28:37 +01:00
Sebastian @ HfG 9878b7b2a4 alsa lib removed 2024-11-05 17:44:12 +01:00
Sebastian 4a346350f4 static id fixed 2024-11-05 00:51:25 +01:00
Sebastian @ HfG dc7eae1931 changed the name! 2024-11-04 19:28:12 +01:00
Sebastian 0c797807dd id is not set correctly, fix needed 2024-11-04 09:53:08 +01:00
Sebastian 374efbe47c state save/load working! signal pause/play button added, and speed control is now better. 2024-11-04 01:00:59 +01:00
Sebastian bfd8bb192e partial reload from json working, but edges make problems 2024-11-03 15:15:42 +01:00
Sebastian 7bdd68bdcc json is loading, but does nothing yet 2024-11-03 03:48:49 +01:00
Sebastian a4ea20b2b3 still in the middle of json export, last attempt did not work 2024-11-02 23:12:38 +01:00
Sebastian 522e9d456a json serialization beginnings, but still much to do 2024-11-02 15:52:16 +01:00
Sebastian 70d0a186a4 split edge: check, copy paste: check with a little bug 2024-11-02 02:06:26 +01:00
Sebastian 41cad27806 copy paste mysteries with ghost nodes and disappearing connections 2024-11-01 14:46:51 +01:00
Sebastian a10335ed47 polar snapping now very goood! 2024-11-01 14:08:27 +01:00
Sebastian 19b2c9549f conversion to smart pointers done but still buggy 2024-11-01 02:38:47 +01:00
Sebastian c0eb7c98ba heavy restructuring with smart pointers, not finished 2024-10-31 01:43:00 +01:00
Sebastian f7a13e48b7 fishy loss of changes here... 2024-10-30 14:55:24 +01:00
Sebastian ba5874837c connection split button added, but no functionality yes 2024-10-30 11:27:37 +01:00
Sebastian c0f5138765 a little clean up 2024-10-30 02:03:03 +01:00
Sebastian ad7b0e4524 GEO mode for signals takes direction of next connection into account 2024-10-30 02:00:40 +01:00
Sebastian c63eeed1b4 polar snapping (almost bug free) 2024-10-29 00:50:39 +01:00
Sebastian fc48c2e7ed merge win32 2024-10-28 21:52:28 +01:00
Sebastian @ HfG 1e3755148c working on win32 uwp! and libremidi update 2024-10-28 19:24:18 +01:00
Sebastian 06cb6d8319 distance to closest node displayed, timing problems with midi notes being sent 1 to 3ms too early, or too late 2024-10-28 02:07:47 +01:00
Sebastian 58766cc373 node ui note / gate display changed 2024-10-27 00:48:13 +02:00
Sebastian bd17288ab0 node ui and interaction improvements 2024-10-27 00:40:57 +02:00
Sebastian cb08f69e3b copy nodes with connections 2024-10-26 16:31:23 +02:00
Sebastian 53289bb9e2 tidied up main 2024-10-26 02:28:49 +02:00
Sebastian d747f80d11 cosmetics 2024-10-26 02:25:46 +02:00
146 changed files with 40679 additions and 3460 deletions
+2
View File
@@ -1,4 +1,6 @@
build/** build/**
.cache .cache
viseq viseq
viseq.exe
compile_commands.json compile_commands.json
*.json
+2924
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,14 +1,14 @@
CXX = g++ CXX = g++
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1
CXXINC = -Iinclude CXXINC = -isysteminclude
LDLIBS = -lraylib -lasound -pthread LDLIBS = -lraylib -lasound -llibremidi -pthread
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib
hdr_files = ${wildcard src/*.h} hdr_files = ${wildcard src/*.h}
src_files = ${wildcard src/*.cpp} src_files = ${wildcard src/*.cpp}
src_files += ${wildcard include/libremidi/*.cpp} #src_files += ${wildcard include/libremidi/*.cpp}
all: viseq all: viseq
@@ -20,13 +20,13 @@ $(src_files): %.cpp : %_src
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC) $(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
$(hdr_files): %.h : %_hdr $(hdr_files): %.h : %_hdr
@echo "G++ >---------- $(*F).h -----------<" $(NOECHO) $(NOOP)
%_src: %_src:
@echo "G++ >---------- $(*F).cpp -----------<" $(NOECHO) $(NOOP)
%_hdr: %_hdr:
@echo "G++ >---------- $(*F).h -----------<" $(NOECHO) $(NOOP)
clean: clean:
rm -f build/* rm -f build/*
+33
View File
@@ -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
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#if __cplusplus
extern "C" {
#endif
//! MIDI API specifier arguments.
//! To get information on which feature is supported by each back-end, check their backend file
//! in e.g. backends/winmm.hpp, etc.
typedef enum libremidi_api
{
UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */
// MIDI 1.0 APIs
COREMIDI = 0x1, /*!< macOS CoreMidi API. */
ALSA_SEQ, /*!< Linux ALSA Sequencer API. */
ALSA_RAW, /*!< Linux Raw ALSA API. */
JACK_MIDI, /*!< JACK Low-Latency MIDI Server API. */
WINDOWS_MM, /*!< Microsoft Multimedia MIDI API. */
WINDOWS_UWP, /*!< Microsoft WinRT MIDI API. */
WEBMIDI, /*!< Web MIDI API through Emscripten */
PIPEWIRE, /*!< PipeWire */
KEYBOARD, /*!< Computer keyboard input */
NETWORK, /*!< MIDI over IP */
// MIDI 2.0 APIs
ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */
ALSA_SEQ_UMP, /*!< Linux ALSA Sequencer API for MIDI 2.0 */
COREMIDI_UMP, /*!< macOS CoreMidi API for MIDI 2.0. Requires macOS 11+ */
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
KEYBOARD_UMP, /*!< Computer keyboard input */
NETWORK_UMP, /*!< MIDI2 over IP */
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
} libremidi_api;
#if __cplusplus
}
#endif
+17 -22
View File
@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <libremidi/api-c.h>
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <any>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -9,28 +11,7 @@ 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.
@@ -50,6 +31,9 @@ LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;
*/ */
LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept; LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;
LIBREMIDI_EXPORT
libremidi::API midi_api(const std::any& conf);
//! A static function to determine the current version. //! A static function to determine the current version.
LIBREMIDI_EXPORT std::string_view get_version() noexcept; LIBREMIDI_EXPORT std::string_view get_version() noexcept;
@@ -60,6 +44,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.
+71 -8
View File
@@ -1,4 +1,7 @@
#pragma once #pragma once
#include <libremidi/config.hpp>
#include <any>
#include <tuple> #include <tuple>
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>) #if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
@@ -44,9 +47,10 @@
#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>) #endif
#if defined(LIBREMIDI_WINMIDI)
#include <libremidi/backends/winmidi.hpp> #include <libremidi/backends/winmidi.hpp>
#endif
#endif #endif
#if defined(LIBREMIDI_EMSCRIPTEN) #if defined(LIBREMIDI_EMSCRIPTEN)
@@ -55,6 +59,15 @@
#include <libremidi/backends/dummy.hpp> #include <libremidi/backends/dummy.hpp>
#if defined(LIBREMIDI_KEYBOARD)
#include <libremidi/backends/keyboard.hpp>
#endif
#if defined(LIBREMIDI_NETWORK)
#include <libremidi/backends/network.hpp>
#include <libremidi/backends/network_ump.hpp>
#endif
namespace libremidi 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
@@ -100,6 +113,14 @@ 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 #endif
, ,
dummy_backend{}); dummy_backend{});
@@ -135,15 +156,13 @@ 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 #endif
#if defined(LIBREMIDI_EMSCRIPTEN) #if defined(LIBREMIDI_NETWORK)
,
net_ump::backend{}
#endif #endif
, ,
dummy_backend{}); dummy_backend{});
@@ -183,5 +202,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, std::any& api_conf)
{
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
{
f(*conf);
return true;
}
return false;
};
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
return;
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
}
void for_output_configuration(auto f, std::any& api_conf)
{
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))
{
f(*conf);
return true;
}
return false;
};
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
return;
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
}
void for_observer_configuration(auto f, std::any& api_conf)
{
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))
{
f(*conf);
return true;
}
return false;
};
if (std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi1::available_backends))
return;
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
}
} }
} }
@@ -61,6 +61,7 @@ struct manual_poll_parameters
struct alsa_raw_input_configuration 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
+28 -60
View File
@@ -3,14 +3,9 @@
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <libremidi/detail/observer.hpp> #include <libremidi/detail/observer.hpp>
#include <chrono>
#include <functional> #include <functional>
#include <iostream>
#include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <thread>
#include <vector> #include <vector>
// Credits: greatly inspired from // Credits: greatly inspired from
@@ -87,39 +82,16 @@ 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 +113,9 @@ 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, "Cannot get rawmidi information: " + device_identifier(card, device, sub)
snd.strerror(status)); + " : " + snd.strerror(status));
return status; return status;
} }
else else
@@ -185,26 +157,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 +182,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 +196,22 @@ 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"s + name + " : " + snd.strerror(status));
"cannot open control for card",
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 +231,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;
} }
+58 -52
View File
@@ -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(&params); snd_rawmidi_params_alloca(&params);
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
@@ -210,26 +202,33 @@ public:
{ {
if (this->termination_event < 0) if (this->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->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(), period);
if (err == -EAGAIN) if (err == -EAGAIN)
continue; continue;
else if (err < 0) else if (err < 0)
@@ -245,7 +244,7 @@ private:
} }
} }
[[nodiscard]] ssize_t start_thread() [[nodiscard]] stdx::error start_thread()
{ {
try try
{ {
@@ -258,36 +257,37 @@ private:
this->thread_ = std::thread{ this->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,
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what()); "error starting MIDI input thread: "s + e.what());
return false; return e.code();
} }
return true; return stdx::error{};
} }
bool open_port(const input_port& port, std::string_view /*name*/) override stdx::error open_port(const input_port& port, std::string_view /*name*/) override
{ {
if (const int err = midi_in_impl::init_port(port); err < 0) 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(); termination_event.notify();
if (thread_.joinable()) if (thread_.joinable())
thread_.join(); thread_.join();
termination_event.consume(); // Reset to zero termination_event.consume(); // Reset to zero
midi_in_impl::close_port(); return midi_in_impl::close_port();
} }
std::thread thread_; std::thread thread_;
@@ -297,12 +297,18 @@ private:
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:
@@ -326,12 +332,12 @@ private:
} }
} }
bool open_port(const input_port& p, std::string_view /*name*/) override stdx::error open_port(const input_port& p, std::string_view /*name*/) override
{ {
if (midi_in_impl::init_port(p) < 0) 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{};
} }
}; };
} }
@@ -26,83 +26,73 @@ 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, "midi_out_alsa_raw::open_port: cannot open device."); this->configuration, "cannot open device.");
return status; return from_errc(status);
} }
return status; return stdx::error{};
} }
bool open_port(const output_port& p, std::string_view) override stdx::error open_port(const output_port& p, std::string_view) override
{ {
return connect_port(raw_from_port_handle(p.port).to_string().c_str()) == 0; return connect_port(raw_from_port_handle(p.port).to_string().c_str());
} }
void close_port() override stdx::error close_port() override
{ {
if (midiport_) 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, "midi_out_alsa_raw::send_message: cannot write message."); this->configuration, "cannot write message.");
return false; return from_errc(err);
} }
return true; return stdx::error{};
} }
std::size_t get_chunk_size() const noexcept std::size_t get_chunk_size() const noexcept
@@ -125,7 +115,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 +125,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 +139,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 +155,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_{};
@@ -8,12 +8,12 @@
#include <libremidi/backends/linux/udev.hpp> #include <libremidi/backends/linux/udev.hpp>
#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
@@ -54,7 +54,7 @@ public:
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 +67,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)
@@ -147,7 +147,7 @@ private:
void check_devices() void check_devices()
{ {
Enumerator new_devs; Enumerator new_devs{*this};
new_devs.enumerate_cards(); new_devs.enumerate_cards();
@@ -6,6 +6,7 @@ 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
@@ -7,8 +7,10 @@ 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 +30,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;
} }
@@ -35,32 +35,17 @@ 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 from_errc(err);
return err;
} }
snd_rawmidi_params_t* params{}; snd_rawmidi_params_t* params{};
@@ -69,49 +54,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 +131,60 @@ 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({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
// err is the amount of bytes read
// 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({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
return 1;
// 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
@@ -243,26 +196,32 @@ public:
{ {
if (this->termination_event < 0) if (this->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(); this->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->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(), period);
if (err == -EAGAIN) if (err == -EAGAIN)
continue; continue;
else if (err < 0) else if (err < 0)
@@ -278,7 +237,7 @@ private:
} }
} }
[[nodiscard]] int start_thread() [[nodiscard]] stdx::error start_thread()
{ {
try try
{ {
@@ -291,36 +250,37 @@ private:
this->thread_ = std::thread{ this->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,
"midi_in_alsa::start_thread: error starting MIDI input thread: "s + e.what()); "error starting MIDI input thread: "s + e.what());
return false; return e.code();
} }
return true; return stdx::error{};
} }
bool open_port(const input_port& port, [[maybe_unused]] std::string_view name) override stdx::error open_port(const input_port& port, [[maybe_unused]] std::string_view name) override
{ {
if (int err = midi_in_impl::init_port(port); err < 0) 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(); termination_event.notify();
if (thread_.joinable()) if (thread_.joinable())
thread_.join(); thread_.join();
termination_event.consume(); // Reset to zero termination_event.consume(); // Reset to zero
midi_in_impl::close_port(); return midi_in_impl::close_port();
} }
std::thread thread_; std::thread thread_;
@@ -330,12 +290,18 @@ private:
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(); this->close_port();
client_open_ = std::errc::not_connected;
} }
private: private:
@@ -359,12 +325,12 @@ private:
} }
} }
bool open_port(const input_port& p, [[maybe_unused]] std::string_view name) override stdx::error open_port(const input_port& p, [[maybe_unused]] std::string_view name) override
{ {
if (midi_in_impl::init_port(p) < 0) 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{};
} }
}; };
} }
@@ -29,76 +29,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_{};
@@ -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;
}; };
@@ -177,18 +177,20 @@ struct alsa_data
} }
} }
void set_client_name(std::string_view clientName) stdx::error set_client_name(std::string_view clientName)
{ {
snd.seq.set_client_name(seq, clientName.data()); int ret = snd.seq.set_client_name(seq, clientName.data());
return from_errc(ret);
} }
void set_port_name(std::string_view portName) stdx::error set_port_name(std::string_view portName)
{ {
snd_seq_port_info_t* pinfo; snd_seq_port_info_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 +211,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 +272,8 @@ 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, "create_connection: ALSA error allocation port subscription."); self.configuration, "ALSA error allocation port subscription.");
return err; return err;
} }
+78 -104
View File
@@ -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,21 @@ 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_client_name(std::string_view clientName) override
{ {
alsa_data::set_client_name(clientName); return alsa_data::set_client_name(clientName);
} }
void set_port_name(std::string_view portName) override { alsa_data::set_port_name(portName); } stdx::error set_port_name(std::string_view portName) override
protected:
void set_timestamp(const auto& ev, auto& msg) noexcept
{ {
static constexpr int64_t nanos = 1e9; return alsa_data::set_port_name(portName);
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
@@ -303,12 +265,14 @@ protected:
snd_seq_event_t* ev{}; snd_seq_event_t* ev{};
event_handle handle{snd}; event_handle handle{snd};
int result = 0; int 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 (int err = process_event(*ev); err < 0)
{
return err; return err;
} }
}
return result; return result;
} }
else else
@@ -346,11 +310,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 +340,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);
@@ -386,62 +355,65 @@ public:
{ {
if (this->termination_event < 0) if (this->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()
{
this->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->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(); termination_event.notify();
@@ -449,6 +421,7 @@ private:
this->thread.join(); this->thread.join();
termination_event.consume(); termination_event.consume();
return stdx::error{};
} }
void thread_handler() void thread_handler()
@@ -458,12 +431,15 @@ private:
poll_fds[0] = this->termination_event; poll_fds[0] = this->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, period) >= 0)
{ {
// We got our stop-thread signal // We got our stop-thread signal
if (termination_event.ready(poll_fds[0])) if (termination_event.ready(poll_fds[0]))
@@ -486,12 +462,8 @@ 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
} }
} }
@@ -527,31 +499,33 @@ public:
~midi_in_alsa_manual() { this->close_port(); } ~midi_in_alsa_manual() { this->close_port(); }
bool open_port(const input_port& pt, std::string_view local_port_name) override stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
{ {
if (int err = this->init_port(this->to_address(pt), local_port_name); err < 0) 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();
} }
}; };
} }
@@ -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->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,55 +66,60 @@ 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 nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (nSrc < 1) if (nSrc < 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_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
void send_message(const unsigned char* message, std::size_t size) override stdx::error set_port_name(std::string_view portName) override
{
return alsa_data::set_port_name(portName);
}
stdx::error send_message(const unsigned char* message, std::size_t size) override
{ {
int64_t result{}; int64_t result{};
if (size > this->bufferSize) if (size > this->bufferSize)
@@ -122,11 +128,11 @@ public:
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;
} }
} }
@@ -144,14 +150,14 @@ public:
result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev); result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - 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,13 +165,12 @@ 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:
@@ -2,6 +2,7 @@
#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>
@@ -26,6 +27,7 @@ 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 +42,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 +71,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,7 +82,8 @@ 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;
} }
} }
} }
@@ -98,9 +106,13 @@ public:
const auto tp = snd.seq.port_info_get_type(pinfo); const auto tp = snd.seq.port_info_get_type(pinfo);
bool ok = this->configuration.track_any; bool ok = this->configuration.track_any;
static constexpr auto virtual_port
= SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER;
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
ok = true; ok = true;
else if ((tp & SND_SEQ_PORT_TYPE_SOFTWARE) && this->configuration.track_virtual) else if ((tp & virtual_port) && this->configuration.track_virtual)
ok = true; ok = true;
if (!ok) if (!ok)
return {}; return {};
@@ -271,9 +283,12 @@ public:
// Start the listening thread // Start the listening thread
thread = std::thread{[this] { thread = std::thread{[this] {
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(), period);
if (err >= 0) if (err >= 0)
{ {
// We got our stop-thread signal // We got our stop-thread signal
@@ -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;
}; };
@@ -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,60 @@ 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 nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
if (nSrc < 1) if (nSrc < 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_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
void send_ump(const uint32_t* ump_stream, std::size_t count) override stdx::error set_port_name(std::string_view portName) override
{
return alsa_data::set_port_name(portName);
}
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
{ {
snd_seq_ump_event_t ev; snd_seq_ump_event_t ev;
@@ -128,24 +125,20 @@ 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; return std::errc{0};
}; };
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};
}; };
} }
@@ -16,6 +16,7 @@ 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
{ {
@@ -0,0 +1,72 @@
#pragma once
#include <libremidi/error.hpp>
#include <CoreMIDI/CoreMIDI.h>
namespace libremidi
{
struct coremidi_error_domain : public stdx::error_domain
{
public:
constexpr coremidi_error_domain() noexcept
: error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}
{
}
stdx::string_ref name() const noexcept override { return "coremidi"; }
bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override
{
if (lhs.domain() == rhs.domain())
return error_cast<std::errc>(lhs) == error_cast<std::errc>(rhs);
return false;
}
stdx::string_ref message(const stdx::error& e) const noexcept override
{
switch (error_cast<OSStatus>(e))
{
case kMIDIInvalidClient:
return "Invalid Client";
case kMIDIInvalidPort:
return "Invalid Port";
case kMIDIWrongEndpointType:
return "Wrong EndpointT ype";
case kMIDINoConnection:
return "No Connection";
case kMIDIUnknownEndpoint:
return "Unknown Endpoint";
case kMIDIUnknownProperty:
return "Unknown Property";
case kMIDIWrongPropertyType:
return "Wrong Property Type";
case kMIDINoCurrentSetup:
return "No Current Setup";
case kMIDIMessageSendErr:
return "Message Send Error";
case kMIDIServerStartErr:
return "Server Start Error";
case kMIDISetupFormatErr:
return "Setup Format Error";
case kMIDIWrongThread:
return "Wrong Thread";
case kMIDIObjectNotFound:
return "Object Not Found";
case kMIDIIDNotUnique:
return "ID Not Unique";
case kMIDINotPermitted:
return "Not Permitted";
case kMIDIUnknownError:
return "Unknown Error";
}
return "Unknown error code";
}
};
inline stdx::error from_osstatus(OSStatus ret) noexcept
{
static constexpr coremidi_error_domain domain{};
return {ret, domain};
}
}
@@ -1,7 +1,9 @@
#pragma once #pragma once
#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 <libremidi/backends/coremidi/error_domain.hpp>
#include <CoreMIDI/CoreMIDI.h> #include <CoreMIDI/CoreMIDI.h>
#include <CoreServices/CoreServices.h> #include <CoreServices/CoreServices.h>
@@ -28,7 +30,8 @@ namespace
static inline 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);
@@ -215,15 +218,15 @@ 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, "coremidi::locate_object: cannot find port: " + info.port_name); self.configuration, "cannot find port: " + info.port_name);
return 0; return 0;
} }
if (type != requested_type || object == 0) if (type != requested_type || object == 0)
{ {
self.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;
} }
@@ -255,6 +258,13 @@ struct coremidi_data
} }
} }
void close_client(auto& self)
{
self.client_open_ = std::errc::not_connected;
if (!self.configuration.context)
MIDIClientDispose(self.client);
}
static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept static uint64_t time_in_nanos(MIDITimeStamp tp) noexcept
{ {
if (tp == 0) if (tp == 0)
@@ -319,6 +329,23 @@ struct coremidi_data
} }
} }
} }
stdx::error close_port()
{
if (this->endpoint)
{
MIDIEndpointDispose(this->endpoint);
this->endpoint = 0;
}
if (this->port)
{
MIDIPortDispose(this->port);
this->port = 0;
}
return stdx::error{};
}
}; };
} }
+28 -52
View File
@@ -8,10 +8,11 @@ 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,14 @@ 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,
"midi_in_core: error creating MIDI client object: " + std::to_string(result)); "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return; return;
} }
client_open_ = stdx::error{};
} }
~midi_in_core() override ~midi_in_core() 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; } 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 +62,29 @@ 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 false; return from_osstatus(result);
} }
// 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, "midi_in_core::open_port: error connecting macOS MIDI input port."); this->configuration, "error connecting macOS MIDI input port.");
return false; return from_osstatus(result);
} }
// 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,31 +93,21 @@ 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
{ {
if (this->endpoint) return coremidi_data::close_port();
{
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 +137,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);
} }
@@ -7,10 +7,11 @@ 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,14 @@ 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,
"midi_out_core: error creating MIDI client object: " + std::to_string(result)); "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return; return;
} }
client_open_ = stdx::error{};
} }
~midi_out_core() ~midi_out_core()
@@ -36,112 +40,79 @@ 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, "midi_out_core::open_port: error creating macOS MIDI output port."); this->configuration, "error creating macOS MIDI output port.");
return false; return from_osstatus(result);
} }
// 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, this->configuration,
"midi_out_core::initialize: error creating macOS virtual MIDI source."); "error creating macOS virtual MIDI source.");
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
{ {
if (this->endpoint) return coremidi_data::close_port();
{
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();
@@ -166,9 +137,10 @@ public:
if (!packet) if (!packet)
{ {
error<driver_error>( libremidi_handle_error(
this->configuration, "midi_out_core::send_message: could not allocate packet list"); this->configuration, "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 +149,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 +163,14 @@ 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, this->configuration,
"midi_out_core::send_message: error sending MIDI message to port."); "error sending MIDI message to port.");
return std::errc::io_error;
} }
} }
} }
return stdx::error{};
} }
MIDIEndpointRef destinationId{}; MIDIEndpointRef destinationId{};
@@ -38,9 +38,9 @@ public:
if (result != noErr) if (result != noErr)
{ {
error<driver_error>( libremidi_handle_error(
this->configuration, this->configuration,
"midi_in_core: error creating MIDI client object: " + std::to_string(result)); "error creating MIDI client object: " + std::to_string(result));
return; return;
} }
@@ -69,11 +69,11 @@ 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 {};
+3 -3
View File
@@ -10,9 +10,9 @@ 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 auto name = "core_ump";
static const constexpr auto display_name = "CoreMIDI UMP"; static const constexpr auto display_name = "CoreMIDI UMP";
@@ -3,7 +3,21 @@
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,37 @@
#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,
"midi_in_core: error creating MIDI client object: " + std::to_string(result)); "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return; return;
} }
client_open_ = stdx::error{};
} }
~midi_in_impl() override ~midi_in_impl() override
@@ -38,33 +43,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 +67,29 @@ 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 false; return from_osstatus(result);
} }
// 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, "midi_in_core::open_port: error connecting macOS MIDI input port."); this->configuration, "error connecting macOS MIDI input port.");
return false; return from_osstatus(result);
} }
// 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,37 +101,21 @@ 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
{
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
{ {
return coremidi_data::close_port();
} }
timestamp absolute_timestamp() const noexcept override timestamp absolute_timestamp() const noexcept override
@@ -151,33 +125,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};
}; };
} }
@@ -9,10 +9,11 @@ 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,14 @@ 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,
"midi_out_impl: error creating MIDI client object: " + std::to_string(result)); "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return; return;
} }
client_open_ = stdx::error{};
} }
~midi_out_impl() ~midi_out_impl()
@@ -39,64 +43,40 @@ 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, "midi_out_impl::open_port: error creating macOS MIDI output port."); this->configuration, "error creating macOS MIDI output port.");
return false; return from_osstatus(result);
} }
// 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 +84,33 @@ public:
if (result != noErr) if (result != noErr)
{ {
this->endpoint = 0; this->endpoint = 0;
error<driver_error>( libremidi_handle_error(
this->configuration, this->configuration,
"midi_out_impl::initialize: error creating macOS virtual MIDI source."); "error creating macOS virtual MIDI source.");
return false;
return from_osstatus(result);
} }
return true; return stdx::error{};
} }
void close_port() override stdx::error close_port() override
{ {
if (this->endpoint) return coremidi_data::close_port();
{
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 +119,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 +142,13 @@ 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, this->configuration,
"midi_out_core::send_message: error sending MIDI message to port."); "error sending MIDI message to port.");
return std::errc::io_error;
} }
} }
return stdx::error{};
} }
MIDIEndpointRef destinationId{}; MIDIEndpointRef destinationId{};
@@ -1,4 +1,5 @@
#pragma once #pragma once
#include <libremidi/backends/coremidi_ump/config.hpp>
#include <libremidi/backends/coremidi/observer.hpp> #include <libremidi/backends/coremidi/observer.hpp>
namespace libremidi::coremidi_ump namespace libremidi::coremidi_ump
@@ -6,7 +7,13 @@ 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}}
{
}
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; }
}; };
+42 -17
View File
@@ -23,20 +23,33 @@ 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, std::any)
{ {
warning(configuration, "midi_in_dummy: This class provides no functionality."); libremidi_handle_warning(configuration, "This class provides no functionality.");
}
explicit midi_in_dummy(const ump_input_configuration& configuration, std::any)
{
libremidi_handle_warning(configuration, "This class provides no functionality.");
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; } libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
bool open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override stdx::error open_port(const input_port& /*pt*/, std::string_view /*local_port_name*/) override
{ {
return true; return stdx::error{};
}
stdx::error open_virtual_port(std::string_view /*portName*/) override
{
return stdx::error{};
}
stdx::error close_port() override { return stdx::error{}; }
stdx::error set_client_name(std::string_view /*clientName*/) override
{
return stdx::error{};
}
stdx::error set_port_name(std::string_view /*portName*/) override
{
return stdx::error{};
} }
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
void close_port() override { }
void set_client_name(std::string_view /*clientName*/) override { }
void set_port_name(std::string_view /*portName*/) override { }
timestamp absolute_timestamp() const noexcept override { return 0; } timestamp absolute_timestamp() const noexcept override { return 0; }
}; };
@@ -45,22 +58,34 @@ 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, std::any)
{ {
warning(configuration, "midi_out_dummy: This class provides no functionality."); libremidi_handle_warning(configuration, "This class provides no functionality.");
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; } libremidi::API get_current_api() const noexcept override { return libremidi::API::DUMMY; }
bool open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override stdx::error open_port(const output_port& /*pt*/, std::string_view /*local_port_name*/) override
{ {
return true; return stdx::error{};
}
stdx::error open_virtual_port(std::string_view /*portName*/) override
{
return stdx::error{};
} }
bool open_virtual_port(std::string_view /*portName*/) override { return true; }
void close_port() override { } stdx::error close_port() override { return stdx::error{}; }
void set_client_name(std::string_view /*clientName*/) override { } stdx::error set_client_name(std::string_view /*clientName*/) override
void set_port_name(std::string_view /*portName*/) override { } {
void send_message(const unsigned char* /*message*/, size_t /*size*/) override { } return stdx::error{};
}
stdx::error set_port_name(std::string_view /*portName*/) override
{
return stdx::error{};
}
stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override
{
return stdx::error{};
}
}; };
struct dummy_backend struct dummy_backend
@@ -204,7 +204,7 @@ public:
} }
} }
void send_message(int port_index, const char* bytes, int len) stdx::error send_message(int port_index, const char* bytes, int len)
{ {
const auto& id = m_current_outputs[port_index].id; const auto& id = m_current_outputs[port_index].id;
EM_ASM( EM_ASM(
@@ -11,12 +11,14 @@ 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,35 @@ 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, "midi_in_emscripten::open_port: no MIDI output sources found."); this->configuration, "no MIDI output sources found.");
return false; return std::errc::invalid_argument;
} }
midi.open_input(portNumber, *this); midi.open_input(portNumber, *this);
portNumber_ = portNumber; 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(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
@@ -22,13 +22,10 @@ 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);
@@ -8,12 +8,14 @@ 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,41 @@ 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, "midi_out_emscripten::open_port: no MIDI output sources found."); this->configuration, "no MIDI output sources found.");
return false; return std::errc::invalid_argument;
} }
portNumber_ = portNumber; 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() {
return stdx::error{};
LIBREMIDI_INLINE void midi_out_emscripten::set_client_name(std::string_view clientName)
{
warning(configuration, "midi_out_emscripten::set_client_name: unsupported.");
} }
LIBREMIDI_INLINE void midi_out_emscripten::set_port_name(std::string_view portName) LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size)
{
warning(configuration, "midi_out_emscripten::set_port_name: unsupported.");
}
LIBREMIDI_INLINE bool midi_out_emscripten::open_virtual_port(std::string_view)
{
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
return false;
}
LIBREMIDI_INLINE void midi_out_emscripten::send_message(const unsigned char* message, size_t size)
{ {
if (portNumber_ < 0) if (portNumber_ < 0)
error<invalid_use_error>( libremidi_handle_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); portNumber_, reinterpret_cast<const char*>(message), size);
return stdx::error{};
} }
} }
#endif #endif
@@ -21,15 +21,11 @@ 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 portNumber_{-1};
+4 -5
View File
@@ -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>
@@ -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};
}
}
+19 -19
View File
@@ -1,14 +1,6 @@
#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>
@@ -157,6 +149,10 @@ struct jack_helpers : jack_client
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
if (this->client != nullptr) if (this->client != nullptr)
{ {
if(status & JackNameNotUnique) {
self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed.");
}
jack_set_process_callback( jack_set_process_callback(
this->client, this->client,
+[](jack_nframes_t nf, void* ctx) -> int { +[](jack_nframes_t nf, void* ctx) -> int {
@@ -192,9 +188,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, JackPortFlags flags)
{ {
// full name: "client_name:port_name\0" // full name: "client_name:port_name\0"
if (portName.empty()) if (portName.empty())
@@ -203,9 +202,9 @@ 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, "JACK: port name length limit exceeded"); self.configuration, "port name length limit exceeded");
return false; return std::errc::invalid_argument;
} }
if (!this->port) if (!this->port)
@@ -216,16 +215,16 @@ struct jack_helpers : jack_client
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 +234,8 @@ 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);
} }
}; };
} }
+23 -20
View File
@@ -14,6 +14,7 @@ class midi_in_jack final
, 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 +25,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 +42,35 @@ 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, JackPortIsInput); err != stdx::error{})
return false; 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, "could not connect to port: " + port.port_name + " -> "
+ jack_port_name(this->port)); + 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, 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
+42 -29
View File
@@ -37,16 +37,18 @@ public:
jack_ringbuffer_free(ringbuffer); jack_ringbuffer_free(ringbuffer);
} }
void write(const unsigned char* data, int64_t sz) const noexcept stdx::error write(const unsigned char* data, int64_t sz) const noexcept
{ {
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space) if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
return; return std::errc::no_buffer_space;
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
sched_yield(); sched_yield();
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz); jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz); jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
return stdx::error{};
} }
void read(void* jack_events) const noexcept void read(void* jack_events) const noexcept
@@ -74,6 +76,7 @@ class midi_out_jack
, 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 +90,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, JackPortIsOutput); err != stdx::error{})
return false; 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, "JACK: could not connect to port" + port.port_name); configuration, "could not connect to port" + port.port_name);
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, JackPortIsOutput); return create_local_port(*this, portName, JackPortIsOutput);
} }
void close_port() override { return do_close_port(); } stdx::error close_port() override { return do_close_port(); }
void set_port_name(std::string_view portName) override stdx::error set_port_name(std::string_view portName) override
{ {
jack_port_rename(this->client, this->port, portName.data()); int ret = jack_port_rename(this->client, this->port, portName.data());
return from_errc(ret);
} }
}; };
@@ -131,8 +131,14 @@ public:
, queue{configuration.ringbuffer_size} , 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 +148,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 queue.write(message, size);
} }
int process(jack_nframes_t nframes) int process(jack_nframes_t nframes)
@@ -168,10 +174,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 +199,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,10 +219,11 @@ public:
} }
} }
void schedule_message(int64_t ts, const unsigned char* message, size_t size) override stdx::error schedule_message(int64_t ts, const unsigned char* message, size_t size) override
{ {
void* buff = jack_port_get_buffer(this->port, buffer_size); 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{};
+1 -1
View File
@@ -34,7 +34,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)
{ {
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <libremidi/backends/dummy.hpp>
#include <libremidi/backends/keyboard/midi_in.hpp>
namespace libremidi
{
struct kbd_backend
{
using midi_in = midi_in_kbd;
using midi_in_configuration = kbd_input_configuration;
using midi_out = midi_out_dummy;
using midi_observer = observer_dummy;
using midi_out_configuration = dummy_configuration;
using midi_observer_configuration = dummy_configuration;
static const constexpr auto API = libremidi::API::KEYBOARD;
static const constexpr auto name = "keyboard";
static const constexpr auto display_name = "Computer keyboard";
static inline bool available() noexcept { return true; }
};
}
@@ -0,0 +1,92 @@
#pragma once
#include <libremidi/config.hpp>
#include <functional>
#include <map>
namespace libremidi
{
/**
* Used to set up keyboard input.
* Your app should pass a function that will give you a callback
* that you should call whenever you are getting a key input (press or release).
*/
struct kbd_input_configuration
{
using scancode_callback = std::function<void(int)>;
// First argument is on key press, second on key release
std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks
= [](scancode_callback, scancode_callback) {};
enum kbd_event
{
NOTE_0 = 0x0, // C
VEL_0 = NOTE_0 + 128, // Set velocity to 0
OCT_0 = VEL_0 + 128, // Set octave to 0
OCTAVE_PLUS = OCT_0 + 128,
OCTAVE_MINUS,
VELOCITY_PLUS,
VELOCITY_MINUS,
};
// Default map
//
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
// | ctrl | | alt | |altgr | | ctrl |
// '------' '-----'--------------------------'------' '------'
// clang-format off
// Use: https://kbdlayout.info/KBDUSX/scancodes
std::map<int, int> scancode_map{
{ 0x1E, NOTE_0}, // C0
{ 0x11, NOTE_0 + 1},
{ 0x1F, NOTE_0 + 2},
{ 0x12, NOTE_0 + 3},
{ 0x20, NOTE_0 + 4},
{ 0x21, NOTE_0 + 5},
{ 0x14, NOTE_0 + 6},
{ 0x22, NOTE_0 + 7},
{ 0x15, NOTE_0 + 8},
{ 0x23, NOTE_0 + 9},
{ 0x16, NOTE_0 + 10},
{ 0x24, NOTE_0 + 11},
{ 0x25, NOTE_0 + 12}, // C1
{ 0x18, NOTE_0 + 13},
{ 0x26, NOTE_0 + 14},
{ 0x19, NOTE_0 + 15},
{ 0x27, NOTE_0 + 16},
{ 0x28, NOTE_0 + 17},
{ 0x1B, NOTE_0 + 18},
{ 0x2B, NOTE_0 + 19},
{ 0x29, VEL_0 + int(0)},
{ 0x02, VEL_0 + int(1 * 127 / 12)},
{ 0x03, VEL_0 + int(2 * 127 / 12)},
{ 0x04, VEL_0 + int(3 * 127 / 12)},
{ 0x05, VEL_0 + int(4 * 127 / 12)},
{ 0x06, VEL_0 + int(5 * 127 / 12)},
{ 0x07, VEL_0 + int(6 * 127 / 12)},
{ 0x08, VEL_0 + int(7 * 127 / 12)},
{ 0x09, VEL_0 + int(8 * 127 / 12)},
{ 0x0A, VEL_0 + int(9 * 127 / 12)},
{ 0x0B, VEL_0 + int(10 * 127 / 12)},
{ 0x0C, VEL_0 + int(11 * 127 / 12)},
{ 0x0D, VEL_0 + int(127)},
{ 0x2C, OCTAVE_MINUS },
{ 0x2D, OCTAVE_PLUS },
{ 0x2E, VELOCITY_MINUS },
{ 0x2F, VELOCITY_PLUS},
};
// clang-format on
};
}
@@ -0,0 +1,112 @@
#pragma once
#include <libremidi/backends/keyboard/config.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <chrono>
#include <unordered_map>
namespace libremidi
{
class midi_in_kbd final
: public midi1::in_api
, public error_handler
{
public:
using midi_api::client_open_;
struct
: input_configuration
, kbd_input_configuration
{
} configuration;
explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
configuration.set_input_scancode_callbacks(
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
}
~midi_in_kbd() override { }
libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }
stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; }
stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; }
stdx::error close_port() override { return stdx::error{}; }
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
}
void on_keypress(int scancode)
{
using kevent = kbd_input_configuration::kbd_event;
auto it = configuration.scancode_map.find(scancode);
if (it == configuration.scancode_map.end())
return;
if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))
{
int note = it->second - kevent::NOTE_0 + 12 * m_current_octave;
this->configuration.on_message(
libremidi::channel_events::note_on(0, note, m_current_velocity));
m_current_notes_scancodes[scancode] = note;
}
else if (it->second >= kevent::VEL_0 && it->second < (kevent::VEL_0 + 128))
{
m_current_velocity = it->second - kevent::VEL_0;
}
else if (it->second >= kevent::OCT_0 && it->second < (kevent::OCT_0 + 128))
{
m_current_octave = it->second - kevent::OCT_0;
}
else
{
switch (it->second)
{
case kevent::VELOCITY_MINUS:
m_current_velocity = std::clamp(m_current_velocity - 10, 0, 127);
break;
case kevent::VELOCITY_PLUS:
m_current_velocity = std::clamp(m_current_velocity + 10, 0, 127);
break;
case kevent::OCTAVE_MINUS:
m_current_octave = std::clamp(m_current_octave - 1, 0, 127);
break;
case kevent::OCTAVE_PLUS:
m_current_octave = std::clamp(m_current_octave + 1, 0, 127);
break;
}
}
}
void on_keyrelease(int scancode)
{
using kevent = kbd_input_configuration::kbd_event;
auto it = configuration.scancode_map.find(scancode);
if (it == configuration.scancode_map.end())
return;
if (it->second >= kevent::NOTE_0 && it->second < (kevent::NOTE_0 + 128))
{
if (auto note_it = m_current_notes_scancodes.find(scancode);
note_it != m_current_notes_scancodes.end())
{
this->configuration.on_message(libremidi::channel_events::note_off(0, note_it->second, 0));
m_current_notes_scancodes.erase(note_it);
}
}
}
int m_current_octave{3};
int m_current_velocity{80};
std::unordered_map<int, int> m_current_notes_scancodes;
};
}
+228 -228
View File
@@ -57,13 +57,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 +80,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 +98,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 +117,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 +139,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 +168,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 +237,118 @@ 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_set_client)
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof); LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, close); LIBREMIDI_SYMBOL_INIT(snd_seq, close)
LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from); LIBREMIDI_SYMBOL_INIT(snd_seq, connect_from)
LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue); LIBREMIDI_SYMBOL_INIT(snd_seq, control_queue)
LIBREMIDI_SYMBOL_INIT(snd_seq, create_port); LIBREMIDI_SYMBOL_INIT(snd_seq, create_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port); LIBREMIDI_SYMBOL_INIT(snd_seq, delete_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output); LIBREMIDI_SYMBOL_INIT(snd_seq, drain_output)
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input); LIBREMIDI_SYMBOL_INIT(snd_seq, event_input)
LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending); LIBREMIDI_SYMBOL_INIT(snd_seq, event_input_pending)
LIBREMIDI_SYMBOL_INIT(snd_seq, event_output); LIBREMIDI_SYMBOL_INIT(snd_seq, event_output)
LIBREMIDI_SYMBOL_INIT(snd_seq, free_event); LIBREMIDI_SYMBOL_INIT(snd_seq, free_event)
LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue); LIBREMIDI_SYMBOL_INIT(snd_seq, free_queue)
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info); LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_client_info)
LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info); LIBREMIDI_SYMBOL_INIT(snd_seq, get_any_port_info)
LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info); LIBREMIDI_SYMBOL_INIT(snd_seq, get_port_info)
LIBREMIDI_SYMBOL_INIT(snd_seq, open); LIBREMIDI_SYMBOL_INIT(snd_seq, open)
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors); LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors)
LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count); LIBREMIDI_SYMBOL_INIT(snd_seq, poll_descriptors_count)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_addr)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_capability)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_get_type)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_capability)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_client)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_midi_channels)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_name)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamping)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_queue)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_timestamp_real)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_set_type)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof); LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free); LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc); LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest); LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender); LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real); LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update); LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_update)
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client); LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_client)
LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port); LIBREMIDI_SYMBOL_INIT(snd_seq, query_next_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq); LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_ppq)
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo); LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_set_tempo)
LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof); LIBREMIDI_SYMBOL_INIT(snd_seq, queue_tempo_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name); LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_name)
LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info); LIBREMIDI_SYMBOL_INIT(snd_seq, set_port_info)
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo); LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo)
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port); LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port); LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port)
} }
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_set_client)
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof); LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, close); LIBREMIDI_SYMBOL_DEF(snd_seq, close)
LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from); LIBREMIDI_SYMBOL_DEF(snd_seq, connect_from)
LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue); LIBREMIDI_SYMBOL_DEF(snd_seq, control_queue)
LIBREMIDI_SYMBOL_DEF(snd_seq, create_port); LIBREMIDI_SYMBOL_DEF(snd_seq, create_port)
LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port); LIBREMIDI_SYMBOL_DEF(snd_seq, delete_port)
LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output); LIBREMIDI_SYMBOL_DEF(snd_seq, drain_output)
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input); LIBREMIDI_SYMBOL_DEF(snd_seq, event_input)
LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending); LIBREMIDI_SYMBOL_DEF(snd_seq, event_input_pending)
LIBREMIDI_SYMBOL_DEF(snd_seq, event_output); LIBREMIDI_SYMBOL_DEF(snd_seq, event_output)
LIBREMIDI_SYMBOL_DEF(snd_seq, free_event); LIBREMIDI_SYMBOL_DEF(snd_seq, free_event)
LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue); LIBREMIDI_SYMBOL_DEF(snd_seq, free_queue)
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info); LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_client_info)
LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info); LIBREMIDI_SYMBOL_DEF(snd_seq, get_any_port_info)
LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info); LIBREMIDI_SYMBOL_DEF(snd_seq, get_port_info)
LIBREMIDI_SYMBOL_DEF(snd_seq, open); LIBREMIDI_SYMBOL_DEF(snd_seq, open)
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors); LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors)
LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count); LIBREMIDI_SYMBOL_DEF(snd_seq, poll_descriptors_count)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_addr)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_capability)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_port)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_get_type)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_capability)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_client)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_midi_channels)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_name)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_port)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamping)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_queue)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_timestamp_real)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_set_type)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof); LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free); LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc); LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest); LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender); LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real); LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update); LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_update)
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client); LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_client)
LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port); LIBREMIDI_SYMBOL_DEF(snd_seq, query_next_port)
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq); LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_ppq)
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo); LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_set_tempo)
LIBREMIDI_SYMBOL_DEF(snd_seq, queue_tempo_sizeof); 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 #if LIBREMIDI_ALSA_HAS_UMP
struct ump_t struct ump_t
@@ -360,18 +360,18 @@ struct libasound
available = false; available = false;
return; return;
} }
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version); 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, 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 +387,39 @@ struct libasound
return; return;
} }
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name); LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof); LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof)
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, endpoint_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof); LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_ump, open); LIBREMIDI_SYMBOL_INIT(snd_ump, open)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors); LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count); LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents); LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi); LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params); LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current); LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current)
LIBREMIDI_SYMBOL_INIT(snd_ump, read); LIBREMIDI_SYMBOL_INIT(snd_ump, read)
LIBREMIDI_SYMBOL_INIT(snd_ump, tread); LIBREMIDI_SYMBOL_INIT(snd_ump, tread)
LIBREMIDI_SYMBOL_INIT(snd_ump, write); LIBREMIDI_SYMBOL_INIT(snd_ump, write)
} }
bool available{true}; bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name); LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof); LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_ump, close); LIBREMIDI_SYMBOL_DEF(snd_ump, close)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name); LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof); LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_ump, open); LIBREMIDI_SYMBOL_DEF(snd_ump, open)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors); LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count); LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents); LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi); LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params); LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current); LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current)
LIBREMIDI_SYMBOL_DEF(snd_ump, read); LIBREMIDI_SYMBOL_DEF(snd_ump, read)
LIBREMIDI_SYMBOL_DEF(snd_ump, tread); LIBREMIDI_SYMBOL_DEF(snd_ump, tread)
LIBREMIDI_SYMBOL_DEF(snd_ump, write); LIBREMIDI_SYMBOL_DEF(snd_ump, write)
} ump{library}; } ump{library};
#endif #endif
}; };
+20 -20
View File
@@ -23,16 +23,16 @@ 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_unref)
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving); LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd); LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink); LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device); LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref); LIBREMIDI_SYMBOL_INIT(udev, monitor_unref)
LIBREMIDI_SYMBOL_INIT2(udev, new, create); LIBREMIDI_SYMBOL_INIT2(udev, new, create)
LIBREMIDI_SYMBOL_INIT(udev, unref); LIBREMIDI_SYMBOL_INIT(udev, unref)
} }
static const libudev& instance() static const libudev& instance()
@@ -44,16 +44,16 @@ 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_unref)
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving); LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd); LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink); LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device); LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref); LIBREMIDI_SYMBOL_DEF(udev, monitor_unref)
LIBREMIDI_SYMBOL_DEF2(udev, new, create); LIBREMIDI_SYMBOL_DEF2(udev, new, create)
LIBREMIDI_SYMBOL_DEF(udev, unref); LIBREMIDI_SYMBOL_DEF(udev, unref)
}; };
struct udev_helper struct udev_helper
+86
View File
@@ -0,0 +1,86 @@
#pragma once
#include <libremidi/config.hpp>
#include <string>
namespace boost::asio
{
struct io_context;
}
namespace libremidi::net
{
enum class protocol
{
OSC_MIDI,
};
struct dgram_input_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI;
std::string accept = "0.0.0.0";
int port{};
boost::asio::io_context* io_context{};
};
struct dgram_output_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI;
std::string host = "127.0.0.1";
int port{};
bool broadcast{};
boost::asio::io_context* io_context{};
};
struct dgram_observer_configuration
{
std::string client_name = "libremidi client";
boost::asio::io_context* io_context{};
};
}
namespace libremidi::net_ump
{
enum class protocol
{
OSC_MIDI2,
};
struct dgram_input_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI2;
std::string accept = "0.0.0.0";
int port{};
boost::asio::io_context* io_context{};
};
struct dgram_output_configuration
{
std::string client_name = "libremidi client";
enum protocol protocol = protocol::OSC_MIDI2;
std::string host = "127.0.0.1";
int port{};
bool broadcast{};
boost::asio::io_context* io_context{};
};
struct dgram_observer_configuration
{
std::string client_name = "libremidi client";
boost::asio::io_context* io_context{};
};
}
@@ -0,0 +1,91 @@
#pragma once
#include <libremidi/config.hpp>
#include <boost/asio/ip/udp.hpp>
#include <memory>
namespace stdx
{ /*
class boost_system_error_domain : public error_domain
{
public:
constexpr boost_system_error_domain() noexcept
: error_domain{
{1 - 0x3c223c0aa3cf45e5ULL, 1 - 0x80dac24345cfb9fcULL},
default_error_resource_management_t<detail::exception_ptr_wrapper>{}}
{
}
string_ref name() const noexcept override { return "boost::system_error domain"; }
bool equivalent(const error& lhs, const error& rhs) const noexcept override { return false; }
string_ref message(const error& e) const noexcept override {
auto err = e.m_value;
}
[[noreturn]] void throw_exception(const error& e) const override
{
assert(e.domain() == *this);
std::rethrow_exception(error_cast<detail::exception_ptr_wrapper>(e).get());
}
};
*/
}
namespace libremidi
{
template <typename T>
struct optionally_owned
{
public:
explicit optionally_owned(T* maybe_existing)
: storage{.ref = maybe_existing}
{
if (storage.ref)
{
ownership = unowned;
return;
}
else
{
std::destroy_at(&storage.ref);
std::construct_at<T>(reinterpret_cast<T*>(storage.object));
ownership = owned;
}
}
~optionally_owned()
{
if (is_owned())
std::destroy_at(reinterpret_cast<T*>(storage.object));
}
T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); }
const T& get() const noexcept
{
return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref);
}
optionally_owned(const optionally_owned&) = delete;
optionally_owned(optionally_owned&&) noexcept = delete;
optionally_owned& operator=(const optionally_owned&) = delete;
optionally_owned& operator=(optionally_owned&&) noexcept = delete;
bool is_owned() const noexcept { return ownership == owned; }
private:
union
{
alignas(T) unsigned char object[sizeof(T)];
T* ref;
} storage;
enum
{
owned,
unowned
} ownership{};
};
}
+440
View File
@@ -0,0 +1,440 @@
#pragma once
#include <libremidi/backends/net/config.hpp>
#include <libremidi/backends/net/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
{
template <typename Impl, typename F>
struct osc_parser
{
[[no_unique_address]]
Impl impl;
F on_message;
std::string_view port_name;
stdx::error parse_bundle(const char* data, std::size_t sz)
{
std::string_view txt(data, sz);
if (!txt.starts_with("#bundle"))
return std::errc::bad_message;
return std::errc::protocol_not_supported;
}
stdx::error parse_message(const char* data, std::size_t sz)
{
const auto begin = data;
const auto end = data + sz;
std::size_t pattern_len = strnlen(data, sz);
if (pattern_len == sz)
return std::errc::bad_message;
if (auto pat = std::string_view(data, pattern_len); pat != port_name)
return std::errc::bad_address;
data += pattern_len;
for (; data < end; ++data)
if (*data == 0)
continue;
else
break;
// Now we shold reach the typetag beginning, ","
if (data == end)
return std::errc::bad_message;
if ((data - begin) % 4 != 0)
return std::errc::bad_message;
if (*data++ != ',')
return std::errc::bad_message;
// Count the ,mmmmm arguments... yummy
int num_msgs = 0;
for (; data < end; ++data)
if (*data == Impl::typetag)
num_msgs++;
else if (*data == 0)
break;
else
return std::errc::bad_message;
if (num_msgs == 0)
return std::errc::no_message;
// By now there's at least 5 bytes left as the ,mmm has to be round-up to 4 and padded with zeros:
if (end - data < 4)
return std::errc::bad_message;
switch ((1 + num_msgs) % 4)
{
case 0:
data += 4;
break;
case 1:
data += 3;
break;
case 2:
data += 2;
break;
case 3:
data += 1;
break;
}
// Data starts
return impl.process_midi_bytes(on_message, num_msgs, data, end - data);
}
stdx::error parse_int_message(const char* /* data */, std::size_t /* sz */)
{
return std::errc::protocol_not_supported;
}
stdx::error parse_packet(const char* data, std::size_t sz)
{
if (sz == 0)
return std::errc::no_message;
switch (data[0])
{
case '#':
return parse_bundle(data, sz);
break;
case '/':
return parse_message(data, sz);
break;
case '\0':
return parse_int_message(data, sz);
break;
default:
return std::errc::bad_message;
}
}
};
}
namespace libremidi::net
{
struct osc_parser_midi1
{
static constexpr char typetag = 'm';
stdx::error
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t sz)
{
if (sz != num_msgs * 4)
return std::errc::bad_message;
auto end = data + sz;
for (; data < end; data += 4)
on_message(data + 1);
return {};
}
};
class midi_in final
: public midi1::in_api
, public error_handler
{
public:
using midi_api::client_open_;
struct
: input_configuration
, dgram_input_configuration
{
} configuration;
explicit midi_in(input_configuration&& conf, dgram_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, m_ctx{configuration.io_context}
, m_socket{m_ctx.get()}
{
m_socket.open(boost::asio::ip::udp::v4());
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
m_endpoint
= {boost::asio::ip::make_address(this->configuration.accept),
static_cast<unsigned short>(this->configuration.port)};
boost::system::error_code ec;
m_socket.bind(m_endpoint, ec);
if (ec != boost::system::error_code{})
{
client_open_ = std::errc::address_in_use;
return;
}
client_open_ = stdx::error{};
}
~midi_in() override { close_port(); }
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK; }
stdx::error open_port(const input_port&, std::string_view port) override
{
return open_virtual_port(port);
}
stdx::error open_virtual_port(std::string_view port) override
{
m_portname = std::string(port);
receive();
if (m_ctx.is_owned())
{
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
auto wg = boost::asio::make_work_guard(m_ctx);
ctx.run();
}};
}
return stdx::error{};
}
void receive()
{
m_socket.async_receive_from(
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
[this](auto ec, std::size_t sz) {
if (ec == boost::asio::error::operation_aborted)
return;
if (!ec && sz > 0)
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
this->receive();
});
}
stdx::error close_port() override
{
// FIXME async close
if (m_socket.is_open())
m_socket.close();
return {};
}
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
}
void on_bytes(const char* data, std::size_t size)
{
const auto on_msg = [this](const char* bytes) { this->on_message(bytes); };
osc_parser<osc_parser_midi1, decltype(on_msg)> parser{{}, on_msg, m_portname};
parser.parse_packet(data, size);
}
void on_message(const char* data)
{
// OSC enforces always 3 bytes
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = false,
.absolute_is_monotonic = false,
.has_samples = false,
};
static constexpr auto to_ns = []() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
};
auto ptr = reinterpret_cast<const uint8_t*>(data);
m_processing.on_bytes({ptr, ptr + 3}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
midi1::input_state_machine m_processing{this->configuration};
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
boost::asio::ip::udp::endpoint m_endpoint;
boost::asio::ip::udp::socket m_socket;
std::jthread m_thread;
std::string m_portname;
alignas(16) unsigned char m_data[65535];
};
}
namespace libremidi::net_ump
{
struct osc_parser_midi2
{
static constexpr char typetag = 'M';
stdx::error
process_midi_bytes(auto& on_message, std::size_t num_msgs, const char* data, std::size_t byte_sz)
{
if (byte_sz % 4 != 0)
return std::errc::bad_message;
auto sz = byte_sz / 4;
auto begin = reinterpret_cast<const uint32_t*>(data);
auto end = begin + sz;
std::size_t accounted = 0;
for (auto it = begin; it < end && accounted < num_msgs;)
{
const auto N = cmidi2_ump_get_message_size_bytes(it) / 4;
switch (N)
{
case 1:
case 2:
case 4:
if (it + N <= end)
{
on_message(it, N);
accounted++;
}
else
return std::errc::bad_message;
break;
default:
return std::errc::bad_message;
}
it += N;
}
return {};
}
};
class midi_in final
: public midi2::in_api
, public error_handler
{
public:
using midi_api::client_open_;
struct
: ump_input_configuration
, dgram_input_configuration
{
} configuration;
explicit midi_in(ump_input_configuration&& conf, dgram_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
, m_ctx{configuration.io_context}
, m_socket{m_ctx.get()}
{
m_socket.open(boost::asio::ip::udp::v4());
m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
m_endpoint
= {boost::asio::ip::make_address(this->configuration.accept),
static_cast<unsigned short>(this->configuration.port)};
boost::system::error_code ec;
m_socket.bind(m_endpoint, ec);
if (ec != boost::system::error_code{})
{
client_open_ = std::errc::address_in_use;
return;
}
client_open_ = stdx::error{};
}
~midi_in() override { close_port(); }
libremidi::API get_current_api() const noexcept override { return libremidi::API::NETWORK_UMP; }
stdx::error open_port(const input_port&, std::string_view port) override
{
return open_virtual_port(port);
}
stdx::error open_virtual_port(std::string_view port) override
{
m_portname = std::string(port);
receive();
if (m_ctx.is_owned())
{
m_thread = std::jthread{[this, &ctx = m_ctx.get()] {
auto wg = boost::asio::make_work_guard(m_ctx);
ctx.run();
}};
}
return stdx::error{};
}
void receive()
{
m_socket.async_receive_from(
boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
[this](auto ec, std::size_t sz) {
if (ec == boost::asio::error::operation_aborted)
return;
if (!ec && sz > 0)
this->on_bytes(reinterpret_cast<const char*>(m_data), sz);
this->receive();
});
}
stdx::error close_port() override
{
// FIXME async close
if (m_ctx.is_owned())
m_ctx.get().stop();
if (m_socket.is_open())
m_socket.close();
return {};
}
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override
{
return std::chrono::steady_clock::now().time_since_epoch().count();
}
void on_bytes(const char* data, std::size_t size)
{
const auto on_msg
= [this](const uint32_t* bytes, std::size_t N) { this->on_message(bytes, N); };
osc_parser<osc_parser_midi2, decltype(on_msg)> parser{{}, on_msg, m_portname};
parser.parse_packet(data, size);
}
void on_message(const uint32_t* ump, std::size_t sz)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = false,
.absolute_is_monotonic = false,
.has_samples = false,
};
static constexpr auto to_ns = []() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
};
m_processing.on_bytes({ump, sz}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
midi2::input_state_machine m_processing{this->configuration};
libremidi::optionally_owned<boost::asio::io_context> m_ctx;
boost::asio::ip::udp::endpoint m_endpoint;
boost::asio::ip::udp::socket m_socket;
std::jthread m_thread;
std::string m_portname;
alignas(16) unsigned char m_data[65535];
};
}
+288
View File
@@ -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;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <libremidi/backends/dummy.hpp>
#include <libremidi/backends/net/config.hpp>
#include <libremidi/backends/net/midi_in.hpp>
#include <libremidi/backends/net/midi_out.hpp>
#include <libremidi/backends/net/observer.hpp>
namespace libremidi::net
{
struct backend
{
using midi_in = libremidi::net::midi_in;
using midi_out = libremidi::net::midi_out;
using midi_observer = libremidi::net::observer;
using midi_in_configuration = libremidi::net::dgram_input_configuration;
using midi_out_configuration = libremidi::net::dgram_output_configuration;
using midi_observer_configuration = libremidi::net::net_observer_configuration;
static const constexpr auto API = libremidi::API::NETWORK;
static const constexpr auto name = "network";
static const constexpr auto display_name = "Network";
static inline bool available() noexcept { return true; }
};
}
@@ -0,0 +1,23 @@
#pragma once
#include <libremidi/backends/net/config.hpp>
#include <libremidi/backends/net/midi_in.hpp>
#include <libremidi/backends/net/midi_out.hpp>
#include <libremidi/backends/net/observer.hpp>
namespace libremidi::net_ump
{
struct backend
{
using midi_in = libremidi::net_ump::midi_in;
using midi_out = libremidi::net_ump::midi_out;
using midi_observer = libremidi::net_ump::observer;
using midi_in_configuration = libremidi::net_ump::dgram_input_configuration;
using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;
using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;
static const constexpr auto API = libremidi::API::NETWORK_UMP;
static const constexpr auto name = "network (UMP)";
static const constexpr auto display_name = "Network (UMP)";
static inline bool available() noexcept { return true; }
};
}
@@ -27,8 +27,8 @@ struct pipewire_input_configuration
pw_main_loop* context{}; pw_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};
}; };
+31 -19
View File
@@ -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>
@@ -128,7 +129,7 @@ struct pipewire_context
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 +140,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;
} }
@@ -297,7 +298,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;
} }
@@ -476,7 +476,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 +498,7 @@ 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)
{ {
// clang-format off // clang-format off
this->port = (struct port*)pw.filter_add_port( this->port = (struct port*)pw.filter_add_port(
@@ -512,7 +512,9 @@ struct pipewire_filter
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(int bytes)
@@ -537,31 +539,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{};
} }
} }
+57 -48
View File
@@ -2,6 +2,7 @@
#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>
@@ -55,10 +56,10 @@ 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)
@@ -90,8 +91,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 +119,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 +135,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 +175,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 +197,7 @@ 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)
{ {
assert(this->global_context); assert(this->global_context);
assert(this->filter); assert(this->filter);
@@ -200,15 +207,15 @@ 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);
if (ret != stdx::error{})
{
self.libremidi_handle_error(self.configuration, "error creating port");
return ret;
}
} }
if (!this->filter->port) return stdx::error{};
{
self.template error<driver_error>(self.configuration, "PipeWire: error creating port");
return false;
}
return true;
} }
void add_callbacks(const observer_configuration& conf) void add_callbacks(const observer_configuration& conf)
@@ -293,12 +300,12 @@ struct pipewire_helpers
} }
} }
void do_close_port() stdx::error do_close_port()
{ {
if (!this->filter) 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 +314,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 +338,7 @@ struct pipewire_helpers
} }
} }
bool link_ports(auto& self, const input_port& in_port) stdx::error link_ports(auto& self, const input_port& in_port)
{ {
// Wait for the pipewire server to send us back our node's info // 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 +348,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 +362,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 +382,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 +390,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,13 +399,13 @@ 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>
@@ -432,7 +437,8 @@ struct pipewire_helpers
// Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer) // 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>
static auto get_ports(const pipewire_context& ctx) noexcept -> std::vector< static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept
-> std::vector<
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>> std::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 +446,23 @@ 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)); ret.push_back(to_port_info<Direction>(port));
} }
} }
if (conf.track_any || conf.track_virtual)
for (auto& node : ctx.current_graph.software_midi) for (auto& 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)); ret.push_back(to_port_info<Direction>(port));
} }
} }
} }
+28 -22
View File
@@ -4,8 +4,6 @@
#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_pipewire final class midi_in_pipewire final
@@ -23,8 +21,17 @@ public:
explicit midi_in_pipewire(input_configuration&& conf, pipewire_input_configuration&& apiconf) explicit midi_in_pipewire(input_configuration&& conf, pipewire_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{ {
create_context(*this); if (auto ret = create_context(*this); ret != stdx::error{})
create_filter(*this); {
client_open_ = ret;
return;
}
if (auto ret = create_filter(*this); ret != stdx::error{})
{
client_open_ = ret;
return;
}
client_open_ = stdx::error{};
} }
~midi_in_pipewire() override ~midi_in_pipewire() override
@@ -33,43 +40,42 @@ public:
do_close_port(); do_close_port();
destroy_filter(*this); destroy_filter(*this);
destroy_context(); destroy_context();
} client_open_ = std::errc::not_connected;
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_pipewire: set_client_name unsupported");
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; } libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
bool open_port(const input_port& in_port, std::string_view name) override stdx::error open_port(const input_port& in_port, std::string_view name) override
{ {
if (!create_local_port(*this, name, SPA_DIRECTION_INPUT)) if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
return false; return err;
if (!link_ports(*this, in_port)) if (auto err = link_ports(*this, in_port); err != stdx::error{})
return false; return err;
start_thread(); start_thread();
return true; return stdx::error{};
} }
bool open_virtual_port(std::string_view name) override stdx::error open_virtual_port(std::string_view name) override
{ {
if (!create_local_port(*this, name, SPA_DIRECTION_INPUT)) if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
return false; return err;
start_thread(); start_thread();
return true; return stdx::error{};
} }
void close_port() override stdx::error close_port() override
{ {
stop_thread(); stop_thread();
do_close_port(); return do_close_port();
} }
void set_port_name(std::string_view port_name) override { rename_port(port_name); } stdx::error set_port_name(std::string_view port_name) override
{
return rename_port(port_name);
}
timestamp absolute_timestamp() const noexcept override { return system_ns(); } timestamp absolute_timestamp() const noexcept override { return system_ns(); }
@@ -24,8 +24,17 @@ public:
midi_out_pipewire(output_configuration&& conf, pipewire_output_configuration&& apiconf) midi_out_pipewire(output_configuration&& conf, pipewire_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{ {
create_context(*this); if (auto ret = create_context(*this); ret != stdx::error{})
create_filter(*this); {
client_open_ = ret;
return;
}
if (auto ret = create_filter(*this); ret != stdx::error{})
{
client_open_ = ret;
return;
}
client_open_ = stdx::error{};
} }
~midi_out_pipewire() override ~midi_out_pipewire() override
@@ -34,47 +43,46 @@ public:
do_close_port(); do_close_port();
destroy_filter(*this); destroy_filter(*this);
destroy_context(); destroy_context();
} client_open_ = std::errc::not_connected;
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_pipewire: set_client_name unsupported");
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; } libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
bool open_port(const output_port& out_port, std::string_view name) override stdx::error open_port(const output_port& out_port, std::string_view name) override
{ {
if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT)) if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
return false; return err;
this->filter->set_port_buffer(configuration.output_buffer_size); this->filter->set_port_buffer(configuration.output_buffer_size);
if (!link_ports(*this, out_port)) if (auto err = link_ports(*this, out_port); err != stdx::error{})
return false; return err;
start_thread(); start_thread();
return true; return stdx::error{};
} }
bool open_virtual_port(std::string_view name) override stdx::error open_virtual_port(std::string_view name) override
{ {
if (!create_local_port(*this, name, SPA_DIRECTION_OUTPUT)) if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
return false; return err;
this->filter->set_port_buffer(configuration.output_buffer_size); this->filter->set_port_buffer(configuration.output_buffer_size);
start_thread(); start_thread();
return true; return stdx::error{};
} }
void close_port() override stdx::error close_port() override
{ {
stop_thread(); stop_thread();
do_close_port(); return do_close_port();
} }
void set_port_name(std::string_view port_name) override { rename_port(port_name); } stdx::error set_port_name(std::string_view port_name) override
{
return rename_port(port_name);
}
int process(spa_io_position* pos) int process(spa_io_position* pos)
{ {
@@ -120,6 +128,8 @@ public:
if (res == -ENOSPC) if (res == -ENOSPC)
break; break;
// Recycle the memory
m_gcqueue.enqueue(std::move(m));
m_queue.pop(); m_queue.pop();
} }
spa_pod_builder_pop(&build, &f); spa_pod_builder_pop(&build, &f);
@@ -141,9 +151,14 @@ 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
{ {
m_queue.enqueue(libremidi::message(midi_bytes{message, message + size}, 0)); libremidi::message m;
m_gcqueue.try_dequeue(m);
m.bytes.assign(message, message + size);
m.timestamp = 0;
m_queue.enqueue(std::move(m));
return stdx::error{};
} }
int convert_timestamp(int64_t user) const noexcept int convert_timestamp(int64_t user) const noexcept
@@ -159,13 +174,15 @@ public:
} }
} }
void schedule_message(int64_t ts, const unsigned char* message, size_t size) override stdx::error schedule_message(int64_t ts, const unsigned char* message, size_t size) override
{ {
m_queue.enqueue( m_queue.enqueue(
libremidi::message(midi_bytes{message, message + size}, convert_timestamp(ts))); libremidi::message(midi_bytes{message, message + size}, convert_timestamp(ts)));
return stdx::error{};
} }
moodycamel::ReaderWriterQueue<libremidi::message> m_queue; moodycamel::ReaderWriterQueue<libremidi::message> m_queue;
moodycamel::ReaderWriterQueue<libremidi::message> m_gcqueue;
std::atomic_int64_t m_process_clock = 0; std::atomic_int64_t m_process_clock = 0;
}; };
} }
@@ -39,18 +39,29 @@ public:
this->add_callbacks(configuration); this->add_callbacks(configuration);
this->start_thread(); this->start_thread();
} }
if (configuration.notify_in_constructor)
{
if (configuration.input_added)
for (const auto& p : get_input_ports())
configuration.input_added(p);
if (configuration.output_added)
for (const auto& p : get_output_ports())
configuration.output_added(p);
}
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; } libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE; }
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<SPA_DIRECTION_OUTPUT>(*this->global_context); return get_ports<SPA_DIRECTION_OUTPUT>(this->configuration, *this->global_context);
} }
std::vector<libremidi::output_port> get_output_ports() const noexcept override std::vector<libremidi::output_port> get_output_ports() const noexcept override
{ {
return get_ports<SPA_DIRECTION_INPUT>(*this->global_context); return get_ports<SPA_DIRECTION_INPUT>(this->configuration, *this->global_context);
} }
~observer_pipewire() ~observer_pipewire()
+35 -2
View File
@@ -5,15 +5,23 @@
#include <libremidi/detail/midi_api.hpp> #include <libremidi/detail/midi_api.hpp>
#include <mutex> #include <mutex>
#include <cctype>
#include <string> #include <string>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <guiddef.h> #include <guiddef.h>
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Devices.Enumeration.h> #include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Midi2.h>
namespace midi2 = winrt::Windows::Devices::Midi2;
namespace foundation = winrt::Windows::Foundation;
namespace collections = winrt::Windows::Foundation::Collections;
#include <winrt/Microsoft.Devices.Midi2.h>
// clang-format on // clang-format on
namespace libremidi::winmidi namespace libremidi::winmidi
@@ -22,6 +30,31 @@ using namespace winrt;
using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Storage::Streams; using namespace winrt::Windows::Storage::Streams;
using namespace Microsoft::Devices::Midi2; using namespace winrt::Windows::Devices::Midi2;
using namespace Windows::Devices::Enumeration; using namespace Windows::Devices::Enumeration;
inline bool ichar_equals(char a, char b)
{
return std::tolower(static_cast<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(b));
}
inline bool iequals(std::string_view lhs, std::string_view rhs)
{
return std::ranges::equal(lhs, rhs, ichar_equals);
}
inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation
get_port_by_name(const std::string& port) {
auto eps = MidiEndpointDeviceInformation::FindAll();
for (const auto& ep : eps)
{
auto str = to_string(ep.Id());
if (str.empty())
continue;
if (iequals(str, port))
return ep;
}
return {nullptr};
}
} }
+48 -45
View File
@@ -3,6 +3,9 @@
#include <libremidi/backends/winmidi/helpers.hpp> #include <libremidi/backends/winmidi/helpers.hpp>
#include <libremidi/backends/winmidi/observer.hpp> #include <libremidi/backends/winmidi/observer.hpp>
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <iostream>
namespace libremidi::winmidi namespace libremidi::winmidi
{ {
@@ -21,23 +24,15 @@ public:
explicit midi_in_impl( explicit midi_in_impl(
libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf) libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
, m_session{MidiSession::CreateSession(L"libremidi session")}
{ {
this->client_open_ = stdx::error{};
} }
~midi_in_impl() override { close_port(); } ~midi_in_impl() override
bool open_virtual_port(std::string_view) override
{ {
warning(configuration, "midi_in_winmidi: open_virtual_port unsupported"); close_port();
return false; this->client_open_ = std::errc::not_connected;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_winmidi: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_winmidi: set_port_name unsupported");
} }
libremidi::API get_current_api() const noexcept override libremidi::API get_current_api() const noexcept override
@@ -45,50 +40,58 @@ public:
return libremidi::API::WINDOWS_MIDI_SERVICES; return libremidi::API::WINDOWS_MIDI_SERVICES;
} }
bool open_port(const input_port& port, std::string_view) override stdx::error open_port(const input_port& port, std::string_view) override
{ {
#if 0 auto ep = get_port_by_name(port.port_name);
const auto id = winrt::to_hstring(port.port_name); if (!ep)
if (id.empty()) return std::errc::address_not_available;
return false;
port_ = get(MidiInPort::FromIdAsync(id)); m_endpoint = m_session.CreateEndpointConnection(ep.Id());
if (!port_)
return false; m_revoke_token = m_endpoint.MessageReceived(
port_.MessageReceived( [&](const foundation::IInspectable& sender,
[=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort, const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) { process_message(args);
this->process_message(args.Message());
}); });
#endif m_endpoint.Open();
return true;
return stdx::error{};
} }
#if 0 void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg)
void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)
{ {
auto reader = DataReader::FromBuffer(msg.RawData()); static constexpr timestamp_backend_info timestamp_info{
auto begin = msg.RawData().data(); .has_absolute_timestamps = true,
auto end = begin + msg.RawData().Length(); .absolute_is_monotonic = false,
.has_samples = false,
};
auto t = msg.Timestamp().count(); const auto& ump = msg.GetMessagePacket();
this->configuration.on_message(libremidi::message{{begin, end}, t}); const auto& b = ump.GetAllWords();
}
#endif
void close_port() override uint32_t ump_space[64];
{ array_view<uint32_t> ref{ump_space};
#if 0 b.GetMany(0, ref);
if (port_)
{ auto to_ns = [t = ump.Timestamp()] { return t; };
port_.Close(); m_processing.on_bytes(
port_ = nullptr; {ump_space, ump_space + b.Size()}, m_processing.timestamp<timestamp_info>(to_ns, 0));
} }
#endif
stdx::error close_port() override
{
m_endpoint.MessageReceived(m_revoke_token);
m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());
return stdx::error{};
} }
virtual timestamp absolute_timestamp() const noexcept override { return {}; }
private: private:
// winrt::Microsoft::Devices::Midi2::IMidiInPort port_{nullptr}; MidiSession m_session;
winrt::event_token m_revoke_token{};
winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
midi2::input_state_machine m_processing{this->configuration};
}; };
} }
+42 -44
View File
@@ -1,4 +1,5 @@
#pragma once #pragma once
#include <libremidi/detail/ump_stream.hpp>
#include <libremidi/backends/winmidi/config.hpp> #include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmidi/helpers.hpp> #include <libremidi/backends/winmidi/helpers.hpp>
#include <libremidi/backends/winmidi/observer.hpp> #include <libremidi/backends/winmidi/observer.hpp>
@@ -20,72 +21,69 @@ public:
midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf) midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
, m_session{MidiSession::CreateSession(L"libremidi session")}
{ {
this->client_open_ = stdx::error{};
} }
~midi_out_impl() override { close_port(); } ~midi_out_impl() override { close_port(); }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_out_winmidi: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_winmidi: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_winmidi: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override libremidi::API get_current_api() const noexcept override
{ {
return libremidi::API::WINDOWS_MIDI_SERVICES; return libremidi::API::WINDOWS_MIDI_SERVICES;
} }
bool open_port(const output_port& port, std::string_view) override stdx::error open_port(const output_port& port, std::string_view) override
{ {
#if 0 auto ep = get_port_by_name(port.port_name);
const auto id = winrt::to_hstring(port.port_name); if (!ep)
if (id.empty()) return std::errc::address_not_available;
return false;
port_ = get(MidiOutPort::FromIdAsync(id)); m_endpoint = m_session.CreateEndpointConnection(ep.Id());
return bool(port_); m_endpoint.Open();
#endif
return true; return stdx::error{};
} }
void close_port() override stdx::error close_port() override
{ {
#if 0 m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());
if (port_) return stdx::error{};
{
port_.Close();
port_ = {};
}
#endif
} }
void send_ump(const uint32_t* message, size_t size) override stdx::error send_ump(const uint32_t* message, size_t size) override
{ {
#if 0 auto write_func = [this](const uint32_t* ump, int64_t bytes) -> std::errc {
if (!port_) MidiSendMessageResults ret{};
return; switch(bytes / 4)
{
case 1:
ret = m_endpoint.SendSingleMessagePacket(MidiMessage32(0, ump[0]));
break;
case 2:
ret = m_endpoint.SendSingleMessagePacket(MidiMessage64(0, ump[0], ump[1]));
break;
case 3:
ret = m_endpoint.SendSingleMessagePacket(MidiMessage96(0, ump[0], ump[1], ump[2]));
break;
case 4:
ret = m_endpoint.SendSingleMessagePacket(MidiMessage128(0, ump[0], ump[1], ump[2], ump[3]));
break;
default:
return std::errc::bad_message;
}
InMemoryRandomAccessStream str; if(ret != MidiSendMessageResults::Succeeded)
DataWriter rb(str); return std::errc::bad_message;
rb.WriteBytes( return std::errc{0};
winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size}); };
port_.SendBuffer(rb.DetachBuffer());
#endif return segment_ump_stream(message, size, write_func, []() {});
} }
private: private:
#if 0 MidiSession m_session;
winrt::Windows::Devices::Midi2::IMidiOutPort port_{nullptr}; winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
#endif
}; };
} }
+18 -21
View File
@@ -20,13 +20,11 @@ public:
{ {
} configuration; } configuration;
MidiSession session;
explicit observer_impl( explicit observer_impl(
libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf) libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
, session{ , session{MidiSession::CreateSession(L"libremidi session")}
MidiSession::CreateNewSession(L"libremidi session", MidiSessionSettings::Default())}
{ {
if (!configuration.has_callbacks()) if (!configuration.has_callbacks())
return; return;
@@ -36,6 +34,7 @@ public:
if (configuration.input_added) if (configuration.input_added)
for (const auto& p : get_input_ports()) for (const auto& p : get_input_ports())
configuration.input_added(p); configuration.input_added(p);
if (configuration.output_added) if (configuration.output_added)
for (const auto& p : get_output_ports()) for (const auto& p : get_output_ports())
configuration.output_added(p); configuration.output_added(p);
@@ -87,10 +86,14 @@ public:
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
for (const auto& selectedEndpointInformation : endpointDevices) for (const auto& ep : endpointDevices)
{ {
if(ep.Name().starts_with(L"Diagnostics")) {
continue;
}
// FIXME if(has input...) // FIXME if(has input...)
ret.emplace_back(to_port_info(selectedEndpointInformation));
ret.emplace_back(to_port_info<true>(ep));
} }
return ret; return ret;
@@ -102,10 +105,13 @@ public:
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
for (const auto& selectedEndpointInformation : endpointDevices) for (const auto& ep : endpointDevices)
{ {
if(ep.Name().starts_with(L"Diagnostics")) {
continue;
}
// FIXME if(has output...) // FIXME if(has output...)
ret.emplace_back(to_port_info(selectedEndpointInformation)); ret.emplace_back(to_port_info<false>(ep));
} }
return ret; return ret;
@@ -114,38 +120,29 @@ public:
void on_input_added(const DeviceInformation& name) void on_input_added(const DeviceInformation& name)
{ {
if (configuration.input_added) if (configuration.input_added)
configuration.input_added(to_port_info(name)); configuration.input_added(to_port_info<true>(name));
} }
void on_input_removed(const DeviceInformation& name) void on_input_removed(const DeviceInformation& name)
{ {
if (configuration.input_removed) if (configuration.input_removed)
configuration.input_removed(to_port_info(name)); configuration.input_removed(to_port_info<true>(name));
} }
void on_output_added(const DeviceInformation& name) void on_output_added(const DeviceInformation& name)
{ {
if (configuration.output_added) if (configuration.output_added)
configuration.output_added(to_port_info(name)); configuration.output_added(to_port_info<false>(name));
} }
void on_output_removed(const DeviceInformation& name) void on_output_removed(const DeviceInformation& name)
{ {
if (configuration.output_removed) if (configuration.output_removed)
configuration.output_removed(to_port_info(name)); configuration.output_removed(to_port_info<false>(name));
} }
private: private:
#if 0 MidiSession session;
static inline observer_winmidi_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};
static inline observer_winmidi_internal internalOutPortObserver_{
MidiOutPort::GetDeviceSelector()};
int evTokenOnInputAdded_{-1};
int evTokenOnInputRemoved_{-1};
int evTokenOnOutputAdded_{-1};
int evTokenOnOutputRemoved_{-1};
#endif
}; };
} }
@@ -0,0 +1,70 @@
#pragma once
// clang-format off
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include <mmsystem.h>
// clang-format on
#include <libremidi/error.hpp>
namespace libremidi
{
struct winmm_error_domain : public stdx::error_domain
{
public:
constexpr winmm_error_domain() noexcept
: error_domain{{0xa32b080ac770514eULL, 0xef59a407f921da43ULL}}
{
}
stdx::string_ref name() const noexcept override { return "winmm"; }
bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override
{
if (lhs.domain() == rhs.domain())
return error_cast<int>(lhs) == error_cast<int>(rhs);
return false;
}
stdx::string_ref message(const stdx::error& e) const noexcept override
{
switch (error_cast<int>(e))
{
case MMSYSERR_NOERROR: return "No error";
case MMSYSERR_ERROR: return "Error";
case MMSYSERR_BADDEVICEID: return "Bad device ID";
case MMSYSERR_NOTENABLED: return "Not enabled";
case MMSYSERR_ALLOCATED: return "Allocated";
case MMSYSERR_INVALHANDLE: return "Invalid handle";
case MMSYSERR_NODRIVER: return "No driver";
case MMSYSERR_NOMEM: return "No memory";
case MMSYSERR_NOTSUPPORTED: return "Not supported";
case MMSYSERR_BADERRNUM: return "Bad errnum";
case MMSYSERR_INVALFLAG: return "Invalid flag";
case MMSYSERR_INVALPARAM: return "Invalid parameter";
case MMSYSERR_HANDLEBUSY: return "Handle busy";
case MMSYSERR_INVALIDALIAS: return "Invalid alias";
case MMSYSERR_BADDB: return "Bad database";
case MMSYSERR_KEYNOTFOUND: return "Key not found";
case MMSYSERR_READERROR: return "Read error";
case MMSYSERR_WRITEERROR: return "Write error";
case MMSYSERR_DELETEERROR: return "Delete error";
case MMSYSERR_VALNOTFOUND: return "Value not found";
case MMSYSERR_NODRIVERCB: return "No driver callback";
case MMSYSERR_MOREDATA: return "More data";
}
return "Unknown error code";
}
};
inline stdx::error from_mmerr(int ret) noexcept
{
static constexpr winmm_error_domain domain;
return {ret, domain};
}
}
+5 -12
View File
@@ -1,15 +1,8 @@
#pragma once #pragma once
#define NOMINMAX 1 #include <libremidi/backends/winmm/error_domain.hpp>
#define WIN32_LEAN_AND_MEAN 1
#include <libremidi/detail/midi_api.hpp> #include <libremidi/detail/midi_api.hpp>
#include <algorithm> #include <algorithm>
// clang-format off
#include <windows.h>
#include <mmsystem.h>
// clang-format on
namespace libremidi namespace libremidi
{ {
@@ -44,10 +37,10 @@ inline std::string ConvertToUTF8(const TCHAR* str)
// Next functions add the portNumber to the name so that // Next functions add the portNumber to the name so that
// the device's names are sure to be listed with individual names // the device's names are sure to be listed with individual names
// even when they have the same brand name // even when they have the same brand name
inline void MakeUniqueInPortName(std::string& deviceName, unsigned int portNumber) inline void MakeUniqueInPortName(std::string& deviceName, std::size_t portNumber)
{ {
int x = 1; int x = 1;
for (unsigned int i = 0; i < portNumber; i++) for (std::size_t i = 0; i < portNumber; i++)
{ {
MIDIINCAPS deviceCaps; MIDIINCAPS deviceCaps;
midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS)); midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS));
@@ -61,10 +54,10 @@ inline void MakeUniqueInPortName(std::string& deviceName, unsigned int portNumbe
deviceName += std::to_string(x); deviceName += std::to_string(x);
} }
inline void MakeUniqueOutPortName(std::string& deviceName, unsigned int portNumber) inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumber)
{ {
int x = 1; int x = 1;
for (unsigned int i = 0; i < portNumber; i++) for (std::size_t i = 0; i < portNumber; i++)
{ {
MIDIOUTCAPS deviceCaps; MIDIOUTCAPS deviceCaps;
midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS)); midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS));
+34 -51
View File
@@ -22,20 +22,17 @@ public:
explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf) explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{ {
// We'll issue a warning here if no devices are available but not
// throw an error since the user can plugin something later.
if (midiInGetNumDevs() == 0)
{
warning(
configuration, "midi_in_winmm::initialize: no MIDI input devices currently available.");
}
if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400)) if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400))
{ {
warning( libremidi_handle_error(
configuration, configuration,
"midi_in_winmm::initialize: InitializeCriticalSectionAndSpinCount failed."); "InitializeCriticalSectionAndSpinCount failed.");
this->client_open_ = std::errc::too_many_files_open;
return;
} }
this->client_open_ = stdx::error{};
} }
~midi_in_winmm() override ~midi_in_winmm() override
@@ -43,35 +40,22 @@ public:
// Close a connection if it exists. // Close a connection if it exists.
midi_in_winmm::close_port(); midi_in_winmm::close_port();
if(this->client_open_ == stdx::error{})
DeleteCriticalSection(&(this->_mutex)); DeleteCriticalSection(&(this->_mutex));
} }
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_in_winmm: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_winmm: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_winmm: set_port_name unsupported");
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
bool do_open(unsigned int portNumber) stdx::error do_open(std::size_t portNumber)
{ {
MMRESULT result = midiInOpen( MMRESULT result = midiInOpen(
&this->inHandle, portNumber, std::bit_cast<DWORD_PTR>(&midiInputCallback), &this->inHandle, portNumber, std::bit_cast<DWORD_PTR>(&midiInputCallback),
std::bit_cast<DWORD_PTR>(this), CALLBACK_FUNCTION); std::bit_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
if (result != MMSYSERR_NOERROR) if (result != MMSYSERR_NOERROR)
{ {
error<driver_error>( libremidi_handle_error(
configuration, "midi_in_winmm::open_port: error creating Windows MM MIDI input port."); configuration, "error creating Windows MM MIDI input port.");
return false; return from_mmerr(result);
} }
// Allocate and init the sysex buffers. // Allocate and init the sysex buffers.
@@ -90,11 +74,11 @@ public:
{ {
midiInClose(this->inHandle); midiInClose(this->inHandle);
this->inHandle = nullptr; this->inHandle = nullptr;
error<driver_error>( libremidi_handle_error(
configuration, configuration,
"midi_in_winmm::open_port: error starting Windows MM MIDI input port " "error starting Windows MM MIDI input port "
"(PrepareHeader)."); "(PrepareHeader).");
return false; return from_mmerr(result);
} }
// Register the buffer. // Register the buffer.
@@ -103,11 +87,11 @@ public:
{ {
midiInClose(this->inHandle); midiInClose(this->inHandle);
this->inHandle = nullptr; this->inHandle = nullptr;
error<driver_error>( libremidi_handle_error(
configuration, configuration,
"midi_in_winmm::open_port: error starting Windows MM MIDI input port " "error starting Windows MM MIDI input port "
"(AddBuffer)."); "(AddBuffer).");
return false; return from_mmerr(result);
} }
} }
@@ -117,15 +101,15 @@ public:
{ {
midiInClose(this->inHandle); midiInClose(this->inHandle);
this->inHandle = nullptr; this->inHandle = nullptr;
error<driver_error>( libremidi_handle_error(
configuration, "midi_in_winmm::open_port: error starting Windows MM MIDI input port."); configuration, "error starting Windows MM MIDI input port.");
return false; return from_mmerr(result);
} }
return true; return stdx::error{};
} }
bool open_port(const input_port& p, std::string_view) override stdx::error open_port(const input_port& p, std::string_view) override
{ {
observer_winmm obs{{}, winmm_observer_configuration{}}; observer_winmm obs{{}, winmm_observer_configuration{}};
auto ports = obs.get_input_ports(); auto ports = obs.get_input_ports();
@@ -142,12 +126,12 @@ public:
if (p.port_name == port.port_name) if (p.port_name == port.port_name)
return do_open(port.port); return do_open(port.port);
} }
error<invalid_parameter_error>( libremidi_handle_error(
configuration, "midi_in_winmm::open_port: port not found: " + p.port_name); configuration, "port not found: " + p.port_name);
return false; return std::errc::invalid_argument;
} }
void close_port() override stdx::error close_port() override
{ {
if (connected_) if (connected_)
{ {
@@ -170,9 +154,9 @@ public:
if (res != MMSYSERR_NOERROR) if (res != MMSYSERR_NOERROR)
{ {
warning( libremidi_handle_warning(
configuration, configuration,
"midi_in_winmm::open_port: error closing Windows MM MIDI input " "error closing Windows MM MIDI input "
"port (midiInUnprepareHeader)."); "port (midiInUnprepareHeader).");
continue; continue;
} }
@@ -187,6 +171,7 @@ public:
this->inHandle = nullptr; this->inHandle = nullptr;
LeaveCriticalSection(&(this->_mutex)); LeaveCriticalSection(&(this->_mutex));
} }
return stdx::error{};
} }
private: private:
@@ -255,8 +240,7 @@ private:
const auto* sysex = reinterpret_cast<MIDIHDR*>(midiMessage); const auto* sysex = reinterpret_cast<MIDIHDR*>(midiMessage);
if(inputStatus == MIM_LONGERROR) if(inputStatus == MIM_LONGERROR)
{ {
self.m_processing.message.bytes.clear(); self.m_processing.reset();
self.m_processing.state = self.m_processing.main;
} }
else if (!self.configuration.ignore_sysex) else if (!self.configuration.ignore_sysex)
{ {
@@ -286,10 +270,9 @@ private:
LeaveCriticalSection(&(self._mutex)); LeaveCriticalSection(&(self._mutex));
if (result != MMSYSERR_NOERROR) if (result != MMSYSERR_NOERROR)
{ {
#if defined(__LIBREMIDI_DEBUG__) LIBREMIDI_LOG(
std::cerr << "\nmidi_in::midiInputCallback: error sending sysex to " "error sending sysex to "
"Midi device!!\n\n"; "Midi device!!");
#endif
} }
} }
} }
+31 -48
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <libremidi/backends/winmm/config.hpp> #include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winmm/helpers.hpp> #include <libremidi/backends/winmm/helpers.hpp>
#include <libremidi/backends/winmm/observer.hpp>
#include <libremidi/detail/midi_out.hpp> #include <libremidi/detail/midi_out.hpp>
namespace libremidi namespace libremidi
@@ -20,55 +21,34 @@ public:
midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf) midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{ {
// We'll issue a warning here if no devices are available but not this->client_open_ = stdx::error{};
// throw an error since the user can plug something in later.
if (midiOutGetNumDevs() == 0)
{
warning(
configuration,
"midi_out_winmm::initialize: no MIDI output devices currently "
"available.");
}
} }
~midi_out_winmm() override ~midi_out_winmm() override
{ {
// Close a connection if it exists. // Close a connection if it exists.
midi_out_winmm::close_port(); midi_out_winmm::close_port();
} this->client_open_ = std::errc::not_connected;
bool open_virtual_port(std::string_view) override
{
warning(configuration, "midi_out_winmm: open_virtual_port unsupported");
return false;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_winmm: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_winmm: set_port_name unsupported");
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
[[nodiscard]] bool do_open(unsigned int portNumber) [[nodiscard]] stdx::error do_open(unsigned int portNumber)
{ {
MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL); MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL);
if (result != MMSYSERR_NOERROR) if (result != MMSYSERR_NOERROR)
{ {
error<driver_error>( libremidi_handle_error(
configuration, configuration,
"midi_out_winmm::open_port: error creating Windows MM MIDI output " "error creating Windows MM MIDI output "
"port."); "port.");
return false; return from_mmerr(result);
} }
return true; return stdx::error{};
} }
bool open_port(const output_port& p, std::string_view) override stdx::error open_port(const output_port& p, std::string_view) override
{ {
observer_winmm obs{{}, winmm_observer_configuration{}}; observer_winmm obs{{}, winmm_observer_configuration{}};
auto ports = obs.get_output_ports(); auto ports = obs.get_output_ports();
@@ -85,29 +65,30 @@ public:
if (p.port_name == port.port_name) if (p.port_name == port.port_name)
return do_open(port.port); return do_open(port.port);
} }
error<invalid_parameter_error>( libremidi_handle_error(
configuration, "midi_out_winmm::open_port: port not found: " + p.port_name); configuration, "port not found: " + p.port_name);
return false; return std::errc::invalid_argument;
} }
void close_port() override stdx::error close_port() override
{ {
if (this->outHandle) if (this->outHandle)
midiOutClose(this->outHandle); midiOutClose(this->outHandle);
this->outHandle = nullptr; this->outHandle = nullptr;
connected_ = false; connected_ = false;
return stdx::error{};
} }
void send_message(const unsigned char* message, size_t size) override stdx::error send_message(const unsigned char* message, size_t size) override
{ {
if (!connected_) if (!connected_)
return; return std::errc::not_connected;
if (size == 0) if (size == 0)
{ {
warning(configuration, "midi_out_winmm::send_message: message argument is empty!"); libremidi_handle_warning(configuration, "message argument is empty!");
return; return std::errc::invalid_argument;
} }
if (message[0] == 0xF0) if (message[0] == 0xF0)
@@ -126,18 +107,18 @@ public:
auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)); auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR) if (result != MMSYSERR_NOERROR)
{ {
error<driver_error>( libremidi_handle_error(
configuration, "midi_out_winmm::send_message: error preparing sysex header."); configuration, "error preparing sysex header.");
return; return from_mmerr(result);
} }
// Send the message. // Send the message.
result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR)); result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR) if (result != MMSYSERR_NOERROR)
{ {
error<driver_error>( libremidi_handle_error(
configuration, "midi_out_winmm::send_message: error sending sysex message."); configuration, "error sending sysex message.");
return; return from_mmerr(result);
} }
// Unprepare the buffer and MIDIHDR. // Unprepare the buffer and MIDIHDR.
@@ -152,11 +133,11 @@ public:
// Make sure the message size isn't too big. // Make sure the message size isn't too big.
if (size > 3) if (size > 3)
{ {
warning( libremidi_handle_warning(
configuration, configuration,
"midi_out_winmm::send_message: message size is greater than 3 bytes " "message size is greater than 3 bytes "
"(and not sysex)!"); "(and not sysex)!");
return; return std::errc::message_size;
} }
// Pack MIDI bytes into double word. // Pack MIDI bytes into double word.
@@ -167,10 +148,12 @@ public:
auto result = midiOutShortMsg(this->outHandle, packet); auto result = midiOutShortMsg(this->outHandle, packet);
if (result != MMSYSERR_NOERROR) if (result != MMSYSERR_NOERROR)
{ {
error<driver_error>( libremidi_handle_error(
configuration, "midi_out_winmm::send_message: error sending MIDI message."); configuration, "error sending MIDI message.");
return from_mmerr(result);
} }
} }
return stdx::error{};
} }
private: private:
+10 -19
View File
@@ -21,38 +21,28 @@ public:
explicit midi_in_winuwp(input_configuration&& conf, winuwp_input_configuration&& apiconf) explicit midi_in_winuwp(input_configuration&& conf, winuwp_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{ {
winrt_init(); winrt_init();
this->client_open_ = stdx::error{};
} }
~midi_in_winuwp() override { close_port(); } ~midi_in_winuwp() override
bool open_virtual_port(std::string_view) override
{ {
warning(configuration, "midi_in_winuwp: open_virtual_port unsupported"); close_port();
return false; this->client_open_ = std::errc::not_connected;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_in_winuwp: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_in_winuwp: set_port_name unsupported");
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
bool open_port(const input_port& port, std::string_view) override stdx::error open_port(const input_port& port, std::string_view) override
{ {
const auto id = winrt::to_hstring(port.port_name); const auto id = winrt::to_hstring(port.port_name);
if (id.empty()) if (id.empty())
return false; return std::errc::invalid_argument;
port_ = get(MidiInPort::FromIdAsync(id)); port_ = get(MidiInPort::FromIdAsync(id));
if (!port_) if (!port_)
return false; return std::errc::io_error;
midi_start_timestamp = std::chrono::steady_clock::now(); midi_start_timestamp = std::chrono::steady_clock::now();
@@ -62,7 +52,7 @@ public:
this->process_message(args.Message()); this->process_message(args.Message());
}); });
return true; return stdx::error{};
} }
void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg) void process_message(const winrt::Windows::Devices::Midi::IMidiMessage& msg)
@@ -81,13 +71,14 @@ public:
m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0)); m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0));
} }
void close_port() override stdx::error close_port() override
{ {
if (port_) if (port_)
{ {
port_.Close(); port_.Close();
port_ = nullptr; port_ = nullptr;
} }
return stdx::error{};
} }
timestamp absolute_timestamp() const noexcept override timestamp absolute_timestamp() const noexcept override
+16 -19
View File
@@ -22,55 +22,52 @@ public:
: configuration{std::move(conf), std::move(apiconf)} : configuration{std::move(conf), std::move(apiconf)}
{ {
winrt_init(); winrt_init();
this->client_open_ = stdx::error{};
} }
~midi_out_winuwp() override { close_port(); } ~midi_out_winuwp() override
bool open_virtual_port(std::string_view) override
{ {
warning(configuration, "midi_out_winuwp: open_virtual_port unsupported"); close_port();
return false; this->client_open_ = std::errc::not_connected;
}
void set_client_name(std::string_view) override
{
warning(configuration, "midi_out_winuwp: set_client_name unsupported");
}
void set_port_name(std::string_view) override
{
warning(configuration, "midi_out_winuwp: set_port_name unsupported");
} }
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
bool open_port(const output_port& port, std::string_view) override stdx::error open_port(const output_port& port, std::string_view) override
{ {
const auto id = winrt::to_hstring(port.port_name); const auto id = winrt::to_hstring(port.port_name);
if (id.empty()) if (id.empty())
return false; return std::errc::invalid_argument;
port_ = get(MidiOutPort::FromIdAsync(id)); port_ = get(MidiOutPort::FromIdAsync(id));
return bool(port_); if (!bool(port_))
return std::errc::io_error;
return stdx::error{};
} }
void close_port() override stdx::error close_port() override
{ {
if (port_) if (port_)
{ {
port_.Close(); port_.Close();
port_ = {}; port_ = {};
} }
return stdx::error{};
} }
void send_message(const unsigned char* message, size_t size) override stdx::error send_message(const unsigned char* message, size_t size) override
{ {
if (!port_) if (!port_)
return; return std::errc::not_connected;
InMemoryRandomAccessStream str; InMemoryRandomAccessStream str;
DataWriter rb(str); DataWriter rb(str);
rb.WriteBytes( rb.WriteBytes(
winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size}); winrt::array_view<const uint8_t>{(const uint8_t*)message, (const uint8_t*)message + size});
port_.SendBuffer(rb.DetachBuffer()); port_.SendBuffer(rb.DetachBuffer());
return stdx::error{};
} }
private: private:
+20 -12
View File
@@ -31,7 +31,7 @@ struct client_configuration
best to set the error callback function before opening a port. best to set the error callback function before opening a port.
*/ */
midi_error_callback on_error{}; midi_error_callback on_error{};
midi_error_callback on_warning{}; midi_warning_callback on_warning{};
//! Poll period for observation polling operations, if relevant to the backend //! Poll period for observation polling operations, if relevant to the backend
std::chrono::milliseconds poll_period{100}; std::chrono::milliseconds poll_period{100};
@@ -115,9 +115,9 @@ public:
port, port,
input_configuration{ input_configuration{
.on_message .on_message
= [this, port](libremidi::message&& m) { = [this,
configuration.on_message(port, std::move(m)); port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); },
}, .on_raw_data = {},
.get_timestamp = {}, .get_timestamp = {},
.on_error = configuration.on_error, .on_error = configuration.on_error,
@@ -153,32 +153,40 @@ public:
void remove_input(const input_port& port) { m_inputs.erase(port); } void remove_input(const input_port& port) { m_inputs.erase(port); }
void remove_output(const output_port& port) { m_outputs.erase(port); } void remove_output(const output_port& port) { m_outputs.erase(port); }
void send_message(const unsigned char* message, size_t size) stdx::error send_message(const unsigned char* message, size_t size)
{ {
for (auto& [_, out] : m_outputs) for (auto& [_, out] : m_outputs)
{ {
out.send_message(message, size); if (auto err = out.send_message(message, size); err != stdx::error{})
return err;
} }
return stdx::error{};
} }
void send_ump(const uint32_t* message, size_t size) stdx::error send_ump(const uint32_t* message, size_t size)
{ {
for (auto& [_, out] : m_outputs) for (auto& [_, out] : m_outputs)
{ {
out.send_ump(message, size); if (auto err = out.send_ump(message, size); err != stdx::error{})
return err;
} }
return stdx::error{};
} }
void send_message(const output_port& port, const unsigned char* message, size_t size) stdx::error send_message(const output_port& port, const unsigned char* message, size_t size)
{ {
if (auto it = m_outputs.find(port); it != m_outputs.end()) if (auto it = m_outputs.find(port); it != m_outputs.end())
it->second.send_message(message, size); return it->second.send_message(message, size);
return stdx::error{};
} }
void send_ump(const output_port& port, const uint32_t* message, size_t size) stdx::error send_ump(const output_port& port, const uint32_t* message, size_t size)
{ {
if (auto it = m_outputs.find(port); it != m_outputs.end()) if (auto it = m_outputs.find(port); it != m_outputs.end())
it->second.send_ump(message, size); return it->second.send_ump(message, size);
return stdx::error{};
} }
private: private:
+6 -1
View File
@@ -2559,6 +2559,7 @@ enum cmidi2_midi_conversion_result
CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE = 0x11, CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE = 0x11,
CMIDI2_CONVERSION_RESULT_INVALID_STATUS = 0x13, CMIDI2_CONVERSION_RESULT_INVALID_STATUS = 0x13,
CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7 = 0x20, CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7 = 0x20,
CMIDI2_CONVERSION_RESULT_INVALID_INPUT = 0x40,
}; };
static inline void static inline void
@@ -2686,7 +2687,11 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context)
else else
{ {
// fixed sized message // fixed sized message
size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, sLen - *sIdx); size_t remaining = sLen - *sIdx;
size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, remaining);
if (len > remaining)
return CMIDI2_CONVERSION_RESULT_INVALID_INPUT;
uint8_t byte2 = context->midi1[*sIdx + 1]; uint8_t byte2 = context->midi1[*sIdx + 1];
uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0; uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0;
uint8_t channel = context->midi1[*sIdx] & 0xF; uint8_t channel = context->midi1[*sIdx] & 0xF;
+1 -5
View File
@@ -4,11 +4,6 @@
#define NOMINMAX 1 #define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#include <algorithm>
#include <cinttypes>
#include <memory>
#include <stdexcept>
#include <vector>
#if defined(LIBREMIDI_EXPORTS) #if defined(LIBREMIDI_EXPORTS)
#if defined(_MSC_VER) #if defined(_MSC_VER)
@@ -51,6 +46,7 @@ using midi_bytes = boost::container::small_vector<unsigned char, small_vector_mi
} }
#endif #endif
#else #else
#include <vector>
namespace libremidi namespace libremidi
{ {
using midi_bytes = std::vector<unsigned char>; using midi_bytes = std::vector<unsigned char>;
+13 -24
View File
@@ -1,29 +1,19 @@
#pragma once #pragma once
#if defined(__linux__) #include <libremidi/backends/alsa_raw/config.hpp>
#include <libremidi/backends/alsa_raw/config.hpp> #include <libremidi/backends/alsa_raw_ump/config.hpp>
#include <libremidi/backends/alsa_raw_ump/config.hpp> #include <libremidi/backends/alsa_seq/config.hpp>
#include <libremidi/backends/alsa_seq/config.hpp> #include <libremidi/backends/alsa_seq_ump/config.hpp>
#include <libremidi/backends/alsa_seq_ump/config.hpp> #include <libremidi/backends/coremidi/config.hpp>
#include <libremidi/backends/pipewire/config.hpp> #include <libremidi/backends/coremidi_ump/config.hpp>
#endif #include <libremidi/backends/emscripten/config.hpp>
#if defined(__APPLE__)
#include <libremidi/backends/coremidi/config.hpp>
#include <libremidi/backends/coremidi_ump/config.hpp>
#endif
#if defined(_WIN32)
#include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winuwp/config.hpp>
#endif
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/config.hpp>
#endif
#include <libremidi/backends/jack/config.hpp> #include <libremidi/backends/jack/config.hpp>
#include <libremidi/backends/keyboard/config.hpp>
#include <libremidi/backends/net/config.hpp>
#include <libremidi/backends/pipewire/config.hpp>
#include <libremidi/backends/winmidi/config.hpp>
#include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winuwp/config.hpp>
namespace libremidi namespace libremidi
{ {
@@ -31,5 +21,4 @@ namespace libremidi
struct dummy_configuration struct dummy_configuration
{ {
}; };
} }
+59
View File
@@ -0,0 +1,59 @@
#pragma once
// clang-format off
#include <libremidi/config.hpp>
// clang-format on
#include <libremidi/cmidi2.hpp>
#include <libremidi/error.hpp>
#include <libremidi/message.hpp>
#include <libremidi/ump.hpp>
namespace libremidi
{
struct midi1_to_midi2
{
stdx::error
convert(const unsigned char* message, std::size_t size, int64_t timestamp, auto on_ump)
{
context.midi1 = const_cast<unsigned char*>(message);
context.midi1_num_bytes = size;
context.midi1_proceeded_bytes = 0;
context.ump = ump;
context.ump_num_bytes = sizeof(ump);
context.ump_proceeded_bytes = 0;
if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK)
return std::errc::invalid_argument;
return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp);
}
cmidi2_midi_conversion_context context = [] {
cmidi2_midi_conversion_context tmp;
cmidi2_midi_conversion_context_initialize(&tmp);
return tmp;
}();
uint32_t ump[65536 / 4];
};
struct midi2_to_midi1
{
stdx::error
convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi)
{
auto n
= cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));
if (n > 0)
return on_midi(midi, n, timestamp);
else
return std::errc::no_buffer_space;
}
cmidi2_midi_conversion_context context = [] {
cmidi2_midi_conversion_context tmp;
cmidi2_midi_conversion_context_initialize(&tmp);
return tmp;
}();
uint8_t midi[65536];
};
}
+17 -56
View File
@@ -2,61 +2,11 @@
#include <libremidi/api.hpp> #include <libremidi/api.hpp>
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <libremidi/error.hpp> #include <libremidi/error.hpp>
#include <libremidi/observer_configuration.hpp>
#include <iostream>
#include <string_view> #include <string_view>
namespace libremidi namespace libremidi
{ {
struct error_handler
{
//! Error reporting function for libremidi classes. Throws.
template <typename Error_T>
void error(auto& configuration, std::string_view errorString) const
{
if (configuration.on_error)
{
if (first_error)
return;
first_error = true;
configuration.on_error(Error_T::code, errorString);
first_error = false;
}
else
{
#if defined(__LIBREMIDI_DEBUG__)
std::cerr << '\n' << errorString << "\n\n";
#endif
throw Error_T{errorString.data()};
}
}
//! Warning reporting function for libremidi classes.
void warning(auto& configuration, std::string_view errorString) const
{
if (configuration.on_warning)
{
if (first_warning)
return;
first_warning = true;
configuration.on_warning(midi_error::WARNING, errorString);
first_warning = false;
return;
}
#if defined(__LIBREMIDI_DEBUG__)
std::cerr << '\n' << errorString << "\n\n";
#endif
}
// To prevent infinite error loops
mutable bool first_error{};
mutable bool first_warning{};
};
class midi_api class midi_api
{ {
public: public:
@@ -69,18 +19,29 @@ public:
[[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0; [[nodiscard]] virtual libremidi::API get_current_api() const noexcept = 0;
[[nodiscard]] virtual bool open_virtual_port(std::string_view) = 0; [[nodiscard]] virtual stdx::error open_virtual_port(std::string_view)
{
return std::errc::function_not_supported;
}
virtual stdx::error set_client_name(std::string_view)
{
return std::errc::function_not_supported;
}
virtual stdx::error set_port_name(std::string_view)
{
return std::errc::function_not_supported;
}
virtual void close_port() = 0; virtual stdx::error close_port() = 0;
virtual void set_client_name(std::string_view) = 0;
virtual void set_port_name(std::string_view) = 0;
bool is_port_open() const noexcept { return bool(port_open_); } stdx::error is_client_open() const noexcept { return client_open_; }
bool is_port_connected() const noexcept { return bool(connected_); } bool is_port_open() const noexcept { return port_open_; }
bool is_port_connected() const noexcept { return connected_; }
protected: protected:
friend class midi_in; friend class midi_in;
friend class midi_out; friend class midi_out;
stdx::error client_open_{std::errc::not_connected};
bool port_open_{}; bool port_open_{};
bool connected_{}; bool connected_{};
}; };
+5 -1
View File
@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <libremidi/detail/midi_api.hpp> #include <libremidi/detail/midi_api.hpp>
#include <libremidi/observer_configuration.hpp>
#include <libremidi/input_configuration.hpp> #include <libremidi/input_configuration.hpp>
#include <libremidi/error_handler.hpp>
namespace libremidi namespace libremidi
{ {
@@ -14,7 +16,9 @@ public:
midi_in_api& operator=(const midi_in_api&) = delete; midi_in_api& operator=(const midi_in_api&) = delete;
midi_in_api& operator=(midi_in_api&&) = delete; midi_in_api& operator=(midi_in_api&&) = delete;
[[nodiscard]] virtual bool open_port(const input_port& pt, std::string_view local_port_name) = 0; [[nodiscard]] virtual stdx::error
open_port(const input_port& pt, std::string_view local_port_name)
= 0;
[[nodiscard]] virtual timestamp absolute_timestamp() const noexcept = 0; [[nodiscard]] virtual timestamp absolute_timestamp() const noexcept = 0;
}; };
+23 -29
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <libremidi/cmidi2.hpp> #include <libremidi/detail/conversion.hpp>
#include <libremidi/detail/midi_api.hpp> #include <libremidi/detail/midi_api.hpp>
#include <libremidi/error_handler.hpp>
#include <libremidi/output_configuration.hpp> #include <libremidi/output_configuration.hpp>
#include <string_view> #include <string_view>
@@ -18,19 +19,21 @@ public:
midi_out_api& operator=(const midi_out_api&) = delete; midi_out_api& operator=(const midi_out_api&) = delete;
midi_out_api& operator=(midi_out_api&&) = delete; midi_out_api& operator=(midi_out_api&&) = delete;
[[nodiscard]] virtual bool open_port(const output_port& pt, std::string_view local_port_name) [[nodiscard]] virtual stdx::error
open_port(const output_port& pt, std::string_view local_port_name)
= 0; = 0;
[[nodiscard]] virtual int64_t current_time() const noexcept { return 0; } [[nodiscard]] virtual int64_t current_time() const noexcept { return 0; }
virtual void send_message(const unsigned char* message, std::size_t size) = 0; virtual stdx::error send_message(const unsigned char* message, std::size_t size) = 0;
virtual void schedule_message(int64_t /*ts*/, const unsigned char* message, std::size_t size) virtual stdx::error
schedule_message(int64_t /*ts*/, const unsigned char* message, std::size_t size)
{ {
return send_message(message, size); return send_message(message, size);
} }
virtual void send_ump(const uint32_t* message, std::size_t size) = 0; virtual stdx::error send_ump(const uint32_t* message, std::size_t size) = 0;
virtual void schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t size) virtual stdx::error schedule_ump(int64_t /*ts*/, const uint32_t* ump, std::size_t size)
{ {
return send_ump(ump, size); return send_ump(ump, size);
} }
@@ -45,14 +48,15 @@ class out_api : public midi_out_api
public: public:
using midi_out_api::midi_out_api; using midi_out_api::midi_out_api;
void send_ump(const uint32_t* message, std::size_t /*size*/) stdx::error send_ump(const uint32_t* message, std::size_t size)
{ {
uint8_t midi[65536]; return converter.convert(
const auto n message, size, 0, [this](const unsigned char* midi, std::size_t n, int64_t /* ts */) {
= cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message)); return send_message(midi, n);
if (n > 0) });
send_message(midi, n);
} }
midi2_to_midi1 converter;
}; };
} }
@@ -65,25 +69,15 @@ class out_api : public midi_out_api
public: public:
using midi_out_api::midi_out_api; using midi_out_api::midi_out_api;
void send_message(const unsigned char* message, std::size_t size) stdx::error send_message(const unsigned char* message, std::size_t size)
{ {
cmidi2_midi_conversion_context context{}; return converter.convert(
cmidi2_midi_conversion_context_initialize(&context); message, size, 0, [this](const uint32_t* ump, std::size_t count, int64_t /* ts */) {
return send_ump(ump, count);
uint32_t ump[65536 / 4]; });
context.midi1 = const_cast<unsigned char*>(message);
context.midi1_num_bytes = size;
context.midi1_proceeded_bytes = 0;
context.ump = ump;
context.ump_num_bytes = sizeof(ump);
context.ump_proceeded_bytes = 0;
if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK)
return;
send_ump(context.ump, context.ump_proceeded_bytes / 4);
} }
midi1_to_midi2 converter;
}; };
} }
+225 -74
View File
@@ -2,6 +2,7 @@
#include <libremidi/detail/midi_in.hpp> #include <libremidi/detail/midi_in.hpp>
#include <libremidi/cmidi2.hpp>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <cinttypes> #include <cinttypes>
@@ -28,16 +29,87 @@ struct timestamp_backend_info
bool has_samples{}; bool has_samples{};
}; };
namespace midi1 template<typename Configuration>
struct input_state_machine_base
{ {
struct input_state_machine const Configuration& configuration;
{
const input_configuration& configuration; explicit input_state_machine_base(const Configuration& conf)
explicit input_state_machine(const input_configuration& conf)
: configuration{conf} : configuration{conf}
{ {
} }
template <timestamp_backend_info info>
int64_t timestamp(auto to_ns, int64_t samples)
{
switch (configuration.timestamps)
{
default:
case timestamp_mode::NoTimestamp:
return 0;
case timestamp_mode::Relative: {
int64_t time_ns;
if constexpr (info.has_absolute_timestamps)
time_ns = to_ns();
else
time_ns = system_ns();
int64_t res;
if (first_message)
{
first_message = false;
res = 0;
}
else
{
res = time_ns - last_time_ns;
}
last_time_ns = time_ns;
return res;
}
case timestamp_mode::Absolute:
if constexpr (info.has_absolute_timestamps)
return to_ns();
else
return system_ns();
case timestamp_mode::SystemMonotonic:
if constexpr (info.absolute_is_monotonic)
return to_ns();
else
return system_ns();
case timestamp_mode::AudioFrame:
if constexpr (info.has_samples)
return samples;
else
return 0;
case timestamp_mode::Custom:
return configuration.get_timestamp(to_ns());
}
}
int64_t last_time_ns = 0;
bool first_message = true;
};
namespace midi1
{
struct input_state_machine : input_state_machine_base<input_configuration>
{
using input_state_machine_base::input_state_machine_base;
void reset()
{
message.bytes.clear();
message.timestamp = {};
state = main;
}
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
{ {
return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7)); return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));
@@ -46,6 +118,26 @@ struct input_state_machine
// Function to process a byte stream which may contain multiple successive // Function to process a byte stream which may contain multiple successive
// MIDI events (CoreMIDI, ALSA Sequencer can work like this) // MIDI events (CoreMIDI, ALSA Sequencer can work like this)
void on_bytes_multi(std::span<const uint8_t> bytes, int64_t timestamp) void on_bytes_multi(std::span<const uint8_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}
// Function to process bytes corresponding to at most one midi event
// e.g. a midi channel event or a single sysex
void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}
private:
void on_bytes_multi_segmented(
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
{ {
int64_t nBytes = bytes.size(); int64_t nBytes = bytes.size();
int64_t iByte = 0; int64_t iByte = 0;
@@ -54,7 +146,7 @@ struct input_state_machine
switch (state) switch (state)
{ {
case in_sysex: { case in_sysex: {
return on_continue_sysex(bytes, finished_sysex); return on_continue_sysex(cb, bytes, finished_sysex);
} }
case main: { case main: {
while (iByte < nBytes) while (iByte < nBytes)
@@ -150,7 +242,7 @@ struct input_state_machine
message.assign(begin, begin + size); message.assign(begin, begin + size);
message.timestamp = timestamp; message.timestamp = timestamp;
this->configuration.on_message(std::move(message)); cb(std::move(message));
message.clear(); message.clear();
iByte += size; iByte += size;
@@ -160,7 +252,8 @@ struct input_state_machine
} }
} }
void on_continue_sysex(std::span<const uint8_t> bytes, bool finished_sysex) void on_continue_sysex(
const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)
{ {
if (finished_sysex) if (finished_sysex)
state = main; state = main;
@@ -174,14 +267,16 @@ struct input_state_machine
message.insert(message.end(), bytes.begin(), bytes.end()); message.insert(message.end(), bytes.begin(), bytes.end());
if (finished_sysex) if (finished_sysex)
{ {
this->configuration.on_message(std::move(message)); cb(std::move(message));
message.clear(); message.clear();
} }
} }
return; return;
} }
void on_main(std::span<const uint8_t> bytes, int64_t timestamp, bool finished_sysex) void on_main(
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp,
bool finished_sysex)
{ {
switch (bytes[0]) switch (bytes[0])
{ {
@@ -196,7 +291,7 @@ struct input_state_machine
message.timestamp = timestamp; message.timestamp = timestamp;
if (finished_sysex) if (finished_sysex)
{ {
this->configuration.on_message(std::move(message)); cb(std::move(message));
message.clear(); message.clear();
} }
} }
@@ -222,13 +317,12 @@ struct input_state_machine
message.assign(bytes.begin(), bytes.end()); message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp; message.timestamp = timestamp;
this->configuration.on_message(std::move(message)); cb(std::move(message));
message.clear(); message.clear();
} }
// Function to process bytes corresponding to at most one midi event void
// e.g. a midi channel event or a single sysex on_bytes_segmented(const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)
{ {
if (bytes.empty()) if (bytes.empty())
return; return;
@@ -237,78 +331,135 @@ struct input_state_machine
switch (state) switch (state)
{ {
case in_sysex: case in_sysex:
return on_continue_sysex(bytes, finished_sysex); return on_continue_sysex(cb, bytes, finished_sysex);
case main: case main:
return on_main(bytes, timestamp, finished_sysex); return on_main(cb, bytes, timestamp, finished_sysex);
}
}
template <timestamp_backend_info info>
int64_t timestamp(auto to_ns, int64_t samples)
{
switch (configuration.timestamps)
{
default:
case timestamp_mode::NoTimestamp:
return 0;
case timestamp_mode::Relative: {
int64_t time_ns;
if constexpr (info.has_absolute_timestamps)
time_ns = to_ns();
else
time_ns = system_ns();
int64_t res;
if (first_message)
{
first_message = false;
res = 0;
}
else
{
res = time_ns - last_time_ns;
}
last_time_ns = time_ns;
return res;
}
case timestamp_mode::Absolute:
if constexpr (info.has_absolute_timestamps)
return to_ns();
else
return system_ns();
case timestamp_mode::SystemMonotonic:
if constexpr (info.absolute_is_monotonic)
return to_ns();
else
return system_ns();
case timestamp_mode::AudioFrame:
if constexpr (info.has_samples)
return samples;
else
return 0;
case timestamp_mode::Custom:
return configuration.get_timestamp(to_ns());
} }
} }
public:
libremidi::message message; libremidi::message message;
int64_t last_time_ns = 0; private:
enum enum
{ {
main, main,
in_sysex in_sysex
} state{main}; } state{main};
};
}
bool first_message = true; namespace midi2
{
struct input_state_machine : input_state_machine_base<ump_input_configuration>
{
using input_state_machine_base::input_state_machine_base;
public:
void on_bytes_multi(std::span<const unsigned char> bytes, int64_t timestamp)
{
auto ptr = reinterpret_cast<const uint32_t*>(bytes.data());
auto sz = bytes.size() / 4;
return on_bytes_multi({ptr, sz}, timestamp);
}
void on_bytes_multi(std::span<const uint32_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}
void on_bytes(std::span<const uint32_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}
private:
// Function to process a byte stream which may contain multiple successive
// MIDI events (CoreMIDI, ALSA Sequencer can work like this)
void on_bytes_multi_segmented(
const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)
{
auto count = bytes.size();
auto ump_stream = bytes.data();
while (count > 0)
{
// Handle NOOP (or padding)
while (count > 0 && ump_stream[0] == 0)
{
count--;
ump_stream++;
}
if (count == 0)
break;
const auto ump_uints = cmidi2_ump_get_num_bytes(ump_stream[0]) / 4;
on_bytes_segmented(cb, {ump_stream, ump_stream + ump_uints}, timestamp);
ump_stream += ump_uints;
count -= ump_uints;
}
}
// Function to process bytes corresponding to at most one midi event
void
on_bytes_segmented(const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)
{
// Filter according to message type
switch(cmidi2_ump_get_message_type(bytes.data()))
{
case CMIDI2_MESSAGE_TYPE_UTILITY:
{
// All the utility messages are about timing
if (this->configuration.ignore_timing)
return;
break;
}
case CMIDI2_MESSAGE_TYPE_SYSTEM:
{
if (this->configuration.ignore_timing)
{
auto status = cmidi2_ump_get_system_message_byte2(bytes.data());
switch(status)
{
case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE:
case CMIDI2_SYSTEM_STATUS_SONG_POSITION:
case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK:
return;
}
}
if (this->configuration.ignore_sensing)
{
auto status = cmidi2_ump_get_system_message_byte2(bytes.data());
if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING)
return;
}
break;
}
case CMIDI2_MESSAGE_TYPE_SYSEX7:
case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:
{
if (this->configuration.ignore_sysex)
return;
break;
}
}
libremidi::ump msg;
std::copy(bytes.begin(), bytes.end(), msg.data);
msg.timestamp = timestamp;
cb(std::move(msg));
}
}; };
} }
} }
+1
View File
@@ -2,6 +2,7 @@
#include <libremidi/api.hpp> #include <libremidi/api.hpp>
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <libremidi/observer_configuration.hpp> #include <libremidi/observer_configuration.hpp>
#include <libremidi/error_handler.hpp>
#include <memory> #include <memory>
#include <vector> #include <vector>
+12 -8
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <libremidi/cmidi2.hpp> #include <libremidi/cmidi2.hpp>
#include <libremidi/error.hpp>
#include <cinttypes> #include <cinttypes>
#include <cstdint> #include <cstdint>
@@ -17,7 +18,7 @@ enum class segmentation_error
* Utility function to segment an ump stream into individual messages. * Utility function to segment an ump stream into individual messages.
* Used to send a stream to APIs that work message-by-message. * Used to send a stream to APIs that work message-by-message.
*/ */
inline void inline stdx::error
segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, auto realloc_func) segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, auto realloc_func)
{ {
while (count > 0) while (count > 0)
@@ -35,24 +36,27 @@ segment_ump_stream(const uint32_t* ump_stream, int64_t count, auto write_func, a
const auto ump_bytes = cmidi2_ump_get_num_bytes(ump_stream[0]); const auto ump_bytes = cmidi2_ump_get_num_bytes(ump_stream[0]);
// FIXME std::expected, propagate the error back to caller? // FIXME std::expected, propagate the error back to caller?
switch (write_func(ump_stream, ump_bytes)) switch (int err = static_cast<int>(write_func(ump_stream, ump_bytes)))
{ {
case segmentation_error::no_error: case 0:
break; break;
case segmentation_error::need_space: case -ENOMEM:
case ENOMEM:
// Try again if we didn't have enough space in the OS queue // Try again if we didn't have enough space in the OS queue
realloc_func(); realloc_func();
if (write_func(ump_stream, ump_bytes) != segmentation_error::no_error) if (auto err = write_func(ump_stream, ump_bytes); err != std::errc{})
return; return std::make_error_code(err);
break; break;
case segmentation_error::other: default:
return; return from_errc(err);
} }
const auto ump_uints = ump_bytes / 4; const auto ump_uints = ump_bytes / 4;
ump_stream += ump_uints; ump_stream += ump_uints;
count -= ump_uints; count -= ump_uints;
} }
return stdx::error{};
} }
} }
+46 -71
View File
@@ -1,82 +1,37 @@
#pragma once #pragma once
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <libremidi/system_error2.hpp>
#pragma GCC diagnostic pop
#include <functional> #include <functional>
#include <stdexcept>
#include <string_view> #include <string_view>
#include <version>
#if __has_include(<source_location>) && (__cpp_lib_source_location >= 201907L)
#include <source_location>
namespace libremidi { using source_location = std::source_location; }
#else
namespace libremidi {
struct source_location {
static source_location current() noexcept { return {}; }
int line() const noexcept { return -1; }
int offset() const noexcept { return -1; }
const char* function_name() const noexcept { return "(unknown)"; }
const char* file_name() const noexcept { return "(unknown)"; }
};
}
#endif
namespace libremidi namespace libremidi
{ {
//! Defines various error types. inline auto from_errc(int ret) noexcept
enum midi_error
{ {
WARNING, /*!< A non-critical error. */ return static_cast<std::errc>(-ret);
UNSPECIFIED, /*!< The default, unspecified error type. */ }
NO_DEVICES_FOUND, /*!< No devices found on system. */
INVALID_DEVICE, /*!< An invalid device ID was specified. */
MEMORY_ERROR, /*!< An error occured during memory allocation. */
INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */
INVALID_USE, /*!< The function was called incorrectly. */
DRIVER_ERROR, /*!< A system driver error occured. */
SYSTEM_ERROR, /*!< A system error occured. */
THREAD_ERROR /*!< A thread error occured. */
};
//! Base exception class for MIDI problems
struct LIBREMIDI_EXPORT midi_exception : public std::runtime_error
{
using std::runtime_error::runtime_error;
~midi_exception() override;
};
struct LIBREMIDI_EXPORT no_devices_found_error final : public midi_exception
{
static constexpr auto code = midi_error::NO_DEVICES_FOUND;
using midi_exception::midi_exception;
~no_devices_found_error() override;
};
struct LIBREMIDI_EXPORT invalid_device_error final : public midi_exception
{
static constexpr auto code = midi_error::INVALID_DEVICE;
using midi_exception::midi_exception;
~invalid_device_error() override;
};
struct LIBREMIDI_EXPORT memory_error final : public midi_exception
{
static constexpr auto code = midi_error::MEMORY_ERROR;
using midi_exception::midi_exception;
~memory_error() override;
};
struct LIBREMIDI_EXPORT invalid_parameter_error final : public midi_exception
{
static constexpr auto code = midi_error::INVALID_PARAMETER;
using midi_exception::midi_exception;
~invalid_parameter_error() override;
};
struct LIBREMIDI_EXPORT invalid_use_error final : public midi_exception
{
static constexpr auto code = midi_error::INVALID_USE;
using midi_exception::midi_exception;
~invalid_use_error() override;
};
struct LIBREMIDI_EXPORT driver_error final : public midi_exception
{
static constexpr auto code = midi_error::DRIVER_ERROR;
using midi_exception::midi_exception;
~driver_error() override;
};
struct LIBREMIDI_EXPORT system_error final : public midi_exception
{
static constexpr auto code = midi_error::SYSTEM_ERROR;
using midi_exception::midi_exception;
~system_error() override;
};
struct LIBREMIDI_EXPORT thread_error final : public midi_exception
{
static constexpr auto code = midi_error::THREAD_ERROR;
using midi_exception::midi_exception;
~thread_error() override;
};
/*! \brief Error callback function /*! \brief Error callback function
\param type Type of error. \param type Type of error.
@@ -85,5 +40,25 @@ struct LIBREMIDI_EXPORT thread_error final : public midi_exception
Note that class behaviour is undefined after a critical error (not Note that class behaviour is undefined after a critical error (not
a warning) is reported. a warning) is reported.
*/ */
using midi_error_callback = std::function<void(midi_error type, std::string_view errorText)>; using midi_error_callback = std::function<void(std::string_view errorText, const source_location&)>;
using midi_warning_callback = std::function<void(std::string_view errorText, const source_location&)>;
} }
#if !defined(LIBREMIDI_LOG)
#if !defined(__LIBREMIDI_DEBUG__)
#define LIBREMIDI_LOG(...) \
do \
{ \
} while (0)
#else
#include <iostream>
#define LIBREMIDI_LOG(...) \
do \
{ \
[](auto&&... args) { \
(std::cerr << ... << args); \
std::cerr << std::endl; \
}(__VA_ARGS__); \
} while (0)
#endif
#endif
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include <libremidi/api.hpp>
#include <libremidi/config.hpp>
#include <libremidi/error.hpp>
#include <libremidi/observer_configuration.hpp>
#include <libremidi/input_configuration.hpp>
#include <libremidi/output_configuration.hpp>
#include <string_view>
namespace libremidi
{
struct error_handler
{
//! Error reporting function for libremidi classes.
void error_impl(
const midi_error_callback& callback, std::string_view errorString,
const source_location& location) const
{
if (callback)
{
if (first_error)
return;
first_error = true;
callback(errorString, location);
first_error = false;
}
else
{
LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");
}
}
//! Warning reporting function for libremidi classes.
void warning_impl(
const midi_warning_callback& callback, std::string_view errorString,
const source_location& location) const
{
if (callback)
{
if (first_warning)
return;
first_warning = true;
callback(errorString, location);
first_warning = false;
}
else
{
LIBREMIDI_LOG(errorString, " (", location.function_name(), ":", location.line(), ")");
}
}
// To prevent infinite error loops
mutable bool first_error{};
mutable bool first_warning{};
};
// Needed as apple still doesn't support source_location in xcode 15.3
#define libremidi_handle_error(config, str) error_impl(config.on_error, str, libremidi::source_location::current())
#define libremidi_handle_warning(config, str) warning_impl(config.on_warning, str, libremidi::source_location::current())
}
+18 -9
View File
@@ -5,7 +5,6 @@
#include <libremidi/ump.hpp> #include <libremidi/ump.hpp>
#include <functional> #include <functional>
#include <string>
namespace libremidi namespace libremidi
{ {
@@ -41,19 +40,23 @@ enum timestamp_mode
using timestamp = int64_t; using timestamp = int64_t;
using message_callback = std::function<void(message&& message)>; using message_callback = std::function<void(message&& message)>;
using raw_callback = std::function<void(std::span<uint8_t>, timestamp)>; using raw_callback = std::function<void(std::span<const uint8_t>, timestamp)>;
using timestamp_callback = std::function<timestamp(timestamp)>; using timestamp_callback = std::function<timestamp(timestamp)>;
struct input_configuration struct input_configuration
{ {
//! Set a callback function to be invoked for incoming MIDI messages. //! Set a callback function to be invoked for incoming MIDI messages.
//! Mandatory! //! Either this or on_raw_message must be set
message_callback on_message; message_callback on_message{};
//! Invoked for incoming MIDI bytes. No transformation, no filtering, no packetization,
//! just the MIDI data straight from the source.
raw_callback on_raw_data{};
//! Set a custom callback function to be invoked for timestamping MIDI messages. //! Set a custom callback function to be invoked for timestamping MIDI messages.
//! Input: the API provided timestamp in nanoseconds, if available, for reference. //! Input: the API provided timestamp in nanoseconds, if available, for reference.
//! (e.g. the same as "Absolute"). //! (e.g. the same as "Absolute").
//! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise. //! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.
timestamp_callback get_timestamp; timestamp_callback get_timestamp{};
//! Set an error callback function to be invoked when an error has occured. //! Set an error callback function to be invoked when an error has occured.
/*! /*!
@@ -61,7 +64,7 @@ struct input_configuration
best to set the error callback function before opening a port. best to set the error callback function before opening a port.
*/ */
midi_error_callback on_error{}; midi_error_callback on_error{};
midi_error_callback on_warning{}; midi_warning_callback on_warning{};
//! Specify whether certain MIDI message types should be queued or ignored //! Specify whether certain MIDI message types should be queued or ignored
//! during input. //! during input.
@@ -81,16 +84,22 @@ struct input_configuration
}; };
using ump_callback = std::function<void(ump&&)>; using ump_callback = std::function<void(ump&&)>;
using raw_ump_callback = std::function<void(std::span<const uint32_t>, timestamp)>;
struct ump_input_configuration struct ump_input_configuration
{ {
//! Set a callback function to be invoked for incoming UMP messages. //! Set a callback function to be invoked for incoming UMP messages.
ump_callback on_message; //! Either this or on_raw_message must be set
ump_callback on_message{};
//! Invoked for incoming UMP bytes. No transformation, no filtering, no packetization,
//! just the UMP data straight from the source.
raw_ump_callback on_raw_data{};
//! Set a custom callback function to be invoked for timestamping MIDI messages. //! Set a custom callback function to be invoked for timestamping MIDI messages.
//! Input: the API provided timestamp in nanoseconds, if available, for reference. //! Input: the API provided timestamp in nanoseconds, if available, for reference.
//! (e.g. the same as "Absolute"). //! (e.g. the same as "Absolute").
//! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise. //! Mandatory if timestamps == timestamp_mode::Custom, unused otherwise.
timestamp_callback get_timestamp; timestamp_callback get_timestamp{};
//! Set an error callback function to be invoked when an error has occured. //! Set an error callback function to be invoked when an error has occured.
/*! /*!
@@ -98,7 +107,7 @@ struct ump_input_configuration
best to set the error callback function before opening a port. best to set the error callback function before opening a port.
*/ */
midi_error_callback on_error{}; midi_error_callback on_error{};
midi_error_callback on_warning{}; midi_warning_callback on_warning{};
//! Specify whether certain MIDI message types should be queued or ignored //! Specify whether certain MIDI message types should be queued or ignored
//! during input. //! during input.
+507
View File
@@ -0,0 +1,507 @@
// clang-format off
#include <libremidi/libremidi-c.h>
#include <libremidi/libremidi.hpp>
#include <libremidi/backends.hpp>
// clang-format on
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <limits>
struct libremidi_midi_observer_handle
{
libremidi::observer self;
};
struct libremidi_midi_in_handle
{
libremidi::midi_in self;
};
struct libremidi_midi_out_handle
{
libremidi::midi_out self;
};
namespace libremidi
{
static void assign_error_callback(const auto& src, auto& dst)
{
if (src.callback)
{
dst = [src](std::string_view errorText, const source_location& loc) {
src.callback(src.context, errorText.data(), errorText.size(), &loc);
};
}
}
}
extern "C" {
const char* libremidi_get_version(void)
{
return LIBREMIDI_VERSION;
}
void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
{
if (!cb)
return;
libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); });
}
void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
{
if (!cb)
return;
libremidi::midi2::for_all_backends([=](auto b) { cb(ctx, b.API); });
}
const char* libremidi_api_identifier(libremidi_api api)
{
return libremidi::get_api_name(api).data();
}
const char* libremidi_api_display_name(libremidi_api api)
{
return libremidi::get_api_display_name(api).data();
}
libremidi_api libremidi_get_compiled_api_by_identifier(const char* name)
{
libremidi_api ret = libremidi_api::UNSPECIFIED;
libremidi::midi_any::for_all_backends([&](auto& b) {
if (name == b.name)
ret = b.API;
});
return ret;
}
int libremidi_midi_api_configuration_init(libremidi_api_configuration* conf)
{
memset(conf, 0, sizeof(*conf));
return 0;
}
int libremidi_midi_observer_configuration_init(libremidi_observer_configuration* conf)
{
memset(conf, 0, sizeof(*conf));
return 0;
}
int libremidi_midi_configuration_init(libremidi_midi_configuration* conf)
{
memset(conf, 0, sizeof(*conf));
return 0;
}
int libremidi_midi_in_port_clone(const libremidi_midi_in_port* port, libremidi_midi_in_port** dst)
{
if (!port || !dst)
return -EINVAL;
auto copied = new libremidi::input_port{*reinterpret_cast<const libremidi::input_port*>(port)};
*dst = reinterpret_cast<libremidi_midi_in_port*>(copied);
return 0;
}
int libremidi_midi_in_port_free(libremidi_midi_in_port* port)
{
delete reinterpret_cast<libremidi::input_port*>(port);
return 0;
}
int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** name, size_t* len)
{
if (!port || !name || !len)
return -EINVAL;
auto& p = *reinterpret_cast<const libremidi::input_port*>(port);
*name = p.port_name.data();
*len = p.port_name.size();
return 0;
}
int libremidi_midi_out_port_clone(
const libremidi_midi_out_port* port, libremidi_midi_out_port** dst)
{
if (!port || !dst)
return -EINVAL;
auto copied = new libremidi::output_port{*reinterpret_cast<const libremidi::output_port*>(port)};
*dst = reinterpret_cast<libremidi_midi_out_port*>(copied);
return 0;
}
int libremidi_midi_out_port_free(libremidi_midi_out_port* port)
{
delete reinterpret_cast<libremidi::output_port*>(port);
return 0;
}
int libremidi_midi_out_port_name(
const libremidi_midi_out_port* port, const char** name, size_t* len)
{
if (!port || !name || !len)
return -EINVAL;
auto& p = *reinterpret_cast<const libremidi::output_port*>(port);
*name = p.port_name.data();
*len = p.port_name.size();
return 0;
}
int libremidi_midi_observer_new(
const libremidi_observer_configuration* c, libremidi_api_configuration* api,
libremidi_midi_observer_handle** out)
{
if (!out || !c)
return -EINVAL;
libremidi::observer_configuration conf;
libremidi::assign_error_callback(c->on_error, conf.on_error);
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
conf.track_hardware = c->track_hardware;
conf.track_virtual = c->track_virtual;
conf.track_any = c->track_any;
conf.notify_in_constructor = c->notify_in_constructor;
auto api_conf = libremidi::observer_configuration_for(static_cast<libremidi::API>(api->api));
if (c->input_added.callback)
{
conf.input_added = [cb = c->input_added](const auto& port) {
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_in_port*>(&port));
};
}
if (c->input_removed.callback)
{
conf.input_removed = [cb = c->input_removed](const auto& port) {
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_in_port*>(&port));
};
}
if (c->output_added.callback)
{
conf.output_added = [cb = c->output_added](const auto& port) {
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_out_port*>(&port));
};
}
if (c->output_removed.callback)
{
conf.output_removed = [cb = c->output_removed](const auto& port) {
cb.callback(cb.context, reinterpret_cast<const libremidi_midi_out_port*>(&port));
};
}
try
{
auto ptr = new libremidi_midi_observer_handle{
libremidi::observer{std::move(conf), std::move(api_conf)}};
*out = ptr;
return 0;
}
catch (...)
{
return -EINVAL;
}
}
int libremidi_midi_observer_enumerate_input_ports(
libremidi_midi_observer_handle* ptr, void* context,
void (*cb)(void* ctx, const libremidi_midi_in_port*))
{
if (!ptr || !cb)
return -EINVAL;
for (const auto& port : ptr->self.get_input_ports())
{
cb(context, reinterpret_cast<const libremidi_midi_in_port*>(&port));
}
return 0;
}
int libremidi_midi_observer_enumerate_output_ports(
libremidi_midi_observer_handle* ptr, void* context,
void (*cb)(void* ctx, const libremidi_midi_out_port*))
{
if (!ptr || !cb)
return -EINVAL;
for (const auto& port : ptr->self.get_output_ports())
{
cb(context, reinterpret_cast<const libremidi_midi_out_port*>(&port));
}
return 0;
}
int libremidi_midi_observer_free(libremidi_midi_observer_handle* ptr)
{
delete ptr;
return 0;
}
int libremidi_midi_in_new(
const libremidi_midi_configuration* c, const libremidi_api_configuration* api,
libremidi_midi_in_handle** out)
{
if (!out || !c)
return -EINVAL;
if (!c->virtual_port && !c->in_port)
return -EINVAL;
*out = nullptr;
auto api_conf = libremidi::midi_in_configuration_for(static_cast<libremidi::API>(api->api));
// Create the MIDI object
switch (c->version)
{
case libremidi_midi_configuration::MIDI1:
case libremidi_midi_configuration::MIDI1_RAW: {
libremidi::input_configuration conf;
libremidi::assign_error_callback(c->on_error, conf.on_error);
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
conf.ignore_sensing = c->ignore_sensing;
conf.ignore_sysex = c->ignore_sysex;
conf.ignore_timing = c->ignore_timing;
conf.timestamps = c->timestamps;
if (c->get_timestamp.callback)
{
conf.get_timestamp
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
}
if (c->on_midi1_message.callback)
conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) {
cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size());
};
else if (c->on_midi1_raw_data.callback)
{
conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span<const uint8_t> msg, int64_t ts) {
cb.callback(cb.context, ts, msg.data(), msg.size());
};
}
else
{
return -EINVAL;
}
try
{
auto ptr = new libremidi_midi_in_handle{
libremidi::midi_in{std::move(conf), std::move(api_conf)}};
*out = ptr;
}
catch (...)
{
return -EINVAL;
}
break;
}
case libremidi_midi_configuration::MIDI2:
case libremidi_midi_configuration::MIDI2_RAW: {
libremidi::ump_input_configuration conf;
libremidi::assign_error_callback(c->on_error, conf.on_error);
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
conf.ignore_sensing = c->ignore_sensing;
conf.ignore_sysex = c->ignore_sysex;
conf.ignore_timing = c->ignore_timing;
conf.timestamps = c->timestamps;
if (c->get_timestamp.callback)
{
conf.get_timestamp
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
}
if (c->on_midi2_message.callback)
conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) {
cb.callback(cb.context, msg.timestamp, msg.data, msg.size());
};
else if (c->on_midi2_raw_data.callback)
{
conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span<const uint32_t> msg, int64_t ts) {
cb.callback(cb.context, ts, msg.data(), msg.size());
};
}
else
{
return -EINVAL;
}
try
{
auto ptr = new libremidi_midi_in_handle{
libremidi::midi_in{std::move(conf), std::move(api_conf)}};
*out = ptr;
}
catch (...)
{
return -EINVAL;
}
break;
}
default:
return -EINVAL;
}
// Open the port
auto ptr = *out;
if (!ptr)
return -EINVAL;
if (c->virtual_port)
{
if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{})
{
*out = nullptr;
delete ptr;
return -EIO;
}
}
else
{
auto port = reinterpret_cast<const libremidi::input_port*>(c->in_port);
if (auto ret = ptr->self.open_port(*port); ret != stdx::error{})
{
*out = nullptr;
delete ptr;
return -EIO;
}
}
return 0;
}
int libremidi_midi_in_is_connected(const libremidi_midi_in_handle* in)
{
if (!in)
return -EINVAL;
return in->self.is_port_connected() ? 1 : 0;
}
libremidi_timestamp libremidi_midi_in_absolute_timestamp(libremidi_midi_in_handle* in)
{
if (!in)
return -EINVAL;
return in->self.absolute_timestamp();
}
int libremidi_midi_in_free(libremidi_midi_in_handle* ptr)
{
delete ptr;
return 0;
}
int libremidi_midi_out_new(
const libremidi_midi_configuration* c, const libremidi_api_configuration* api,
libremidi_midi_out_handle** out)
{
if (!out || !c)
return -EINVAL;
if (!c->virtual_port && !c->out_port)
return -EINVAL;
*out = nullptr;
auto api_conf = libremidi::midi_out_configuration_for(static_cast<libremidi::API>(api->api));
// Create the MIDI object
libremidi::output_configuration conf;
libremidi::assign_error_callback(c->on_error, conf.on_error);
libremidi::assign_error_callback(c->on_warning, conf.on_warning);
conf.timestamps = c->timestamps;
try
{
auto ptr
= new libremidi_midi_out_handle{libremidi::midi_out{std::move(conf), std::move(api_conf)}};
*out = ptr;
}
catch (...)
{
return -EINVAL;
}
// Open the port
auto ptr = *out;
if (!ptr)
return -EINVAL;
if (c->virtual_port)
{
if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{})
{
*out = nullptr;
delete ptr;
return -EIO;
}
}
else
{
auto port = reinterpret_cast<const libremidi::output_port*>(c->in_port);
if (auto ret = ptr->self.open_port(*port); ret != stdx::error{})
{
*out = nullptr;
delete ptr;
return -EIO;
}
}
return 0;
}
int libremidi_midi_out_is_connected(const libremidi_midi_out_handle* out)
{
if (!out)
return -EINVAL;
return out->self.is_port_connected() ? 1 : 0;
}
int libremidi_midi_out_send_message(
libremidi_midi_out_handle* out, const libremidi_midi1_symbol* msg, size_t sz)
{
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
return -EINVAL;
auto res = out->self.send_message(msg, sz);
return res != stdx::error{} ? -EIO : 0;
}
int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz)
{
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
return -EINVAL;
auto res = out->self.send_ump(msg, sz);
return res != stdx::error{} ? -EIO : 0;
}
int libremidi_midi_out_schedule_message(
libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi1_symbol* msg, size_t sz)
{
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
return -EINVAL;
auto res = out->self.schedule_message(ts, msg, sz);
return res != stdx::error{} ? -EIO : 0;
}
int libremidi_midi_out_schedule_ump(
libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi2_symbol* msg, size_t sz)
{
if (!out || !msg || sz > std::numeric_limits<int32_t>::max())
return -EINVAL;
auto res = out->self.schedule_ump(ts, msg, sz);
return res != stdx::error{} ? -EIO : 0;
}
int libremidi_midi_out_free(libremidi_midi_out_handle* ptr)
{
delete ptr;
return 0;
}
}
+275
View File
@@ -0,0 +1,275 @@
#pragma once
#include <libremidi/api-c.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#if defined(LIBREMIDI_EXPORTS)
#if defined(_MSC_VER)
#define LIBREMIDI_EXPORT __declspec(dllexport)
#elif defined(__GNUC__) || defined(__clang__)
#define LIBREMIDI_EXPORT __attribute__((visibility("default")))
#endif
#else
#define LIBREMIDI_EXPORT
#endif
#if __cplusplus
extern "C" {
#endif
typedef unsigned char libremidi_midi1_symbol;
typedef libremidi_midi1_symbol* libremidi_midi1_message;
typedef uint32_t libremidi_midi2_symbol;
typedef libremidi_midi2_symbol* libremidi_midi2_message;
typedef int64_t libremidi_timestamp;
typedef struct libremidi_midi_in_port libremidi_midi_in_port;
typedef struct libremidi_midi_out_port libremidi_midi_out_port;
typedef struct libremidi_midi_in_handle libremidi_midi_in_handle;
typedef struct libremidi_midi_out_handle libremidi_midi_out_handle;
typedef struct libremidi_midi_observer_handle libremidi_midi_observer_handle;
typedef struct libremidi_api_configuration libremidi_api_configuration;
enum libremidi_timestamp_mode
{
NoTimestamp,
Relative,
Absolute,
SystemMonotonic,
AudioFrame,
Custom
};
typedef struct libremidi_api_configuration
{
enum libremidi_api api;
enum
{
Observer,
Input,
Output
} configuration_type;
void* data;
} libremidi_api_configuration;
typedef struct libremidi_observer_configuration
{
struct
{
void* context;
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
} on_error;
struct
{
void* context;
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
} on_warning;
struct
{
void* context;
void (*callback)(void* ctx, const libremidi_midi_in_port*);
} input_added;
struct
{
void* context;
void (*callback)(void* ctx, const libremidi_midi_in_port*);
} input_removed;
struct
{
void* context;
void (*callback)(void* ctx, const libremidi_midi_out_port*);
} output_added;
struct
{
void* context;
void (*callback)(void* ctx, const libremidi_midi_out_port*);
} output_removed;
bool track_hardware;
bool track_virtual;
bool track_any;
bool notify_in_constructor;
} libremidi_observer_configuration;
typedef struct libremidi_midi1_callback
{
void* context;
void (*callback)(void* ctx, libremidi_timestamp, const libremidi_midi1_symbol*, size_t len);
} libremidi_midi1_callback;
typedef struct libremidi_midi2_callback
{
void* context;
void (*callback)(void* ctx, libremidi_timestamp, const libremidi_midi2_symbol*, size_t len);
} libremidi_midi2_callback;
typedef struct libremidi_midi_configuration
{
// Indicates the kind of callback requested, e.g. set MIDI1_RAW if you use the on_midi1_raw_data
enum
{
MIDI1 = (1 << 1),
MIDI1_RAW = (1 << 2),
MIDI2 = (1 << 3),
MIDI2_RAW = (1 << 4)
} version;
union
{
libremidi_midi_in_port* in_port;
libremidi_midi_out_port* out_port;
};
union
{
libremidi_midi1_callback on_midi1_message;
libremidi_midi1_callback on_midi1_raw_data;
libremidi_midi2_callback on_midi2_message;
libremidi_midi2_callback on_midi2_raw_data;
};
struct
{
void* context;
libremidi_timestamp (*callback)(void* ctx, libremidi_timestamp);
} get_timestamp;
struct
{
void* context;
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
} on_error;
struct
{
void* context;
void (*callback)(void* ctx, const char* error, size_t error_len, const void* source_location);
} on_warning;
const char* port_name;
bool virtual_port;
bool ignore_sysex;
bool ignore_timing;
bool ignore_sensing;
enum libremidi_timestamp_mode timestamps;
} libremidi_midi_configuration;
/// API utilities
LIBREMIDI_EXPORT
const char* libremidi_get_version(void);
LIBREMIDI_EXPORT
void libremidi_midi1_available_apis(void* ctx, void (*)(void*, libremidi_api));
LIBREMIDI_EXPORT
void libremidi_midi2_available_apis(void* ctx, void (*)(void*, libremidi_api));
LIBREMIDI_EXPORT
const char* libremidi_api_identifier(libremidi_api);
LIBREMIDI_EXPORT
const char* libremidi_api_display_name(libremidi_api);
LIBREMIDI_EXPORT
libremidi_api libremidi_get_compiled_api_by_identifier(const char*);
/// Create configurations
LIBREMIDI_EXPORT
int libremidi_midi_api_configuration_init(libremidi_api_configuration*);
LIBREMIDI_EXPORT
int libremidi_midi_observer_configuration_init(libremidi_observer_configuration*);
LIBREMIDI_EXPORT
int libremidi_midi_configuration_init(libremidi_midi_configuration*);
/// Read information about port objects
LIBREMIDI_EXPORT
int libremidi_midi_in_port_clone(const libremidi_midi_in_port* port, libremidi_midi_in_port** dst);
LIBREMIDI_EXPORT
int libremidi_midi_in_port_free(libremidi_midi_in_port* port);
LIBREMIDI_EXPORT
int libremidi_midi_in_port_name(
const libremidi_midi_in_port* port, const char** name, size_t* len);
LIBREMIDI_EXPORT
int libremidi_midi_out_port_clone(
const libremidi_midi_out_port* port, libremidi_midi_out_port** dst);
LIBREMIDI_EXPORT
int libremidi_midi_out_port_free(libremidi_midi_out_port* port);
LIBREMIDI_EXPORT
int libremidi_midi_out_port_name(
const libremidi_midi_out_port* port, const char** name, size_t* len);
/// Observer API
LIBREMIDI_EXPORT
int libremidi_midi_observer_new(
const libremidi_observer_configuration*, libremidi_api_configuration*,
libremidi_midi_observer_handle**);
LIBREMIDI_EXPORT
int libremidi_midi_observer_enumerate_input_ports(
libremidi_midi_observer_handle*, void* context,
void (*)(void* ctx, const libremidi_midi_in_port*));
LIBREMIDI_EXPORT
int libremidi_midi_observer_enumerate_output_ports(
libremidi_midi_observer_handle*, void* context,
void (*)(void* ctx, const libremidi_midi_out_port*));
LIBREMIDI_EXPORT
int libremidi_midi_observer_free(libremidi_midi_observer_handle*);
/// MIDI input API (read MIDI messages)
LIBREMIDI_EXPORT
int libremidi_midi_in_new(
const libremidi_midi_configuration*, const libremidi_api_configuration*,
libremidi_midi_in_handle**);
LIBREMIDI_EXPORT
int libremidi_midi_in_is_connected(const libremidi_midi_in_handle*);
LIBREMIDI_EXPORT
libremidi_timestamp libremidi_midi_in_absolute_timestamp(libremidi_midi_in_handle*);
LIBREMIDI_EXPORT
int libremidi_midi_in_free(libremidi_midi_in_handle*);
/// MIDI output API (send MIDI messages)
LIBREMIDI_EXPORT
int libremidi_midi_out_new(
const libremidi_midi_configuration*, const libremidi_api_configuration*,
libremidi_midi_out_handle**);
LIBREMIDI_EXPORT
int libremidi_midi_out_is_connected(const libremidi_midi_out_handle*);
LIBREMIDI_EXPORT
int libremidi_midi_out_send_message(
libremidi_midi_out_handle*, const libremidi_midi1_symbol*, size_t);
LIBREMIDI_EXPORT
int libremidi_midi_out_send_ump(libremidi_midi_out_handle*, const libremidi_midi2_symbol*, size_t);
LIBREMIDI_EXPORT
int libremidi_midi_out_schedule_message(
libremidi_midi_out_handle*, int64_t ts, const libremidi_midi1_symbol*, size_t);
LIBREMIDI_EXPORT
int libremidi_midi_out_schedule_ump(
libremidi_midi_out_handle*, int64_t ts, const libremidi_midi2_symbol*, size_t);
LIBREMIDI_EXPORT
int libremidi_midi_out_free(libremidi_midi_out_handle*);
#if __cplusplus
}
#endif
+14 -14
View File
@@ -7,9 +7,6 @@
#include <cmath> #include <cmath>
#include <array>
#include <thread>
namespace libremidi namespace libremidi
{ {
@@ -56,17 +53,20 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name)
midi2::for_all_backends([&](auto b) { apis.push_back(b.API); }); midi2::for_all_backends([&](auto b) { apis.push_back(b.API); });
return apis; return apis;
} }
LIBREMIDI_INLINE
LIBREMIDI_INLINE midi_exception::~midi_exception() = default; libremidi::API midi_api(const std::any& conf)
LIBREMIDI_INLINE no_devices_found_error::~no_devices_found_error() = default; {
LIBREMIDI_INLINE invalid_device_error::~invalid_device_error() = default; libremidi::API ret = libremidi::API::UNSPECIFIED;
LIBREMIDI_INLINE memory_error::~memory_error() = default; midi_any::for_all_backends([&]<typename T>(T) {
LIBREMIDI_INLINE invalid_parameter_error::~invalid_parameter_error() = default; if (std::any_cast<typename T::midi_in_configuration>(&conf)
LIBREMIDI_INLINE invalid_use_error::~invalid_use_error() = default; || std::any_cast<typename T::midi_out_configuration>(&conf)
LIBREMIDI_INLINE driver_error::~driver_error() = default; || std::any_cast<typename T::midi_observer_configuration>(&conf))
LIBREMIDI_INLINE system_error::~system_error() = default; {
LIBREMIDI_INLINE thread_error::~thread_error() = default; ret = T::API;
}
});
return ret;
}
LIBREMIDI_INLINE LIBREMIDI_INLINE
std::any midi_in_configuration_for(libremidi::API api) std::any midi_in_configuration_for(libremidi::API api)
{ {
+35 -27
View File
@@ -69,7 +69,6 @@
#include <midi/universal_packet.h> #include <midi/universal_packet.h>
#endif #endif
#include <any> #include <any>
#include <optional>
namespace libremidi namespace libremidi
{ {
@@ -132,19 +131,20 @@ public:
[[nodiscard]] libremidi::API get_current_api() const noexcept; [[nodiscard]] libremidi::API get_current_api() const noexcept;
//! Open a MIDI input connection //! Open a MIDI input connection
void open_port(const input_port& pt, std::string_view local_port_name = "libremidi input"); stdx::error
open_port(const input_port& pt, std::string_view local_port_name = "libremidi input");
//! Create a virtual input port, with optional name, to allow software //! Create a virtual input port, with optional name, to allow software
//! connections. //! connections.
//! //!
//! \param portName An optional name for the application port that is //! \param portName An optional name for the application port that is
//! used to connect to portId can be specified. //! used to connect to portId can be specified.
void open_virtual_port(std::string_view portName = "libremidi virtual port"); stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port");
void set_port_name(std::string_view portName); stdx::error set_port_name(std::string_view portName);
//! Close an open MIDI connection (if one exists). //! Close an open MIDI connection (if one exists).
void close_port(); stdx::error close_port();
//! Returns true if a port has been opened successfully with open_port or open_virtual_port //! Returns true if a port has been opened successfully with open_port or open_virtual_port
[[nodiscard]] bool is_port_open() const noexcept; [[nodiscard]] bool is_port_open() const noexcept;
@@ -182,11 +182,11 @@ public:
[[nodiscard]] libremidi::API get_current_api() const noexcept; [[nodiscard]] libremidi::API get_current_api() const noexcept;
//! Open a MIDI output connection. //! Open a MIDI output connection.
void stdx::error
open_port(const output_port& pt, std::string_view local_port_name = "libremidi output") const; open_port(const output_port& pt, std::string_view local_port_name = "libremidi output") const;
//! Close an open MIDI connection (if one exists). //! Close an open MIDI connection (if one exists).
void close_port() const; stdx::error close_port() const;
//! Returns true if a port has been opened successfully with open_port or open_virtual_port //! Returns true if a port has been opened successfully with open_port or open_virtual_port
[[nodiscard]] bool is_port_open() const noexcept; [[nodiscard]] bool is_port_open() const noexcept;
@@ -200,48 +200,56 @@ public:
//! //!
//! \param portName An optional name for the application port that is //! \param portName An optional name for the application port that is
//! used to connect to portId can be specified. //! used to connect to portId can be specified.
void open_virtual_port(std::string_view portName = "libremidi virtual port") const; stdx::error open_virtual_port(std::string_view portName = "libremidi virtual port") const;
void set_port_name(std::string_view portName) const; stdx::error set_port_name(std::string_view portName) const;
//! Immediately send a single message out an open MIDI output port. //! Immediately send a single message out an open MIDI output port.
/*! /*!
An exception is thrown if an error occurs during output or an An exception is thrown if an error occurs during output or an
output connection was not previously established. output connection was not previously established.
*/ */
void send_message(const libremidi::message& message) const; stdx::error send_message(const libremidi::message& message) const;
//! Immediately send a single message to an open MIDI output port. //! Immediately send a single message to an open MIDI output port.
void send_message(const unsigned char* message, size_t size) const; stdx::error send_message(const unsigned char* message, size_t size) const;
void send_message(std::span<const unsigned char>) const; stdx::error send_message(std::span<const unsigned char>) const;
void send_message(unsigned char b0) const; stdx::error send_message(unsigned char b0) const;
void send_message(unsigned char b0, unsigned char b1) const; stdx::error send_message(unsigned char b0, unsigned char b1) const;
void send_message(unsigned char b0, unsigned char b1, unsigned char b2) const; stdx::error send_message(unsigned char b0, unsigned char b1, unsigned char b2) const;
// Avoid silly mistakes:
stdx::error send_message(auto* message) const noexcept = delete;
stdx::error send_message(const auto* message) const noexcept = delete;
//! Current time in the timestamp referential //! Current time in the timestamp referential
int64_t current_time(); int64_t current_time();
//! Try to schedule a message later in time if the underlying API supports it //! Try to schedule a message later in time if the underlying API supports it
//! (currently not implemented anywhere) //! (currently not implemented anywhere)
void schedule_message(int64_t timestamp, const unsigned char* message, size_t size); stdx::error schedule_message(int64_t timestamp, const unsigned char* message, size_t size) const;
//! Immediately send a single UMP packet to an open MIDI output port. //! Immediately send a single UMP packet to an open MIDI output port.
void send_ump(const uint32_t* message, size_t size) const; stdx::error send_ump(const uint32_t* message, size_t size) const;
void send_ump(const libremidi::ump&) const; stdx::error send_ump(const libremidi::ump&) const;
void send_ump(std::span<const uint32_t>) const; stdx::error send_ump(std::span<const uint32_t>) const;
void send_ump(uint32_t b0) const; stdx::error send_ump(uint32_t b0) const;
void send_ump(uint32_t b0, uint32_t b1) const; stdx::error send_ump(uint32_t b0, uint32_t b1) const;
void send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const; stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const;
void send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const; stdx::error send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const;
// Better compat with cmidi2
stdx::error send_ump(int32_t b0) const;
stdx::error send_ump(int64_t b01) const;
stdx::error send_ump(uint64_t b01) const;
// Interop with ni-midi2 // Interop with ni-midi2
#if LIBREMIDI_NI_MIDI2_COMPAT #if LIBREMIDI_NI_MIDI2_COMPAT
void send_ump(const midi::universal_packet& pkt) const { send_ump(pkt.data, pkt.size()); } stdx::error send_ump(const midi::universal_packet& pkt) const { send_ump(pkt.data, pkt.size()); }
void send_ump(const midi::sysex7& msg, int group = 0) stdx::error send_ump(const midi::sysex7& msg, int group = 0)
{ {
midi::send_sysex7(msg, group, [&](const midi::sysex7_packet& x) { send_ump(x.data); }); midi::send_sysex7(msg, group, [&](const midi::sysex7_packet& x) { send_ump(x.data); });
} }
void send_ump(const midi::sysex8& msg, int stream, int group = 0) stdx::error send_ump(const midi::sysex8& msg, int stream, int group = 0)
{ {
midi::send_sysex8(msg, stream, group, [&](const midi::sysex8_packet& x) { send_ump(x.data); }); midi::send_sysex8(msg, stream, group, [&](const midi::sysex8_packet& x) { send_ump(x.data); });
} }
@@ -249,7 +257,7 @@ public:
//! Try to schedule an UMP packet later in time if the underlying API supports it //! Try to schedule an UMP packet later in time if the underlying API supports it
//! (currently not implemented anywhere) //! (currently not implemented anywhere)
void schedule_ump(int64_t timestamp, const uint32_t* message, size_t size); stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const;
private: private:
std::unique_ptr<class midi_out_api> impl_; std::unique_ptr<class midi_out_api> impl_;
+18 -5
View File
@@ -1,7 +1,12 @@
#pragma once #pragma once
#include <libremidi/config.hpp> #include <libremidi/config.hpp>
#include <algorithm>
#include <span> #include <span>
#include <vector>
#if defined(__cpp_exceptions)
#include <stdexcept>
#endif
namespace libremidi namespace libremidi
{ {
@@ -120,7 +125,7 @@ struct message
{ {
#if defined(__cpp_exceptions) #if defined(__cpp_exceptions)
if (channel <= 0 || channel > 16) if (channel <= 0 || channel > 16)
throw std::range_error("message::uses_channel: out of range"); throw std::range_error("out of range");
#endif #endif
return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0); return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0);
} }
@@ -240,7 +245,9 @@ struct meta_events
static_cast<uint8_t>(mpqn)}; static_cast<uint8_t>(mpqn)};
} }
static message time_signature(int numerator, int denominator) static message time_signature(
int numerator, int denominator, int clocks_per_click = 1,
int notated_32nd_notes_per_beat = 96)
{ {
int n = 1; int n = 1;
int powTwo = 0; int powTwo = 0;
@@ -251,8 +258,14 @@ struct meta_events
++powTwo; ++powTwo;
} }
return {0xff, 0x58, 0x04, static_cast<uint8_t>(numerator), static_cast<uint8_t>(powTwo), return {
1, 96}; 0xff,
0x58,
0x04,
static_cast<uint8_t>(numerator),
static_cast<uint8_t>(powTwo),
static_cast<uint8_t>(clocks_per_click),
static_cast<uint8_t>(notated_32nd_notes_per_beat)};
} }
// Where key index goes from -7 (7 flats, C♭ Major) to +7 (7 sharps, C♯ // Where key index goes from -7 (7 flats, C♭ Major) to +7 (7 sharps, C♯
@@ -261,7 +274,7 @@ struct meta_events
{ {
#if defined(__cpp_exceptions) #if defined(__cpp_exceptions)
if (keyIndex < -7 || keyIndex > 7) if (keyIndex < -7 || keyIndex > 7)
throw std::range_error("meta_events::key_signature: out of range"); throw std::range_error("out of range");
#endif #endif
return { return {
0xff, 0x59, 0x02, static_cast<uint8_t>(keyIndex), 0xff, 0x59, 0x02, static_cast<uint8_t>(keyIndex),
+243 -34
View File
@@ -9,11 +9,63 @@
namespace libremidi namespace libremidi
{ {
LIBREMIDI_INLINE auto make_midi_in(auto base_conf, std::any api_conf, auto backends)
static libremidi::ump_input_configuration
convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) noexcept
{
libremidi::ump_input_configuration c2;
c2.on_message = [cb = base_conf.on_message,
converter = midi2_to_midi1{}](libremidi::ump&& msg) mutable -> void {
converter.convert(
msg.data, 1, msg.timestamp, [cb](const unsigned char* midi, std::size_t n, int64_t ts) {
cb(libremidi::message{{midi, midi + n}, ts});
return stdx::error{};
});
};
c2.get_timestamp = base_conf.get_timestamp;
c2.on_error = base_conf.on_error;
c2.on_warning = base_conf.on_warning;
c2.ignore_sysex = base_conf.ignore_sysex;
c2.ignore_timing = base_conf.ignore_timing;
c2.ignore_sensing = base_conf.ignore_sensing;
c2.timestamps = base_conf.timestamps;
return c2;
}
static libremidi::input_configuration
convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_conf) noexcept
{
libremidi::input_configuration c2;
c2.on_message = [cb = base_conf.on_message,
converter = midi1_to_midi2{}](libremidi::message&& msg) mutable -> void {
converter.convert(
msg.bytes.data(), msg.bytes.size(), msg.timestamp,
[cb](const uint32_t* ump, std::size_t n, int64_t ts) {
if(n >= 4)
{
libremidi::ump u{ump[0],ump[1],ump[2],ump[3]};
u.timestamp = ts;
cb(std::move(u));
}
return stdx::error{};
});
};
c2.get_timestamp = base_conf.get_timestamp;
c2.on_error = base_conf.on_error;
c2.on_warning = base_conf.on_warning;
c2.ignore_sysex = base_conf.ignore_sysex;
c2.ignore_timing = base_conf.ignore_timing;
c2.ignore_sensing = base_conf.ignore_sensing;
c2.timestamps = base_conf.timestamps;
return c2;
}
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
make_midi_in(auto base_conf, std::any api_conf, auto backends)
{ {
std::unique_ptr<midi_in_api> ptr; std::unique_ptr<midi_in_api> ptr;
assert(base_conf.on_message); assert(base_conf.on_message || base_conf.on_raw_data);
auto from_api = [&]<typename T>(T& /*backend*/) mutable { auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf)) if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
@@ -27,58 +79,199 @@ LIBREMIDI_INLINE auto make_midi_in(auto base_conf, std::any api_conf, auto backe
return ptr; return ptr;
} }
LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept /// MIDI 1 helpers
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
make_midi1_in(const input_configuration& base_conf)
{ {
for (const auto& api : available_apis()) for (const auto& api : available_apis())
{ {
try try
{ {
impl_ = make_midi_in(base_conf, midi_in_configuration_for(api), midi1::available_backends); auto impl
= make_midi_in(base_conf, midi_in_configuration_for(api), midi1::available_backends);
if (impl)
return impl;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
} }
if (impl_)
return;
} }
if (!impl_)
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{}); // No MIDI 1 backend, try the MIDI 2 ones with a wrap:
{
auto c2 = convert_midi1_to_midi2_input_configuration(base_conf);
for (const auto& api : available_ump_apis())
{
try
{
auto impl = make_midi_in(c2, midi_in_configuration_for(api), midi2::available_backends);
if (impl)
return impl;
}
catch (const std::exception& e)
{
}
}
}
return std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
}
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
{
if (libremidi::is_midi1(api))
{
return make_midi_in(base_conf, api_conf, midi1::available_backends);
}
else if (libremidi::is_midi2(api))
{
auto c2 = convert_midi1_to_midi2_input_configuration(base_conf);
return make_midi_in(c2, api_conf, midi2::available_backends);
}
return {};
}
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf)
{
if (!api_conf.has_value())
{
return make_midi1_in(base_conf);
}
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
{
if (*api_p == libremidi::API::UNSPECIFIED)
{
return make_midi1_in(base_conf);
}
else
{
return make_midi1_in(base_conf, midi_in_configuration_for(*api_p), *api_p);
}
}
else
{
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
return {};
else
return make_midi1_in(base_conf, api_conf, libremidi::midi_api(api_conf));
}
}
/// MIDI 1 constructors
LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept
: impl_{make_midi1_in(base_conf)}
{
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
midi_in::midi_in(input_configuration base_conf, std::any api_conf) midi_in::midi_in(input_configuration base_conf, std::any api_conf)
: impl_{make_midi_in(base_conf, api_conf, midi1::available_backends)} : impl_{make_midi1_in(base_conf, api_conf)}
{ {
if (!impl_) if (!impl_)
throw midi_exception("Could not open midi in for the given api"); {
error_handler e;
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
}
} }
LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept /// MIDI 2 helpers
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
make_midi2_in(const ump_input_configuration& base_conf)
{ {
for (const auto& api : available_apis()) for (const auto& api : available_ump_apis())
{ {
try try
{ {
impl_ = make_midi_in(base_conf, midi_in_configuration_for(api), midi2::available_backends); if (auto ret
= make_midi_in(base_conf, midi_in_configuration_for(api), midi2::available_backends))
return ret;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
} }
if (impl_)
return;
} }
if (!impl_)
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{}); // No MIDI 2 backend, try the MIDI 1 ones with a wrap:
{
auto c2 = convert_midi2_to_midi1_input_configuration(base_conf);
for (const auto& api : available_apis())
{
try
{
if (auto ret = make_midi_in(c2, midi_in_configuration_for(api), midi1::available_backends))
return ret;
}
catch (const std::exception& e)
{
}
}
}
return {};
}
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(
const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
{
if (is_midi2(api))
{
return make_midi_in(base_conf, api_conf, midi2::available_backends);
}
else if (is_midi1(api))
{
auto c2 = convert_midi2_to_midi1_input_configuration(base_conf);
return make_midi_in(c2, api_conf, midi1::available_backends);
}
return {};
}
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf)
{
if (!api_conf.has_value())
{
return make_midi2_in(base_conf);
}
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
{
if (*api_p == libremidi::API::UNSPECIFIED)
{
return make_midi2_in(base_conf);
}
else
{
return make_midi2_in(base_conf, midi_in_configuration_for(*api_p), *api_p);
}
}
else
{
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
return {};
else
return make_midi2_in(base_conf, api_conf, libremidi::midi_api(api_conf));
}
}
/// MIDI 2 constructors
LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept
: impl_{make_midi2_in(base_conf)}
{
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf) midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf)
: impl_{make_midi_in(base_conf, api_conf, midi2::available_backends)} : impl_{make_midi2_in(base_conf, api_conf)}
{ {
if (!impl_) if (!impl_)
throw midi_exception("Could not open midi in for the given api"); {
error_handler e;
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
}
} }
LIBREMIDI_INLINE midi_in::~midi_in() = default; LIBREMIDI_INLINE midi_in::~midi_in() = default;
@@ -91,9 +284,12 @@ LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_in::set_port_name(std::string_view portName) stdx::error midi_in::set_port_name(std::string_view portName)
{ {
impl_->set_port_name(portName); if(impl_->is_port_open())
return impl_->set_port_name(portName);
return std::errc::not_connected;
} }
LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept
@@ -111,37 +307,50 @@ libremidi::API midi_in::get_current_api() const noexcept
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_in::open_port(const input_port& port, std::string_view portName) stdx::error midi_in::open_port(const input_port& port, std::string_view portName)
{ {
if (impl_->is_port_open()) if (auto err = impl_->is_client_open(); err != stdx::error{})
return; return std::errc::not_connected;
if (impl_->open_port(port, portName)) if (impl_->is_port_open())
return std::errc::operation_not_supported;
auto ret = impl_->open_port(port, portName);
if (ret == stdx::error{})
{ {
impl_->connected_ = true; impl_->connected_ = true;
impl_->port_open_ = true; impl_->port_open_ = true;
} }
return ret;
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_in::open_virtual_port(std::string_view portName) stdx::error midi_in::open_virtual_port(std::string_view portName)
{ {
if (auto err = impl_->is_client_open(); err != stdx::error{})
return std::errc::not_connected;
if (impl_->is_port_open()) if (impl_->is_port_open())
return; return std::errc::operation_not_supported;
if (impl_->open_virtual_port(portName)) auto ret = impl_->open_virtual_port(portName);
{ if (ret == stdx::error{})
impl_->port_open_ = true; impl_->port_open_ = true;
} return ret;
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_in::close_port() stdx::error midi_in::close_port()
{ {
impl_->close_port(); if (auto err = impl_->is_client_open(); err != stdx::error{})
return std::errc::not_connected;
auto ret = impl_->close_port();
impl_->connected_ = false; impl_->connected_ = false;
impl_->port_open_ = false; impl_->port_open_ = false;
return ret;
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
+159 -48
View File
@@ -10,7 +10,8 @@
namespace libremidi namespace libremidi
{ {
LIBREMIDI_INLINE auto make_midi_out(auto base_conf, std::any api_conf) static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
make_midi_out_impl(auto base_conf, std::any api_conf)
{ {
std::unique_ptr<midi_out_api> ptr; std::unique_ptr<midi_out_api> ptr;
auto from_api = [&]<typename T>(T& /*backend*/) mutable { auto from_api = [&]<typename T>(T& /*backend*/) mutable {
@@ -26,23 +27,66 @@ LIBREMIDI_INLINE auto make_midi_out(auto base_conf, std::any api_conf)
return ptr; return ptr;
} }
LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
make_midi_out(const output_configuration& base_conf)
{ {
for (const auto& api : available_apis()) for (const auto& api : available_apis())
{ {
try try
{ {
impl_ = make_midi_out(base_conf, midi_out_configuration_for(api)); if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api)))
return ret;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
} }
if (impl_)
return;
} }
if (!impl_)
impl_ = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{}); for (const auto& api : available_ump_apis())
{
try
{
if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api)))
return ret;
}
catch (const std::exception& e)
{
}
}
return std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
}
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
make_midi_out(const output_configuration& base_conf, const std::any& api_conf)
{
if (!api_conf.has_value())
{
return make_midi_out(base_conf);
}
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
{
if (*api_p == libremidi::API::UNSPECIFIED)
{
return make_midi_out(base_conf);
}
else
{
return make_midi_out_impl(base_conf, midi_out_configuration_for(*api_p));
}
}
else
{
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
return make_midi_out(base_conf);
else
return make_midi_out_impl(base_conf, api_conf);
}
}
LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept
: impl_{make_midi_out(base_conf)}
{
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
@@ -50,7 +94,11 @@ midi_out::midi_out(output_configuration base_conf, std::any api_conf)
: impl_{make_midi_out(base_conf, api_conf)} : impl_{make_midi_out(base_conf, api_conf)}
{ {
if (!impl_) if (!impl_)
throw midi_exception("Could not open midi out for the given api"); {
error_handler e;
e.libremidi_handle_error(base_conf, "Could not open midi out for the given api");
impl_ = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
}
} }
LIBREMIDI_INLINE midi_out::~midi_out() = default; LIBREMIDI_INLINE midi_out::~midi_out() = default;
@@ -61,6 +109,7 @@ LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept
other.impl_ other.impl_
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{}); = std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
} }
LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
{ {
this->impl_ = std::move(other.impl_); this->impl_ = std::move(other.impl_);
@@ -70,9 +119,12 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::set_port_name(std::string_view portName) const stdx::error midi_out::set_port_name(std::string_view portName) const
{ {
impl_->set_port_name(portName); if(impl_->is_port_open())
return impl_->set_port_name(portName);
return std::errc::not_connected;
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
@@ -82,36 +134,48 @@ libremidi::API midi_out::get_current_api() const noexcept
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::open_port(const output_port& port, std::string_view portName) const stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const
{ {
if (impl_->is_port_open()) if (auto err = impl_->is_client_open(); err != stdx::error{})
return; return std::errc::not_connected;
if (impl_->open_port(port, portName)) if (impl_->is_port_open())
return std::errc::operation_not_supported;
auto ret = impl_->open_port(port, portName);
if (ret == stdx::error{})
{ {
impl_->connected_ = true; impl_->connected_ = true;
impl_->port_open_ = true; impl_->port_open_ = true;
} }
return ret;
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::open_virtual_port(std::string_view portName) const stdx::error midi_out::open_virtual_port(std::string_view portName) const
{ {
if (auto err = impl_->is_client_open(); err != stdx::error{})
return std::errc::not_connected;
if (impl_->is_port_open()) if (impl_->is_port_open())
return; return std::errc::operation_not_supported;
if (impl_->open_virtual_port(portName)) auto ret = impl_->open_virtual_port(portName);
{ if (ret == stdx::error{})
impl_->port_open_ = true; impl_->port_open_ = true;
} return ret;
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::close_port() const stdx::error midi_out::close_port() const
{ {
impl_->close_port(); if (auto err = impl_->is_client_open(); err != stdx::error{})
return std::errc::not_connected;
auto ret = impl_->close_port();
impl_->connected_ = false; impl_->connected_ = false;
impl_->port_open_ = false; impl_->port_open_ = false;
return ret;
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
@@ -127,89 +191,136 @@ bool midi_out::is_port_connected() const noexcept
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_message(const libremidi::message& message) const stdx::error midi_out::send_message(const libremidi::message& message) const
{ {
send_message(message.bytes.data(), message.bytes.size()); return send_message(message.bytes.data(), message.bytes.size());
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_message(std::span<const unsigned char> message) const stdx::error midi_out::send_message(std::span<const unsigned char> message) const
{ {
send_message(message.data(), message.size()); return send_message(message.data(), message.size());
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_message(unsigned char b0) const stdx::error midi_out::send_message(unsigned char b0) const
{ {
send_message(&b0, 1); return send_message(&b0, 1);
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_message(unsigned char b0, unsigned char b1) const stdx::error midi_out::send_message(unsigned char b0, unsigned char b1) const
{ {
send_message(std::to_array({b0, b1})); return send_message(std::to_array({b0, b1}));
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_message(unsigned char b0, unsigned char b1, unsigned char b2) const stdx::error midi_out::send_message(unsigned char b0, unsigned char b1, unsigned char b2) const
{ {
send_message(std::to_array({b0, b1, b2})); return send_message(std::to_array({b0, b1, b2}));
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_message(const unsigned char* message, size_t size) const stdx::error midi_out::send_message(const unsigned char* message, size_t size) const
{ {
#if defined(LIBREMIDI_ASSERTIONS) #if defined(LIBREMIDI_ASSERTIONS)
assert(size > 0); assert(size > 0);
#endif #endif
impl_->send_message(message, size); return impl_->send_message(message, size);
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_ump(const uint32_t* message, size_t size) const int64_t midi_out::current_time()
{
return impl_->current_time();
}
LIBREMIDI_INLINE
stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message, size_t size) const
{
#if defined(LIBREMIDI_ASSERTIONS)
assert(size > 0);
#endif
return impl_->schedule_message(ts, message, size);
}
LIBREMIDI_INLINE
stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const
{ {
#if defined(LIBREMIDI_ASSERTIONS) #if defined(LIBREMIDI_ASSERTIONS)
assert(size > 0); assert(size > 0);
assert(size <= 4); assert(size <= 4);
#endif #endif
impl_->send_ump(message, size); return impl_->send_ump(message, size);
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_ump(const libremidi::ump& message) const stdx::error midi_out::send_ump(const libremidi::ump& message) const
{ {
send_ump(message.data, message.size()); return send_ump(message.data, message.size());
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_ump(std::span<const uint32_t> message) const stdx::error midi_out::send_ump(std::span<const uint32_t> message) const
{ {
send_ump(message.data(), message.size()); return send_ump(message.data(), message.size());
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_ump(uint32_t b0) const stdx::error midi_out::send_ump(uint32_t b0) const
{ {
send_ump(&b0, 1); return send_ump(&b0, 1);
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_ump(uint32_t b0, uint32_t b1) const stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1) const
{ {
send_ump(std::to_array({b0, b1})); return send_ump(std::to_array({b0, b1}));
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const
{ {
send_ump(std::to_array({b0, b1, b2})); return send_ump(std::to_array({b0, b1, b2}));
} }
LIBREMIDI_INLINE LIBREMIDI_INLINE
void midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const
{ {
send_ump(std::to_array({b0, b1, b2, b3})); return send_ump(std::to_array({b0, b1, b2, b3}));
} }
LIBREMIDI_INLINE
stdx::error midi_out::send_ump(int32_t b0) const
{
return send_ump(reinterpret_cast<uint32_t*>(&b0), 1);
}
LIBREMIDI_INLINE
stdx::error midi_out::send_ump(int64_t b01) const
{
auto ptr = reinterpret_cast<uint32_t*>(&b01);
uint32_t msg[2]{ptr[1], ptr[0]};
return send_ump(msg, 2);
}
LIBREMIDI_INLINE
stdx::error midi_out::send_ump(uint64_t b01) const
{
auto ptr = reinterpret_cast<uint32_t*>(&b01);
uint32_t msg[2]{ptr[1], ptr[0]};
return send_ump(msg, 2);
}
LIBREMIDI_INLINE
stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t size) const
{
#if defined(LIBREMIDI_ASSERTIONS)
assert(size > 0);
#endif
return impl_->schedule_ump(ts, message, size);
}
} }
+59 -13
View File
@@ -7,7 +7,37 @@
namespace libremidi namespace libremidi
{ {
LIBREMIDI_INLINE auto make_observer(auto base_conf, std::any api_conf)
static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf)
{
for (const auto& api : available_apis())
{
try
{
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
return impl_;
}
catch (const std::exception& e)
{
}
}
for (const auto& api : available_ump_apis())
{
try
{
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
return impl_;
}
catch (const std::exception& e)
{
}
}
return std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
}
LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf)
{ {
std::unique_ptr<observer_api> ptr; std::unique_ptr<observer_api> ptr;
auto from_api = [&]<typename T>(T& /*backend*/) mutable { auto from_api = [&]<typename T>(T& /*backend*/) mutable {
@@ -23,30 +53,46 @@ LIBREMIDI_INLINE auto make_observer(auto base_conf, std::any api_conf)
return ptr; return ptr;
} }
LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf, std::any api_conf)
{ {
for (const auto& api : available_apis()) if (!api_conf.has_value())
{ {
try return make_observer(base_conf);
{
impl_ = make_observer(base_conf, observer_configuration_for(api));
} }
catch (const std::exception& e) else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
{ {
if (*api_p == libremidi::API::UNSPECIFIED)
{
return make_observer(base_conf);
} }
else
{
return make_observer_impl(base_conf, observer_configuration_for(*api_p));
}
}
else
{
if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)
return {};
else
return make_observer_impl(base_conf, api_conf);
}
}
if (impl_) LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept
return; : impl_{make_observer(base_conf)}
} {
if (!impl_)
impl_ = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
} }
LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf) LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf)
: impl_{make_observer(base_conf, api_conf)} : impl_{make_observer(base_conf, api_conf)}
{ {
if (!impl_) if (!impl_)
throw midi_exception("Could not open observer for the given API"); {
error_handler e;
e.libremidi_handle_error(base_conf, "Could not open observer for the given api");
impl_ = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
}
} }
LIBREMIDI_INLINE observer::observer(observer&& other) noexcept LIBREMIDI_INLINE observer::observer(observer&& other) noexcept
+12 -12
View File
@@ -22,22 +22,22 @@ struct LIBREMIDI_EXPORT port_information
// PipeWire: unused // PipeWire: unused
// WinMM: unused // WinMM: unused
// WinUWP: unused // WinUWP: unused
client_handle client; client_handle client = static_cast<client_handle>(-1);
// ALSA Raw: { uint16_t card, device, sub, padding; } // ALSA Raw: { uint16_t card, device, sub, padding; }
// ALSA Seq: { uint32_t client, uint32_t port; } // ALSA Seq: { uint32_t client, uint32_t port; }
// CoreMIDI: MidiObjectRef // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t)
// WebMIDI: unused // WebMIDI: unused
// JACK: jack_port_id_t // JACK: jack_port_id_t
// PipeWire: port.id // PipeWire: port.id
// WinMM: unset, identified by port_name // WinMM: unset, identified by port_name
// WinUWP: unused // WinUWP: unused
port_handle port; port_handle port = static_cast<port_handle>(-1);
std::string manufacturer; std::string manufacturer{};
std::string device_name; std::string device_name{};
std::string port_name; std::string port_name{};
std::string display_name; std::string display_name{};
bool operator==(const port_information& other) const noexcept = default; bool operator==(const port_information& other) const noexcept = default;
std::strong_ordering operator<=>(const port_information& other) const noexcept = default; std::strong_ordering operator<=>(const port_information& other) const noexcept = default;
@@ -59,12 +59,12 @@ using output_port_callback = std::function<void(const output_port&)>;
struct observer_configuration struct observer_configuration
{ {
midi_error_callback on_error{}; midi_error_callback on_error{};
midi_error_callback on_warning{}; midi_warning_callback on_warning{};
input_port_callback input_added; input_port_callback input_added{};
input_port_callback input_removed; input_port_callback input_removed{};
output_port_callback output_added; output_port_callback output_added{};
output_port_callback output_removed; output_port_callback output_removed{};
// Observe hardware ports // Observe hardware ports
uint32_t track_hardware : 1 = true; uint32_t track_hardware : 1 = true;
+1 -3
View File
@@ -3,8 +3,6 @@
#include <libremidi/error.hpp> #include <libremidi/error.hpp>
#include <libremidi/input_configuration.hpp> #include <libremidi/input_configuration.hpp>
#include <string>
namespace libremidi namespace libremidi
{ {
struct output_configuration struct output_configuration
@@ -15,7 +13,7 @@ struct output_configuration
best to set the error callback function before opening a port. best to set the error callback function before opening a port.
*/ */
midi_error_callback on_error{}; midi_error_callback on_error{};
midi_error_callback on_warning{}; midi_warning_callback on_warning{};
//! Timestamp mode for the timestamps passed to schedule_message //! Timestamp mode for the timestamps passed to schedule_message
uint32_t timestamps : 3 = timestamp_mode::Absolute; uint32_t timestamps : 3 = timestamp_mode::Absolute;
+12 -12
View File
@@ -71,7 +71,7 @@ struct validator
if (track.empty()) if (track.empty())
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: empty track" << std::endl; std::cerr << "empty track" << std::endl;
#endif #endif
return false; return false;
} }
@@ -85,7 +85,7 @@ struct validator
if (it == track.end()) if (it == track.end())
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: track has no END OF TRACK" << std::endl; std::cerr << "track has no END OF TRACK" << std::endl;
#endif #endif
return false; return false;
} }
@@ -94,7 +94,7 @@ struct validator
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << std::distance(it, track.end()); std::cerr << std::distance(it, track.end());
std::cerr << "libremidi::reader: track does not end with END OF TRACK" << std::endl; std::cerr << "track does not end with END OF TRACK" << std::endl;
#endif #endif
return false; return false;
} }
@@ -544,7 +544,7 @@ try
if (size == 0) if (size == 0)
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: empty buffer passed to parse." << std::endl; std::cerr << "empty buffer passed to parse." << std::endl;
#endif #endif
return parse_result::invalid; return parse_result::invalid;
} }
@@ -557,7 +557,7 @@ try
if (static_cast<int>(headerId) != str_to_headerid("MThd") || headerLength != 6) if (static_cast<int>(headerId) != str_to_headerid("MThd") || headerLength != 6)
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: couldn't parse header" << std::endl; std::cerr << "couldn't parse header" << std::endl;
#endif #endif
return parse_result::invalid; return parse_result::invalid;
} }
@@ -567,7 +567,7 @@ try
if (format > 2) if (format > 2)
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: unknown format" << std::endl; std::cerr << "unknown format" << std::endl;
#endif #endif
return parse_result::invalid; return parse_result::invalid;
} }
@@ -579,7 +579,7 @@ try
if (timeDivision & 0x8000) if (timeDivision & 0x8000)
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: found SMPTE time frames (unsupported)" << std::endl; std::cerr << "found SMPTE time frames (unsupported)" << std::endl;
int fps = (timeDivision >> 16) & 0x7f; int fps = (timeDivision >> 16) & 0x7f;
if (fps != -30 && fps != -29 && fps != -25 && fps != -24) if (fps != -30 && fps != -29 && fps != -25 && fps != -24)
return parse_result::invalid; return parse_result::invalid;
@@ -604,7 +604,7 @@ try
if (headerId != str_to_headerid("MTrk")) if (headerId != str_to_headerid("MTrk"))
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: couldn't find track header" << std::endl; std::cerr << "couldn't find track header" << std::endl;
#endif #endif
return parse_result::incomplete; return parse_result::incomplete;
} }
@@ -613,7 +613,7 @@ try
if (available < headerLength) if (available < headerLength)
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: not enough data available" << std::endl; std::cerr << "not enough data available" << std::endl;
#endif #endif
return parse_result::incomplete; return parse_result::incomplete;
} }
@@ -652,7 +652,7 @@ try
else else
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: could not read event" << std::endl; std::cerr << "could not read event" << std::endl;
#endif #endif
dataPtr = trackEnd; dataPtr = trackEnd;
result = parse_result::incomplete; result = parse_result::incomplete;
@@ -664,7 +664,7 @@ try
catch (const std::exception& e) catch (const std::exception& e)
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: " << e.what() << std::endl; std::cerr << "" << e.what() << std::endl;
#endif #endif
dataPtr = trackEnd; dataPtr = trackEnd;
result = parse_result::incomplete; result = parse_result::incomplete;
@@ -697,7 +697,7 @@ try
catch (const std::exception& e) catch (const std::exception& e)
{ {
#if defined(__LIBREMIDI_DEBUG__) #if defined(__LIBREMIDI_DEBUG__)
std::cerr << "libremidi::reader: " << e.what() << std::endl; std::cerr << "" << e.what() << std::endl;
#endif #endif
return parse_result::invalid; return parse_result::invalid;
} }

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