Compare commits
76 Commits
main
..
virtual-midi
| Author | SHA1 | Date | |
|---|---|---|---|
| ce675b1430 | |||
| 3de17a3b43 | |||
| 5a1f5acae8 | |||
| 51cd271b6f | |||
| dc0003232a | |||
| cae4acfdef | |||
| b8438ade34 | |||
| 657c23702c | |||
| 5886052228 | |||
| d69368322e | |||
| d881ef43a3 | |||
| 77a227f40d | |||
| f086834da6 | |||
| 2cc6839009 | |||
| 62454960d1 | |||
| e5f614ce06 | |||
| 011552c020 | |||
| 2725806749 | |||
| bf2f34c902 | |||
| 0e6f2c7256 | |||
| 9e9c32bff4 | |||
| 6aef199984 | |||
| c3543cffce | |||
| 9b75b36c41 | |||
| 996ed16c01 | |||
| 96c2bde0e1 | |||
| b2ed35a704 | |||
| 7f2d16932f | |||
| bc8c433e47 | |||
| 456528f230 | |||
| ddf9e640b1 | |||
| 3b08037f92 | |||
| 558829f619 | |||
| d9c97be326 | |||
| 3b0e7e986f | |||
| 36273ba7ac | |||
| ade33b0608 | |||
| 638df54ee6 | |||
| b6c2029386 | |||
| 036c6f4723 | |||
| 1fde652495 | |||
| 0040956801 | |||
| 722848d46b | |||
| f926b95092 | |||
| b6a049ea0d | |||
| c63ee3eabe | |||
| c7e76ae7ac | |||
| fcc5d74e18 | |||
| 635c94e3cf | |||
| 492971ed4f | |||
| 813958002b | |||
| 9c0aac8618 | |||
| f04838bffa | |||
| 62a5c53969 | |||
| b12f1d0f9d | |||
| a6b6ff1d16 | |||
| bfc14d5f6e | |||
| df91a1b107 | |||
| 5779c93339 | |||
| 22842f9deb | |||
| e9633ff19d | |||
| 55926a5021 | |||
| 96526f709a | |||
| 9cccbab703 | |||
| ab34081dbb | |||
| 2e103dc561 | |||
| 5d3b72d4ef | |||
| 300001d385 | |||
| e6f370ba4f | |||
| 1e0a131169 | |||
| 96082e1190 | |||
| 31a1a264d4 | |||
| 4f48aa2d5a | |||
| 71dc08d21f | |||
| cdc0fb7075 | |||
| 529106e815 |
@@ -0,0 +1,3 @@
|
||||
CompileFlags:
|
||||
Add: [-I/home/sebastian/code/viseq/include, -std=c++23]
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
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))
|
||||
|
||||
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 -----------<"
|
||||
|
||||
%_src:
|
||||
@echo "G++ >---------- $(*F).cpp -----------<"
|
||||
|
||||
%_hdr:
|
||||
@echo "G++ >---------- $(*F).h -----------<"
|
||||
$(OBJECTS): build/%.o : src/%.cpp
|
||||
@mkdir -p build
|
||||
$(CXX) -c $< -o $@ $(CXXFLAGS) $(CXXINC)
|
||||
|
||||
clean:
|
||||
rm -f build/*
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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, ×tamp);
|
||||
|
||||
if (num_messages < 0)
|
||||
{
|
||||
LOGE("Error receiving MIDI data: %d", (int)num_messages);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (num_messages > 0 && num_bytes > 0)
|
||||
{
|
||||
const auto to_ns = [=] { return timestamp; };
|
||||
|
||||
m_processing.on_bytes_multi(
|
||||
{buffer, buffer + num_bytes},
|
||||
m_processing.timestamp<timestamp_info>(to_ns, timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void open_callback(midi_in* self, AMidiDevice* device)
|
||||
{
|
||||
self->receive_device = device;
|
||||
AMidiOutputPort_open(device, 0, &self->midi_output_port);
|
||||
self->start_midi_thread();
|
||||
}
|
||||
|
||||
private:
|
||||
libremidi::input_configuration configuration;
|
||||
AMidiDevice* receive_device = nullptr;
|
||||
AMidiOutputPort* midi_output_port = nullptr;
|
||||
std::thread poll_thread;
|
||||
std::atomic<bool> port_open{false};
|
||||
std::atomic<bool> running{false};
|
||||
midi1::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/android/config.hpp>
|
||||
#include <libremidi/backends/android/helpers.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
NAMESPACE_LIBREMIDI
|
||||
{
|
||||
namespace android
|
||||
{
|
||||
class midi_out final
|
||||
: public midi1::out_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
explicit midi_out(
|
||||
libremidi::output_configuration&& conf, libremidi::android::output_configuration aconf)
|
||||
: configuration{std::move(conf)}
|
||||
{
|
||||
if (!context::client_name.empty() && context::client_name != aconf.client_name)
|
||||
{
|
||||
libremidi_handle_error(
|
||||
configuration, "Android backend only supports one client name per process");
|
||||
return;
|
||||
}
|
||||
context::client_name = std::string(aconf.client_name);
|
||||
client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out() override { close_port(); }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override
|
||||
{
|
||||
return libremidi::API::ANDROID_AMIDI;
|
||||
}
|
||||
|
||||
stdx::error open_port(const output_port& port, std::string_view /*name*/) override
|
||||
{
|
||||
if (is_port_open())
|
||||
{
|
||||
libremidi_handle_error(configuration, "midi_out::open_port: a port is already open");
|
||||
return std::errc{};
|
||||
}
|
||||
|
||||
const unsigned int port_number = port.port;
|
||||
|
||||
auto env = context::get_thread_env();
|
||||
if (!env)
|
||||
{
|
||||
libremidi_handle_error(configuration, "midi_out::open_port: failed to get JNI environment");
|
||||
return std::errc{};
|
||||
}
|
||||
|
||||
auto ctx = context::get_context(env);
|
||||
if (!ctx)
|
||||
{
|
||||
libremidi_handle_error(configuration, "midi_out::open_port: failed to get Android context");
|
||||
return std::errc{};
|
||||
}
|
||||
|
||||
context::refresh_midi_devices(env, ctx, true);
|
||||
|
||||
if (port_number >= context::midi_devices.size())
|
||||
{
|
||||
libremidi_handle_error(configuration, "midi_out::open_port: invalid port number");
|
||||
return std::errc{};
|
||||
}
|
||||
|
||||
port_open = true;
|
||||
context::open_device(context::midi_devices[port_number], this, true);
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (!is_port_open())
|
||||
return std::errc{};
|
||||
|
||||
if (midi_input_port)
|
||||
{
|
||||
AMidiInputPort_close(midi_input_port);
|
||||
midi_input_port = nullptr;
|
||||
}
|
||||
|
||||
if (send_device)
|
||||
{
|
||||
AMidiDevice_release(send_device);
|
||||
send_device = nullptr;
|
||||
}
|
||||
|
||||
port_open = false;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
bool is_port_open() const noexcept { return port_open; }
|
||||
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
if (!is_port_open() || !midi_input_port)
|
||||
{
|
||||
libremidi_handle_error(configuration, "midi_out::send_message: port is not open");
|
||||
return std::errc{};
|
||||
}
|
||||
|
||||
ssize_t result = AMidiInputPort_send(midi_input_port, message, size);
|
||||
if (result < 0)
|
||||
{
|
||||
libremidi_handle_error(configuration, "midi_out::send_message: failed to send MIDI data");
|
||||
return std::errc{};
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
static void open_callback(midi_out* self, AMidiDevice* device)
|
||||
{
|
||||
self->send_device = device;
|
||||
AMidiInputPort_open(device, 0, &self->midi_input_port);
|
||||
}
|
||||
|
||||
private:
|
||||
libremidi::output_configuration configuration;
|
||||
AMidiDevice* send_device = nullptr;
|
||||
AMidiInputPort* midi_input_port = nullptr;
|
||||
bool port_open = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/android/config.hpp>
|
||||
#include <libremidi/backends/android/helpers.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
NAMESPACE_LIBREMIDI
|
||||
{
|
||||
namespace android
|
||||
{
|
||||
class observer final
|
||||
: public libremidi::observer_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
explicit observer(
|
||||
libremidi::observer_configuration&& conf, libremidi::android::observer_configuration aconf)
|
||||
: configuration{std::move(conf)}
|
||||
{
|
||||
if (!context::client_name.empty() && context::client_name != aconf.client_name)
|
||||
{
|
||||
LOGW("Android backend only supports one client name per process");
|
||||
}
|
||||
context::client_name = std::string(aconf.client_name);
|
||||
}
|
||||
|
||||
~observer()
|
||||
{
|
||||
auto env = context::get_thread_env();
|
||||
if (env)
|
||||
{
|
||||
context::cleanup_devices(env);
|
||||
}
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override
|
||||
{
|
||||
return libremidi::API::ANDROID_AMIDI;
|
||||
}
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
auto env = context::get_thread_env();
|
||||
if (!env)
|
||||
return {};
|
||||
|
||||
auto ctx = context::get_context(env);
|
||||
if (!ctx)
|
||||
return {};
|
||||
|
||||
context::refresh_midi_devices(env, ctx, false);
|
||||
|
||||
std::vector<libremidi::input_port> ports;
|
||||
for (size_t i = 0; i < context::midi_devices.size(); ++i)
|
||||
{
|
||||
libremidi::input_port port;
|
||||
port.api = libremidi::API::ANDROID_AMIDI;
|
||||
port.port_name = context::port_name(env, i);
|
||||
port.port = i;
|
||||
ports.push_back(std::move(port));
|
||||
}
|
||||
|
||||
return ports;
|
||||
}
|
||||
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
auto env = context::get_thread_env();
|
||||
if (!env)
|
||||
return {};
|
||||
|
||||
auto ctx = context::get_context(env);
|
||||
if (!ctx)
|
||||
return {};
|
||||
|
||||
context::refresh_midi_devices(env, ctx, true);
|
||||
|
||||
std::vector<libremidi::output_port> ports;
|
||||
for (size_t i = 0; i < context::midi_devices.size(); ++i)
|
||||
{
|
||||
libremidi::output_port port;
|
||||
port.port_name = context::port_name(env, i);
|
||||
port.port = i;
|
||||
ports.push_back(std::move(port));
|
||||
}
|
||||
|
||||
return ports;
|
||||
}
|
||||
|
||||
private:
|
||||
libremidi::observer_configuration configuration;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//
|
||||
// *********************************************************************//
|
||||
|
||||
namespace libremidi
|
||||
NAMESPACE_LIBREMIDI
|
||||
{
|
||||
struct jack_backend
|
||||
{
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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>(
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)},
|
||||
}};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::net
|
||||
NAMESPACE_LIBREMIDI::net
|
||||
{
|
||||
struct backend
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace libremidi::net_ump
|
||||
NAMESPACE_LIBREMIDI::net_ump
|
||||
{
|
||||
struct backend
|
||||
{
|
||||
|
||||
@@ -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, ®istry_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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user