101 Commits

Author SHA1 Message Date
Sebastian 9e284be109 wip rework of interaction event system - broken right now 2026-07-19 02:39:19 +02:00
Sebastian c7c489fde5 signal timing needs work 2026-07-18 02:07:50 +02:00
Sebastian 1f7f5314a3 midi modes added, context menu adapted 2026-07-17 02:03:27 +02:00
Sebastian 1f2cbc4c31 modifier chains implemented for notes 2026-07-17 01:07:09 +02:00
Sebastian 183d89ceaf modifier chains added 2026-07-17 00:13:01 +02:00
Sebastian 0be3d6cd77 changed node font to Inter 2026-07-16 00:23:32 +02:00
Sebastian 4613bfd072 fixed connector mismatch after breaking it after fixing it last time 2026-07-14 02:19:29 +02:00
Sebastian 585e5e1167 connectors fixed 2026-07-02 08:28:21 +02:00
Sebastian 30408393e7 debugging connector problem 2026-06-30 10:54:45 +02:00
Sebastian e9c8cc9aea connectors need fixing 2026-06-28 23:45:54 +02:00
Sebastian 03b2989130 penpot file added 2026-06-28 00:46:18 +02:00
Sebastian e9c2872187 more textures and ui refinements 2026-06-28 00:34:57 +02:00
Sebastian b9ad5293bf blocker not showing 2026-06-27 03:13:40 +02:00
Sebastian e4b2df21bc small steps forward - placement of edge buttons changed and one more texture added 2026-06-27 01:35:20 +02:00
Sebastian 970faad32d wip 2026-06-26 19:32:00 +02:00
Sebastian 0fb517f282 position fixed finally 2026-06-26 17:43:45 +02:00
Sebastian 9fefa903bd still wrong position 2026-06-26 10:26:41 +02:00
Sebastian 3f8984b69f delete button texturized but still at incorrect position 2026-06-26 02:23:08 +02:00
Sebastian 9ea1305c27 ui improvements and texture tests 2026-06-26 01:50:32 +02:00
Sebastian 7c7c3b37ff ui improvements for modifier value changes 2026-06-25 19:34:23 +02:00
Sebastian 792e537f2b removed orbits unified ctrl/alt/shift modifiers for alle node types 2026-06-25 11:41:55 +02:00
Sebastian 5c433111e2 standard values for midi nodes added 2026-06-25 08:53:11 +02:00
Sebastian c15188d456 copy paste repaired 2026-06-24 23:37:47 +02:00
Sebastian a76954b6f7 clipboard singleton 2026-06-24 18:11:34 +02:00
Sebastian b02eb7e1d4 cleanup and copy/paste bug identified 2026-06-24 11:04:28 +02:00
Sebastian ce675b1430 virtual ports working - ui needs some work 2026-06-24 00:38:49 +02:00
Sebastian 3de17a3b43 Merge branch 'refactor-orbit-ui' into orbit-ui 2026-06-23 09:52:30 +02:00
Sebastian 5a1f5acae8 window resizing sorted sort of 2026-06-23 01:26:20 +02:00
Sebastian 51cd271b6f libremidi update to v5.4.3 and added virtual midi out as default 2026-06-23 00:05:41 +02:00
Sebastian dc0003232a working again 2026-05-04 22:24:29 +02:00
Sebastian cae4acfdef Merge branch 'refactor-orbit-ui' of ssh://code.sebastiankujas.com:20002/sebastian/viseq into refactor-orbit-ui 2026-05-04 20:34:42 +02:00
Sebastian b8438ade34 merge conflict stuff 2026-05-04 20:30:16 +02:00
Sebastian 657c23702c moved event_queue to state singleton 2026-05-04 18:40:00 +02:00
Sebastian 5886052228 makefile updated 2026-05-04 14:21:48 +02:00
Sebastian d69368322e show ui only on selected nodes 2026-05-04 13:42:31 +02:00
Sebastian d881ef43a3 compiling but mouse interaction is dead 2026-01-12 00:16:36 +01:00
Sebastian 77a227f40d more refactoring and breaking things 2026-01-06 02:44:18 +01:00
Sebastian f086834da6 refctoring WIP 2026-01-05 02:56:30 +01:00
Sebastian @ HfG 2cc6839009 upgrade to libremidi 5.1, raylib and raygui latest 2025-06-13 19:16:35 +02:00
Sebastian @ HfG 62454960d1 cmath define added for windows - failing at coroutine handle void 2025-06-13 19:14:16 +02:00
Sebastian e5f614ce06 More UI tweaks and some refactoring. Nice! 2024-12-29 01:17:44 +01:00
Sebastian 011552c020 UI tweaks done. Code tweaks now 2024-12-28 15:13:16 +01:00
Sebastian 2725806749 UI tweaks 2024-12-28 14:45:57 +01:00
Sebastian bf2f34c902 Exp scale for gate time 2024-12-28 02:10:30 +01:00
Sebastian 0e6f2c7256 Values and indicators match now 2024-12-28 01:02:29 +01:00
Sebastian 9e9c32bff4 orbit ui done 2024-12-25 02:05:29 +01:00
Sebastian 6aef199984 Serialization of handles working! 2024-12-20 01:57:28 +01:00
Sebastian c3543cffce Workaround for vector_shared_ptr_templated serialization mess. 2024-12-19 01:00:38 +01:00
Sebastian 9b75b36c41 Serialization of NodeHandle<T> stays a mystery... 2024-12-16 02:10:58 +01:00
Sebastian 996ed16c01 Better centered text. Pass by reference for midi variable is not going to work with json serialization. 2024-12-15 03:17:10 +01:00
Sebastian 96c2bde0e1 Vector UI on its way! 2024-12-15 01:47:53 +01:00
Sebastian b2ed35a704 ControlNodes init. Feels like a good idea! 2024-12-13 01:15:55 +01:00
Sebastian 7f2d16932f Resize on hover taken out. ModNode introduced but needs a better ui approach. 2024-12-13 00:30:31 +01:00
Sebastian bc8c433e47 Fun with config nodes that modifiy other config nodes. Circular connections lead to a crash. 2024-12-11 01:16:06 +01:00
Sebastian 456528f230 Some tweaks, not much progress. 2024-12-10 00:00:53 +01:00
Sebastian ddf9e640b1 Good working protoype with config nodes and a experimental channel setting ui. 2024-12-09 01:40:13 +01:00
Sebastian 3b08037f92 Config nodes do what they should do now. UX is still off, modifier keys do different things on MidiNodes and ConfigNodes. 2024-12-08 14:21:46 +01:00
Sebastian 558829f619 Config nodes not working yet, but everything is prepared. 2024-12-08 02:25:57 +01:00
Sebastian d9c97be326 Inbetween states. Midi nodes and config nodes use same interface now. 2024-12-07 22:09:06 +01:00
Sebastian 3b0e7e986f Config nodes change midi notes. Better would be midi nodes read from config nodes. 2024-12-07 15:28:33 +01:00
Sebastian 36273ba7ac Working on config nodes. They exist now, but do nothing yet. 2024-12-07 03:44:03 +01:00
Sebastian ade33b0608 Context menu is back, but not quite yet. 2024-12-05 23:39:01 +01:00
Sebastian 638df54ee6 Connecting works again (almost). Connectors need to be copied when splitting edges. 2024-12-05 01:25:00 +01:00
Sebastian b6c2029386 Running again. Next step: making connections work again! 2024-12-05 00:01:42 +01:00
Sebastian 036c6f4723 Running again after weak_ptr refactoring. No connecting of nodes atm. Copy/Paste might not work, too. 2024-12-04 01:43:06 +01:00
Sebastian 1fde652495 More weak_ptr and clipboard shenanigans :) 2024-12-03 11:08:40 +01:00
Sebastian 0040956801 Rework to weak_ptr from lots of shared_ptr. 2024-12-03 01:25:49 +01:00
Sebastian 722848d46b Deleting Connectable from the connectables vector is resulting in segfault. 2024-12-02 00:28:44 +01:00
Sebastian f926b95092 Still the ownership problem. 5, why 5? It should be 3... 2024-12-01 01:45:36 +01:00
Sebastian b6a049ea0d Connector shared_ptr keeps one owner and i don't know where it is! 2024-12-01 00:55:59 +01:00
Sebastian c63ee3eabe Double connections fixed 2024-11-30 12:47:33 +01:00
Sebastian c7e76ae7ac Tidied up main 2024-11-30 03:01:26 +01:00
Sebastian fcc5d74e18 Node interactions working again, graph editing functions not complete yet 2024-11-30 02:54:40 +01:00
Sebastian 635c94e3cf On the road to connector -> connector with edges... 2024-11-28 08:49:35 +01:00
Sebastian 492971ed4f Dragging and selecting works. Copy / Paste does not copy connectors yet. 2024-11-28 01:08:04 +01:00
Sebastian 813958002b Drag selection and snap to grid ok. Deselect not working though... 2024-11-26 01:58:09 +01:00
Sebastian 9c0aac8618 Dragging and selecting works. Dragging the selection not yet. 2024-11-25 00:53:10 +01:00
Sebastian f04838bffa Super simple events added. Seems to be a good way for handling interaction. Modified a few base classes. 2024-11-24 03:08:06 +01:00
Sebastian 62a5c53969 Mini commit 2024-11-23 00:54:31 +01:00
Sebastian b12f1d0f9d Might be a better way to handle hovering, dragging and mouse interaction in general 2024-11-22 00:24:31 +01:00
Sebastian a6b6ff1d16 Introduction of mouse.h for z-index mouse events 2024-11-21 23:58:35 +01:00
Sebastian bfc14d5f6e copy/paste partially. z-index experiments, but not working yet. 2024-11-21 02:03:32 +01:00
Sebastian df91a1b107 Back to work! 2024-11-20 11:13:13 +01:00
Sebastian 5779c93339 Clipboard introduced, but not incorporated yet. Some defines for convenience and better readability. 2024-11-19 01:04:06 +01:00
Sebastian 22842f9deb Drag selection working again - not much progress 2024-11-18 01:57:10 +01:00
Sebastian e9633ff19d Some progress on selecting, hovering but still buggy and not a good design really. 2024-11-17 02:40:31 +01:00
Sebastian 55926a5021 Selection working partially. Needs global z-index hovering to work reliably. 2024-11-16 00:29:01 +01:00
Sebastian 96526f709a preparing for node->connector->edge->connector->node 2024-11-15 02:09:06 +01:00
Sebastian 9cccbab703 refactored from main to canvas.h - basic functionality working 2024-11-15 01:49:10 +01:00
Sebastian ab34081dbb raw_mouse changed to screen_mouse 2024-11-14 23:21:04 +01:00
Sebastian 2e103dc561 global mouse moved to state and removed some GetMousePosition()s. 2024-11-14 01:37:16 +01:00
Sebastian 5d3b72d4ef Major refactoring: State, mouse and more... 2024-11-14 00:55:09 +01:00
Sebastian 300001d385 Global snap to grid. 2024-11-13 15:35:39 +01:00
Sebastian e6f370ba4f Connectables working now. On to more refactoring! 2024-11-13 14:39:46 +01:00
Sebastian 1e0a131169 connecting works, but position is off. addConnector is the problem 2024-11-13 01:58:44 +01:00
Sebastian 96082e1190 connecting does weird things! 2024-11-12 01:42:05 +01:00
Sebastian 31a1a264d4 little progress on the connector idea 2024-11-11 02:11:54 +01:00
Sebastian 4f48aa2d5a dragging works. introducing connectors maybe 2024-11-10 01:56:33 +01:00
Sebastian 71dc08d21f interactable base class 2024-11-09 23:56:40 +01:00
Sebastian cdc0fb7075 working on node ui 2024-11-08 01:43:06 +01:00
Sebastian 529106e815 introduction of config nodes 2024-11-07 00:56:33 +01:00
239 changed files with 9310 additions and 3094 deletions
+3
View File
@@ -0,0 +1,3 @@
CompileFlags:
Add: [-I/home/sebastian/code/viseq/include, -std=c++23]
+16 -23
View File
@@ -1,32 +1,25 @@
CXX = g++
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1
CXXINC = -Iinclude
.PHONY: clean
LDLIBS = -lraylib -lasound -pthread
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib
CXX := g++
CXXFLAGS := -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1
CXXINC := -Iinclude -isysteminclude
hdr_files = ${wildcard src/*.h}
LDLIBS := -lraylib -lasound -llibremidi -pthread
LDFLAGS := -Llib -L/usr/lib -L/usr/local/lib
src_files = ${wildcard src/*.cpp}
src_files += ${wildcard include/libremidi/*.cpp}
SRCS := $(wildcard src/*.cpp)
OBJECTS := $(patsubst src/%.cpp,build/%.o, $(SRCS))
DEPENDS := $(patsubst src/%.cpp,build/%.d, $(SRCS))
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)
viseq: $(OBJECTS)
$(CXX) $(OBJECTS) -o $@ $(LDFLAGS) $(LDLIBS)
$(hdr_files): %.h : %_hdr
@echo "G++ >---------- $(*F).h -----------<"
-include $(DEPENDS)
%_src:
@echo "G++ >---------- $(*F).cpp -----------<"
%_hdr:
@echo "G++ >---------- $(*F).h -----------<"
#$(OBJECTS): build/%.o : src/%.cpp
build/%.o: src/%.cpp Makefile
@mkdir -p build
$(CXX) -c $< -o $@ $(CXXFLAGS) $(CXXINC) -MMD -MP
clean:
rm -f build/*
-57
View File
@@ -1,57 +0,0 @@
# VISEQ - THE VISUAL SEQUENCER
## BUILD
### LINUX
```
make
```
### WINDOWS
You need MingW64.
```
make -f MakefileMingW64
```
## USAGE
### CHANGING NODES
Nodes can be moved around, connected and modified. A right-cick opens a context menu with settings. Midi settings can also be changed with the mouse-wheel and modifier keys while hovering a node. All selected nodes can be changed at once. Draw a selectionbox around all nodes that you want to change, or select them by holding shift while clicking or dragging.
#### MOUSE WHEEL
- ctrl + wheel -> change midi note
- shift + wheel -> change midi velocity
- alt + wheel -> change midi gate
#### DRAGGING NODES
While dragging nodes one can use ctrl and shift to activate two different snapping modes. Ctrl enables snap to grid, while shift enables snap to next nodes. The grid is divided into 32nd notes. Snap to next nodes shows the time divisions to the closest nodes and can snap to two nodes at once.
#### SELECTING NODES
Dragging the mouse on the canvas, creates a selection rectangle. All nodes in the rectanlge will be highlighted. Using the above mentioned modification methods, one can change all selected nodes at once.
All selected nodes can also be moved together.
### CONNECTIONS
Connect nodes by dragging from the orbiting circle to another node. A new connection appears. Connections can be closed for signals in one, or both directions. Click the small green circle and it will block traffic in this direction.
#### DISTRIBUTION MODES
Nodes can have three distribution modes, "Geometric", "RR" (Round Robin), and "Random".
**Geometric Distribution**
Geometric distribution means that signals take the straightest path that is available, when more than two paths are available. By moving around nodes so that another connection becomes the straighter path, one can divert signals to other areas of the canvas.
**Round Robin Distribution**
Round Robin means, that every signal that arrives at a node with RR distribution switches the node to the next connection. With multiple signals in one path, this can lead to some beautiful patterns emerging.
**Random Distribution**
Random distribution sends every arriving signal to a random connection.
+11 -4
View File
@@ -1,13 +1,15 @@
#pragma once
#ifndef LIBREMIDI_API_C_H
#define LIBREMIDI_API_C_H
#if __cplusplus
#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD)
extern "C" {
#endif
//! MIDI API specifier arguments.
//! To get information on which feature is supported by each back-end, check their backend file
//! in e.g. backends/winmm.hpp, etc.
typedef enum libremidi_api
enum libremidi_api
{
UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */
@@ -22,6 +24,8 @@ typedef enum libremidi_api
PIPEWIRE, /*!< PipeWire */
KEYBOARD, /*!< Computer keyboard input */
NETWORK, /*!< MIDI over IP */
ANDROID_AMIDI, /*!< Android AMidi API */
KDMAPI, /*!< OmniMIDI KDMAPI (Windows) */
// MIDI 2.0 APIs
ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */
@@ -34,8 +38,11 @@ typedef enum libremidi_api
PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
} libremidi_api;
};
#if __cplusplus
typedef enum libremidi_api libremidi_api;
#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD)
}
#endif
#endif
+3 -1
View File
@@ -5,7 +5,7 @@
#include <string_view>
#include <vector>
namespace libremidi
NAMESPACE_LIBREMIDI
{
//! MIDI API specifier arguments.
//! To get information on which feature is supported by each back-end, check their backend file
@@ -64,6 +64,8 @@ inline constexpr libremidi::API default_api() noexcept
return API::ALSA_SEQ;
#elif defined(__emscripten__)
return API::EMSCRIPTEN_WEBMIDI;
#elif defined(LIBREMIDI_ANDROID)
return API::ANDROID_AMIDI;
#else
return API::DUMMY;
#endif
+26 -8
View File
@@ -29,7 +29,9 @@
#if defined(LIBREMIDI_JACK)
#include <libremidi/backends/jack.hpp>
#include <libremidi/backends/jack_ump.hpp>
#if defined(LIBREMIDI_HAS_JACK_GET_VERSION)
#include <libremidi/backends/jack_ump.hpp>
#endif
#endif
#if defined(LIBREMIDI_PIPEWIRE)
@@ -48,6 +50,10 @@
#include <libremidi/backends/winmm.hpp>
#endif
#if defined(LIBREMIDI_KDMAPI)
#include <libremidi/backends/kdmapi.hpp>
#endif
#if defined(LIBREMIDI_WINUWP)
#include <libremidi/backends/winuwp.hpp>
#endif
@@ -71,7 +77,11 @@
#include <libremidi/backends/network_ump.hpp>
#endif
namespace libremidi
#if defined(LIBREMIDI_ANDROID)
#include <libremidi/backends/android/android.hpp>
#endif
NAMESPACE_LIBREMIDI
{
// The order here will control the order of the API search in
// the constructor.
@@ -83,7 +93,7 @@ constexpr auto make_tl(unused, Args...)
namespace midi1
{
static constexpr auto available_backends = make_tl(
LIBREMIDI_STATIC constexpr auto available_backends = make_tl(
0
#if defined(LIBREMIDI_ALSA)
,
@@ -101,6 +111,10 @@ static constexpr auto available_backends = make_tl(
,
winmm_backend{}
#endif
#if defined(LIBREMIDI_KDMAPI)
,
kdmapi_backend{}
#endif
#if defined(LIBREMIDI_WINUWP)
,
winuwp_backend{}
@@ -124,6 +138,10 @@ static constexpr auto available_backends = make_tl(
#if defined(LIBREMIDI_NETWORK)
,
net::backend{}
#endif
#if defined(LIBREMIDI_ANDROID)
,
android::backend{}
#endif
,
dummy_backend{});
@@ -149,7 +167,7 @@ auto for_backend(libremidi::API api, F&& f)
namespace midi2
{
static constexpr auto available_backends = make_tl(
LIBREMIDI_STATIC constexpr auto available_backends = make_tl(
0
#if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP
,
@@ -167,7 +185,7 @@ static constexpr auto available_backends = make_tl(
,
net_ump::backend{}
#endif
#if defined(LIBREMIDI_JACK)
#if defined(LIBREMIDI_JACK) && defined(LIBREMIDI_HAS_JACK_GET_VERSION)
,
jack_ump::backend{}
#endif
@@ -216,7 +234,7 @@ auto for_backend(libremidi::API api, F&& f)
void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf)
{
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::get_if<typename T::midi_in_configuration>(&api_conf))
if (auto conf = get_if<typename T::midi_in_configuration>(&api_conf))
{
f(*conf);
return true;
@@ -231,7 +249,7 @@ void for_input_configuration(auto f, libremidi::input_api_configuration& api_con
void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf)
{
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::get_if<typename T::midi_out_configuration>(&api_conf))
if (auto conf = get_if<typename T::midi_out_configuration>(&api_conf))
{
f(*conf);
return true;
@@ -246,7 +264,7 @@ void for_output_configuration(auto f, libremidi::output_api_configuration& api_c
void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf)
{
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::get_if<typename T::midi_observer_configuration>(&api_conf))
if (auto conf = get_if<typename T::midi_observer_configuration>(&api_conf))
{
f(*conf);
return true;
+1 -1
View File
@@ -9,7 +9,7 @@
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>
namespace libremidi::alsa_raw
NAMESPACE_LIBREMIDI::alsa_raw
{
struct backend
{
@@ -10,12 +10,12 @@
#if __has_include(<poll.h>)
#include <poll.h>
namespace libremidi
NAMESPACE_LIBREMIDI
{
using poll_descriptors = pollfd;
}
#else
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct poll_descriptors
{
@@ -26,7 +26,7 @@ struct poll_descriptors
}
#endif
namespace libremidi
NAMESPACE_LIBREMIDI
{
/**
@@ -20,9 +20,7 @@
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
// Thanks Craig Stuart Sapp <craig@ccrma.stanford.edu>
namespace libremidi
{
namespace
NAMESPACE_LIBREMIDI
{
struct alsa_raw_port_id
{
@@ -47,12 +45,12 @@ inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept
static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102);
static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7);
static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3);
}
namespace alsa_raw
{
struct alsa_raw_port_info
{
// hw:1,2,0
std::string device;
std::string card_name;
std::string device_name;
@@ -98,7 +96,6 @@ struct enumerator
: handler{self}
, configuration{self.configuration}
{
}
// 1: is an input / output
@@ -121,8 +118,9 @@ struct enumerator
else if (status < 0 && status != -ENXIO)
{
handler.libremidi_handle_error(
configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub)
+ " : " + snd.strerror(status));
configuration,
"Cannot get rawmidi information: " + device_identifier(card, device, sub) + " : "
+ snd.strerror(status));
return status;
}
else
@@ -208,7 +206,8 @@ inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name)
if (status < 0)
{
self.handler.libremidi_handle_error(
self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status));
self.configuration,
"cannot open control for card"s + name + " : " + snd.strerror(status));
}
}
+15 -14
View File
@@ -10,7 +10,7 @@
#include <chrono>
#include <thread>
namespace libremidi::alsa_raw
NAMESPACE_LIBREMIDI::alsa_raw
{
class midi_in_impl
: public midi1::in_api
@@ -264,8 +264,7 @@ private:
using namespace std::literals;
libremidi_handle_error(
this->configuration,
"error starting MIDI input thread: "s + e.what());
this->configuration, "error starting MIDI input thread: "s + e.what());
return e.code();
}
return stdx::error{};
@@ -316,19 +315,21 @@ private:
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
configuration.manual_poll(
manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
}
else
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
configuration.manual_poll(
manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
}
}
@@ -342,7 +343,7 @@ private:
};
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <>
inline std::unique_ptr<midi_in_api> make<alsa_raw::midi_in_impl>(
@@ -8,7 +8,7 @@
#include <atomic>
#include <thread>
namespace libremidi::alsa_raw
NAMESPACE_LIBREMIDI::alsa_raw
{
class midi_out_impl final
: public midi1::out_api
@@ -44,8 +44,7 @@ public:
int status = snd.rawmidi.open(NULL, &midiport_, portname, mode);
if (status < 0)
{
libremidi_handle_error(
this->configuration, "cannot open device.");
libremidi_handle_error(this->configuration, "cannot open device.");
return from_errc(status);
}
return stdx::error{};
@@ -87,8 +86,7 @@ public:
{
if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0)
{
libremidi_handle_error(
this->configuration, "cannot write message.");
libremidi_handle_error(this->configuration, "cannot write message.");
return from_errc(err);
}
@@ -9,7 +9,7 @@
#endif
#include <libremidi/detail/observer.hpp>
namespace libremidi::alsa_raw
NAMESPACE_LIBREMIDI::alsa_raw
{
template <typename Enumerator>
class observer_impl_base
@@ -28,6 +28,21 @@ public:
explicit observer_impl_base(
observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
~observer_impl_base()
{
#if LIBREMIDI_HAS_UDEV
m_termination_event.notify();
if (m_thread.joinable())
m_thread.join();
#endif
}
// Must be called for child classes due to virtual function calls
void finish_init()
{
if (!configuration.has_callbacks())
return;
@@ -48,16 +63,6 @@ public:
#endif
}
~observer_impl_base()
{
#if LIBREMIDI_HAS_UDEV
m_termination_event.notify();
if (m_thread.joinable())
m_thread.join();
#endif
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
std::vector<libremidi::input_port> ret;
@@ -84,7 +89,6 @@ public:
return ret;
}
private:
#if LIBREMIDI_HAS_UDEV
void run()
{
@@ -98,6 +102,12 @@ private:
return;
}
// Check eventfd to see if we need to exit
if (m_fds[1].revents & POLLIN)
{
break;
}
// Check udev
if (m_fds[0].revents & POLLIN)
{
@@ -122,12 +132,6 @@ private:
m_fds[0].revents = 0;
}
// Check eventfd
if (m_fds[1].revents & POLLIN)
{
break;
}
// Check timer
if (m_fds[2].revents & POLLIN)
{
@@ -146,26 +150,35 @@ private:
-> std::conditional_t<Input, input_port, output_port>
{
#if LIBREMIDI_HAS_UDEV
auto [container, device, type] = get_udev_soundcard_info(m_udev, p.card);
auto [container, device, type, manufacturer, product, serial]
= get_udev_soundcard_info(m_udev, p.card);
#else
container_identifier container{};
device_identifier device{};
libremidi::port_information::port_type type{};
libremidi::transport_type type{};
std::string manufacturer;
std::string product;
std::string serial;
#endif
std::string display_name = p.subdevice_name;
if (display_name.empty())
display_name = p.device_name;
if (display_name.empty())
display_name = p.card_name;
if (display_name.empty())
display_name = product;
if (display_name.empty())
display_name = "unknown";
return {
{.client = 0,
{.api = get_current_api(),
.client = 0,
.container = container,
.device = device,
.port = raw_to_port_handle({p.card, p.dev, p.sub}),
.manufacturer = p.card_name,
.manufacturer = manufacturer,
.product = product,
.serial = serial,
.device_name = p.device_name,
.port_name = p.subdevice_name,
.display_name = std::move(display_name),
@@ -243,11 +256,16 @@ private:
};
}
namespace libremidi::alsa_raw
NAMESPACE_LIBREMIDI::alsa_raw
{
struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>
{
using alsa_raw::observer_impl_base<midi1_enumerator>::observer_impl_base;
observer_impl(observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf)
: observer_impl_base<alsa_raw::midi1_enumerator>{std::move(conf), std::move(apiconf)}
{
finish_init();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; }
};
}
+1 -1
View File
@@ -7,7 +7,7 @@
#include <string_view>
namespace libremidi::alsa_raw_ump
NAMESPACE_LIBREMIDI::alsa_raw_ump
{
struct backend
{
@@ -4,7 +4,7 @@
#include <chrono>
#include <functional>
namespace libremidi::alsa_raw_ump
NAMESPACE_LIBREMIDI::alsa_raw_ump
{
struct input_configuration
{
@@ -5,7 +5,7 @@
#include <cstdio>
namespace libremidi::alsa_raw_ump
NAMESPACE_LIBREMIDI::alsa_raw_ump
{
struct midi2_enumerator : alsa_raw::enumerator
{
@@ -10,7 +10,7 @@
#include <atomic>
#include <thread>
namespace libremidi::alsa_raw_ump
NAMESPACE_LIBREMIDI::alsa_raw_ump
{
class midi_in_impl
: public midi2::in_api
@@ -44,7 +44,8 @@ public:
SND_RAWMIDI_NONBLOCK; // fixme
if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0)
{
libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
libremidi_handle_error(
this->configuration, "alsa_raw_ump::ump::open_port: cannot open device.");
return from_errc(err);
}
@@ -143,7 +144,8 @@ public:
while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0)
{
const auto to_ns = [this] { return absolute_timestamp(); };
m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
m_processing.on_bytes(
{words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
return err;
}
@@ -166,7 +168,8 @@ public:
const auto to_ns = [ts] {
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000 + static_cast<int64_t>(ts.tv_nsec);
};
m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
m_processing.on_bytes(
{words, words + err / 4}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
return err;
}
@@ -257,8 +260,7 @@ private:
using namespace std::literals;
libremidi_handle_error(
this->configuration,
"error starting MIDI input thread: "s + e.what());
this->configuration, "error starting MIDI input thread: "s + e.what());
return e.code();
}
return stdx::error{};
@@ -309,19 +311,21 @@ private:
{
if (configuration.timestamps == timestamp_mode::NoTimestamp)
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
configuration.manual_poll(
manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer, fds);
}});
}
else
{
configuration.manual_poll(manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
configuration.manual_poll(
manual_poll_parameters{
.fds = {this->fds_.data(), this->fds_.size()},
.callback = [this](std::span<pollfd> fds) {
return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds);
}});
}
}
@@ -335,7 +339,7 @@ private:
};
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <>
inline std::unique_ptr<midi_in_api> make<alsa_raw_ump::midi_in_impl>(
@@ -13,7 +13,7 @@
#include <system_error>
#include <utility>
namespace libremidi::alsa_raw_ump
NAMESPACE_LIBREMIDI::alsa_raw_ump
{
class midi_out_impl final
: public midi2::out_api
@@ -2,13 +2,18 @@
#include <libremidi/backends/alsa_raw/observer.hpp>
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
namespace libremidi::alsa_raw_ump
NAMESPACE_LIBREMIDI::alsa_raw_ump
{
class observer_impl : public alsa_raw::observer_impl_base<midi2_enumerator>
{
using alsa_raw::observer_impl_base<midi2_enumerator>::observer_impl_base;
public:
observer_impl(
libremidi::observer_configuration&& conf, alsa_raw_ump::observer_configuration&& apiconf)
: observer_impl_base<alsa_raw_ump::midi2_enumerator>{std::move(conf), std::move(apiconf)}
{
finish_init();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; }
};
}
+2 -2
View File
@@ -20,7 +20,7 @@
#include <string_view>
namespace libremidi::alsa_seq
NAMESPACE_LIBREMIDI::alsa_seq
{
struct backend
@@ -42,7 +42,7 @@ struct backend
if (!snd.available || !snd.seq.available)
return false;
return ::access("/dev/snd/seq", F_OK) == 0;
return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0;
}
};
@@ -15,7 +15,7 @@ typedef struct snd_seq_addr
}
#endif
namespace libremidi::alsa_seq
NAMESPACE_LIBREMIDI::alsa_seq
{
// Possible parameters:
@@ -9,7 +9,7 @@
#include <optional>
#include <string>
namespace libremidi::alsa_seq
NAMESPACE_LIBREMIDI::alsa_seq
{
struct event_handle
{
@@ -42,8 +42,6 @@ struct event_handle
~event_handle() { snd.seq.free_event(ev); }
};
namespace
{
inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept
{
return (port << 32) + client;
@@ -56,6 +54,24 @@ inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcep
return {client, port};
}
inline void for_all_clients(
const libasound& snd, snd_seq_t* seq, const std::function<void(snd_seq_client_info_t&)>& func)
{
snd_seq_client_info_t* cinfo{};
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_t* pinfo{};
snd_seq_port_info_alloca(&pinfo);
snd.seq.client_info_set_client(cinfo, -1);
while (snd.seq.query_next_client(seq, cinfo) >= 0)
{
int client = snd.seq.client_info_get_client(cinfo);
if (client == 0)
continue;
func(*cinfo);
}
}
inline void for_all_ports(
const libasound& snd, snd_seq_t* seq,
const std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)>& func)
@@ -82,6 +98,21 @@ inline void for_all_ports(
}
}
inline void for_all_ports(
const libasound& snd, snd_seq_t* seq, int client,
const std::function<void(snd_seq_port_info_t&)>& func)
{
snd_seq_port_info_t* pinfo{};
snd_seq_port_info_alloca(&pinfo);
snd.seq.port_info_set_client(pinfo, client);
snd.seq.port_info_set_port(pinfo, -1);
while (snd.seq.query_next_port(seq, pinfo) >= 0)
{
func(*pinfo);
}
}
// This function is used to count or get the pinfo structure for a given port
// number.
inline unsigned int iterate_port_info(
@@ -125,7 +156,6 @@ inline unsigned int iterate_port_info(
return count;
return 0;
}
}
// A structure to hold variables related to the ALSA API
// implementation.
@@ -159,15 +189,15 @@ struct alsa_data
snd.seq.set_client_name(seq, configuration.client_name.data());
#if __has_include(<alsa/ump.h>)
if (snd.seq.ump.set_client_midi_version)
if (snd.seq.set_client_midi_version)
{
switch (configuration.midi_version)
{
case 1:
snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI);
snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI);
break;
case 2:
snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0);
snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0);
break;
}
}
@@ -272,8 +302,7 @@ struct alsa_data
// Make subscription
if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0)
{
self.libremidi_handle_error(
self.configuration, "ALSA error allocation port subscription.");
self.libremidi_handle_error(self.configuration, "ALSA error allocation port subscription.");
return err;
}
@@ -5,7 +5,7 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi::alsa_seq
NAMESPACE_LIBREMIDI::alsa_seq
{
struct dummy_processing
{
@@ -195,11 +195,6 @@ public:
return stdx::error{};
}
stdx::error set_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
stdx::error set_port_name(std::string_view portName) override
{
return alsa_data::set_port_name(portName);
@@ -487,13 +482,13 @@ public:
using poll_params = typename ConfigurationImpl::poll_parameters_type;
this->configuration.manual_poll(
poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) {
if constexpr (ConfigurationImpl::midi_version == 1)
return this->process_event(ev);
if constexpr (ConfigurationImpl::midi_version == 1)
return this->process_event(ev);
#if __has_include(<alsa/ump.h>)
else
return this->process_ump_event(ev);
else
return this->process_ump_event(ev);
#endif
}});
}});
return 0;
}
@@ -530,7 +525,7 @@ public:
};
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <>
inline std::unique_ptr<midi_in_api>
@@ -3,7 +3,7 @@
#include <libremidi/backends/alsa_seq/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi::alsa_seq
NAMESPACE_LIBREMIDI::alsa_seq
{
class midi_out_impl final
@@ -110,11 +110,6 @@ public:
return stdx::error{};
}
stdx::error set_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
stdx::error set_port_name(std::string_view portName) override
{
return alsa_data::set_port_name(portName);
+191 -76
View File
@@ -13,19 +13,26 @@
#include <map>
namespace libremidi::alsa_seq
NAMESPACE_LIBREMIDI::alsa_seq
{
struct client_info
{
std::string client_name;
int client_id{};
std::optional<int> card{};
};
struct port_info
{
std::string client_name;
std::string port_name;
int client{};
int port{};
bool isInput{};
bool isOutput{};
bool is_input{};
bool is_output{};
std::optional<int> card{};
libremidi::port_information::port_type type{};
libremidi::transport_type type{};
};
template <typename ConfigurationImpl>
@@ -93,12 +100,69 @@ public:
}
}
std::optional<port_info> get_info(int client, int port) const noexcept
std::optional<client_info>
get_client_info(int client, snd_seq_client_info_t& cinfo) const noexcept
{
client_info p;
p.client_id = client;
if (auto name = snd.seq.client_info_get_name(&cinfo))
p.client_name = name;
if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0)
p.card = card;
return p;
}
std::optional<port_info> get_info(
int client, int port, snd_seq_client_info_t& cinfo,
snd_seq_port_info_t& pinfo) const noexcept
{
const auto tp = snd.seq.port_info_get_type(&pinfo);
const auto cap = snd.seq.port_info_get_capability(&pinfo);
if ((cap & SND_SEQ_PORT_CAP_NO_EXPORT) != 0)
return std::nullopt;
port_info p;
p.client = client;
p.port = port;
bool ok = this->configuration.track_any;
static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER
| SND_SEQ_PORT_TYPE_APPLICATION;
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
{
p.type = libremidi::transport_type::hardware;
ok = true;
}
else if ((tp & virtual_port) && this->configuration.track_virtual)
{
p.type = libremidi::transport_type::software;
ok = true;
}
if (!ok)
return {};
if (auto name = snd.seq.client_info_get_name(&cinfo))
p.client_name = name;
if (auto name = snd.seq.port_info_get_name(&pinfo))
p.port_name = name;
if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0)
p.card = card;
p.is_input = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ);
p.is_output = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE);
return p;
}
std::optional<port_info> get_info(int client, int port) const noexcept
{
snd_seq_client_info_t* cinfo;
snd_seq_client_info_alloca(&cinfo);
if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0)
@@ -109,39 +173,7 @@ public:
if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0)
return std::nullopt;
const auto tp = snd.seq.port_info_get_type(pinfo);
bool ok = this->configuration.track_any;
static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER
| SND_SEQ_PORT_TYPE_APPLICATION;
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
{
p.type = libremidi::port_information::port_type::hardware;
ok = true;
}
else if ((tp & virtual_port) && this->configuration.track_virtual)
{
p.type = libremidi::port_information::port_type::software;
ok = true;
}
if (!ok)
return {};
if (auto name = snd.seq.client_info_get_name(cinfo))
p.client_name = name;
if (auto name = snd.seq.port_info_get_name(pinfo))
p.port_name = name;
if (int card = snd.seq.client_info_get_card(cinfo); card >= 0)
p.card = card;
const auto cap = snd.seq.port_info_get_capability(pinfo);
p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ);
p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE);
return p;
return get_info(client, port, *cinfo, *pinfo);
}
template <bool Input>
@@ -153,7 +185,10 @@ public:
container_identifier container{};
device_identifier device{};
libremidi::port_information::port_type type = p.type;
libremidi::transport_type type = p.type;
std::string manufacturer;
std::string product;
std::string serial;
#if LIBREMIDI_HAS_UDEV
if (p.card)
{
@@ -161,14 +196,20 @@ public:
container = res.container;
device = res.path;
type = res.type;
manufacturer = res.vendor;
product = res.product;
serial = res.serial;
}
#endif
return {
{.client = std::uintptr_t(this->seq),
{.api = get_current_api(),
.client = std::uintptr_t(this->seq),
.container = container,
.device = device,
.port = alsa_seq::seq_to_port_handle(p.client, p.port),
.manufacturer = "",
.manufacturer = manufacturer,
.product = product,
.serial = serial,
.device_name = p.client_name,
.port_name = p.port_name,
.display_name = p.port_name,
@@ -179,10 +220,10 @@ public:
{
alsa_seq::for_all_ports(
snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
register_port(clt, pt);
});
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
register_port(clt, pt);
});
}
libremidi::API get_current_api() const noexcept override
@@ -198,12 +239,12 @@ public:
std::vector<libremidi::input_port> ret;
alsa_seq::for_all_ports(
snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt))
if (p->isInput)
ret.push_back(to_port_info<true>(*p));
});
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt, client, port))
if (p->is_input)
ret.push_back(to_port_info<true>(*p));
});
return ret;
}
@@ -212,12 +253,12 @@ public:
std::vector<libremidi::output_port> ret;
alsa_seq::for_all_ports(
snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) {
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt))
if (p->isOutput)
ret.push_back(to_port_info<false>(*p));
});
int clt = snd.seq.client_info_get_client(&client);
int pt = snd.seq.port_info_get_port(&port);
if (auto p = get_info(clt, pt, client, port))
if (p->is_output)
ret.push_back(to_port_info<false>(*p));
});
return ret;
}
@@ -231,12 +272,12 @@ public:
return;
m_knownClients[{p.client, p.port}] = p;
if (p.isInput && configuration.input_added)
if (p.is_input && configuration.input_added)
{
configuration.input_added(to_port_info<true>(p));
}
if (p.isOutput && configuration.output_added)
if (p.is_output && configuration.output_added)
{
configuration.output_added(to_port_info<false>(p));
}
@@ -250,31 +291,55 @@ public:
auto p = it->second;
m_knownClients.erase(it);
if (p.isInput && configuration.input_removed)
if (p.is_input && configuration.input_removed)
{
configuration.input_removed(to_port_info<true>(p));
}
if (p.isOutput && configuration.output_removed)
if (p.is_output && configuration.output_removed)
{
configuration.output_removed(to_port_info<false>(p));
}
}
}
void handle_event(const snd_seq_event_t& ev)
void handle_event_direct(const snd_seq_event_t& ev)
{
switch (ev.type)
{
case SND_SEQ_EVENT_CLIENT_START: {
// TODO
break;
}
case SND_SEQ_EVENT_CLIENT_EXIT: {
// TODO
break;
}
case SND_SEQ_EVENT_CLIENT_CHANGE: {
// TODO
break;
}
#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS
case SND_SEQ_EVENT_UMP_EP_CHANGE: {
// TODO
break;
}
case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: {
// TODO
break;
}
#endif
case SND_SEQ_EVENT_PORT_START: {
register_port(ev.data.addr.client, ev.data.addr.port);
this->register_port(ev.data.addr.client, ev.data.addr.port);
break;
}
case SND_SEQ_EVENT_PORT_EXIT: {
unregister_port(ev.data.addr.client, ev.data.addr.port);
this->unregister_port(ev.data.addr.client, ev.data.addr.port);
break;
}
case SND_SEQ_EVENT_PORT_CHANGE:
// TODO
break;
default:
break;
}
@@ -309,38 +374,87 @@ public:
{
// Create relevant descriptors
auto& snd = alsa_data::snd;
// 1. Descriptor count
const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN);
descriptors_.resize(n + 1);
snd.seq.poll_descriptors(this->seq, descriptors_.data(), n, POLLIN);
descriptors_.back() = this->termination_event;
int total_descriptors = n;
total_descriptors++; // eventfd for terminating the thread
// 2. Create storage
descriptors.resize(total_descriptors);
// 3. Store descriptors
snd.seq.poll_descriptors(this->seq, descriptors.data(), n, POLLIN);
descriptors[n] = this->termination_event;
// Start the listening thread
thread = std::thread{[this] {
thread = std::thread{[this, n] {
auto& snd = alsa_data::snd;
const auto period
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
.count();
for (;;)
{
int err = poll(descriptors_.data(), descriptors_.size(), static_cast<int32_t>(period));
int err = poll(descriptors.data(), descriptors.size(), static_cast<int32_t>(period));
if (err >= 0)
{
// We got our stop-thread signal
if (descriptors_.back().revents & POLLIN)
if (descriptors[n].revents & POLLIN)
break;
// Put ALSA event in our queue
snd_seq_event_t* ev{};
event_handle handle{snd};
while (snd.seq.event_input(this->seq, &ev) >= 0)
{
handle.reset(ev);
this->handle_event(*ev);
this->handle_event_delayed(*ev);
}
// Process the events in a deferred way.
// This is because udev takes some milliseconds to populate its field after a
// port was added
auto tm = std::chrono::steady_clock::now();
for (auto it = queued_events.begin(); it != queued_events.end();)
{
if ((tm - it->second) >= this->configuration.poll_period)
{
this->handle_event_direct(it->first);
it = queued_events.erase(it);
}
else
{
break;
}
}
}
}
}};
}
void handle_event_delayed(const snd_seq_event_t& ev)
{
switch (ev.type)
{
case SND_SEQ_EVENT_CLIENT_START:
case SND_SEQ_EVENT_CLIENT_EXIT:
case SND_SEQ_EVENT_CLIENT_CHANGE:
#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS
case SND_SEQ_EVENT_UMP_EP_CHANGE:
case SND_SEQ_EVENT_UMP_BLOCK_CHANGE:
#endif
case SND_SEQ_EVENT_PORT_START:
case SND_SEQ_EVENT_PORT_EXIT:
case SND_SEQ_EVENT_PORT_CHANGE:
queued_events.emplace_back(ev, std::chrono::steady_clock::now());
break;
default:
break;
}
}
~observer_threaded()
{
termination_event.notify();
@@ -351,7 +465,8 @@ public:
eventfd_notifier termination_event{};
std::thread thread;
std::vector<pollfd> descriptors_;
std::vector<pollfd> descriptors;
std::vector<std::pair<snd_seq_event_t, std::chrono::steady_clock::time_point>> queued_events;
};
template <typename ConfigurationImpl>
@@ -363,16 +478,16 @@ public:
{
this->configuration.manual_poll(
poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) {
this->handle_event(v);
return 0;
}});
this->handle_event_direct(v);
return 0;
}});
}
~observer_manual() { this->configuration.stop_poll(this->vaddr); }
};
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <>
inline std::unique_ptr<observer_api>
@@ -9,7 +9,7 @@
#include <variant>
namespace libremidi::alsa_seq
NAMESPACE_LIBREMIDI::alsa_seq
{
struct shared_handler : public libremidi::shared_context
@@ -127,13 +127,13 @@ struct shared_handler : public libremidi::shared_context
{
case callback_added: {
auto [addr, cb]
= std::move(*std::get_if<libremidi::alsa_seq::poll_parameters>(&ev.payload));
= std::move(*get_if<libremidi::alsa_seq::poll_parameters>(&ev.payload));
addresses.push_back(addr);
callbacks.push_back(std::move(cb));
break;
}
case callback_removed:
auto addr = *std::get_if<snd_seq_addr_t>(&ev.payload);
auto addr = *get_if<snd_seq_addr_t>(&ev.payload);
if (auto index = index_of_address(addr); index >= 0)
{
addresses.erase(addresses.begin() + index);
@@ -179,7 +179,7 @@ struct shared_handler : public libremidi::shared_context
struct event
{
event_type type;
std::variant<libremidi::alsa_seq::poll_parameters, snd_seq_addr_t> payload;
libremidi_variant_alias::variant<libremidi::alsa_seq::poll_parameters, snd_seq_addr_t> payload;
};
snd_seq_t* client{};
+3 -3
View File
@@ -9,7 +9,7 @@
#include <string_view>
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <>
inline std::unique_ptr<observer_api>
@@ -42,7 +42,7 @@ inline std::unique_ptr<midi_in_api> make<
}
namespace libremidi::alsa_seq_ump
NAMESPACE_LIBREMIDI::alsa_seq_ump
{
struct backend
{
@@ -63,7 +63,7 @@ struct backend
if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available)
return false;
return ::access("/dev/snd/seq", F_OK) == 0;
return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0;
}
};
}
@@ -11,7 +11,7 @@ extern "C" {
typedef struct snd_seq_ump_event snd_seq_ump_event_t;
}
namespace libremidi::alsa_seq_ump
NAMESPACE_LIBREMIDI::alsa_seq_ump
{
struct poll_parameters
@@ -5,7 +5,7 @@
#include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/ump_stream.hpp>
namespace libremidi::alsa_seq_ump
NAMESPACE_LIBREMIDI::alsa_seq_ump
{
class midi_out_impl final
@@ -106,11 +106,6 @@ public:
return stdx::error{};
}
stdx::error set_client_name(std::string_view clientName) override
{
return alsa_data::set_client_name(clientName);
}
stdx::error set_port_name(std::string_view portName) override
{
return alsa_data::set_port_name(portName);
@@ -137,7 +132,7 @@ public:
static_assert(std::errc{0} == std::errc{});
return std::errc{};
};
segment_ump_stream(ump_stream, count, write_func, []() {});
segment_ump_stream(ump_stream, count, write_func, []() { });
snd.seq.drain_output(this->seq);
return stdx::error{};
@@ -0,0 +1,33 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/midi_in.hpp>
#include <libremidi/backends/android/midi_out.hpp>
#include <libremidi/backends/android/observer.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
struct backend
{
using midi_in = android::midi_in;
using midi_out = android::midi_out;
using midi_observer = android::observer;
using midi_in_configuration = android::input_configuration;
using midi_out_configuration = android::output_configuration;
using midi_observer_configuration = android::observer_configuration;
static const constexpr auto API = libremidi::API::ANDROID_AMIDI;
static const constexpr std::string_view name = "android";
static const constexpr std::string_view display_name = "Android MIDI API";
static bool available() noexcept
{
#if defined(__ANDROID__)
return true;
#else
return false;
#endif
}
};
}
}
@@ -0,0 +1,23 @@
#pragma once
#include <libremidi/config.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
struct input_configuration
{
std::string_view client_name = "libremidi client";
};
struct output_configuration
{
std::string_view client_name = "libremidi client";
};
struct observer_configuration
{
std::string_view client_name = "libremidi client";
};
}
}
@@ -0,0 +1,239 @@
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/backends/android/midi_in.hpp>
#include <libremidi/backends/android/midi_out.hpp>
NAMESPACE_LIBREMIDI::android
{
// Main source of knowledge is RtMidi implementation
// which was done by Yellow Labrador
JNIEnv* context::get_thread_env()
{
JNIEnv* env;
JavaVM* jvm;
jsize count;
if (JNI_GetCreatedJavaVMs(&jvm, 1, &count) != JNI_OK || count < 1)
{
LOGE("No JVM found");
return nullptr;
}
if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED)
{
if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK)
{
LOGE("Failed to attach thread");
return nullptr;
}
}
return env;
}
jobject context::get_context(JNIEnv* env)
{
auto activity_thread = env->FindClass("android/app/ActivityThread");
auto current_activity_thread = env->GetStaticMethodID(
activity_thread, "currentActivityThread", "()Landroid/app/ActivityThread;");
auto at = env->CallStaticObjectMethod(activity_thread, current_activity_thread);
if (!at)
{
LOGE("Failed to get ActivityThread");
return nullptr;
}
auto get_application
= env->GetMethodID(activity_thread, "getApplication", "()Landroid/app/Application;");
auto app = env->CallObjectMethod(at, get_application);
if (!app)
{
LOGE("Failed to get Application");
return nullptr;
}
return app;
}
jobject context::get_midi_manager(JNIEnv* env, jobject ctx)
{
auto context_class = env->FindClass("android/content/Context");
auto get_system_service = env->GetMethodID(
context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
auto midi_service_str = env->NewStringUTF("midi");
auto service = env->CallObjectMethod(ctx, get_system_service, midi_service_str);
env->DeleteLocalRef(midi_service_str);
return service;
}
void context::refresh_midi_devices(JNIEnv* env, jobject ctx, bool is_output)
{
cleanup_devices(env);
auto midi_service = get_midi_manager(env, ctx);
if (!midi_service)
return;
auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager");
auto get_devices_method
= env->GetMethodID(midi_mgr_class, "getDevices", "()[Landroid/media/midi/MidiDeviceInfo;");
auto device_array = (jobjectArray)env->CallObjectMethod(midi_service, get_devices_method);
auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo");
auto get_input_count = env->GetMethodID(device_info_class, "getInputPortCount", "()I");
auto get_output_count = env->GetMethodID(device_info_class, "getOutputPortCount", "()I");
jsize count = env->GetArrayLength(device_array);
for (jsize i = 0; i < count; ++i)
{
auto device_info = env->GetObjectArrayElement(device_array, i);
int port_count = is_output ? env->CallIntMethod(device_info, get_input_count)
: env->CallIntMethod(device_info, get_output_count);
if (port_count > 0)
{
midi_devices.push_back(env->NewGlobalRef(device_info));
}
env->DeleteLocalRef(device_info);
}
env->DeleteLocalRef(device_array);
env->DeleteLocalRef(midi_service);
}
std::string context::port_name(JNIEnv* env, unsigned int port_number)
{
if (port_number >= midi_devices.size())
{
LOGE("Invalid port number");
return "";
}
auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo");
auto get_props_method
= env->GetMethodID(device_info_class, "getProperties", "()Landroid/os/Bundle;");
auto bundle = env->CallObjectMethod(midi_devices[port_number], get_props_method);
auto bundle_class = env->FindClass("android/os/Bundle");
auto get_string_method
= env->GetMethodID(bundle_class, "getString", "(Ljava/lang/String;)Ljava/lang/String;");
auto key = env->NewStringUTF("name");
auto name_str = (jstring)env->CallObjectMethod(bundle, get_string_method, key);
std::string result;
if (name_str)
{
const char* name_chars = env->GetStringUTFChars(name_str, nullptr);
result = name_chars;
env->ReleaseStringUTFChars(name_str, name_chars);
env->DeleteLocalRef(name_str);
}
env->DeleteLocalRef(key);
env->DeleteLocalRef(bundle);
return result;
}
void context::cleanup_devices(JNIEnv* env)
{
for (jobject device : midi_devices)
{
env->DeleteGlobalRef(device);
}
midi_devices.clear();
}
void context::open_device(jobject device_info, void* target, bool is_output)
{
auto env = get_thread_env();
if (!env)
return;
auto ctx = get_context(env);
if (!ctx)
return;
auto midi_mgr = get_midi_manager(env, ctx);
if (!midi_mgr)
return;
// Check if we're on the main thread
auto looper_class = env->FindClass("android/os/Looper");
auto get_my_looper_method
= env->GetStaticMethodID(looper_class, "myLooper", "()Landroid/os/Looper;");
auto my_looper = env->CallStaticObjectMethod(looper_class, get_my_looper_method);
jobject handler = nullptr;
if (!my_looper)
{
LOGI("Not on a Looper thread, using main looper");
auto get_main_looper_method
= env->GetStaticMethodID(looper_class, "getMainLooper", "()Landroid/os/Looper;");
auto main_looper = env->CallStaticObjectMethod(looper_class, get_main_looper_method);
auto handler_class = env->FindClass("android/os/Handler");
auto handler_ctor = env->GetMethodID(handler_class, "<init>", "(Landroid/os/Looper;)V");
handler = env->NewObject(handler_class, handler_ctor, main_looper);
env->DeleteLocalRef(main_looper);
}
// Create the callback listener
auto callback_class = env->FindClass("dev/celtera/libremidi/MidiDeviceCallback");
if (!callback_class)
{
LOGE("MidiDeviceCallback class not found - ensure the Java class is loaded");
if (handler)
env->DeleteLocalRef(handler);
return;
}
auto callback_ctor = env->GetMethodID(callback_class, "<init>", "(JZ)V");
auto callback
= env->NewObject(callback_class, callback_ctor, (jlong)target, (jboolean)is_output);
// Open the device
auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager");
auto open_device_method = env->GetMethodID(
midi_mgr_class, "openDevice",
"(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/"
"MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V");
env->CallVoidMethod(midi_mgr, open_device_method, device_info, callback, handler);
env->DeleteLocalRef(callback);
if (handler)
env->DeleteLocalRef(handler);
}
extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened(
JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output)
{
if (!midi_device || target_ptr == 0)
{
LOGE("Invalid device or target pointer in callback");
return;
}
AMidiDevice* amidi_device = nullptr;
AMidiDevice_fromJava(env, midi_device, &amidi_device);
if (!amidi_device)
{
LOGE("Failed to convert Java MIDI device to AMidiDevice");
return;
}
if (is_output)
{
midi_out::open_callback(reinterpret_cast<midi_out*>(target_ptr), amidi_device);
}
else
{
midi_in::open_callback(reinterpret_cast<midi_in*>(target_ptr), amidi_device);
}
}
}
@@ -0,0 +1,40 @@
#pragma once
#include <libremidi/error.hpp>
#include <amidi/AMidi.h>
#include <android/log.h>
#include <jni.h>
#include <pthread.h>
#include <string>
#include <vector>
#define LOG_TAG "libremidi"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
NAMESPACE_LIBREMIDI
{
namespace android
{
struct context
{
static inline std::string client_name;
static inline std::vector<jobject> midi_devices;
static JNIEnv* get_thread_env();
static jobject get_context(JNIEnv* env);
static jobject get_midi_manager(JNIEnv* env, jobject context);
static void refresh_midi_devices(JNIEnv* env, jobject context, bool is_output);
static void open_device(jobject device_info, void* target, bool is_output);
static std::string port_name(JNIEnv* env, unsigned int port_number);
static void cleanup_devices(JNIEnv* env);
};
extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened(
JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output);
}
}
@@ -0,0 +1,165 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <atomic>
#include <chrono>
#include <thread>
NAMESPACE_LIBREMIDI
{
namespace android
{
class midi_in
: public midi1::in_api
, public error_handler
{
public:
explicit midi_in(
libremidi::input_configuration&& conf, libremidi::android::input_configuration aconf)
: configuration{std::move(conf)}
{
if (!context::client_name.empty() && context::client_name != aconf.client_name)
{
libremidi_handle_error(
configuration, "Android backend only supports one client name per process");
return;
}
context::client_name = std::string(aconf.client_name);
client_open_ = stdx::error{};
}
~midi_in() override { close_port(); }
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::ANDROID_AMIDI;
}
stdx::error open_port(const input_port& port, std::string_view /*name*/) override
{
if (is_port_open())
{
libremidi_handle_error(configuration, "midi_in::open_port: a port is already open");
return std::errc{};
}
const unsigned int port_number = port.port;
auto env = context::get_thread_env();
if (!env)
{
libremidi_handle_error(configuration, "midi_in::open_port: failed to get JNI environment");
return std::errc{};
}
auto ctx = context::get_context(env);
if (!ctx)
{
libremidi_handle_error(configuration, "midi_in::open_port: failed to get Android context");
return std::errc{};
}
context::refresh_midi_devices(env, ctx, false);
if (port_number >= context::midi_devices.size())
{
libremidi_handle_error(configuration, "midi_in::open_port: invalid port number");
return std::errc{};
}
port_open = true;
running = true;
context::open_device(context::midi_devices[port_number], this, false);
return stdx::error{};
}
stdx::error close_port() override
{
if (!is_port_open())
return std::errc{};
running = false;
if (poll_thread.joinable())
{
poll_thread.join();
}
if (midi_output_port)
{
AMidiOutputPort_close(midi_output_port);
midi_output_port = nullptr;
}
if (receive_device)
{
AMidiDevice_release(receive_device);
receive_device = nullptr;
}
port_open = false;
return stdx::error{};
}
bool is_port_open() const noexcept { return port_open; }
void start_midi_thread() { poll_thread = std::thread(&midi_in::poll_midi, this); }
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
void poll_midi()
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = false,
.absolute_is_monotonic = false,
.has_samples = false,
};
uint8_t buffer[1024];
int32_t op_code;
int64_t timestamp;
size_t num_bytes;
while (running)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
ssize_t num_messages = AMidiOutputPort_receive(
midi_output_port, &op_code, buffer, sizeof(buffer), &num_bytes, &timestamp);
if (num_messages < 0)
{
LOGE("Error receiving MIDI data: %d", (int)num_messages);
continue;
}
if (num_messages > 0 && num_bytes > 0)
{
const auto to_ns = [=] { return timestamp; };
m_processing.on_bytes_multi(
{buffer, buffer + num_bytes},
m_processing.timestamp<timestamp_info>(to_ns, timestamp));
}
}
}
static void open_callback(midi_in* self, AMidiDevice* device)
{
self->receive_device = device;
AMidiOutputPort_open(device, 0, &self->midi_output_port);
self->start_midi_thread();
}
private:
libremidi::input_configuration configuration;
AMidiDevice* receive_device = nullptr;
AMidiOutputPort* midi_output_port = nullptr;
std::thread poll_thread;
std::atomic<bool> port_open{false};
std::atomic<bool> running{false};
midi1::input_state_machine m_processing{this->configuration};
};
}
}
@@ -0,0 +1,126 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
class midi_out final
: public midi1::out_api
, public error_handler
{
public:
explicit midi_out(
libremidi::output_configuration&& conf, libremidi::android::output_configuration aconf)
: configuration{std::move(conf)}
{
if (!context::client_name.empty() && context::client_name != aconf.client_name)
{
libremidi_handle_error(
configuration, "Android backend only supports one client name per process");
return;
}
context::client_name = std::string(aconf.client_name);
client_open_ = stdx::error{};
}
~midi_out() override { close_port(); }
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::ANDROID_AMIDI;
}
stdx::error open_port(const output_port& port, std::string_view /*name*/) override
{
if (is_port_open())
{
libremidi_handle_error(configuration, "midi_out::open_port: a port is already open");
return std::errc{};
}
const unsigned int port_number = port.port;
auto env = context::get_thread_env();
if (!env)
{
libremidi_handle_error(configuration, "midi_out::open_port: failed to get JNI environment");
return std::errc{};
}
auto ctx = context::get_context(env);
if (!ctx)
{
libremidi_handle_error(configuration, "midi_out::open_port: failed to get Android context");
return std::errc{};
}
context::refresh_midi_devices(env, ctx, true);
if (port_number >= context::midi_devices.size())
{
libremidi_handle_error(configuration, "midi_out::open_port: invalid port number");
return std::errc{};
}
port_open = true;
context::open_device(context::midi_devices[port_number], this, true);
return stdx::error{};
}
stdx::error close_port() override
{
if (!is_port_open())
return std::errc{};
if (midi_input_port)
{
AMidiInputPort_close(midi_input_port);
midi_input_port = nullptr;
}
if (send_device)
{
AMidiDevice_release(send_device);
send_device = nullptr;
}
port_open = false;
return stdx::error{};
}
bool is_port_open() const noexcept { return port_open; }
stdx::error send_message(const unsigned char* message, size_t size) override
{
if (!is_port_open() || !midi_input_port)
{
libremidi_handle_error(configuration, "midi_out::send_message: port is not open");
return std::errc{};
}
ssize_t result = AMidiInputPort_send(midi_input_port, message, size);
if (result < 0)
{
libremidi_handle_error(configuration, "midi_out::send_message: failed to send MIDI data");
return std::errc{};
}
return stdx::error{};
}
static void open_callback(midi_out* self, AMidiDevice* device)
{
self->send_device = device;
AMidiInputPort_open(device, 0, &self->midi_input_port);
}
private:
libremidi::output_configuration configuration;
AMidiDevice* send_device = nullptr;
AMidiInputPort* midi_input_port = nullptr;
bool port_open = false;
};
}
}
@@ -0,0 +1,93 @@
#pragma once
#include <libremidi/backends/android/config.hpp>
#include <libremidi/backends/android/helpers.hpp>
#include <libremidi/detail/observer.hpp>
NAMESPACE_LIBREMIDI
{
namespace android
{
class observer final
: public libremidi::observer_api
, public error_handler
{
public:
explicit observer(
libremidi::observer_configuration&& conf, libremidi::android::observer_configuration aconf)
: configuration{std::move(conf)}
{
if (!context::client_name.empty() && context::client_name != aconf.client_name)
{
LOGW("Android backend only supports one client name per process");
}
context::client_name = std::string(aconf.client_name);
}
~observer()
{
auto env = context::get_thread_env();
if (env)
{
context::cleanup_devices(env);
}
}
libremidi::API get_current_api() const noexcept override
{
return libremidi::API::ANDROID_AMIDI;
}
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
auto env = context::get_thread_env();
if (!env)
return {};
auto ctx = context::get_context(env);
if (!ctx)
return {};
context::refresh_midi_devices(env, ctx, false);
std::vector<libremidi::input_port> ports;
for (size_t i = 0; i < context::midi_devices.size(); ++i)
{
libremidi::input_port port;
port.api = libremidi::API::ANDROID_AMIDI;
port.port_name = context::port_name(env, i);
port.port = i;
ports.push_back(std::move(port));
}
return ports;
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
auto env = context::get_thread_env();
if (!env)
return {};
auto ctx = context::get_context(env);
if (!ctx)
return {};
context::refresh_midi_devices(env, ctx, true);
std::vector<libremidi::output_port> ports;
for (size_t i = 0; i < context::midi_devices.size(); ++i)
{
libremidi::output_port port;
port.port_name = context::port_name(env, i);
port.port = i;
ports.push_back(std::move(port));
}
return ports;
}
private:
libremidi::observer_configuration configuration;
};
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
#include <string_view>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct core_backend
{
@@ -18,7 +18,7 @@ typedef MIDIObjectRef MIDIClientRef;
using MIDIClientRef = uint32_t;
using MIDIObjectRef = uint32_t;
#endif
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct coremidi_input_configuration
@@ -3,7 +3,7 @@
#include <CoreMIDI/CoreMIDI.h>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct coremidi_error_domain : public stdx::error_domain
{
+10 -14
View File
@@ -1,9 +1,9 @@
#pragma once
#include <libremidi/backends/coremidi/error_domain.hpp>
#include <libremidi/detail/memory.hpp>
#include <libremidi/detail/midi_api.hpp>
#include <libremidi/error_handler.hpp>
#include <libremidi/input_configuration.hpp>
#include <libremidi/backends/coremidi/error_domain.hpp>
#include <CoreMIDI/CoreMIDI.h>
#include <CoreServices/CoreServices.h>
@@ -21,13 +21,12 @@
#define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime
#endif
namespace libremidi
NAMESPACE_LIBREMIDI
{
using CFString_handle = unique_handle<const __CFString, CFRelease>;
using CFStringMutable_handle = unique_handle<__CFString, CFRelease>;
namespace
{
static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept
LIBREMIDI_STATIC std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept
{
CFStringRef res;
if (MIDIObjectGetStringProperty(object, property, &res) || !res)
@@ -39,14 +38,14 @@ static inline std::string get_string_property(MIDIObjectRef object, CFStringRef
return name;
}
static inline int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept
LIBREMIDI_STATIC int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept
{
SInt32 res;
MIDIObjectGetIntegerProperty(object, property, &res);
return res;
}
static inline CFString_handle toCFString(std::string_view str) noexcept
LIBREMIDI_STATIC CFString_handle toCFString(std::string_view str) noexcept
{
return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)};
}
@@ -72,12 +71,12 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)
}
#endif
static inline auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name)
LIBREMIDI_STATIC auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name)
{
CFStringRef str = nullptr;
MIDIObjectGetStringProperty(prop, name, &str);
return CFString_handle{str};
};
}
// This function was submitted by Douglas Casey Tucker and apparently
// derived largely from PortMidi.
@@ -220,22 +219,19 @@ locate_object(auto& self, const port_information& info, MIDIObjectType requested
auto ret = MIDIObjectFindByUniqueID(uid, &object, &type);
if (ret != noErr)
{
self.libremidi_handle_error(
self.configuration, "cannot find port: " + info.port_name);
self.libremidi_handle_error(self.configuration, "cannot find port: " + info.port_name);
return 0;
}
if (type != requested_type || object == 0)
{
self.libremidi_handle_error(
self.configuration, "invalid object: " + info.port_name + " : "
+ std::to_string(object));
self.configuration, "invalid object: " + info.port_name + " : " + std::to_string(object));
return 0;
}
return object;
}
}
// A structure to hold variables related to the CoreMIDI API
// implementation.
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_in_core final
: public midi1::in_api
@@ -25,8 +25,7 @@ public:
if (auto result = init_client(configuration); result != noErr)
{
libremidi_handle_error(
this->configuration,
"error creating MIDI client object: " + std::to_string(result));
this->configuration, "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return;
}
@@ -64,8 +63,7 @@ public:
{
close_client(*this);
libremidi_handle_error(
this->configuration, "error creating macOS MIDI input port: "
+ std::to_string(result));
this->configuration, "error creating macOS MIDI input port: " + std::to_string(result));
return from_osstatus(result);
}
@@ -74,8 +72,7 @@ public:
{
MIDIPortDispose(port);
close_client(*this);
libremidi_handle_error(
this->configuration, "error connecting macOS MIDI input port.");
libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port.");
return from_osstatus(result);
}
@@ -105,10 +102,7 @@ public:
return stdx::error{};
}
stdx::error close_port() override
{
return coremidi_data::close_port();
}
stdx::error close_port() override { return coremidi_data::close_port(); }
timestamp absolute_timestamp() const noexcept override
{
@@ -3,7 +3,7 @@
#include <libremidi/backends/coremidi/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_out_core final
: public midi1::out_api
@@ -24,8 +24,7 @@ public:
if (auto result = init_client(configuration); result != noErr)
{
libremidi_handle_error(
this->configuration,
"error creating MIDI client object: " + std::to_string(result));
this->configuration, "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return;
}
@@ -60,8 +59,7 @@ public:
if (result != noErr)
{
close_client(*this);
libremidi_handle_error(
this->configuration, "error creating macOS MIDI output port.");
libremidi_handle_error(this->configuration, "error creating macOS MIDI output port.");
return from_osstatus(result);
}
@@ -80,9 +78,7 @@ public:
if (result != noErr)
{
libremidi_handle_error(
this->configuration,
"error creating macOS virtual MIDI source.");
libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source.");
return from_osstatus(result);
}
@@ -92,10 +88,7 @@ public:
return stdx::error{};
}
stdx::error close_port() override
{
return coremidi_data::close_port();
}
stdx::error close_port() override { return coremidi_data::close_port(); }
stdx::error send_message(const unsigned char* message, size_t size) override
{
@@ -118,8 +111,8 @@ public:
const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME();
const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes;
Byte buffer[bufsize + 16]; // pad for other struct members
ByteCount listSize = sizeof(buffer);
Byte* buffer = (Byte*)alloca(bufsize + 16); // pad for other struct members
ByteCount listSize = bufsize + 16;
MIDIPacketList* packetList = (MIDIPacketList*)buffer;
ByteCount remainingBytes = nBytes;
@@ -137,8 +130,7 @@ public:
if (!packet)
{
libremidi_handle_error(
this->configuration, "could not allocate packet list");
libremidi_handle_error(this->configuration, "could not allocate packet list");
return std::errc::message_size;
}
@@ -163,9 +155,7 @@ public:
auto result = MIDISend(this->port, this->destinationId, packetList);
if (result != noErr)
{
libremidi_handle_warning(
this->configuration,
"error sending MIDI message to port.");
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
return std::errc::io_error;
}
}
@@ -7,7 +7,7 @@
#include <bit>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class observer_core
: public observer_api
@@ -22,6 +22,10 @@ public:
explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
void finish_init()
{
if (configuration.client_name.empty())
configuration.client_name = "libremidi observer";
@@ -32,15 +36,13 @@ public:
auto result = MIDIClientCreate(
toCFString(configuration.client_name).get(),
+[](const MIDINotification* message, void* ctx) {
((observer_core*)ctx)->notify(message);
},
this, &client);
((observer_core*)ctx)->notify(message);
}, this, &client);
if (result != noErr)
{
libremidi_handle_error(
this->configuration,
"error creating MIDI client object: " + std::to_string(result));
this->configuration, "error creating MIDI client object: " + std::to_string(result));
return;
}
@@ -79,7 +81,7 @@ public:
return {};
// Get the MIDI device from the entity
libremidi::port_information::port_type type{};
libremidi::transport_type type{};
libremidi::container_identifier usb_location_id{};
libremidi::device_identifier usb_vendor_product{};
{
@@ -88,11 +90,11 @@ public:
{
if (device)
{
using enum libremidi::port_information::port_type;
using enum libremidi::transport_type;
SInt32 devid_res{};
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr)
{
type = (libremidi::port_information::port_type)(hardware | usb);
type = (libremidi::transport_type)(hardware | usb);
usb_location_id = (uint64_t)devid_res;
}
@@ -100,25 +102,26 @@ public:
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res)
== noErr)
{
type = (libremidi::port_information::port_type)(hardware | usb);
type = (libremidi::transport_type)(hardware | usb);
usb_vendor_product = (uint64_t)vendor_res;
}
const auto driver = get_string_property(device, kMIDIPropertyDriverOwner);
if (driver == "com.apple.AppleMIDIUSBDriver")
type = (libremidi::port_information::port_type)(hardware | usb);
type = (libremidi::transport_type)(hardware | usb);
if (driver == "com.apple.AppleMIDIBluetoothDriver")
type = (libremidi::port_information::port_type)(hardware | bluetooth);
type = (libremidi::transport_type)(hardware | bluetooth);
if (driver == "com.apple.AppleMIDIIACDriver")
type = (libremidi::port_information::port_type)(software);
type = (libremidi::transport_type)(software);
if (driver == "com.apple.AppleMIDIRTPDriver")
type = (libremidi::port_information::port_type)(network);
type = (libremidi::transport_type)(network);
}
}
}
return std::conditional_t<Input, input_port, output_port>{
{.client = (std::uintptr_t)this->client,
{.api = get_current_api(),
.client = (std::uintptr_t)this->client,
.container = usb_location_id,
.device = usb_vendor_product,
.port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),
@@ -215,3 +218,17 @@ private:
MIDIClientRef client{};
};
}
NAMESPACE_LIBREMIDI::coremidi
{
struct observer_impl : observer_core
{
observer_impl(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)
: observer_core{std::move(conf), std::move(apiconf)}
{
finish_init();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
};
}
+1 -1
View File
@@ -5,7 +5,7 @@
#include <string_view>
namespace libremidi::coremidi_ump
NAMESPACE_LIBREMIDI::coremidi_ump
{
struct backend
{
@@ -1,7 +1,7 @@
#pragma once
#include <libremidi/backends/coremidi/config.hpp>
namespace libremidi::coremidi_ump
NAMESPACE_LIBREMIDI::coremidi_ump
{
struct input_configuration
{
@@ -4,7 +4,7 @@
#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
@@ -26,8 +26,7 @@ public:
if (auto result = init_client(configuration); result != noErr)
{
libremidi_handle_error(
this->configuration,
"error creating MIDI client object: " + std::to_string(result));
this->configuration, "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return;
}
@@ -69,8 +68,7 @@ public:
{
close_client(*this);
libremidi_handle_error(
this->configuration, "error creating macOS MIDI input port: "
+ std::to_string(result));
this->configuration, "error creating macOS MIDI input port: " + std::to_string(result));
return from_osstatus(result);
}
@@ -79,8 +77,7 @@ public:
{
MIDIPortDispose(port);
close_client(*this);
libremidi_handle_error(
this->configuration, "error connecting macOS MIDI input port.");
libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port.");
return from_osstatus(result);
}
@@ -113,10 +110,7 @@ public:
return stdx::error{};
}
stdx::error close_port() override
{
return coremidi_data::close_port();
}
stdx::error close_port() override { return coremidi_data::close_port(); }
timestamp absolute_timestamp() const noexcept override
{
@@ -5,7 +5,7 @@
#include <libremidi/detail/midi_out.hpp>
#include <libremidi/detail/ump_stream.hpp>
namespace libremidi::coremidi_ump
NAMESPACE_LIBREMIDI::coremidi_ump
{
class midi_out_impl final
: public midi2::out_api
@@ -27,8 +27,7 @@ public:
if (auto result = init_client(configuration); result != noErr)
{
libremidi_handle_error(
this->configuration,
"error creating MIDI client object: " + std::to_string(result));
this->configuration, "error creating MIDI client object: " + std::to_string(result));
client_open_ = from_osstatus(result);
return;
}
@@ -63,8 +62,7 @@ public:
if (result != noErr)
{
close_client(*this);
libremidi_handle_error(
this->configuration, "error creating macOS MIDI output port.");
libremidi_handle_error(this->configuration, "error creating macOS MIDI output port.");
return from_osstatus(result);
}
@@ -84,9 +82,7 @@ public:
if (result != noErr)
{
this->endpoint = 0;
libremidi_handle_error(
this->configuration,
"error creating macOS virtual MIDI source.");
libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source.");
return from_osstatus(result);
}
@@ -94,10 +90,7 @@ public:
return stdx::error{};
}
stdx::error close_port() override
{
return coremidi_data::close_port();
}
stdx::error close_port() override { return coremidi_data::close_port(); }
stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override
{
@@ -142,9 +135,7 @@ public:
auto result = MIDISendEventList(this->port, this->destinationId, eventList);
if (result != noErr)
{
libremidi_handle_warning(
this->configuration,
"error sending MIDI message to port.");
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
return std::errc::io_error;
}
}
@@ -1,17 +1,20 @@
#pragma once
#include <libremidi/backends/coremidi_ump/config.hpp>
#include <libremidi/backends/coremidi/observer.hpp>
#include <libremidi/backends/coremidi_ump/config.hpp>
namespace libremidi::coremidi_ump
NAMESPACE_LIBREMIDI::coremidi_ump
{
class observer_impl final : public libremidi::observer_core
{
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}}
explicit observer_impl(
libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf)
: observer_core{
std::move(conf),
coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}}
{
finish_init();
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; }
+5 -25
View File
@@ -7,7 +7,7 @@
#include <string_view>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class observer_dummy : public observer_api
{
@@ -40,19 +40,9 @@ public:
{
return stdx::error{};
}
stdx::error open_virtual_port(std::string_view /*portName*/) override
{
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{};
}
stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; }
timestamp absolute_timestamp() const noexcept override { return 0; }
};
@@ -72,20 +62,10 @@ public:
{
return stdx::error{};
}
stdx::error open_virtual_port(std::string_view /*portName*/) override
{
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{};
}
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{};
+1 -1
View File
@@ -9,7 +9,7 @@
#include <string_view>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct emscripten_backend
@@ -1,7 +1,7 @@
#pragma once
#include <libremidi/config.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct emscripten_input_configuration
@@ -3,7 +3,7 @@
#include <string>
namespace libremidi
NAMESPACE_LIBREMIDI
{
namespace webmidi_helpers
{
@@ -16,7 +16,7 @@ EMSCRIPTEN_KEEPALIVE
void libremidi_devices_input(int port, double timestamp, int len, char* bytes);
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
namespace webmidi_helpers
{
@@ -37,16 +37,25 @@ public:
int input_count() const noexcept
{
if (!available())
return 0;
return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;);
}
int output_count() const noexcept
{
if (!available())
return 0;
return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;);
}
void load_current_infos() noexcept
{
if (!available())
return;
#define get_js_string(variable_to_read, ...) \
(char*)EM_ASM_INT( \
{ \
@@ -206,6 +215,9 @@ public:
stdx::error send_message(int port_index, const char* bytes, int len)
{
if (!available())
return std::errc::operation_not_supported;
const auto& id = m_current_outputs[port_index].id;
EM_ASM(
{
@@ -216,6 +228,8 @@ public:
output.send(Array.from(bytes));
},
bytes, len, id.c_str());
return stdx::error{};
}
const std::vector<device_information>& inputs() const noexcept { return m_current_inputs; }
@@ -248,6 +262,9 @@ private:
void start_stream(int port_index)
{
if (!available())
return;
// Isn't life great...
// https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays
const auto& id = m_current_inputs[port_index].id;
@@ -282,6 +299,9 @@ private:
void stop_stream(int port_index)
{
if (!available())
return;
const auto& id = m_current_inputs[port_index].id;
EM_ASM(const id = UTF8ToString($1);
@@ -5,7 +5,7 @@
#include <chrono>
namespace libremidi
NAMESPACE_LIBREMIDI
{
LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten(
input_configuration&& conf, emscripten_input_configuration&& apiconf)
@@ -32,8 +32,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std::
if (portNumber < 0 || portNumber >= midi.input_count())
{
libremidi_handle_error(
this->configuration, "no MIDI output sources found.");
libremidi_handle_error(this->configuration, "no MIDI output sources found.");
return std::errc::invalid_argument;
}
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_in_emscripten final
: public midi1::in_api
@@ -2,7 +2,7 @@
#include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/midi_out.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten(
output_configuration&& conf, emscripten_output_configuration&& apiconf)
@@ -29,8 +29,7 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std:
if (portNumber >= midi.output_count())
{
libremidi_handle_error(
this->configuration, "no MIDI output sources found.");
libremidi_handle_error(this->configuration, "no MIDI output sources found.");
return std::errc::invalid_argument;
}
@@ -38,16 +37,19 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std:
return stdx::error{};
}
LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
LIBREMIDI_INLINE stdx::error
midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
{
return open_port(p.port, nm);
}
LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() {
LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port()
{
return stdx::error{};
}
LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size)
LIBREMIDI_INLINE stdx::error
midi_out_emscripten::send_message(const unsigned char* message, size_t size)
{
if (m_portNumber < 0)
libremidi_handle_error(
@@ -3,7 +3,7 @@
#include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_out_emscripten final
: public midi1::out_api
@@ -4,7 +4,7 @@
#include <cassert>
namespace libremidi
NAMESPACE_LIBREMIDI
{
LIBREMIDI_INLINE observer_emscripten::observer_emscripten(
observer_configuration&& conf, emscripten_observer_configuration&& apiconf)
@@ -42,7 +42,8 @@ static auto to_port_info(int index, const webmidi_helpers::device_information& d
-> std::conditional_t<Input, input_port, output_port>
{
return {
{.client = 0,
{.api = libremidi::API::WEBMIDI,
.client = 0,
.port = (uint64_t)index,
.manufacturer = "",
.device_name = "",
@@ -5,7 +5,7 @@
#include <vector>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class observer_emscripten final : public observer_api
{
+1 -1
View File
@@ -13,7 +13,7 @@
//
// *********************************************************************//
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct jack_backend
{
+1 -1
View File
@@ -11,7 +11,7 @@ typedef uint32_t jack_nframes_t;
typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg);
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
using jack_callback_function = std::function<void(int nframes)>;
struct jack_callback
@@ -11,7 +11,7 @@
#include <jack/ringbuffer.h>
#endif
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct jack_error_domain : public stdx::error_domain
{
@@ -34,36 +34,36 @@ public:
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{})
if (status == jack_status_t{})
return "Success";
if(status & JackInvalidOption)
if (status & JackInvalidOption)
return "The operation contained an invalid or unsupported option";
if(status & JackServerFailed)
if (status & JackServerFailed)
return "Unable to connect to the JACK server";
if(status & JackServerError)
if (status & JackServerError)
return "Communication error with the JACK server";
if(status & JackNoSuchClient)
if (status & JackNoSuchClient)
return "Requested client does not exist";
if(status & JackLoadFailure)
if (status & JackLoadFailure)
return "Unable to load internal client";
if(status & JackInitFailure)
if (status & JackInitFailure)
return "Unable to initialize client";
if(status & JackShmFailure)
if (status & JackShmFailure)
return "Unable to access shared memory";
if(status & JackVersionError)
if (status & JackVersionError)
return "Client's protocol version does not match";
if(status & JackBackendError)
if (status & JackBackendError)
return "Backend error";
if(status & JackClientZombie)
if (status & JackClientZombie)
return "Client zombified failure";
if(status & JackFailure)
if (status & JackFailure)
return "Failure";
if(status & JackNameNotUnique)
if (status & JackNameNotUnique)
return "The desired client name was not unique";
// Can't happen in libremidi as we set JackNoStartServer
if(status & JackServerStarted)
if (status & JackServerStarted)
return "Server was started";
return "Unknown JACK status code";
+27 -28
View File
@@ -9,7 +9,7 @@
#include <cstring>
#include <memory>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct jack_client
{
@@ -41,21 +41,22 @@ struct jack_client
}
}
template <bool Input>
template <bool Input, libremidi::API Api>
static auto to_port_info(jack_client_t* client, jack_port_t* port)
-> std::conditional_t<Input, input_port, output_port>
{
return {{
.api = Api,
.client = reinterpret_cast<std::uintptr_t>(client),
.port = 0,
.manufacturer = "",
.device_name = "",
.device_name = jack_get_client_name(client),
.port_name = jack_port_name(port),
.display_name = get_port_display_name(port),
}};
}
template <bool Input>
template <bool Input, libremidi::API Api>
static auto get_ports(
jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags,
bool midi2) noexcept -> std::vector<std::conditional_t<Input, input_port, output_port>>
@@ -78,7 +79,7 @@ struct jack_client
if (port)
{
if (bool(midi2) == bool(jack_port_flags(port) & 0x20))
ret.push_back(to_port_info<Input>(client, port));
ret.push_back(to_port_info<Input, Api>(client, port));
}
i++;
}
@@ -137,13 +138,13 @@ struct jack_helpers : jack_client
configuration.set_process_func(
{.token = this_instance,
.callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {
if (auto pt = p.lock())
if (pt->load())
self.process(nf);
if (auto pt = p.lock())
if (pt->load())
self.process(nf);
self.thread_lock.check_client_released();
return 0;
}});
self.thread_lock.check_client_released();
return 0;
}});
this->client = configuration.context;
return jack_status_t{};
@@ -155,26 +156,25 @@ struct jack_helpers : jack_client
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
if (this->client != nullptr)
{
if(status & JackNameNotUnique) {
self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed.");
if (status & JackNameNotUnique)
{
self.libremidi_handle_warning(
self.configuration, "JACK client with the same name already exists, renamed.");
}
jack_set_process_callback(
this->client,
+[](jack_nframes_t nf, void* ctx) -> int {
auto& self = *static_cast<Self*>(ctx);
jack_port_t* port = self.port;
jack_set_process_callback(this->client, +[](jack_nframes_t nf, void* ctx) -> int {
auto& self = *static_cast<Self*>(ctx);
jack_port_t* port = self.port;
// Is port created?
if (port == nullptr)
return 0;
// Is port created?
if (port == nullptr)
return 0;
self.process(nf);
self.process(nf);
self.thread_lock.check_client_released();
return 0;
},
&self);
self.thread_lock.check_client_released();
return 0;
}, &self);
jack_activate(this->client);
}
return status;
@@ -208,8 +208,7 @@ struct jack_helpers : jack_client
if (self.configuration.client_name.size() + portName.size() + 2u
>= static_cast<size_t>(jack_port_name_size()))
{
self.libremidi_handle_error(
self.configuration, "port name length limit exceeded");
self.libremidi_handle_error(self.configuration, "port name length limit exceeded");
return std::errc::invalid_argument;
}
+3 -3
View File
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_in_jack final
: public midi1::in_api
@@ -53,8 +53,8 @@ public:
err != 0 && err != EEXIST)
{
libremidi_handle_error(
configuration, "could not connect to port: " + port.port_name + " -> "
+ jack_port_name(this->port));
configuration,
"could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port));
return from_errc(err);
}
return stdx::error{};
+3 -4
View File
@@ -3,7 +3,7 @@
#include <libremidi/backends/jack/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_out_jack
: public midi1::out_api
@@ -38,8 +38,7 @@ public:
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
err != 0 && err != EEXIST)
{
libremidi_handle_error(
configuration, "could not connect to port" + port.port_name);
libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
return from_errc(err);
}
@@ -167,7 +166,7 @@ public:
};
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <>
inline std::unique_ptr<midi_out_api> make<midi_out_jack>(
+29 -25
View File
@@ -5,7 +5,7 @@
#include <unordered_set>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class observer_jack final
: public observer_api
@@ -70,7 +70,8 @@ public:
{
seen_input_ports.insert(ports[i]);
if (this->configuration.input_added && configuration.notify_in_constructor)
this->configuration.input_added(to_port_info<true>(client, port));
this->configuration.input_added(
to_port_info<true, libremidi::API::JACK_MIDI>(client, port));
}
i++;
}
@@ -101,7 +102,8 @@ public:
{
seen_output_ports.insert(ports[i]);
if (this->configuration.output_added && configuration.notify_in_constructor)
this->configuration.output_added(to_port_info<false>(client, port));
this->configuration.output_added(
to_port_info<false, libremidi::API::JACK_MIDI>(client, port));
}
i++;
}
@@ -138,13 +140,15 @@ public:
{
seen_input_ports.insert(name);
if (this->configuration.input_added)
this->configuration.input_added(to_port_info<true>(client, port));
this->configuration.input_added(
to_port_info<true, libremidi::API::JACK_MIDI>(client, port));
}
else if (flags & JackPortIsInput)
{
seen_output_ports.insert(name);
if (this->configuration.output_added)
this->configuration.output_added(to_port_info<false>(client, port));
this->configuration.output_added(
to_port_info<false, libremidi::API::JACK_MIDI>(client, port));
}
}
else
@@ -152,13 +156,15 @@ public:
if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())
{
if (this->configuration.input_removed)
this->configuration.input_removed(to_port_info<true>(client, port));
this->configuration.input_removed(
to_port_info<true, libremidi::API::JACK_MIDI>(client, port));
seen_input_ports.erase(it);
}
if (auto it = seen_output_ports.find(name); it != seen_output_ports.end())
{
if (this->configuration.output_removed)
this->configuration.output_removed(to_port_info<false>(client, port));
this->configuration.output_removed(
to_port_info<false, libremidi::API::JACK_MIDI>(client, port));
seen_output_ports.erase(it);
}
}
@@ -171,39 +177,37 @@ public:
if (!configuration.has_callbacks())
return;
jack_set_port_registration_callback(
this->client,
+[](jack_port_id_t p, int r, void* arg) {
auto& self = *(observer_jack*)arg;
if (auto port = jack_port_by_id(self.client, p))
{
self.on_port_callback(port, r != 0);
}
},
this);
jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) {
auto& self = *(observer_jack*)arg;
if (auto port = jack_port_by_id(self.client, p))
{
self.on_port_callback(port, r != 0);
}
}, this);
jack_set_port_rename_callback(
this->client,
+[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) {
const auto& self = *static_cast<observer_jack*>(arg);
const auto& self = *static_cast<observer_jack*>(arg);
auto port = jack_port_by_id(self.client, p);
if (!port)
return;
},
this);
auto port = jack_port_by_id(self.client, p);
if (!port)
return;
}, this);
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, false);
return get_ports<true, libremidi::API::JACK_MIDI>(
this->client, nullptr, port_type, JackPortIsOutput, false);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, false);
return get_ports<false, libremidi::API::JACK_MIDI>(
this->client, nullptr, port_type, JackPortIsInput, false);
}
~observer_jack()
@@ -8,7 +8,7 @@
#include <variant>
namespace libremidi::jack
NAMESPACE_LIBREMIDI::jack
{
// Create a JACK client which will be shared across objects
@@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context
client = jack_client_open(v.data(), JackNoStartServer, &status);
assert(client);
assert(status == 0);
jack_set_process_callback(
client,
+[](jack_nframes_t cnt, void* ctx) -> int {
((shared_handler*)ctx)->jack_callback(cnt);
return 0;
},
this);
jack_set_process_callback(client, +[](jack_nframes_t cnt, void* ctx) -> int {
((shared_handler*)ctx)->jack_callback(cnt);
return 0;
}, this);
}
virtual void start_processing() override { jack_activate(client); }
@@ -73,11 +70,10 @@ struct shared_handler : public libremidi::shared_context
switch (ev.type)
{
case in_callback_added:
midiin_callbacks.push_back(
std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));
midiin_callbacks.push_back(std::move(*get_if<libremidi::jack_callback>(&ev.payload)));
break;
case in_callback_removed: {
auto idx = *std::get_if<int64_t>(&ev.payload);
auto idx = *get_if<int64_t>(&ev.payload);
for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)
{
if (it->token == idx)
@@ -93,11 +89,10 @@ struct shared_handler : public libremidi::shared_context
break;
}
case out_callback_added:
midiout_callbacks.push_back(
std::move(*std::get_if<libremidi::jack_callback>(&ev.payload)));
midiout_callbacks.push_back(std::move(*get_if<libremidi::jack_callback>(&ev.payload)));
break;
case out_callback_removed:
auto idx = *std::get_if<int64_t>(&ev.payload);
auto idx = *get_if<int64_t>(&ev.payload);
for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)
{
if (it->token == idx)
@@ -141,7 +136,7 @@ struct shared_handler : public libremidi::shared_context
struct event
{
event_type type;
std::variant<libremidi::jack_callback, int64_t> payload;
libremidi_variant_alias::variant<libremidi::jack_callback, int64_t> payload;
};
boost::lockfree::spsc_queue<event> events{16};
+1 -1
View File
@@ -5,7 +5,7 @@
#include <string_view>
namespace libremidi::jack_ump
NAMESPACE_LIBREMIDI::jack_ump
{
struct backend
{
@@ -1,7 +1,7 @@
#pragma once
#include <libremidi/backends/jack/config.hpp>
namespace libremidi::jack_ump
NAMESPACE_LIBREMIDI::jack_ump
{
struct input_configuration
{
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi::jack_ump
NAMESPACE_LIBREMIDI::jack_ump
{
class midi_in_jack final
: public midi2::in_api
@@ -3,7 +3,7 @@
#include <libremidi/backends/jack_ump/config.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi::jack_ump
NAMESPACE_LIBREMIDI::jack_ump
{
class midi_out_jack
: public midi2::out_api
@@ -170,7 +170,7 @@ public:
};
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <>
inline std::unique_ptr<midi_out_api>
@@ -5,7 +5,7 @@
#include <unordered_set>
namespace libremidi::jack_ump
NAMESPACE_LIBREMIDI::jack_ump
{
class observer_jack final
: public observer_api
@@ -77,7 +77,8 @@ public:
{
seen_input_ports.insert(ports[i]);
if (this->configuration.input_added && configuration.notify_in_constructor)
this->configuration.input_added(to_port_info<true>(client, port));
this->configuration.input_added(
to_port_info<true, libremidi::API::JACK_UMP>(client, port));
}
i++;
}
@@ -114,7 +115,8 @@ public:
{
seen_output_ports.insert(ports[i]);
if (this->configuration.output_added && configuration.notify_in_constructor)
this->configuration.output_added(to_port_info<false>(client, port));
this->configuration.output_added(
to_port_info<false, libremidi::API::JACK_UMP>(client, port));
}
i++;
}
@@ -154,13 +156,15 @@ public:
{
seen_input_ports.insert(name);
if (this->configuration.input_added)
this->configuration.input_added(to_port_info<true>(client, port));
this->configuration.input_added(
to_port_info<true, libremidi::API::JACK_UMP>(client, port));
}
else if (flags & JackPortIsInput)
{
seen_output_ports.insert(name);
if (this->configuration.output_added)
this->configuration.output_added(to_port_info<false>(client, port));
this->configuration.output_added(
to_port_info<false, libremidi::API::JACK_UMP>(client, port));
}
}
else
@@ -168,13 +172,15 @@ public:
if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())
{
if (this->configuration.input_removed)
this->configuration.input_removed(to_port_info<true>(client, port));
this->configuration.input_removed(
to_port_info<true, libremidi::API::JACK_UMP>(client, port));
seen_input_ports.erase(it);
}
if (auto it = seen_output_ports.find(name); it != seen_output_ports.end())
{
if (this->configuration.output_removed)
this->configuration.output_removed(to_port_info<false>(client, port));
this->configuration.output_removed(
to_port_info<false, libremidi::API::JACK_UMP>(client, port));
seen_output_ports.erase(it);
}
}
@@ -210,12 +216,14 @@ public:
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, true);
return get_ports<true, libremidi::API::JACK_UMP>(
this->client, nullptr, port_type, JackPortIsOutput, true);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, true);
return get_ports<false, libremidi::API::JACK_UMP>(
this->client, nullptr, port_type, JackPortIsInput, true);
}
~observer_jack()
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <libremidi/backends/kdmapi/midi_in.hpp>
#include <libremidi/backends/kdmapi/midi_out.hpp>
#include <libremidi/backends/kdmapi/observer.hpp>
#include <string_view>
//*********************************************************************//
// API: OmniMIDI KDMAPI (Keppy's Direct MIDI API)
//*********************************************************************//
// OmniMIDI is a low-latency MIDI synthesizer driver for Windows.
// KDMAPI provides a direct interface to OmniMIDI, bypassing the
// Windows Multimedia API for lower latency.
//
// Note: KDMAPI is output-only. The midi_in class is a placeholder
// that always fails - use a different backend for MIDI input.
//
// https://github.com/KeppySoftware/OmniMIDI
namespace libremidi
{
struct kdmapi_backend
{
using midi_in = kdmapi::midi_in;
using midi_out = kdmapi::midi_out;
using midi_observer = kdmapi::observer;
using midi_in_configuration = kdmapi::input_configuration;
using midi_out_configuration = kdmapi::output_configuration;
using midi_observer_configuration = kdmapi::observer_configuration;
static const constexpr auto API = libremidi::API::KDMAPI;
static const constexpr std::string_view name = "kdmapi";
static const constexpr std::string_view display_name = "OmniMIDI (KDMAPI)";
static inline bool available() noexcept
{
return kdmapi::kdmapi_loader::instance().is_available();
}
};
}
@@ -0,0 +1,23 @@
#pragma once
#include <libremidi/config.hpp>
namespace libremidi::kdmapi
{
// KDMAPI does not support MIDI input
struct input_configuration
{
};
struct output_configuration
{
// Use the no-buffer variant of SendDirectData for lowest latency
// This bypasses OmniMIDI's internal buffer
bool use_no_buffer = false;
};
struct observer_configuration
{
};
}
@@ -0,0 +1,178 @@
#pragma once
// clang-format off
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include <mmsystem.h>
// clang-format on
#include <libremidi/detail/midi_api.hpp>
#include <atomic>
#include <mutex>
namespace libremidi::kdmapi
{
using IsKDMAPIAvailable_t = BOOL(WINAPI*)();
using InitializeKDMAPIStream_t = BOOL(WINAPI*)();
using TerminateKDMAPIStream_t = BOOL(WINAPI*)();
using ResetKDMAPIStream_t = VOID(WINAPI*)();
using SendDirectData_t = VOID(WINAPI*)(DWORD);
using SendDirectDataNoBuf_t = VOID(WINAPI*)(DWORD);
using SendDirectLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
using SendDirectLongDataNoBuf_t = UINT(WINAPI*)(LPSTR, DWORD);
using PrepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
using UnprepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
using ReturnKDMAPIVer_t = BOOL(WINAPI*)(LPDWORD, LPDWORD, LPDWORD, LPDWORD);
class kdmapi_loader
{
public:
static kdmapi_loader& instance()
{
static kdmapi_loader loader;
return loader;
}
bool is_available() const noexcept { return m_available; }
HMODULE handle() const noexcept { return m_handle; }
// KDMAPI function pointers
IsKDMAPIAvailable_t IsKDMAPIAvailable{};
InitializeKDMAPIStream_t InitializeKDMAPIStream{};
TerminateKDMAPIStream_t TerminateKDMAPIStream{};
ResetKDMAPIStream_t ResetKDMAPIStream{};
SendDirectData_t SendDirectData{};
SendDirectDataNoBuf_t SendDirectDataNoBuf{};
SendDirectLongData_t SendDirectLongData{};
SendDirectLongDataNoBuf_t SendDirectLongDataNoBuf{};
PrepareLongData_t PrepareLongData{};
UnprepareLongData_t UnprepareLongData{};
ReturnKDMAPIVer_t ReturnKDMAPIVer{};
private:
kdmapi_loader()
{
// Try to get OmniMIDI if it's already loaded
m_handle = GetModuleHandleW(L"OmniMIDI");
if (!m_handle)
{
// Try to load it explicitly
m_handle = LoadLibraryW(L"OmniMIDI.dll");
}
if (!m_handle)
{
m_available = false;
return;
}
#if !defined(_MSC_VER)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
// Load all function pointers
IsKDMAPIAvailable
= reinterpret_cast<IsKDMAPIAvailable_t>(GetProcAddress(m_handle, "IsKDMAPIAvailable"));
InitializeKDMAPIStream = reinterpret_cast<InitializeKDMAPIStream_t>(
GetProcAddress(m_handle, "InitializeKDMAPIStream"));
TerminateKDMAPIStream = reinterpret_cast<TerminateKDMAPIStream_t>(
GetProcAddress(m_handle, "TerminateKDMAPIStream"));
ResetKDMAPIStream
= reinterpret_cast<ResetKDMAPIStream_t>(GetProcAddress(m_handle, "ResetKDMAPIStream"));
SendDirectData
= reinterpret_cast<SendDirectData_t>(GetProcAddress(m_handle, "SendDirectData"));
SendDirectDataNoBuf
= reinterpret_cast<SendDirectDataNoBuf_t>(GetProcAddress(m_handle, "SendDirectDataNoBuf"));
SendDirectLongData
= reinterpret_cast<SendDirectLongData_t>(GetProcAddress(m_handle, "SendDirectLongData"));
SendDirectLongDataNoBuf = reinterpret_cast<SendDirectLongDataNoBuf_t>(
GetProcAddress(m_handle, "SendDirectLongDataNoBuf"));
PrepareLongData
= reinterpret_cast<PrepareLongData_t>(GetProcAddress(m_handle, "PrepareLongData"));
UnprepareLongData
= reinterpret_cast<UnprepareLongData_t>(GetProcAddress(m_handle, "UnprepareLongData"));
ReturnKDMAPIVer
= reinterpret_cast<ReturnKDMAPIVer_t>(GetProcAddress(m_handle, "ReturnKDMAPIVer"));
#if !defined(_MSC_VER)
#pragma GCC diagnostic pop
#endif
// Check if the minimum required functions are available
if (!IsKDMAPIAvailable || !InitializeKDMAPIStream || !TerminateKDMAPIStream || !SendDirectData)
{
m_available = false;
return;
}
// Call IsKDMAPIAvailable to activate KDMAPI mode in OmniMIDI
m_available = IsKDMAPIAvailable() != FALSE;
}
~kdmapi_loader()
{
}
kdmapi_loader(const kdmapi_loader&) = delete;
kdmapi_loader& operator=(const kdmapi_loader&) = delete;
HMODULE m_handle{};
bool m_available{false};
};
class kdmapi_stream_manager
{
public:
static kdmapi_stream_manager& instance()
{
static kdmapi_stream_manager mgr;
return mgr;
}
bool acquire()
{
std::lock_guard lock{m_mutex};
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
return false;
if (m_rc == 0)
{
if (!loader.InitializeKDMAPIStream())
return false;
}
++m_rc;
return true;
}
void release()
{
std::lock_guard lock{m_mutex};
if (m_rc == 0)
return;
--m_rc;
if (m_rc == 0)
{
auto& loader = kdmapi_loader::instance();
if (loader.TerminateKDMAPIStream)
loader.TerminateKDMAPIStream();
}
}
private:
kdmapi_stream_manager() = default;
~kdmapi_stream_manager() = default;
kdmapi_stream_manager(const kdmapi_stream_manager&) = delete;
kdmapi_stream_manager& operator=(const kdmapi_stream_manager&) = delete;
std::mutex m_mutex;
std::size_t m_rc{0};
};
}
@@ -0,0 +1,11 @@
#pragma once
#include <libremidi/backends/kdmapi/config.hpp>
#include <libremidi/backends/dummy.hpp>
#include <libremidi/detail/midi_in.hpp>
namespace libremidi::kdmapi
{
using midi_in = libremidi::midi_in_dummy;
}
@@ -0,0 +1,173 @@
#pragma once
#include <libremidi/backends/kdmapi/config.hpp>
#include <libremidi/backends/kdmapi/helpers.hpp>
#include <libremidi/backends/kdmapi/observer.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi::kdmapi
{
class midi_out final
: public midi1::out_api
, public error_handler
{
public:
struct
: libremidi::output_configuration
, kdmapi::output_configuration
{
} configuration;
midi_out(libremidi::output_configuration&& conf, kdmapi::output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
{
libremidi_handle_error(
configuration, "KDMAPI is not available. Is OmniMIDI installed?");
this->client_open_ = std::errc::no_such_device;
return;
}
this->client_open_ = stdx::error{};
}
~midi_out() override
{
midi_out::close_port();
this->client_open_ = std::errc::not_connected;
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; }
stdx::error open_port(const output_port& port, std::string_view) override
{
if (port.api != libremidi::API::KDMAPI)
{
libremidi_handle_error(configuration, "port is not a KDMAPI port");
return std::errc::invalid_argument;
}
return do_open();
}
stdx::error do_open()
{
auto& mgr = kdmapi_stream_manager::instance();
if (!mgr.acquire())
{
libremidi_handle_error(configuration, "failed to initialize KDMAPI stream");
return std::errc::io_error;
}
connected_ = true;
return stdx::error{};
}
stdx::error close_port() override
{
if (connected_)
{
auto& mgr = kdmapi_stream_manager::instance();
mgr.release();
connected_ = false;
}
return stdx::error{};
}
stdx::error send_message(const unsigned char* message, size_t size) override
{
if (!connected_)
return std::errc::not_connected;
if (size == 0)
{
libremidi_handle_warning(configuration, "message argument is empty!");
return std::errc::invalid_argument;
}
auto& loader = kdmapi_loader::instance();
if (message[0] == 0xF0)
{
// SysEx message
return send_sysex(message, size);
}
else
{
// Channel or system message
if (size > 3)
{
libremidi_handle_warning(
configuration, "message size is greater than 3 bytes (and not sysex)!");
return std::errc::message_size;
}
// Pack MIDI bytes into DWORD
DWORD packet = 0;
std::copy_n(message, size, reinterpret_cast<unsigned char*>(&packet));
// Send the message
if (configuration.use_no_buffer && loader.SendDirectDataNoBuf)
loader.SendDirectDataNoBuf(packet);
else
loader.SendDirectData(packet);
}
return stdx::error{};
}
private:
stdx::error send_sysex(const unsigned char* message, size_t size)
{
auto& loader = kdmapi_loader::instance();
if (configuration.use_no_buffer && loader.SendDirectLongDataNoBuf)
{
// Use the no-buffer variant - it takes raw data directly
loader.SendDirectLongDataNoBuf(
const_cast<LPSTR>(reinterpret_cast<const char*>(message)), static_cast<DWORD>(size));
return stdx::error{};
}
// Standard path with MIDIHDR
buffer_.assign(message, message + size);
MIDIHDR sysex{};
sysex.lpData = reinterpret_cast<LPSTR>(buffer_.data());
sysex.dwBufferLength = static_cast<DWORD>(size);
sysex.dwFlags = 0;
if (loader.PrepareLongData)
{
UINT result = loader.PrepareLongData(&sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(configuration, "error preparing sysex header");
return std::errc::io_error;
}
}
if (loader.SendDirectLongData)
{
UINT result = loader.SendDirectLongData(&sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(configuration, "error sending sysex message");
// Still try to unprepare
}
}
if (loader.UnprepareLongData)
{
loader.UnprepareLongData(&sysex, sizeof(MIDIHDR));
}
return stdx::error{};
}
std::vector<char> buffer_;
};
}
@@ -0,0 +1,86 @@
#pragma once
#include <libremidi/backends/kdmapi/config.hpp>
#include <libremidi/backends/kdmapi/helpers.hpp>
#include <libremidi/detail/observer.hpp>
namespace libremidi::kdmapi
{
class observer final : public observer_api
{
public:
struct
: libremidi::observer_configuration
, kdmapi::observer_configuration
{
} configuration;
explicit observer(
libremidi::observer_configuration&& conf, kdmapi::observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (!configuration.has_callbacks())
return;
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
return;
// KDMAPI provides a single virtual output port
if (configuration.notify_in_constructor)
{
if (configuration.output_added)
{
auto ports = get_output_ports();
for (const auto& port : ports)
configuration.output_added(port);
}
}
}
~observer() = default;
libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; }
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
// KDMAPI does not provide input ports
return {};
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
auto& loader = kdmapi_loader::instance();
if (!loader.is_available())
return {};
// Get KDMAPI version for display name
std::string display_name = "OmniMIDI";
if (loader.ReturnKDMAPIVer)
{
DWORD major{}, minor{}, build{}, rev{};
if (loader.ReturnKDMAPIVer(&major, &minor, &build, &rev))
{
display_name += " (KDMAPI ";
display_name += std::to_string(major);
display_name += ".";
display_name += std::to_string(minor);
display_name += ".";
display_name += std::to_string(build);
display_name += ")";
}
}
return {libremidi::output_port{
{.api = libremidi::API::KDMAPI,
.client = 0,
.port = 0,
.manufacturer = "KeppySoftware",
.device_name = "OmniMIDI",
.port_name = "OmniMIDI",
.display_name = std::move(display_name)},
}};
}
};
}
+1 -1
View File
@@ -4,7 +4,7 @@
#include <string_view>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct kbd_backend
{
@@ -4,7 +4,7 @@
#include <functional>
#include <map>
namespace libremidi
NAMESPACE_LIBREMIDI
{
enum kbd_event
{
@@ -36,7 +36,7 @@ enum kbd_event
// actually based on macOS virtual codes as it seems
// impossible to get raw scan codes.
// https://eastmanreference.com/complete-list-of-applescript-key-codes
static inline const std::map<int, int>& scancode_map_macos(){
LIBREMIDI_STATIC const std::map<int, int>& scancode_map_macos(){
static const std::map<int, int> ret{
{ 0, NOTE_0}, // C0
{ 13, NOTE_0 + 1},
@@ -83,7 +83,7 @@ static inline const std::map<int, int>& scancode_map_macos(){
// Use: https://kbdlayout.info/KBDUSX/scancodes
// Also valid for windows
static inline const std::map<int, int>& scancode_map_linux() {
LIBREMIDI_STATIC const std::map<int, int>& scancode_map_linux() {
static const std::map<int, int> ret{
{ 0x1E, NOTE_0}, // C0
{ 0x11, NOTE_0 + 1},
@@ -5,7 +5,7 @@
#include <chrono>
#include <unordered_map>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_in_kbd final
: public midi1::in_api
+97 -19
View File
@@ -13,9 +13,13 @@
#if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10)
#define LIBREMIDI_ALSA_HAS_UMP 1
#endif
#if __has_include(<alsa/ump.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 14)
#define LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS 1
#endif
#endif
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct libasound
@@ -278,6 +282,7 @@ struct libasound
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender)
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)
@@ -292,6 +297,12 @@ struct libasound
LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo)
LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port)
LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port)
#if LIBREMIDI_ALSA_HAS_UMP
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version)
LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_endpoint_info)
LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_block_info)
#endif
}
bool available{true};
@@ -336,6 +347,7 @@ struct libasound
LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender)
LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real)
@@ -352,6 +364,12 @@ struct libasound
LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port)
LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port)
#if LIBREMIDI_ALSA_HAS_UMP
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version)
LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_endpoint_info)
LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_block_info)
#endif
#if LIBREMIDI_ALSA_HAS_UMP
struct ump_t
{
@@ -362,7 +380,6 @@ struct libasound
available = false;
return;
}
LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version)
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input)
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output)
LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct)
@@ -370,10 +387,10 @@ struct libasound
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version)
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input)
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output)
LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct)
} ump;
#endif
} seq{library};
@@ -389,39 +406,98 @@ struct libasound
return;
}
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_ump, close)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof)
// Core UMP functions
LIBREMIDI_SYMBOL_INIT(snd_ump, open)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents)
LIBREMIDI_SYMBOL_INIT(snd_ump, close)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params)
LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current)
LIBREMIDI_SYMBOL_INIT(snd_ump, read)
LIBREMIDI_SYMBOL_INIT(snd_ump, tread)
LIBREMIDI_SYMBOL_INIT(snd_ump, write)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents)
// Endpoint info functions
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_card)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_device)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_flags)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol_caps)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_num_blocks)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_version)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_manufacturer_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_family_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_model_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_sw_revision)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name)
LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_product_id)
// Block info functions
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_set_block_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_block_id)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_active)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_flags)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_direction)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_first_group)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_num_groups)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_midi_ci_version)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_sysex8_streams)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_ui_hint)
LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name)
}
bool available{true};
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_ump, close)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof)
// Core UMP functions
LIBREMIDI_SYMBOL_DEF(snd_ump, open)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents)
LIBREMIDI_SYMBOL_DEF(snd_ump, close)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params)
LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current)
LIBREMIDI_SYMBOL_DEF(snd_ump, read)
LIBREMIDI_SYMBOL_DEF(snd_ump, tread)
LIBREMIDI_SYMBOL_DEF(snd_ump, write)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count)
LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents)
// Endpoint info functions
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_card)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_device)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_flags)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol_caps)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_num_blocks)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_version)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_manufacturer_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_family_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_model_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_sw_revision)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name)
LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_product_id)
// Block info functions
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_set_block_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_block_id)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_active)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_flags)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_direction)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_first_group)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_num_groups)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_midi_ci_version)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_sysex8_streams)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_ui_hint)
LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name)
} ump{library};
#endif
};
@@ -449,6 +525,8 @@ struct libasound
#define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info)
#undef snd_seq_port_info_alloca
#define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info)
#undef snd_seq_port_subscribe_alloca
#define snd_seq_port_subscribe_alloca(ptr) snd_dylib_alloca(ptr, seq, port_subscribe)
#undef snd_seq_queue_tempo_alloca
#define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo)
@@ -1,10 +1,11 @@
#pragma once
#include <libremidi/config.hpp>
#if __has_include(<dlfcn.h>)
#include <dlfcn.h>
#include <cassert>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class dylib_loader
{
+1 -1
View File
@@ -7,7 +7,7 @@
#include <iostream>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct eventfd_notifier
{
@@ -4,7 +4,7 @@
#include <pipewire/pipewire.h>
namespace libremidi
NAMESPACE_LIBREMIDI
{
#pragma GCC diagnostic push
+42 -7
View File
@@ -8,7 +8,7 @@
#include <cassert>
#include <optional>
namespace libremidi
NAMESPACE_LIBREMIDI
{
struct libudev
@@ -149,7 +149,10 @@ struct udev_soundcard_info
{
container_identifier container;
device_identifier path;
port_information::port_type type{};
transport_type type{};
std::string vendor;
std::string product;
std::string serial;
};
inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card)
@@ -174,18 +177,50 @@ inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, in
const auto usb_device
= udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
const auto pci_device = udev.device_get_parent_with_subsystem_devtype(dev, "pci", nullptr);
port_information::port_type type = port_information::port_type::hardware;
transport_type type = transport_type::hardware;
if (usb_device)
type = (port_information::port_type)(type | port_information::port_type::usb);
else
type = (port_information::port_type)(type | port_information::port_type::pci);
type = (transport_type)(type | transport_type::usb);
else if (pci_device)
type = (transport_type)(type | transport_type::pci);
auto id_path = udev.device_get_property_value(dev, "ID_PATH");
if (!id_path)
id_path = "";
auto ret = udev_soundcard_info{.container = id_path, .path = path, .type = type};
auto read_property = [&](const char* property) {
auto res = udev.device_get_property_value(dev, property);
if (!res)
res = "";
if (strlen(res) == 0)
{
if (usb_device)
{
res = udev.device_get_property_value(usb_device, property);
if (!res)
res = "";
}
else if (pci_device)
{
res = udev.device_get_property_value(pci_device, property);
if (!res)
res = "";
}
}
return res;
};
auto id_vendor = read_property("ID_VENDOR_FROM_DATABASE");
auto id_product = read_property("ID_MODEL_FROM_DATABASE");
auto id_serial = read_property("ID_SERIAL");
auto ret = udev_soundcard_info{
.container = id_path,
.path = path,
.type = type,
.vendor = id_vendor,
.product = id_product,
.serial = id_serial};
udev.device_unref(dev);
return ret;
}
+5 -2
View File
@@ -2,12 +2,15 @@
#include <libremidi/config.hpp>
#include <string>
#if !defined(BOOST_ASIO_IO_CONTEXT_HPP)
namespace boost::asio
{
struct io_context;
}
#endif
namespace libremidi::net
NAMESPACE_LIBREMIDI::net
{
enum class protocol
{
@@ -46,7 +49,7 @@ struct dgram_observer_configuration
}
namespace libremidi::net_ump
NAMESPACE_LIBREMIDI::net_ump
{
enum class protocol
{
+2 -2
View File
@@ -23,7 +23,7 @@ public:
string_ref message(const error& e) const noexcept override {
auto err = e.m_value;
}
[[noreturn]] void throw_exception(const error& e) const override
@@ -35,7 +35,7 @@ public:
*/
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <typename T>
struct optionally_owned
+3 -3
View File
@@ -5,7 +5,7 @@
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <thread>
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <typename Impl, typename F>
struct osc_parser
@@ -115,7 +115,7 @@ struct osc_parser
}
namespace libremidi::net
NAMESPACE_LIBREMIDI::net
{
struct osc_parser_midi1
@@ -264,7 +264,7 @@ public:
};
}
namespace libremidi::net_ump
NAMESPACE_LIBREMIDI::net_ump
{
struct osc_parser_midi2
{
+2 -2
View File
@@ -8,7 +8,7 @@
#include <boost/asio/ip/udp.hpp>
#include <boost/endian.hpp>
namespace libremidi::net
NAMESPACE_LIBREMIDI::net
{
struct osc_midi1_packet
@@ -146,7 +146,7 @@ public:
}
namespace libremidi::net_ump
NAMESPACE_LIBREMIDI::net_ump
{
struct osc_midi2_packet
{
+2 -2
View File
@@ -1,13 +1,13 @@
#pragma once
#include <libremidi/backends/dummy.hpp>
namespace libremidi::net
NAMESPACE_LIBREMIDI::net
{
using net_observer_configuration = libremidi::dummy_configuration;
using observer = libremidi::observer_dummy;
}
namespace libremidi::net_ump
NAMESPACE_LIBREMIDI::net_ump
{
using net_observer_configuration = libremidi::dummy_configuration;
using observer = libremidi::observer_dummy;
+1 -1
View File
@@ -7,7 +7,7 @@
#include <string_view>
namespace libremidi::net
NAMESPACE_LIBREMIDI::net
{
struct backend
{
+1 -1
View File
@@ -6,7 +6,7 @@
#include <string_view>
namespace libremidi::net_ump
NAMESPACE_LIBREMIDI::net_ump
{
struct backend
{
+1 -1
View File
@@ -9,7 +9,7 @@
#include <string_view>
namespace libremidi::pipewire
NAMESPACE_LIBREMIDI::pipewire
{
struct backend
{
@@ -12,7 +12,7 @@ struct pw_filter;
struct spa_io_position;
}
namespace libremidi
NAMESPACE_LIBREMIDI
{
using pipewire_callback_function = std::function<void(spa_io_position*)>;
struct pipewire_callback
@@ -25,7 +25,7 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
namespace libremidi
NAMESPACE_LIBREMIDI
{
template <typename K, typename V>
using hash_map = std::unordered_map<K, V>;
@@ -188,13 +188,13 @@ struct pipewire_context
pipewire_context& self = *(pipewire_context*)object;
if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
self.register_port(id, type);
},
},
.global_remove =
[](void* object, uint32_t id) {
pipewire_context& self = *(pipewire_context*)object;
self.unregister_port(id);
},
};
},
};
// Start listening
pw_registry_add_listener(this->registry, &this->registry_listener, &registry_events, this);
@@ -245,8 +245,8 @@ struct pipewire_context
// Remove from the listeners
auto it
= std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) {
return l.id == id;
});
return l.id == id;
});
if (it != port_listener.end())
{
pw.proxy_destroy((pw_proxy*)it->port);
@@ -267,14 +267,14 @@ struct pipewire_context
static constexpr struct pw_core_events core_events = {
.version = PW_VERSION_CORE_EVENTS,
.done =
[](void* object, uint32_t id, int seq) {
[](void* object, uint32_t id, int seq) {
auto& self = *(pipewire_context*)object;
if(id == PW_ID_CORE && seq == self.pending)
if (id == PW_ID_CORE && seq == self.pending)
{
self.done = 1;
libpipewire::instance().main_loop_quit(self.main_loop);
}
},
},
};
spa_zero(core_listener);
+24 -13
View File
@@ -11,7 +11,12 @@
#include <atomic>
#include <thread>
namespace libremidi
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-compare"
#endif
NAMESPACE_LIBREMIDI
{
struct pipewire_helpers
{
@@ -82,10 +87,10 @@ struct pipewire_helpers
static constexpr struct pw_filter_events filter_events
= {.version = PW_VERSION_FILTER_EVENTS,
.process = +[](void* _data, struct spa_io_position* position) -> void {
// FIXME likely we need the thread_lock check here too
Self& self = *static_cast<Self*>(_data);
self.process(position);
}};
// FIXME likely we need the thread_lock check here too
Self& self = *static_cast<Self*>(_data);
self.process(position);
}};
#pragma GCC diagnostic pop
this->filter->create_filter(self.configuration.client_name, filter_events, &self);
@@ -217,6 +222,7 @@ struct pipewire_helpers
return stdx::error{};
}
template <libremidi::API Api>
void add_callbacks(std::string format, const observer_configuration& conf)
{
assert(global_context);
@@ -232,12 +238,12 @@ struct pipewire_helpers
if (port.direction == SPA_DIRECTION_INPUT)
{
if (conf.output_added)
conf.output_added(to_port_info<SPA_DIRECTION_INPUT>(port));
conf.output_added(to_port_info<SPA_DIRECTION_INPUT, Api>(port));
}
else
{
if (conf.input_added)
conf.input_added(to_port_info<SPA_DIRECTION_OUTPUT>(port));
conf.input_added(to_port_info<SPA_DIRECTION_OUTPUT, Api>(port));
}
}
};
@@ -254,12 +260,12 @@ struct pipewire_helpers
if (port.direction == SPA_DIRECTION_INPUT)
{
if (conf.output_removed)
conf.output_removed(to_port_info<SPA_DIRECTION_INPUT>(port));
conf.output_removed(to_port_info<SPA_DIRECTION_INPUT, Api>(port));
}
else
{
if (conf.input_removed)
conf.input_removed(to_port_info<SPA_DIRECTION_OUTPUT>(port));
conf.input_removed(to_port_info<SPA_DIRECTION_OUTPUT, Api>(port));
}
}
};
@@ -407,7 +413,7 @@ struct pipewire_helpers
return stdx::error{};
}
template <spa_direction Direction>
template <spa_direction Direction, libremidi::API Api>
static auto to_port_info(const pipewire_context::port_info& port)
-> std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>
{
@@ -424,6 +430,7 @@ struct pipewire_helpers
}
return {{
.api = Api,
.client = 0,
.port = port.id,
.manufacturer = "",
@@ -435,7 +442,7 @@ struct pipewire_helpers
// Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer)
// is an "output" port from the point of view of pipewire as data will come out of it
template <spa_direction Direction>
template <spa_direction Direction, libremidi::API Api>
static auto get_ports(
std::string_view format, const observer_configuration& conf,
const pipewire_context& ctx) noexcept
@@ -454,7 +461,7 @@ struct pipewire_helpers
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{
if (port.format.find(format) != std::string::npos)
ret.push_back(to_port_info<Direction>(port));
ret.push_back(to_port_info<Direction, Api>(port));
}
}
@@ -465,7 +472,7 @@ struct pipewire_helpers
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
{
if (port.format.find(format) != std::string::npos)
ret.push_back(to_port_info<Direction>(port));
ret.push_back(to_port_info<Direction, Api>(port));
}
}
}
@@ -474,3 +481,7 @@ struct pipewire_helpers
}
};
}
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
@@ -4,7 +4,7 @@
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_in_pipewire final
: public midi1::in_api
@@ -74,10 +74,7 @@ public:
return do_close_port();
}
stdx::error set_port_name(std::string_view port_name) override
{
return rename_port(port_name);
}
stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); }
timestamp absolute_timestamp() const noexcept override { return system_ns(); }
@@ -5,7 +5,7 @@
#include <readerwriterqueue.h>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class midi_out_pipewire
: public midi1::out_api
@@ -79,10 +79,7 @@ public:
return do_close_port();
}
stdx::error set_port_name(std::string_view port_name) override
{
return 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)
{
@@ -5,7 +5,7 @@
#include <unordered_set>
namespace libremidi
NAMESPACE_LIBREMIDI
{
class observer_pipewire final
: public observer_api
@@ -19,14 +19,15 @@ public:
{
} configuration;
explicit observer_pipewire(observer_configuration&& conf, pipewire_observer_configuration&& apiconf)
explicit observer_pipewire(
observer_configuration&& conf, pipewire_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
create_context(*this);
// FIXME port rename callback
{
this->add_callbacks("midi", configuration);
this->add_callbacks<libremidi::API::PIPEWIRE>("midi", configuration);
this->start_thread();
}
@@ -46,12 +47,14 @@ public:
std::vector<libremidi::input_port> get_input_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_OUTPUT>("midi", this->configuration, *this->global_context);
return get_ports<SPA_DIRECTION_OUTPUT, libremidi::API::PIPEWIRE>(
"midi", this->configuration, *this->global_context);
}
std::vector<libremidi::output_port> get_output_ports() const noexcept override
{
return get_ports<SPA_DIRECTION_INPUT>("midi", this->configuration, *this->global_context);
return get_ports<SPA_DIRECTION_INPUT, libremidi::API::PIPEWIRE>(
"midi", this->configuration, *this->global_context);
}
~observer_pipewire()
@@ -8,7 +8,7 @@
#include <variant>
namespace libremidi::pipewire
NAMESPACE_LIBREMIDI::pipewire
{
// Create a PipeWire client which will be shared across objects
@@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context
client = pipewire_client_open(v.data(), PipewireNoStartServer, &status);
assert(client);
assert(status == 0);
pipewire_set_process_callback(
client,
+[](pipewire_nframes_t cnt, void* ctx) -> int {
((shared_handler*)ctx)->pipewire_callback(cnt);
return 0;
},
this);
pipewire_set_process_callback(client, +[](pipewire_nframes_t cnt, void* ctx) -> int {
((shared_handler*)ctx)->pipewire_callback(cnt);
return 0;
}, this);
}
virtual void start_processing() override { pipewire_activate(client); }
@@ -74,10 +71,10 @@ struct shared_handler : public libremidi::shared_context
{
case in_callback_added:
midiin_callbacks.push_back(
std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));
std::move(*get_if<libremidi::pipewire_callback>(&ev.payload)));
break;
case in_callback_removed: {
auto idx = *std::get_if<int64_t>(&ev.payload);
auto idx = *get_if<int64_t>(&ev.payload);
for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();)
{
if (it->token == idx)
@@ -94,10 +91,10 @@ struct shared_handler : public libremidi::shared_context
}
case out_callback_added:
midiout_callbacks.push_back(
std::move(*std::get_if<libremidi::pipewire_callback>(&ev.payload)));
std::move(*get_if<libremidi::pipewire_callback>(&ev.payload)));
break;
case out_callback_removed:
auto idx = *std::get_if<int64_t>(&ev.payload);
auto idx = *get_if<int64_t>(&ev.payload);
for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();)
{
if (it->token == idx)
@@ -141,7 +138,7 @@ struct shared_handler : public libremidi::shared_context
struct event
{
event_type type;
std::variant<libremidi::pipewire_callback, int64_t> payload;
libremidi_variant_alias::variant<libremidi::pipewire_callback, int64_t> payload;
};
boost::lockfree::spsc_queue<event> events{16};

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