Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96bd5f30e6 | |||
| bd642576bb |
@@ -1,14 +1,14 @@
|
|||||||
CXX = g++
|
CXX = g++
|
||||||
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1
|
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1
|
||||||
CXXINC = -isysteminclude
|
CXXINC = -Iinclude
|
||||||
|
|
||||||
LDLIBS = -lraylib -lasound -llibremidi -pthread
|
LDLIBS = -lraylib -lasound -pthread
|
||||||
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib
|
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib
|
||||||
|
|
||||||
hdr_files = ${wildcard src/*.h}
|
hdr_files = ${wildcard src/*.h}
|
||||||
|
|
||||||
src_files = ${wildcard src/*.cpp}
|
src_files = ${wildcard src/*.cpp}
|
||||||
#src_files += ${wildcard include/libremidi/*.cpp}
|
src_files += ${wildcard include/libremidi/*.cpp}
|
||||||
|
|
||||||
all: viseq
|
all: viseq
|
||||||
|
|
||||||
@@ -20,13 +20,13 @@ $(src_files): %.cpp : %_src
|
|||||||
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
|
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
|
||||||
|
|
||||||
$(hdr_files): %.h : %_hdr
|
$(hdr_files): %.h : %_hdr
|
||||||
$(NOECHO) $(NOOP)
|
@echo "G++ >---------- $(*F).h -----------<"
|
||||||
|
|
||||||
%_src:
|
%_src:
|
||||||
$(NOECHO) $(NOOP)
|
@echo "G++ >---------- $(*F).cpp -----------<"
|
||||||
|
|
||||||
%_hdr:
|
%_hdr:
|
||||||
$(NOECHO) $(NOOP)
|
@echo "G++ >---------- $(*F).h -----------<"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f build/*
|
rm -f build/*
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ typedef enum libremidi_api
|
|||||||
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
|
WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */
|
||||||
KEYBOARD_UMP, /*!< Computer keyboard input */
|
KEYBOARD_UMP, /*!< Computer keyboard input */
|
||||||
NETWORK_UMP, /*!< MIDI2 over IP */
|
NETWORK_UMP, /*!< MIDI2 over IP */
|
||||||
|
JACK_UMP, /*!< MIDI2 over JACK, type "32 bit raw UMP". Requires PipeWire v1.4+. */
|
||||||
|
PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */
|
||||||
|
|
||||||
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
|
DUMMY = 0xFFFF /*!< A compilable but non-functional API. */
|
||||||
} libremidi_api;
|
} libremidi_api;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#include <libremidi/api-c.h>
|
#include <libremidi/api-c.h>
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#include <any>
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -31,9 +30,6 @@ LIBREMIDI_EXPORT std::vector<libremidi::API> available_apis() noexcept;
|
|||||||
*/
|
*/
|
||||||
LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;
|
LIBREMIDI_EXPORT std::vector<libremidi::API> available_ump_apis() noexcept;
|
||||||
|
|
||||||
LIBREMIDI_EXPORT
|
|
||||||
libremidi::API midi_api(const std::any& conf);
|
|
||||||
|
|
||||||
//! A static function to determine the current version.
|
//! A static function to determine the current version.
|
||||||
LIBREMIDI_EXPORT std::string_view get_version() noexcept;
|
LIBREMIDI_EXPORT std::string_view get_version() noexcept;
|
||||||
|
|
||||||
@@ -64,7 +60,7 @@ inline constexpr libremidi::API default_api() noexcept
|
|||||||
return API::COREMIDI;
|
return API::COREMIDI;
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
return API::WINDOWS_MM;
|
return API::WINDOWS_MM;
|
||||||
#elif defined(__linux__)
|
#elif defined(LIBREMIDI_ALSA)
|
||||||
return API::ALSA_SEQ;
|
return API::ALSA_SEQ;
|
||||||
#elif defined(__emscripten__)
|
#elif defined(__emscripten__)
|
||||||
return API::EMSCRIPTEN_WEBMIDI;
|
return API::EMSCRIPTEN_WEBMIDI;
|
||||||
@@ -83,7 +79,7 @@ inline constexpr libremidi::API default_api() noexcept
|
|||||||
return API::COREMIDI_UMP;
|
return API::COREMIDI_UMP;
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
return API::WINDOWS_MIDI_SERVICES;
|
return API::WINDOWS_MIDI_SERVICES;
|
||||||
#elif defined(__linux__)
|
#elif defined(LIBREMIDI_ALSA)
|
||||||
return API::ALSA_SEQ_UMP;
|
return API::ALSA_SEQ_UMP;
|
||||||
#elif defined(__emscripten__)
|
#elif defined(__emscripten__)
|
||||||
return API::DUMMY;
|
return API::DUMMY;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#include <any>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
|
#if !__has_include(<weak_libjack.h>) && !__has_include(<jack/jack.h>)
|
||||||
@@ -30,10 +29,14 @@
|
|||||||
|
|
||||||
#if defined(LIBREMIDI_JACK)
|
#if defined(LIBREMIDI_JACK)
|
||||||
#include <libremidi/backends/jack.hpp>
|
#include <libremidi/backends/jack.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(LIBREMIDI_PIPEWIRE)
|
#if defined(LIBREMIDI_PIPEWIRE)
|
||||||
#include <libremidi/backends/pipewire.hpp>
|
#include <libremidi/backends/pipewire.hpp>
|
||||||
|
#if defined(LIBREMIDI_PIPEWIRE_UMP)
|
||||||
|
#include <libremidi/backends/pipewire_ump.hpp>
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(LIBREMIDI_COREMIDI)
|
#if defined(LIBREMIDI_COREMIDI)
|
||||||
@@ -163,6 +166,14 @@ static constexpr auto available_backends = make_tl(
|
|||||||
#if defined(LIBREMIDI_NETWORK)
|
#if defined(LIBREMIDI_NETWORK)
|
||||||
,
|
,
|
||||||
net_ump::backend{}
|
net_ump::backend{}
|
||||||
|
#endif
|
||||||
|
#if defined(LIBREMIDI_JACK)
|
||||||
|
,
|
||||||
|
jack_ump::backend{}
|
||||||
|
#endif
|
||||||
|
#if defined(LIBREMIDI_PIPEWIRE_UMP)
|
||||||
|
,
|
||||||
|
pipewire_ump::backend{}
|
||||||
#endif
|
#endif
|
||||||
,
|
,
|
||||||
dummy_backend{});
|
dummy_backend{});
|
||||||
@@ -173,7 +184,7 @@ static_assert(std::tuple_size_v<decltype(available_backends)> >= 1);
|
|||||||
template <typename F>
|
template <typename F>
|
||||||
auto for_all_backends(F&& f)
|
auto for_all_backends(F&& f)
|
||||||
{
|
{
|
||||||
std::apply([&](auto&&... x) { (f(x), ...); }, available_backends);
|
std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
@@ -202,10 +213,10 @@ auto for_backend(libremidi::API api, F&& f)
|
|||||||
midi2::for_backend(api, f);
|
midi2::for_backend(api, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void for_input_configuration(auto f, std::any& api_conf)
|
void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf)
|
||||||
{
|
{
|
||||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
|
if (auto conf = std::get_if<typename T::midi_in_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
f(*conf);
|
f(*conf);
|
||||||
return true;
|
return true;
|
||||||
@@ -217,10 +228,10 @@ void for_input_configuration(auto f, std::any& api_conf)
|
|||||||
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||||
}
|
}
|
||||||
|
|
||||||
void for_output_configuration(auto f, std::any& api_conf)
|
void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf)
|
||||||
{
|
{
|
||||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))
|
if (auto conf = std::get_if<typename T::midi_out_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
f(*conf);
|
f(*conf);
|
||||||
return true;
|
return true;
|
||||||
@@ -232,10 +243,10 @@ void for_output_configuration(auto f, std::any& api_conf)
|
|||||||
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends);
|
||||||
}
|
}
|
||||||
|
|
||||||
void for_observer_configuration(auto f, std::any& api_conf)
|
void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf)
|
||||||
{
|
{
|
||||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))
|
if (auto conf = std::get_if<typename T::midi_observer_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
f(*conf);
|
f(*conf);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <libremidi/backends/alsa_raw/midi_out.hpp>
|
#include <libremidi/backends/alsa_raw/midi_out.hpp>
|
||||||
#include <libremidi/backends/alsa_raw/observer.hpp>
|
#include <libremidi/backends/alsa_raw/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
// Credits: greatly inspired from
|
// Credits: greatly inspired from
|
||||||
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c
|
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c
|
||||||
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
|
// https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c
|
||||||
@@ -19,8 +20,8 @@ struct backend
|
|||||||
using midi_out_configuration = alsa_raw_output_configuration;
|
using midi_out_configuration = alsa_raw_output_configuration;
|
||||||
using midi_observer_configuration = alsa_raw_observer_configuration;
|
using midi_observer_configuration = alsa_raw_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::ALSA_RAW;
|
static const constexpr auto API = libremidi::API::ALSA_RAW;
|
||||||
static const constexpr auto name = "alsa_raw";
|
static const constexpr std::string_view name = "alsa_raw";
|
||||||
static const constexpr auto display_name = "ALSA (raw)";
|
static const constexpr std::string_view display_name = "ALSA (raw)";
|
||||||
|
|
||||||
static inline bool available() noexcept
|
static inline bool available() noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|||||||
@@ -2,10 +2,17 @@
|
|||||||
#include <libremidi/backends/linux/alsa.hpp>
|
#include <libremidi/backends/linux/alsa.hpp>
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
#include <libremidi/detail/observer.hpp>
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
#include <libremidi/observer_configuration.hpp>
|
||||||
|
|
||||||
#include <functional>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <system_error>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Credits: greatly inspired from
|
// Credits: greatly inspired from
|
||||||
@@ -27,14 +34,14 @@ struct alsa_raw_port_id
|
|||||||
};
|
};
|
||||||
inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept
|
inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept
|
||||||
{
|
{
|
||||||
return (uint64_t(id.card) << 32) + (uint64_t(id.dev) << 16) + id.port;
|
return (uint64_t(id.port) << 32) + (uint64_t(id.dev) << 16) + id.card;
|
||||||
}
|
}
|
||||||
inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept
|
inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept
|
||||||
{
|
{
|
||||||
alsa_raw_port_id ret;
|
alsa_raw_port_id ret;
|
||||||
ret.card = (p & 0x00'00'FF'FF'00'00'00'00) >> 32;
|
ret.port = (p & 0x00'00'FF'FF'00'00'00'00) >> 32;
|
||||||
ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16;
|
ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16;
|
||||||
ret.port = (p & 0x00'00'00'00'00'00'FF'FF);
|
ret.card = (p & 0x00'00'00'00'00'00'FF'FF);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102);
|
static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102);
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ public:
|
|||||||
midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
|
midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf)
|
||||||
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
if (this->termination_event < 0)
|
if (this->m_termination_event < 0)
|
||||||
{
|
{
|
||||||
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||||
return;
|
return;
|
||||||
@@ -220,7 +220,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
void run_thread(auto parse_func)
|
void run_thread(auto parse_func)
|
||||||
{
|
{
|
||||||
fds_.push_back(this->termination_event);
|
fds_.push_back(this->m_termination_event);
|
||||||
const auto period
|
const auto period
|
||||||
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||||
.count();
|
.count();
|
||||||
@@ -228,12 +228,12 @@ private:
|
|||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
// Poll
|
// Poll
|
||||||
ssize_t err = poll(fds_.data(), fds_.size(), period);
|
ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
|
||||||
if (err == -EAGAIN)
|
if (err == -EAGAIN)
|
||||||
continue;
|
continue;
|
||||||
else if (err < 0)
|
else if (err < 0)
|
||||||
return;
|
return;
|
||||||
else if (termination_event.ready(fds_.back()))
|
else if (m_termination_event.ready(fds_.back()))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
|
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
|
||||||
@@ -250,11 +250,11 @@ private:
|
|||||||
{
|
{
|
||||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||||
{
|
{
|
||||||
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this->thread_ = std::thread{
|
this->m_thread = std::thread{
|
||||||
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
|
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
|
||||||
}
|
}
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
@@ -282,16 +282,16 @@ private:
|
|||||||
|
|
||||||
stdx::error close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
termination_event.notify();
|
m_termination_event.notify();
|
||||||
if (thread_.joinable())
|
if (m_thread.joinable())
|
||||||
thread_.join();
|
m_thread.join();
|
||||||
termination_event.consume(); // Reset to zero
|
m_termination_event.consume(); // Reset to zero
|
||||||
|
|
||||||
return midi_in_impl::close_port();
|
return midi_in_impl::close_port();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread thread_;
|
std::thread m_thread;
|
||||||
eventfd_notifier termination_event{};
|
eventfd_notifier m_termination_event{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class midi_in_alsa_raw_manual : public midi_in_impl
|
class midi_in_alsa_raw_manual : public midi_in_impl
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
#if LIBREMIDI_HAS_UDEV
|
#if LIBREMIDI_HAS_UDEV
|
||||||
#include <libremidi/backends/linux/helpers.hpp>
|
#include <libremidi/backends/linux/helpers.hpp>
|
||||||
#include <libremidi/backends/linux/udev.hpp>
|
#include <libremidi/backends/linux/udev.hpp>
|
||||||
#include <libremidi/detail/observer.hpp>
|
#endif
|
||||||
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
namespace libremidi::alsa_raw
|
namespace libremidi::alsa_raw
|
||||||
{
|
{
|
||||||
@@ -31,24 +32,30 @@ public:
|
|||||||
if (!configuration.has_callbacks())
|
if (!configuration.has_callbacks())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fds[0] = this->udev;
|
#if LIBREMIDI_HAS_UDEV
|
||||||
fds[1] = termination_event;
|
m_fds[0] = this->m_udev;
|
||||||
fds[2] = timer_fd;
|
m_fds[1] = m_termination_event;
|
||||||
|
m_fds[2] = m_timer_fd;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Set-up initial state
|
// Set-up initial state
|
||||||
if (configuration.notify_in_constructor)
|
if (configuration.notify_in_constructor)
|
||||||
this->check_devices();
|
this->check_devices();
|
||||||
|
|
||||||
|
#if LIBREMIDI_HAS_UDEV
|
||||||
// Start thread
|
// Start thread
|
||||||
thread = std::thread{[this] { this->run(); }};
|
m_thread = std::thread{[this] { this->run(); }};
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
~observer_impl_base()
|
~observer_impl_base()
|
||||||
{
|
{
|
||||||
termination_event.notify();
|
#if LIBREMIDI_HAS_UDEV
|
||||||
|
m_termination_event.notify();
|
||||||
|
|
||||||
if (thread.joinable())
|
if (m_thread.joinable())
|
||||||
thread.join();
|
m_thread.join();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
@@ -78,11 +85,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#if LIBREMIDI_HAS_UDEV
|
||||||
void run()
|
void run()
|
||||||
{
|
{
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (int err = poll(fds, 3, -1); err < 0)
|
if (int err = poll(m_fds, 3, -1); err < 0)
|
||||||
{
|
{
|
||||||
if (err == -EAGAIN)
|
if (err == -EAGAIN)
|
||||||
continue;
|
continue;
|
||||||
@@ -91,58 +99,77 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check udev
|
// Check udev
|
||||||
if (fds[0].revents & POLLIN)
|
if (m_fds[0].revents & POLLIN)
|
||||||
{
|
{
|
||||||
udev_device* dev = udev.udev.monitor_receive_device(udev.monitor);
|
udev_device* dev = m_udev.udev.monitor_receive_device(m_udev.monitor);
|
||||||
if (!dev)
|
if (!dev)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::string_view act = udev.udev.device_get_action(dev);
|
std::string_view act = m_udev.udev.device_get_action(dev);
|
||||||
std::string_view ss = udev.udev.device_get_subsystem(dev);
|
std::string_view ss = m_udev.udev.device_get_subsystem(dev);
|
||||||
if (!act.empty() && ss == "snd_seq")
|
if (!act.empty() && ss == "snd_seq")
|
||||||
{
|
{
|
||||||
if (act == "add" || act == "remove")
|
if (act == "add" || act == "remove")
|
||||||
{
|
{
|
||||||
// Check every 100 milliseconds for ten seconds
|
// Check every 100 milliseconds for ten seconds
|
||||||
this->timer_fd.restart(configuration.poll_period.count());
|
this->m_timer_fd.restart(configuration.poll_period.count());
|
||||||
timer_check_counts = 100;
|
m_timer_check_counts = 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
udev.udev.device_unref(dev);
|
m_udev.udev.device_unref(dev);
|
||||||
|
|
||||||
fds[0].revents = 0;
|
m_fds[0].revents = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check eventfd
|
// Check eventfd
|
||||||
if (fds[1].revents & POLLIN)
|
if (m_fds[1].revents & POLLIN)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check timer
|
// Check timer
|
||||||
if (fds[2].revents & POLLIN)
|
if (m_fds[2].revents & POLLIN)
|
||||||
{
|
{
|
||||||
if (this->timer_check_counts-- <= 0)
|
if (this->m_timer_check_counts-- <= 0)
|
||||||
this->timer_fd.cancel();
|
this->m_timer_fd.cancel();
|
||||||
fds[2].revents = 0;
|
m_fds[2].revents = 0;
|
||||||
|
|
||||||
check_devices();
|
check_devices();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <bool Input>
|
template <bool Input>
|
||||||
auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept
|
auto to_port_info(const alsa_raw::alsa_raw_port_info& p) const noexcept
|
||||||
-> std::conditional_t<Input, input_port, output_port>
|
-> std::conditional_t<Input, input_port, output_port>
|
||||||
{
|
{
|
||||||
|
#if LIBREMIDI_HAS_UDEV
|
||||||
|
auto [container, device, type] = get_udev_soundcard_info(m_udev, p.card);
|
||||||
|
#else
|
||||||
|
container_identifier container{};
|
||||||
|
device_identifier device{};
|
||||||
|
libremidi::port_information::port_type type{};
|
||||||
|
#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 = "unknown";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
{.client = 0,
|
{.client = 0,
|
||||||
|
.container = container,
|
||||||
|
.device = device,
|
||||||
.port = raw_to_port_handle({p.card, p.dev, p.sub}),
|
.port = raw_to_port_handle({p.card, p.dev, p.sub}),
|
||||||
.manufacturer = p.card_name,
|
.manufacturer = p.card_name,
|
||||||
.device_name = p.device_name,
|
.device_name = p.device_name,
|
||||||
.port_name = p.subdevice_name,
|
.port_name = p.subdevice_name,
|
||||||
.display_name = p.subdevice_name}};
|
.display_name = std::move(display_name),
|
||||||
|
.type = type}};
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_devices()
|
void check_devices()
|
||||||
@@ -151,7 +178,7 @@ private:
|
|||||||
|
|
||||||
new_devs.enumerate_cards();
|
new_devs.enumerate_cards();
|
||||||
|
|
||||||
for (auto& in_prev : current_inputs)
|
for (auto& in_prev : m_current_inputs)
|
||||||
{
|
{
|
||||||
if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);
|
if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev);
|
||||||
it == new_devs.inputs.end())
|
it == new_devs.inputs.end())
|
||||||
@@ -165,8 +192,8 @@ private:
|
|||||||
|
|
||||||
for (auto& in_next : new_devs.inputs)
|
for (auto& in_next : new_devs.inputs)
|
||||||
{
|
{
|
||||||
if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next);
|
if (auto it = std::find(m_current_inputs.begin(), m_current_inputs.end(), in_next);
|
||||||
it == current_inputs.end())
|
it == m_current_inputs.end())
|
||||||
{
|
{
|
||||||
if (auto& cb = this->configuration.input_added)
|
if (auto& cb = this->configuration.input_added)
|
||||||
{
|
{
|
||||||
@@ -175,7 +202,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& out_prev : current_outputs)
|
for (auto& out_prev : m_current_outputs)
|
||||||
{
|
{
|
||||||
if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);
|
if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev);
|
||||||
it == new_devs.outputs.end())
|
it == new_devs.outputs.end())
|
||||||
@@ -189,8 +216,8 @@ private:
|
|||||||
|
|
||||||
for (auto& out_next : new_devs.outputs)
|
for (auto& out_next : new_devs.outputs)
|
||||||
{
|
{
|
||||||
if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next);
|
if (auto it = std::find(m_current_outputs.begin(), m_current_outputs.end(), out_next);
|
||||||
it == current_outputs.end())
|
it == m_current_outputs.end())
|
||||||
{
|
{
|
||||||
if (auto& cb = this->configuration.output_added)
|
if (auto& cb = this->configuration.output_added)
|
||||||
{
|
{
|
||||||
@@ -198,38 +225,24 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
current_inputs = std::move(new_devs.inputs);
|
m_current_inputs = std::move(new_devs.inputs);
|
||||||
current_outputs = std::move(new_devs.outputs);
|
m_current_outputs = std::move(new_devs.outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
udev_helper udev{};
|
#if LIBREMIDI_HAS_UDEV
|
||||||
eventfd_notifier termination_event{};
|
udev_helper m_udev{};
|
||||||
timerfd_timer timer_fd{};
|
eventfd_notifier m_termination_event{};
|
||||||
int timer_check_counts = 0;
|
timerfd_timer m_timer_fd{};
|
||||||
std::thread thread;
|
int m_timer_check_counts = 0;
|
||||||
std::vector<alsa_raw_port_info> current_inputs;
|
std::thread m_thread;
|
||||||
std::vector<alsa_raw_port_info> current_outputs;
|
pollfd m_fds[3]{};
|
||||||
|
|
||||||
pollfd fds[3]{};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#include <libremidi/backends/dummy.hpp>
|
|
||||||
namespace libremidi::alsa_raw
|
|
||||||
{
|
|
||||||
template <typename Enumerator>
|
|
||||||
struct observer_impl_base : observer_dummy
|
|
||||||
{
|
|
||||||
explicit observer_impl_base(
|
|
||||||
[[maybe_unused]] observer_configuration&& conf,
|
|
||||||
[[maybe_unused]] alsa_raw_observer_configuration&& apiconf)
|
|
||||||
: observer_dummy{dummy_configuration{}, dummy_configuration{}}
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
std::vector<alsa_raw_port_info> m_current_inputs;
|
||||||
|
std::vector<alsa_raw_port_info> m_current_outputs;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
namespace libremidi::alsa_raw
|
namespace libremidi::alsa_raw
|
||||||
{
|
{
|
||||||
struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>
|
struct observer_impl : observer_impl_base<alsa_raw::midi1_enumerator>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <libremidi/backends/alsa_raw_ump/observer.hpp>
|
#include <libremidi/backends/alsa_raw_ump/observer.hpp>
|
||||||
#include <libremidi/backends/dummy.hpp>
|
#include <libremidi/backends/dummy.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi::alsa_raw_ump
|
namespace libremidi::alsa_raw_ump
|
||||||
{
|
{
|
||||||
struct backend
|
struct backend
|
||||||
@@ -14,12 +16,11 @@ struct backend
|
|||||||
using midi_observer = observer_impl;
|
using midi_observer = observer_impl;
|
||||||
using midi_in_configuration = alsa_raw_ump::input_configuration;
|
using midi_in_configuration = alsa_raw_ump::input_configuration;
|
||||||
using midi_out_configuration = alsa_raw_ump::output_configuration;
|
using midi_out_configuration = alsa_raw_ump::output_configuration;
|
||||||
struct midi_observer_configuration : alsa_raw_observer_configuration
|
using midi_observer_configuration = alsa_raw_ump::observer_configuration;
|
||||||
{
|
|
||||||
};
|
|
||||||
static const constexpr auto API = libremidi::API::ALSA_RAW_UMP;
|
static const constexpr auto API = libremidi::API::ALSA_RAW_UMP;
|
||||||
static const constexpr auto name = "alsa_raw_ump";
|
static const constexpr std::string_view name = "alsa_raw_ump";
|
||||||
static const constexpr auto display_name = "ALSA (raw UMP)";
|
static const constexpr std::string_view display_name = "ALSA (raw UMP)";
|
||||||
|
|
||||||
static inline bool available() noexcept
|
static inline bool available() noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/backends/alsa_raw/config.hpp>
|
#include <libremidi/backends/alsa_raw/config.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace libremidi::alsa_raw_ump
|
namespace libremidi::alsa_raw_ump
|
||||||
{
|
{
|
||||||
struct input_configuration
|
struct input_configuration
|
||||||
@@ -18,8 +21,7 @@ struct output_configuration
|
|||||||
std::optional<chunking_parameters> chunking;
|
std::optional<chunking_parameters> chunking;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct observer_configuration
|
struct observer_configuration : public alsa_raw_observer_configuration
|
||||||
{
|
{
|
||||||
std::chrono::milliseconds poll_period{100};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
#include <libremidi/detail/observer.hpp>
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
namespace libremidi::alsa_raw_ump
|
namespace libremidi::alsa_raw_ump
|
||||||
{
|
{
|
||||||
struct midi2_enumerator : alsa_raw::enumerator
|
struct midi2_enumerator : alsa_raw::enumerator
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public:
|
|||||||
libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
|
libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf)
|
||||||
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
: midi_in_impl{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
if (this->termination_event < 0)
|
if (this->m_termination_event < 0)
|
||||||
{
|
{
|
||||||
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||||
return;
|
return;
|
||||||
@@ -206,14 +206,14 @@ public:
|
|||||||
~midi_in_impl_threaded()
|
~midi_in_impl_threaded()
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
this->close_port();
|
midi_in_impl_threaded::close_port();
|
||||||
client_open_ = std::errc::not_connected;
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void run_thread(auto parse_func)
|
void run_thread(auto parse_func)
|
||||||
{
|
{
|
||||||
fds_.push_back(this->termination_event);
|
fds_.push_back(this->m_termination_event);
|
||||||
const auto period
|
const auto period
|
||||||
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
|
||||||
.count();
|
.count();
|
||||||
@@ -221,12 +221,12 @@ private:
|
|||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
// Poll
|
// Poll
|
||||||
ssize_t err = poll(fds_.data(), fds_.size(), period);
|
ssize_t err = poll(fds_.data(), fds_.size(), static_cast<int32_t>(period));
|
||||||
if (err == -EAGAIN)
|
if (err == -EAGAIN)
|
||||||
continue;
|
continue;
|
||||||
else if (err < 0)
|
else if (err < 0)
|
||||||
return;
|
return;
|
||||||
else if (termination_event.ready(fds_.back()))
|
else if (m_termination_event.ready(fds_.back()))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
|
err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1});
|
||||||
@@ -243,11 +243,11 @@ private:
|
|||||||
{
|
{
|
||||||
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
if (configuration.timestamps == timestamp_mode::NoTimestamp)
|
||||||
{
|
{
|
||||||
this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this->thread_ = std::thread{
|
this->m_thread = std::thread{
|
||||||
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
|
[this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }};
|
||||||
}
|
}
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
@@ -275,16 +275,16 @@ private:
|
|||||||
|
|
||||||
stdx::error close_port() override
|
stdx::error close_port() override
|
||||||
{
|
{
|
||||||
termination_event.notify();
|
m_termination_event.notify();
|
||||||
if (thread_.joinable())
|
if (m_thread.joinable())
|
||||||
thread_.join();
|
m_thread.join();
|
||||||
termination_event.consume(); // Reset to zero
|
m_termination_event.consume(); // Reset to zero
|
||||||
|
|
||||||
return midi_in_impl::close_port();
|
return midi_in_impl::close_port();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread thread_;
|
std::thread m_thread;
|
||||||
eventfd_notifier termination_event{};
|
eventfd_notifier m_termination_event{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class midi_in_impl_manual : public midi_in_impl
|
class midi_in_impl_manual : public midi_in_impl
|
||||||
@@ -299,7 +299,7 @@ public:
|
|||||||
~midi_in_impl_manual()
|
~midi_in_impl_manual()
|
||||||
{
|
{
|
||||||
// Close a connection if it exists.
|
// Close a connection if it exists.
|
||||||
this->close_port();
|
midi_in_impl_manual::close_port();
|
||||||
|
|
||||||
client_open_ = std::errc::not_connected;
|
client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
||||||
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
|
#include <libremidi/backends/alsa_raw_ump/helpers.hpp>
|
||||||
|
#include <libremidi/backends/linux/alsa.hpp>
|
||||||
#include <libremidi/detail/midi_out.hpp>
|
#include <libremidi/detail/midi_out.hpp>
|
||||||
|
#include <libremidi/error_handler.hpp>
|
||||||
|
#include <libremidi/output_configuration.hpp>
|
||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <cassert>
|
||||||
#include <thread>
|
#include <cstdint>
|
||||||
|
#include <system_error>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace libremidi::alsa_raw_ump
|
namespace libremidi::alsa_raw_ump
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
#include <libremidi/backends/alsa_seq/midi_out.hpp>
|
#include <libremidi/backends/alsa_seq/midi_out.hpp>
|
||||||
#include <libremidi/backends/alsa_seq/observer.hpp>
|
#include <libremidi/backends/alsa_seq/observer.hpp>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi::alsa_seq
|
namespace libremidi::alsa_seq
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -29,13 +33,16 @@ struct backend
|
|||||||
using midi_out_configuration = alsa_seq::output_configuration;
|
using midi_out_configuration = alsa_seq::output_configuration;
|
||||||
using midi_observer_configuration = alsa_seq::observer_configuration;
|
using midi_observer_configuration = alsa_seq::observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::ALSA_SEQ;
|
static const constexpr auto API = libremidi::API::ALSA_SEQ;
|
||||||
static const constexpr auto name = "alsa_seq";
|
static const constexpr std::string_view name = "alsa_seq";
|
||||||
static const constexpr auto display_name = "ALSA (sequencer)";
|
static const constexpr std::string_view display_name = "ALSA (sequencer)";
|
||||||
|
|
||||||
static inline bool available() noexcept
|
static inline bool available() noexcept
|
||||||
{
|
{
|
||||||
static const libasound& snd = libasound::instance();
|
static const libasound& snd = libasound::instance();
|
||||||
return snd.available && snd.seq.available;
|
if (!snd.available || !snd.seq.available)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ::access("/dev/snd/seq", F_OK) == 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,19 +46,19 @@ namespace
|
|||||||
{
|
{
|
||||||
inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept
|
inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept
|
||||||
{
|
{
|
||||||
return (client << 32) + port;
|
return (port << 32) + client;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept
|
inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcept
|
||||||
{
|
{
|
||||||
int client = p >> 32;
|
int port = p >> 32;
|
||||||
int port = p & 0xFFFFFFFF;
|
int client = p & 0xFFFFFFFF;
|
||||||
return {client, port};
|
return {client, port};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void for_all_ports(
|
inline void for_all_ports(
|
||||||
const libasound& snd, snd_seq_t* seq,
|
const libasound& snd, snd_seq_t* seq,
|
||||||
std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)> func)
|
const std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)>& func)
|
||||||
{
|
{
|
||||||
snd_seq_client_info_t* cinfo{};
|
snd_seq_client_info_t* cinfo{};
|
||||||
snd_seq_client_info_alloca(&cinfo);
|
snd_seq_client_info_alloca(&cinfo);
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ public:
|
|||||||
.count();
|
.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
int process_event(const snd_seq_event_t& ev)
|
int64_t process_event(const snd_seq_event_t& ev)
|
||||||
{
|
{
|
||||||
if constexpr (ConfigurationImpl::midi_version == 1)
|
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||||
{
|
{
|
||||||
@@ -258,17 +258,17 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int process_events()
|
int64_t process_events()
|
||||||
{
|
{
|
||||||
if constexpr (ConfigurationImpl::midi_version == 1)
|
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||||
{
|
{
|
||||||
snd_seq_event_t* ev{};
|
snd_seq_event_t* ev{};
|
||||||
event_handle handle{snd};
|
event_handle handle{snd};
|
||||||
int result = 0;
|
int64_t result = 0;
|
||||||
if ((result = snd.seq.event_input(seq, &ev)) > 0)
|
if ((result = snd.seq.event_input(seq, &ev)) > 0)
|
||||||
{
|
{
|
||||||
handle.reset(ev);
|
handle.reset(ev);
|
||||||
if (int err = process_event(*ev); err < 0)
|
if (auto err = process_event(*ev); err < 0)
|
||||||
{
|
{
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -353,7 +353,7 @@ public:
|
|||||||
midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)
|
midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf)
|
||||||
: midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}
|
: midi_in_impl<ConfigurationBase, ConfigurationImpl>{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
if (this->termination_event < 0)
|
if (this->m_termination_event < 0)
|
||||||
{
|
{
|
||||||
this->libremidi_handle_error(this->configuration, "error creating eventfd.");
|
this->libremidi_handle_error(this->configuration, "error creating eventfd.");
|
||||||
return;
|
return;
|
||||||
@@ -364,7 +364,7 @@ public:
|
|||||||
|
|
||||||
~midi_in_alsa_threaded()
|
~midi_in_alsa_threaded()
|
||||||
{
|
{
|
||||||
this->close_port();
|
midi_in_alsa_threaded::close_port();
|
||||||
this->client_open_ = std::errc::not_connected;
|
this->client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,7 +399,7 @@ private:
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this->thread = std::thread([this] { thread_handler(); });
|
this->m_thread = std::thread([this] { thread_handler(); });
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
catch (const std::system_error& e)
|
catch (const std::system_error& e)
|
||||||
@@ -415,12 +415,12 @@ private:
|
|||||||
|
|
||||||
stdx::error stop_thread()
|
stdx::error stop_thread()
|
||||||
{
|
{
|
||||||
termination_event.notify();
|
m_termination_event.notify();
|
||||||
|
|
||||||
if (this->thread.joinable())
|
if (this->m_thread.joinable())
|
||||||
this->thread.join();
|
this->m_thread.join();
|
||||||
|
|
||||||
termination_event.consume();
|
m_termination_event.consume();
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +428,7 @@ private:
|
|||||||
{
|
{
|
||||||
int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1;
|
int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1;
|
||||||
auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd));
|
auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd));
|
||||||
poll_fds[0] = this->termination_event;
|
poll_fds[0] = this->m_termination_event;
|
||||||
alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);
|
alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN);
|
||||||
|
|
||||||
const auto period
|
const auto period
|
||||||
@@ -439,10 +439,10 @@ private:
|
|||||||
if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)
|
if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0)
|
||||||
{
|
{
|
||||||
// No data pending
|
// No data pending
|
||||||
if (poll(poll_fds, poll_fd_count, period) >= 0)
|
if (poll(poll_fds, poll_fd_count, static_cast<int32_t>(period)) >= 0)
|
||||||
{
|
{
|
||||||
// We got our stop-thread signal
|
// We got our stop-thread signal
|
||||||
if (termination_event.ready(poll_fds[0]))
|
if (m_termination_event.ready(poll_fds[0]))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -450,7 +450,7 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int res{};
|
int64_t res{};
|
||||||
if constexpr (ConfigurationImpl::midi_version == 1)
|
if constexpr (ConfigurationImpl::midi_version == 1)
|
||||||
{
|
{
|
||||||
res = this->process_events();
|
res = this->process_events();
|
||||||
@@ -467,8 +467,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread thread{};
|
std::thread m_thread{};
|
||||||
eventfd_notifier termination_event{};
|
eventfd_notifier m_termination_event{};
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename ConfigurationBase, typename ConfigurationImpl>
|
template <typename ConfigurationBase, typename ConfigurationImpl>
|
||||||
@@ -497,7 +497,7 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_in_alsa_manual() { this->close_port(); }
|
~midi_in_alsa_manual() { midi_in_alsa_manual::close_port(); }
|
||||||
|
|
||||||
stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
|
stdx::error open_port(const input_port& pt, std::string_view local_port_name) override
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snd.midi.event_new(this->bufferSize, &this->coder) < 0)
|
if (snd.midi.event_new(this->m_bufferSize, &this->coder) < 0)
|
||||||
{
|
{
|
||||||
libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");
|
libremidi_handle_error(this->configuration, "error initializing MIDI event parser.");
|
||||||
return;
|
return;
|
||||||
@@ -68,8 +68,9 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const output_port& p, std::string_view portName) override
|
stdx::error open_port(const output_port& p, std::string_view portName) override
|
||||||
{
|
{
|
||||||
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
unsigned int n_src
|
||||||
if (nSrc < 1)
|
= this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||||
|
if (n_src < 1)
|
||||||
{
|
{
|
||||||
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
||||||
return make_error_code(std::errc::no_such_device);
|
return make_error_code(std::errc::no_such_device);
|
||||||
@@ -122,9 +123,9 @@ public:
|
|||||||
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
||||||
{
|
{
|
||||||
int64_t result{};
|
int64_t result{};
|
||||||
if (size > this->bufferSize)
|
if (size > this->m_bufferSize)
|
||||||
{
|
{
|
||||||
this->bufferSize = size;
|
this->m_bufferSize = size;
|
||||||
result = snd.midi.event_resize_buffer(this->coder, size);
|
result = snd.midi.event_resize_buffer(this->coder, size);
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
@@ -146,8 +147,8 @@ public:
|
|||||||
// FIXME direct is set but snd_seq_event_output_direct is not used...
|
// FIXME direct is set but snd_seq_event_output_direct is not used...
|
||||||
snd_seq_ev_set_direct(&ev);
|
snd_seq_ev_set_direct(&ev);
|
||||||
|
|
||||||
const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below
|
const int64_t n_bytes = size; // signed to avoir potential overflow with size - offset below
|
||||||
result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev);
|
result = snd.midi.event_encode(this->coder, message + offset, (long)(n_bytes - offset), &ev);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
{
|
{
|
||||||
libremidi_handle_warning(this->configuration, "event parsing error!");
|
libremidi_handle_warning(this->configuration, "event parsing error!");
|
||||||
@@ -174,6 +175,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t bufferSize{32};
|
uint64_t m_bufferSize{32};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
#include <bitset>
|
#if LIBREMIDI_HAS_UDEV
|
||||||
|
#include <libremidi/backends/linux/udev.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace libremidi::alsa_seq
|
namespace libremidi::alsa_seq
|
||||||
@@ -21,6 +24,8 @@ struct port_info
|
|||||||
int port{};
|
int port{};
|
||||||
bool isInput{};
|
bool isInput{};
|
||||||
bool isOutput{};
|
bool isOutput{};
|
||||||
|
std::optional<int> card{};
|
||||||
|
libremidi::port_information::port_type type{};
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename ConfigurationImpl>
|
template <typename ConfigurationImpl>
|
||||||
@@ -107,13 +112,19 @@ public:
|
|||||||
const auto tp = snd.seq.port_info_get_type(pinfo);
|
const auto tp = snd.seq.port_info_get_type(pinfo);
|
||||||
bool ok = this->configuration.track_any;
|
bool ok = this->configuration.track_any;
|
||||||
|
|
||||||
static constexpr auto virtual_port
|
static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER
|
||||||
= 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)
|
if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware)
|
||||||
|
{
|
||||||
|
p.type = libremidi::port_information::port_type::hardware;
|
||||||
ok = true;
|
ok = true;
|
||||||
|
}
|
||||||
else if ((tp & virtual_port) && this->configuration.track_virtual)
|
else if ((tp & virtual_port) && this->configuration.track_virtual)
|
||||||
|
{
|
||||||
|
p.type = libremidi::port_information::port_type::software;
|
||||||
ok = true;
|
ok = true;
|
||||||
|
}
|
||||||
if (!ok)
|
if (!ok)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
@@ -123,7 +134,10 @@ public:
|
|||||||
if (auto name = snd.seq.port_info_get_name(pinfo))
|
if (auto name = snd.seq.port_info_get_name(pinfo))
|
||||||
p.port_name = name;
|
p.port_name = name;
|
||||||
|
|
||||||
auto cap = snd.seq.port_info_get_capability(pinfo);
|
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.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);
|
p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE);
|
||||||
|
|
||||||
@@ -131,18 +145,34 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <bool Input>
|
template <bool Input>
|
||||||
auto to_port_info(port_info p) const noexcept
|
auto to_port_info(const port_info& p) const noexcept
|
||||||
-> std::conditional_t<Input, input_port, output_port>
|
-> std::conditional_t<Input, input_port, output_port>
|
||||||
{
|
{
|
||||||
static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle));
|
static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle));
|
||||||
static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle));
|
static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle));
|
||||||
|
|
||||||
|
container_identifier container{};
|
||||||
|
device_identifier device{};
|
||||||
|
libremidi::port_information::port_type type = p.type;
|
||||||
|
#if LIBREMIDI_HAS_UDEV
|
||||||
|
if (p.card)
|
||||||
|
{
|
||||||
|
auto res = get_udev_soundcard_info(m_udev, *p.card);
|
||||||
|
container = res.container;
|
||||||
|
device = res.path;
|
||||||
|
type = res.type;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return {
|
return {
|
||||||
{.client = std::uintptr_t(this->seq),
|
{.client = std::uintptr_t(this->seq),
|
||||||
|
.container = container,
|
||||||
|
.device = device,
|
||||||
.port = alsa_seq::seq_to_port_handle(p.client, p.port),
|
.port = alsa_seq::seq_to_port_handle(p.client, p.port),
|
||||||
.manufacturer = "",
|
.manufacturer = "",
|
||||||
.device_name = p.client_name,
|
.device_name = p.client_name,
|
||||||
.port_name = p.port_name,
|
.port_name = p.port_name,
|
||||||
.display_name = p.port_name}};
|
.display_name = p.port_name,
|
||||||
|
.type = type}};
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_all_ports()
|
void init_all_ports()
|
||||||
@@ -200,7 +230,7 @@ public:
|
|||||||
if (p.client == snd.seq.client_id(seq))
|
if (p.client == snd.seq.client_id(seq))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
knownClients_[{p.client, p.port}] = p;
|
m_knownClients[{p.client, p.port}] = p;
|
||||||
if (p.isInput && configuration.input_added)
|
if (p.isInput && configuration.input_added)
|
||||||
{
|
{
|
||||||
configuration.input_added(to_port_info<true>(p));
|
configuration.input_added(to_port_info<true>(p));
|
||||||
@@ -214,11 +244,11 @@ public:
|
|||||||
|
|
||||||
void unregister_port(int client, int port)
|
void unregister_port(int client, int port)
|
||||||
{
|
{
|
||||||
auto it = knownClients_.find({client, port});
|
auto it = m_knownClients.find({client, port});
|
||||||
if (it != knownClients_.end())
|
if (it != m_knownClients.end())
|
||||||
{
|
{
|
||||||
auto p = it->second;
|
auto p = it->second;
|
||||||
knownClients_.erase(it);
|
m_knownClients.erase(it);
|
||||||
|
|
||||||
if (p.isInput && configuration.input_removed)
|
if (p.isInput && configuration.input_removed)
|
||||||
{
|
{
|
||||||
@@ -263,7 +293,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<std::pair<int, int>, port_info> knownClients_;
|
std::map<std::pair<int, int>, port_info> m_knownClients;
|
||||||
|
|
||||||
|
#if LIBREMIDI_HAS_UDEV
|
||||||
|
udev_helper m_udev{};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename ConfigurationImpl>
|
template <typename ConfigurationImpl>
|
||||||
@@ -275,9 +309,9 @@ public:
|
|||||||
{
|
{
|
||||||
// Create relevant descriptors
|
// Create relevant descriptors
|
||||||
auto& snd = alsa_data::snd;
|
auto& snd = alsa_data::snd;
|
||||||
const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN);
|
const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN);
|
||||||
descriptors_.resize(N + 1);
|
descriptors_.resize(n + 1);
|
||||||
snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN);
|
snd.seq.poll_descriptors(this->seq, descriptors_.data(), n, POLLIN);
|
||||||
descriptors_.back() = this->termination_event;
|
descriptors_.back() = this->termination_event;
|
||||||
|
|
||||||
// Start the listening thread
|
// Start the listening thread
|
||||||
@@ -288,7 +322,7 @@ public:
|
|||||||
.count();
|
.count();
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
int err = poll(descriptors_.data(), descriptors_.size(), period);
|
int err = poll(descriptors_.data(), descriptors_.size(), static_cast<int32_t>(period));
|
||||||
if (err >= 0)
|
if (err >= 0)
|
||||||
{
|
{
|
||||||
// We got our stop-thread signal
|
// We got our stop-thread signal
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ struct shared_handler : public libremidi::shared_context
|
|||||||
{
|
{
|
||||||
auto clt = std::make_shared<shared_handler>(client_name);
|
auto clt = std::make_shared<shared_handler>(client_name);
|
||||||
|
|
||||||
auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) {
|
auto cb = [client = std::weak_ptr{clt}](libremidi::alsa_seq::poll_parameters params) {
|
||||||
if (auto clt = client.lock())
|
if (auto clt = client.lock())
|
||||||
{
|
{
|
||||||
clt->events.push(
|
clt->events.push(
|
||||||
@@ -74,8 +74,7 @@ struct shared_handler : public libremidi::shared_context
|
|||||||
auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) {
|
auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) {
|
||||||
if (auto clt = client.lock())
|
if (auto clt = client.lock())
|
||||||
{
|
{
|
||||||
clt->events.push(
|
clt->events.push({.type = shared_handler::event_type::callback_removed, .payload = id});
|
||||||
{.type = shared_handler::event_type::callback_removed, .payload = std::move(id)});
|
|
||||||
clt->queue_event.notify();
|
clt->queue_event.notify();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -134,7 +133,7 @@ struct shared_handler : public libremidi::shared_context
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case callback_removed:
|
case callback_removed:
|
||||||
auto addr = std::move(*std::get_if<snd_seq_addr_t>(&ev.payload));
|
auto addr = *std::get_if<snd_seq_addr_t>(&ev.payload);
|
||||||
if (auto index = index_of_address(addr); index >= 0)
|
if (auto index = index_of_address(addr); index >= 0)
|
||||||
{
|
{
|
||||||
addresses.erase(addresses.begin() + index);
|
addresses.erase(addresses.begin() + index);
|
||||||
@@ -146,15 +145,14 @@ struct shared_handler : public libremidi::shared_context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Look for who's ready
|
// Look for who's ready
|
||||||
for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++)
|
for (int64_t i = 0, n = std::ssize(fds) - 2; i < n; i++)
|
||||||
{
|
{
|
||||||
if (fds[i].revents & POLLIN)
|
if (fds[i].revents & POLLIN)
|
||||||
{
|
{
|
||||||
// Read alsa event
|
// Read alsa event
|
||||||
snd_seq_event_t* ev{};
|
snd_seq_event_t* ev{};
|
||||||
event_handle handle{snd};
|
event_handle handle{snd};
|
||||||
int result = 0;
|
while (snd.seq.event_input(client, &ev) > 0)
|
||||||
while ((result = snd.seq.event_input(client, &ev)) > 0)
|
|
||||||
{
|
{
|
||||||
handle.reset(ev);
|
handle.reset(ev);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#include <libremidi/backends/alsa_seq_ump/config.hpp>
|
#include <libremidi/backends/alsa_seq_ump/config.hpp>
|
||||||
#include <libremidi/backends/alsa_seq_ump/midi_out.hpp>
|
#include <libremidi/backends/alsa_seq_ump/midi_out.hpp>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
template <>
|
template <>
|
||||||
@@ -50,13 +54,16 @@ struct backend
|
|||||||
using midi_out_configuration = alsa_seq_ump::output_configuration;
|
using midi_out_configuration = alsa_seq_ump::output_configuration;
|
||||||
using midi_observer_configuration = alsa_seq_ump::observer_configuration;
|
using midi_observer_configuration = alsa_seq_ump::observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP;
|
static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP;
|
||||||
static const constexpr auto name = "alsa_seq_ump";
|
static const constexpr std::string_view name = "alsa_seq_ump";
|
||||||
static const constexpr auto display_name = "ALSA (sequencer, UMP)";
|
static const constexpr std::string_view display_name = "ALSA (sequencer, UMP)";
|
||||||
|
|
||||||
static inline bool available() noexcept
|
static inline bool available() noexcept
|
||||||
{
|
{
|
||||||
static const libasound& snd = libasound::instance();
|
static const libasound& snd = libasound::instance();
|
||||||
return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available;
|
if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ::access("/dev/snd/seq", F_OK) == 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,9 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const output_port& p, std::string_view portName) override
|
stdx::error open_port(const output_port& p, std::string_view portName) override
|
||||||
{
|
{
|
||||||
unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
unsigned int n_src
|
||||||
if (nSrc < 1)
|
= this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||||
|
if (n_src < 1)
|
||||||
{
|
{
|
||||||
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
libremidi_handle_error(this->configuration, "no MIDI output sources found!");
|
||||||
return make_error_code(std::errc::no_such_device);
|
return make_error_code(std::errc::no_such_device);
|
||||||
@@ -133,7 +134,8 @@ public:
|
|||||||
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
libremidi_handle_warning(this->configuration, "error sending MIDI message to port.");
|
||||||
return static_cast<std::errc>(-ret);
|
return static_cast<std::errc>(-ret);
|
||||||
}
|
}
|
||||||
return std::errc{0};
|
static_assert(std::errc{0} == std::errc{});
|
||||||
|
return std::errc{};
|
||||||
};
|
};
|
||||||
segment_ump_stream(ump_stream, count, write_func, []() {});
|
segment_ump_stream(ump_stream, count, write_func, []() {});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <libremidi/backends/coremidi/midi_out.hpp>
|
#include <libremidi/backends/coremidi/midi_out.hpp>
|
||||||
#include <libremidi/backends/coremidi/observer.hpp>
|
#include <libremidi/backends/coremidi/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
struct core_backend
|
struct core_backend
|
||||||
@@ -14,8 +16,8 @@ struct core_backend
|
|||||||
using midi_out_configuration = coremidi_output_configuration;
|
using midi_out_configuration = coremidi_output_configuration;
|
||||||
using midi_observer_configuration = coremidi_observer_configuration;
|
using midi_observer_configuration = coremidi_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::COREMIDI;
|
static const constexpr auto API = libremidi::API::COREMIDI;
|
||||||
static const constexpr auto name = "core";
|
static const constexpr std::string_view name = "core";
|
||||||
static const constexpr auto display_name = "CoreMIDI";
|
static const constexpr std::string_view display_name = "CoreMIDI";
|
||||||
|
|
||||||
static constexpr inline bool available() noexcept { return true; }
|
static constexpr inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -71,20 +71,22 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime)
|
|||||||
return static_cast<uint64_t>(res);
|
return static_cast<uint64_t>(res);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static inline auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name)
|
||||||
|
{
|
||||||
|
CFStringRef str = nullptr;
|
||||||
|
MIDIObjectGetStringProperty(prop, name, &str);
|
||||||
|
return CFString_handle{str};
|
||||||
|
};
|
||||||
|
|
||||||
// This function was submitted by Douglas Casey Tucker and apparently
|
// This function was submitted by Douglas Casey Tucker and apparently
|
||||||
// derived largely from PortMidi.
|
// derived largely from PortMidi.
|
||||||
inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
||||||
{
|
{
|
||||||
CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);
|
CFMutableStringRef result = CFStringCreateMutable(nullptr, 0);
|
||||||
|
|
||||||
static constexpr auto getProp = [](MIDIObjectRef prop) {
|
|
||||||
CFStringRef str = nullptr;
|
|
||||||
MIDIObjectGetStringProperty(prop, kMIDIPropertyName, &str);
|
|
||||||
return CFString_handle{str};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Begin with the endpoint's name.
|
// Begin with the endpoint's name.
|
||||||
if (auto endpoint_name = getProp(endpoint))
|
if (auto endpoint_name = get_cfstring_property(endpoint, kMIDIPropertyName))
|
||||||
{
|
{
|
||||||
CFStringAppend(result, endpoint_name.get());
|
CFStringAppend(result, endpoint_name.get());
|
||||||
}
|
}
|
||||||
@@ -101,7 +103,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
|||||||
if (CFStringGetLength(result) == 0)
|
if (CFStringGetLength(result) == 0)
|
||||||
{
|
{
|
||||||
// endpoint name has zero length -- try the entity
|
// endpoint name has zero length -- try the entity
|
||||||
if (auto entity_name = getProp(entity))
|
if (auto entity_name = get_cfstring_property(entity, kMIDIPropertyName))
|
||||||
{
|
{
|
||||||
CFStringAppend(result, entity_name.get());
|
CFStringAppend(result, entity_name.get());
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
|||||||
if (device == 0)
|
if (device == 0)
|
||||||
goto finish;
|
goto finish;
|
||||||
|
|
||||||
if (auto dev_name = getProp(device))
|
if (auto dev_name = get_cfstring_property(device, kMIDIPropertyName))
|
||||||
{
|
{
|
||||||
const auto dev_strlen = CFStringGetLength(dev_name.get());
|
const auto dev_strlen = CFStringGetLength(dev_name.get());
|
||||||
|
|
||||||
|
|||||||
@@ -78,13 +78,55 @@ public:
|
|||||||
if (!ok)
|
if (!ok)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
// Get the MIDI device from the entity
|
||||||
|
libremidi::port_information::port_type type{};
|
||||||
|
libremidi::container_identifier usb_location_id{};
|
||||||
|
libremidi::device_identifier usb_vendor_product{};
|
||||||
|
{
|
||||||
|
MIDIDeviceRef device{};
|
||||||
|
if (MIDIEntityGetDevice(e, &device) == noErr)
|
||||||
|
{
|
||||||
|
if (device)
|
||||||
|
{
|
||||||
|
using enum libremidi::port_information::port_type;
|
||||||
|
SInt32 devid_res{};
|
||||||
|
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr)
|
||||||
|
{
|
||||||
|
type = (libremidi::port_information::port_type)(hardware | usb);
|
||||||
|
usb_location_id = (uint64_t)devid_res;
|
||||||
|
}
|
||||||
|
|
||||||
|
SInt32 vendor_res{};
|
||||||
|
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res)
|
||||||
|
== noErr)
|
||||||
|
{
|
||||||
|
type = (libremidi::port_information::port_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);
|
||||||
|
if (driver == "com.apple.AppleMIDIBluetoothDriver")
|
||||||
|
type = (libremidi::port_information::port_type)(hardware | bluetooth);
|
||||||
|
if (driver == "com.apple.AppleMIDIIACDriver")
|
||||||
|
type = (libremidi::port_information::port_type)(software);
|
||||||
|
if (driver == "com.apple.AppleMIDIRTPDriver")
|
||||||
|
type = (libremidi::port_information::port_type)(network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return std::conditional_t<Input, input_port, output_port>{
|
return std::conditional_t<Input, input_port, output_port>{
|
||||||
{.client = (std::uintptr_t)this->client,
|
{.client = (std::uintptr_t)this->client,
|
||||||
|
.container = usb_location_id,
|
||||||
|
.device = usb_vendor_product,
|
||||||
.port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),
|
.port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),
|
||||||
.manufacturer = get_string_property(obj, kMIDIPropertyManufacturer),
|
.manufacturer = get_string_property(obj, kMIDIPropertyManufacturer),
|
||||||
.device_name = get_string_property(obj, kMIDIPropertyModel),
|
.device_name = get_string_property(obj, kMIDIPropertyModel),
|
||||||
.port_name = get_string_property(obj, kMIDIPropertyName),
|
.port_name = get_string_property(obj, kMIDIPropertyName),
|
||||||
.display_name = get_string_property(obj, kMIDIPropertyDisplayName)}};
|
.display_name = get_string_property(obj, kMIDIPropertyDisplayName),
|
||||||
|
.type = type}};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <libremidi/backends/coremidi_ump/midi_out.hpp>
|
#include <libremidi/backends/coremidi_ump/midi_out.hpp>
|
||||||
#include <libremidi/backends/coremidi_ump/observer.hpp>
|
#include <libremidi/backends/coremidi_ump/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi::coremidi_ump
|
namespace libremidi::coremidi_ump
|
||||||
{
|
{
|
||||||
struct backend
|
struct backend
|
||||||
@@ -14,8 +16,8 @@ struct backend
|
|||||||
using midi_out_configuration = coremidi_ump::output_configuration;
|
using midi_out_configuration = coremidi_ump::output_configuration;
|
||||||
using midi_observer_configuration = coremidi_ump::observer_configuration;
|
using midi_observer_configuration = coremidi_ump::observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::COREMIDI_UMP;
|
static const constexpr auto API = libremidi::API::COREMIDI_UMP;
|
||||||
static const constexpr auto name = "core_ump";
|
static const constexpr std::string_view name = "core_ump";
|
||||||
static const constexpr auto display_name = "CoreMIDI UMP";
|
static const constexpr std::string_view display_name = "CoreMIDI UMP";
|
||||||
|
|
||||||
static constexpr inline bool available() noexcept { return true; /* todo? */ }
|
static constexpr inline bool available() noexcept { return true; /* todo? */ }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <libremidi/detail/midi_out.hpp>
|
#include <libremidi/detail/midi_out.hpp>
|
||||||
#include <libremidi/detail/observer.hpp>
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
class observer_dummy : public observer_api
|
class observer_dummy : public observer_api
|
||||||
@@ -23,11 +25,12 @@ class midi_in_dummy final
|
|||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit midi_in_dummy(const input_configuration& configuration, std::any)
|
explicit midi_in_dummy(const input_configuration& configuration, const input_api_configuration&)
|
||||||
{
|
{
|
||||||
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||||
}
|
}
|
||||||
explicit midi_in_dummy(const ump_input_configuration& configuration, std::any)
|
explicit midi_in_dummy(
|
||||||
|
const ump_input_configuration& configuration, const input_api_configuration&)
|
||||||
{
|
{
|
||||||
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,8 @@ class midi_out_dummy final
|
|||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit midi_out_dummy(const output_configuration& configuration, std::any)
|
explicit midi_out_dummy(
|
||||||
|
const output_configuration& configuration, const output_api_configuration&)
|
||||||
{
|
{
|
||||||
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
libremidi_handle_warning(configuration, "This class provides no functionality.");
|
||||||
}
|
}
|
||||||
@@ -97,8 +101,8 @@ struct dummy_backend
|
|||||||
using midi_out_configuration = dummy_configuration;
|
using midi_out_configuration = dummy_configuration;
|
||||||
using midi_observer_configuration = dummy_configuration;
|
using midi_observer_configuration = dummy_configuration;
|
||||||
static const constexpr auto API = libremidi::API::DUMMY;
|
static const constexpr auto API = libremidi::API::DUMMY;
|
||||||
static const constexpr auto name = "dummy";
|
static const constexpr std::string_view name = "dummy";
|
||||||
static const constexpr auto display_name = "Dummy";
|
static const constexpr std::string_view display_name = "Dummy";
|
||||||
|
|
||||||
static constexpr inline bool available() noexcept { return true; }
|
static constexpr inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <libremidi/backends/emscripten/midi_out.hpp>
|
#include <libremidi/backends/emscripten/midi_out.hpp>
|
||||||
#include <libremidi/backends/emscripten/observer.hpp>
|
#include <libremidi/backends/emscripten/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -19,8 +21,8 @@ struct emscripten_backend
|
|||||||
using midi_out_configuration = emscripten_output_configuration;
|
using midi_out_configuration = emscripten_output_configuration;
|
||||||
using midi_observer_configuration = emscripten_observer_configuration;
|
using midi_observer_configuration = emscripten_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::WEBMIDI;
|
static const constexpr auto API = libremidi::API::WEBMIDI;
|
||||||
static const constexpr auto name = "webmidi";
|
static const constexpr std::string_view name = "webmidi";
|
||||||
static const constexpr auto display_name = "WebMIDI";
|
static const constexpr std::string_view display_name = "WebMIDI";
|
||||||
|
|
||||||
static constexpr inline bool available() noexcept { return true; }
|
static constexpr inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std::
|
|||||||
}
|
}
|
||||||
|
|
||||||
midi.open_input(portNumber, *this);
|
midi.open_input(portNumber, *this);
|
||||||
portNumber_ = portNumber;
|
m_portNumber = portNumber;
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port()
|
|||||||
{
|
{
|
||||||
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
|
||||||
|
|
||||||
midi.close_input(portNumber_, *this);
|
midi.close_input(m_portNumber, *this);
|
||||||
|
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public:
|
|||||||
void on_input(double ts, unsigned char* begin, unsigned char* end);
|
void on_input(double ts, unsigned char* begin, unsigned char* end);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int portNumber_{};
|
int m_portNumber{};
|
||||||
|
|
||||||
midi1::input_state_machine m_processing{this->configuration};
|
midi1::input_state_machine m_processing{this->configuration};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std:
|
|||||||
return std::errc::invalid_argument;
|
return std::errc::invalid_argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
portNumber_ = portNumber;
|
m_portNumber = portNumber;
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,14 +49,14 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() {
|
|||||||
|
|
||||||
LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size)
|
LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size)
|
||||||
{
|
{
|
||||||
if (portNumber_ < 0)
|
if (m_portNumber < 0)
|
||||||
libremidi_handle_error(
|
libremidi_handle_error(
|
||||||
this->configuration,
|
this->configuration,
|
||||||
"trying to send a message without an open "
|
"trying to send a message without an open "
|
||||||
"port.");
|
"port.");
|
||||||
|
|
||||||
webmidi_helpers::midi_access_emscripten::instance().send_message(
|
webmidi_helpers::midi_access_emscripten::instance().send_message(
|
||||||
portNumber_, reinterpret_cast<const char*>(message), size);
|
m_portNumber, reinterpret_cast<const char*>(message), size);
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ public:
|
|||||||
stdx::error send_message(const unsigned char* message, size_t size) override;
|
stdx::error send_message(const unsigned char* message, size_t size) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int portNumber_{-1};
|
int m_portNumber{-1};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <libremidi/backends/jack/midi_out.hpp>
|
#include <libremidi/backends/jack/midi_out.hpp>
|
||||||
#include <libremidi/backends/jack/observer.hpp>
|
#include <libremidi/backends/jack/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
//*********************************************************************//
|
//*********************************************************************//
|
||||||
// API: UNIX JACK
|
// API: UNIX JACK
|
||||||
//
|
//
|
||||||
@@ -22,8 +24,8 @@ struct jack_backend
|
|||||||
using midi_out_configuration = jack_output_configuration;
|
using midi_out_configuration = jack_output_configuration;
|
||||||
using midi_observer_configuration = jack_observer_configuration;
|
using midi_observer_configuration = jack_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::JACK_MIDI;
|
static const constexpr auto API = libremidi::API::JACK_MIDI;
|
||||||
static const constexpr auto name = "jack";
|
static const constexpr std::string_view name = "jack";
|
||||||
static const constexpr auto display_name = "JACK";
|
static const constexpr std::string_view display_name = "JACK";
|
||||||
|
|
||||||
static inline bool available() noexcept
|
static inline bool available() noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include <libremidi/detail/semaphore.hpp>
|
#include <libremidi/detail/semaphore.hpp>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <semaphore>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
@@ -54,16 +56,16 @@ struct jack_client
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <bool Input>
|
template <bool Input>
|
||||||
static auto
|
static auto get_ports(
|
||||||
get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept
|
jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags,
|
||||||
-> std::vector<std::conditional_t<Input, input_port, output_port>>
|
bool midi2) noexcept -> std::vector<std::conditional_t<Input, input_port, output_port>>
|
||||||
{
|
{
|
||||||
std::vector<std::conditional_t<Input, input_port, output_port>> ret;
|
std::vector<std::conditional_t<Input, input_port, output_port>> ret;
|
||||||
|
|
||||||
if (!client)
|
if (!client)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags);
|
const char** ports = jack_get_ports(client, pattern, type, flags);
|
||||||
|
|
||||||
if (ports == nullptr)
|
if (ports == nullptr)
|
||||||
return {};
|
return {};
|
||||||
@@ -73,7 +75,11 @@ struct jack_client
|
|||||||
{
|
{
|
||||||
// FIXME this does not take into account filtering sw / hw ports
|
// FIXME this does not take into account filtering sw / hw ports
|
||||||
auto port = jack_port_by_name(client, ports[i]);
|
auto port = jack_port_by_name(client, ports[i]);
|
||||||
ret.push_back(to_port_info<Input>(client, port));
|
if (port)
|
||||||
|
{
|
||||||
|
if (bool(midi2) == bool(jack_port_flags(port) & 0x20))
|
||||||
|
ret.push_back(to_port_info<Input>(client, port));
|
||||||
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +138,7 @@ struct jack_helpers : jack_client
|
|||||||
{.token = this_instance,
|
{.token = this_instance,
|
||||||
.callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {
|
.callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int {
|
||||||
if (auto pt = p.lock())
|
if (auto pt = p.lock())
|
||||||
if (auto ppt = pt->load())
|
if (pt->load())
|
||||||
self.process(nf);
|
self.process(nf);
|
||||||
|
|
||||||
self.thread_lock.check_client_released();
|
self.thread_lock.check_client_released();
|
||||||
@@ -192,8 +198,8 @@ struct jack_helpers : jack_client
|
|||||||
self.client_open_ = std::errc::not_connected;
|
self.client_open_ = std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
stdx::error
|
stdx::error create_local_port(
|
||||||
create_local_port(const auto& self, std::string_view portName, JackPortFlags flags)
|
const auto& self, std::string_view portName, const char* type, JackPortFlags flags)
|
||||||
{
|
{
|
||||||
// full name: "client_name:port_name\0"
|
// full name: "client_name:port_name\0"
|
||||||
if (portName.empty())
|
if (portName.empty())
|
||||||
@@ -209,8 +215,7 @@ struct jack_helpers : jack_client
|
|||||||
|
|
||||||
if (!this->port)
|
if (!this->port)
|
||||||
{
|
{
|
||||||
this->port
|
this->port = jack_port_register(this->client, portName.data(), type, flags, 0);
|
||||||
= jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->port)
|
if (!this->port)
|
||||||
@@ -238,4 +243,77 @@ struct jack_helpers : jack_client
|
|||||||
return from_errc(err);
|
return from_errc(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct jack_queue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr auto size_sz = sizeof(int32_t);
|
||||||
|
|
||||||
|
jack_queue() = default;
|
||||||
|
jack_queue(const jack_queue&) = delete;
|
||||||
|
jack_queue(jack_queue&&) = delete;
|
||||||
|
jack_queue& operator=(const jack_queue&) = delete;
|
||||||
|
|
||||||
|
jack_queue& operator=(jack_queue&& other) noexcept
|
||||||
|
{
|
||||||
|
ringbuffer = other.ringbuffer;
|
||||||
|
ringbuffer_space = other.ringbuffer_space;
|
||||||
|
other.ringbuffer = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit jack_queue(int64_t sz) noexcept
|
||||||
|
{
|
||||||
|
ringbuffer = jack_ringbuffer_create(sz);
|
||||||
|
ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
~jack_queue() noexcept
|
||||||
|
{
|
||||||
|
if (ringbuffer)
|
||||||
|
jack_ringbuffer_free(ringbuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error write(const unsigned char* data, int64_t sz) const noexcept
|
||||||
|
{
|
||||||
|
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
|
||||||
|
return std::errc::no_buffer_space;
|
||||||
|
|
||||||
|
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
|
||||||
|
sched_yield();
|
||||||
|
|
||||||
|
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
|
||||||
|
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void read(void* jack_events) const noexcept
|
||||||
|
{
|
||||||
|
int32_t sz;
|
||||||
|
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
|
||||||
|
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
|
||||||
|
{
|
||||||
|
jack_ringbuffer_read_advance(ringbuffer, size_sz);
|
||||||
|
|
||||||
|
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
|
||||||
|
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
|
||||||
|
else
|
||||||
|
jack_ringbuffer_read_advance(ringbuffer, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_ringbuffer_t* ringbuffer{};
|
||||||
|
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
|
||||||
|
};
|
||||||
|
|
||||||
|
struct jack_midi1
|
||||||
|
{
|
||||||
|
static constexpr const char* port_type = "8 bit raw midi";
|
||||||
|
};
|
||||||
|
|
||||||
|
struct jack_midi2
|
||||||
|
{
|
||||||
|
static constexpr const char* port_type = "32 bit raw UMP";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
class midi_in_jack final
|
class midi_in_jack final
|
||||||
: public midi1::in_api
|
: public midi1::in_api
|
||||||
, public jack_helpers
|
, public jack_helpers
|
||||||
|
, public jack_midi1
|
||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -46,7 +45,8 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const input_port& port, std::string_view portName) override
|
stdx::error open_port(const input_port& port, std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{})
|
if (auto err = create_local_port(*this, portName, port_type, JackPortIsInput);
|
||||||
|
err != stdx::error{})
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
||||||
@@ -62,7 +62,7 @@ public:
|
|||||||
|
|
||||||
stdx::error open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
return create_local_port(*this, portName, JackPortIsInput);
|
return create_local_port(*this, portName, port_type, JackPortIsInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdx::error close_port() override { return do_close_port(); }
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
@@ -96,8 +96,8 @@ public:
|
|||||||
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
||||||
|
|
||||||
// We have midi events in buffer
|
// We have midi events in buffer
|
||||||
uint32_t evCount = jack_midi_get_event_count(buff);
|
uint32_t ev_count = jack_midi_get_event_count(buff);
|
||||||
for (uint32_t j = 0; j < evCount; j++)
|
for (uint32_t j = 0; j < ev_count; j++)
|
||||||
{
|
{
|
||||||
jack_midi_event_t event{};
|
jack_midi_event_t event{};
|
||||||
jack_midi_event_get(&event, buff, j);
|
jack_midi_event_get(&event, buff, j);
|
||||||
|
|||||||
@@ -3,76 +3,12 @@
|
|||||||
#include <libremidi/backends/jack/helpers.hpp>
|
#include <libremidi/backends/jack/helpers.hpp>
|
||||||
#include <libremidi/detail/midi_out.hpp>
|
#include <libremidi/detail/midi_out.hpp>
|
||||||
|
|
||||||
#include <semaphore>
|
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
struct jack_queue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static constexpr auto size_sz = sizeof(int32_t);
|
|
||||||
|
|
||||||
jack_queue() = default;
|
|
||||||
jack_queue(const jack_queue&) = delete;
|
|
||||||
jack_queue(jack_queue&&) = delete;
|
|
||||||
jack_queue& operator=(const jack_queue&) = delete;
|
|
||||||
|
|
||||||
jack_queue& operator=(jack_queue&& other) noexcept
|
|
||||||
{
|
|
||||||
ringbuffer = other.ringbuffer;
|
|
||||||
ringbuffer_space = other.ringbuffer_space;
|
|
||||||
other.ringbuffer = nullptr;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit jack_queue(int64_t sz) noexcept
|
|
||||||
{
|
|
||||||
ringbuffer = jack_ringbuffer_create(sz);
|
|
||||||
ringbuffer_space = jack_ringbuffer_write_space(ringbuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
~jack_queue() noexcept
|
|
||||||
{
|
|
||||||
if (ringbuffer)
|
|
||||||
jack_ringbuffer_free(ringbuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
stdx::error write(const unsigned char* data, int64_t sz) const noexcept
|
|
||||||
{
|
|
||||||
if (static_cast<std::size_t>(sz + size_sz) > ringbuffer_space)
|
|
||||||
return std::errc::no_buffer_space;
|
|
||||||
|
|
||||||
while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz)
|
|
||||||
sched_yield();
|
|
||||||
|
|
||||||
jack_ringbuffer_write(ringbuffer, reinterpret_cast<char*>(&sz), size_sz);
|
|
||||||
jack_ringbuffer_write(ringbuffer, reinterpret_cast<const char*>(data), sz);
|
|
||||||
|
|
||||||
return stdx::error{};
|
|
||||||
}
|
|
||||||
|
|
||||||
void read(void* jack_events) const noexcept
|
|
||||||
{
|
|
||||||
int32_t sz;
|
|
||||||
while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast<char*>(&sz), size_sz) == size_sz
|
|
||||||
&& jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz)
|
|
||||||
{
|
|
||||||
jack_ringbuffer_read_advance(ringbuffer, size_sz);
|
|
||||||
|
|
||||||
if (auto midi = jack_midi_event_reserve(jack_events, 0, sz))
|
|
||||||
jack_ringbuffer_read(ringbuffer, reinterpret_cast<char*>(midi), sz);
|
|
||||||
else
|
|
||||||
jack_ringbuffer_read_advance(ringbuffer, sz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_ringbuffer_t* ringbuffer{};
|
|
||||||
std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer
|
|
||||||
};
|
|
||||||
|
|
||||||
class midi_out_jack
|
class midi_out_jack
|
||||||
: public midi1::out_api
|
: public midi1::out_api
|
||||||
, public jack_helpers
|
, public jack_helpers
|
||||||
|
, public jack_midi1
|
||||||
, public error_handler
|
, public error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -94,7 +30,8 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const output_port& port, std::string_view portName) override
|
stdx::error open_port(const output_port& port, std::string_view portName) override
|
||||||
{
|
{
|
||||||
if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{})
|
if (auto err = create_local_port(*this, portName, port_type, JackPortIsOutput);
|
||||||
|
err != stdx::error{})
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
// Connecting to the output
|
// Connecting to the output
|
||||||
@@ -111,7 +48,7 @@ public:
|
|||||||
|
|
||||||
stdx::error open_virtual_port(std::string_view portName) override
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
{
|
{
|
||||||
return create_local_port(*this, portName, JackPortIsOutput);
|
return create_local_port(*this, portName, port_type, JackPortIsOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdx::error close_port() override { return do_close_port(); }
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
@@ -128,7 +65,7 @@ class midi_out_jack_queued final : public midi_out_jack
|
|||||||
public:
|
public:
|
||||||
midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf)
|
midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf)
|
||||||
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||||
, queue{configuration.ringbuffer_size}
|
, m_queue{configuration.ringbuffer_size}
|
||||||
{
|
{
|
||||||
auto status = connect(*this);
|
auto status = connect(*this);
|
||||||
if (!this->client)
|
if (!this->client)
|
||||||
@@ -150,7 +87,7 @@ public:
|
|||||||
|
|
||||||
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
stdx::error send_message(const unsigned char* message, std::size_t size) override
|
||||||
{
|
{
|
||||||
return queue.write(message, size);
|
return m_queue.write(message, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
int process(jack_nframes_t nframes)
|
int process(jack_nframes_t nframes)
|
||||||
@@ -158,13 +95,13 @@ public:
|
|||||||
void* buff = jack_port_get_buffer(this->port, nframes);
|
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||||
jack_midi_clear_buffer(buff);
|
jack_midi_clear_buffer(buff);
|
||||||
|
|
||||||
this->queue.read(buff);
|
this->m_queue.read(buff);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
jack_queue queue;
|
jack_queue m_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
class midi_out_jack_direct final : public midi_out_jack
|
class midi_out_jack_direct final : public midi_out_jack
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace libremidi
|
|||||||
class observer_jack final
|
class observer_jack final
|
||||||
: public observer_api
|
: public observer_api
|
||||||
, private jack_client
|
, private jack_client
|
||||||
|
, public jack_midi1
|
||||||
, private error_handler
|
, private error_handler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -48,8 +49,7 @@ public:
|
|||||||
void initial_callback()
|
void initial_callback()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
const char** ports
|
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
|
||||||
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);
|
|
||||||
|
|
||||||
if (ports != nullptr)
|
if (ports != nullptr)
|
||||||
{
|
{
|
||||||
@@ -80,8 +80,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const char** ports
|
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
|
||||||
= jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
|
|
||||||
|
|
||||||
if (ports != nullptr)
|
if (ports != nullptr)
|
||||||
{
|
{
|
||||||
@@ -119,7 +118,7 @@ public:
|
|||||||
if (reg)
|
if (reg)
|
||||||
{
|
{
|
||||||
std::string_view type = jack_port_type(port);
|
std::string_view type = jack_port_type(port);
|
||||||
if (type != JACK_DEFAULT_MIDI_TYPE)
|
if (type != port_type)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool physical = flags & JackPortIsPhysical;
|
bool physical = flags & JackPortIsPhysical;
|
||||||
@@ -199,12 +198,12 @@ public:
|
|||||||
|
|
||||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
{
|
{
|
||||||
return get_ports<true>(this->client, nullptr, JackPortIsOutput);
|
return get_ports<true>(this->client, nullptr, port_type, JackPortIsOutput, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||||
{
|
{
|
||||||
return get_ports<false>(this->client, nullptr, JackPortIsInput);
|
return get_ports<false>(this->client, nullptr, port_type, JackPortIsInput, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
~observer_jack()
|
~observer_jack()
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack_ump/midi_in.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/midi_out.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace libremidi::jack_ump
|
||||||
|
{
|
||||||
|
struct backend
|
||||||
|
{
|
||||||
|
using midi_in = midi_in_jack;
|
||||||
|
using midi_out = midi_out_jack;
|
||||||
|
using midi_observer = observer_jack;
|
||||||
|
using midi_in_configuration = jack_ump::input_configuration;
|
||||||
|
using midi_out_configuration = jack_ump::output_configuration;
|
||||||
|
using midi_observer_configuration = jack_ump::observer_configuration;
|
||||||
|
static const constexpr auto API = libremidi::API::JACK_UMP;
|
||||||
|
static const constexpr std::string_view name = "jack_ump";
|
||||||
|
static const constexpr std::string_view display_name = "JACK (UMP)";
|
||||||
|
|
||||||
|
static inline bool available() noexcept
|
||||||
|
{
|
||||||
|
#if LIBREMIDI_WEAKJACK
|
||||||
|
if (WeakJack::instance().available() != 0)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int major, minor, micro, patch;
|
||||||
|
jack_get_version(&major, &minor, µ, &patch);
|
||||||
|
return (major >= 3 && minor >= 1 && micro >= 4);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack/config.hpp>
|
||||||
|
|
||||||
|
namespace libremidi::jack_ump
|
||||||
|
{
|
||||||
|
struct input_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
jack_client_t* context{};
|
||||||
|
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||||
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct output_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
jack_client_t* context{};
|
||||||
|
std::function<void(libremidi::jack_callback)> set_process_func{};
|
||||||
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
|
||||||
|
int32_t ringbuffer_size = 16384;
|
||||||
|
bool direct = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct observer_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
jack_client_t* context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack/helpers.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||||
|
|
||||||
|
namespace libremidi::jack_ump
|
||||||
|
{
|
||||||
|
class midi_in_jack final
|
||||||
|
: public midi2::in_api
|
||||||
|
, public jack_helpers
|
||||||
|
, private jack_midi1
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
|
struct
|
||||||
|
: libremidi::ump_input_configuration
|
||||||
|
, jack_ump::input_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit midi_in_jack(
|
||||||
|
libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
auto status = connect(*this);
|
||||||
|
if (!this->client)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||||
|
client_open_ = from_jack_status(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_in_jack() override
|
||||||
|
{
|
||||||
|
midi_in_jack::close_port();
|
||||||
|
|
||||||
|
disconnect(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const input_port& port, std::string_view portName) override
|
||||||
|
{
|
||||||
|
if (auto err
|
||||||
|
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port));
|
||||||
|
err != 0 && err != EEXIST)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(
|
||||||
|
configuration,
|
||||||
|
"could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port));
|
||||||
|
return from_errc(err);
|
||||||
|
}
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
|
{
|
||||||
|
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20));
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
|
|
||||||
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
|
{
|
||||||
|
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp absolute_timestamp() const noexcept override
|
||||||
|
{
|
||||||
|
return 1000 * jack_frames_to_time(client, jack_frame_time(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(jack_nframes_t nframes)
|
||||||
|
{
|
||||||
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = true,
|
||||||
|
.absolute_is_monotonic = true,
|
||||||
|
.has_samples = true,
|
||||||
|
};
|
||||||
|
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
jack_nframes_t current_frames{};
|
||||||
|
jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC
|
||||||
|
jack_time_t next_usecs{};
|
||||||
|
float period_usecs{};
|
||||||
|
jack_get_cycle_times(
|
||||||
|
this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs);
|
||||||
|
|
||||||
|
// We have midi events in buffer
|
||||||
|
uint32_t ev_count = jack_midi_get_event_count(buff);
|
||||||
|
for (uint32_t j = 0; j < ev_count; j++)
|
||||||
|
{
|
||||||
|
jack_midi_event_t event{};
|
||||||
|
jack_midi_event_get(&event, buff, j);
|
||||||
|
const auto to_ns
|
||||||
|
= [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); };
|
||||||
|
|
||||||
|
m_processing.on_bytes(
|
||||||
|
{(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)},
|
||||||
|
m_processing.timestamp<timestamp_info>(to_ns, event.time));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
midi2::input_state_machine m_processing{this->configuration};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack/helpers.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/midi_out.hpp>
|
||||||
|
|
||||||
|
namespace libremidi::jack_ump
|
||||||
|
{
|
||||||
|
class midi_out_jack
|
||||||
|
: public midi2::out_api
|
||||||
|
, public jack_helpers
|
||||||
|
, private jack_midi1
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using midi_api::client_open_;
|
||||||
|
struct
|
||||||
|
: libremidi::output_configuration
|
||||||
|
, jack_ump::output_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out_jack() override { }
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const output_port& port, std::string_view portName) override
|
||||||
|
{
|
||||||
|
if (auto err
|
||||||
|
= create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Connecting to the output
|
||||||
|
if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str());
|
||||||
|
err != 0 && err != EEXIST)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "could not connect to port" + port.port_name);
|
||||||
|
return from_errc(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view portName) override
|
||||||
|
{
|
||||||
|
return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20));
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override { return do_close_port(); }
|
||||||
|
|
||||||
|
stdx::error set_port_name(std::string_view portName) override
|
||||||
|
{
|
||||||
|
int ret = jack_port_rename(this->client, this->port, portName.data());
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_out_jack_queued final : public midi_out_jack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
midi_out_jack_queued(
|
||||||
|
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||||
|
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||||
|
, m_queue{configuration.ringbuffer_size}
|
||||||
|
{
|
||||||
|
auto status = connect(*this);
|
||||||
|
if (!this->client)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||||
|
client_open_ = from_jack_status(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out_jack_queued() override
|
||||||
|
{
|
||||||
|
midi_out_jack::close_port();
|
||||||
|
|
||||||
|
disconnect(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||||
|
{
|
||||||
|
return m_queue.write((unsigned char*)message, size * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(jack_nframes_t nframes)
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||||
|
jack_midi_clear_buffer(buff);
|
||||||
|
|
||||||
|
this->m_queue.read(buff);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
libremidi::jack_queue m_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_out_jack_direct final : public midi_out_jack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
midi_out_jack_direct(
|
||||||
|
libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf)
|
||||||
|
: midi_out_jack{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
auto status = connect(*this);
|
||||||
|
if (!this->client)
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "Could not create JACK client");
|
||||||
|
client_open_ = from_jack_status(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_size = jack_get_buffer_size(this->client);
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out_jack_direct() override
|
||||||
|
{
|
||||||
|
midi_out_jack::close_port();
|
||||||
|
|
||||||
|
disconnect(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(jack_nframes_t nframes)
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, nframes);
|
||||||
|
jack_midi_clear_buffer(buff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error send_ump(const uint32_t* message, std::size_t size) override
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||||
|
int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t));
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int convert_timestamp(int64_t user) const noexcept
|
||||||
|
{
|
||||||
|
switch (configuration.timestamps)
|
||||||
|
{
|
||||||
|
case timestamp_mode::AudioFrame:
|
||||||
|
return static_cast<int>(user);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// TODO
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
|
||||||
|
{
|
||||||
|
void* buff = jack_port_get_buffer(this->port, buffer_size);
|
||||||
|
int ret = jack_midi_event_write(
|
||||||
|
buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t));
|
||||||
|
return from_errc(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int buffer_size{};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace libremidi
|
||||||
|
{
|
||||||
|
template <>
|
||||||
|
inline std::unique_ptr<midi_out_api>
|
||||||
|
make<jack_ump::midi_out_jack>(output_configuration&& conf, jack_ump::output_configuration&& api)
|
||||||
|
{
|
||||||
|
if (api.direct)
|
||||||
|
return std::make_unique<jack_ump::midi_out_jack_direct>(std::move(conf), std::move(api));
|
||||||
|
else
|
||||||
|
return std::make_unique<jack_ump::midi_out_jack_queued>(std::move(conf), std::move(api));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/jack/helpers.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace libremidi::jack_ump
|
||||||
|
{
|
||||||
|
class observer_jack final
|
||||||
|
: public observer_api
|
||||||
|
, private jack_client
|
||||||
|
, private jack_midi1 // seems like a bug in PipeWire? MIDI 2 flag is set but the MIDI 1 type string is used
|
||||||
|
, private error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct
|
||||||
|
: libremidi::observer_configuration
|
||||||
|
, jack_ump::observer_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit observer_jack(
|
||||||
|
libremidi::observer_configuration&& conf, jack_ump::observer_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
// Initialize JACK client
|
||||||
|
if (configuration.context)
|
||||||
|
{
|
||||||
|
this->client = configuration.context;
|
||||||
|
set_callbacks();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jack_status_t status{};
|
||||||
|
this->client
|
||||||
|
= jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status);
|
||||||
|
if (status != jack_status_t{})
|
||||||
|
libremidi_handle_error(configuration, std::to_string((int)status));
|
||||||
|
|
||||||
|
if (this->client != nullptr)
|
||||||
|
{
|
||||||
|
set_callbacks();
|
||||||
|
|
||||||
|
jack_activate(this->client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initial_callback()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput);
|
||||||
|
|
||||||
|
if (ports != nullptr)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (ports[i] != nullptr)
|
||||||
|
{
|
||||||
|
const auto port = jack_port_by_name(client, ports[i]);
|
||||||
|
const auto flags = jack_port_flags(port);
|
||||||
|
|
||||||
|
if (!(flags & 0x20)) // midi 2 check
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool physical = flags & JackPortIsPhysical;
|
||||||
|
bool ok = configuration.track_any;
|
||||||
|
if (configuration.track_hardware)
|
||||||
|
ok |= physical;
|
||||||
|
if (configuration.track_virtual)
|
||||||
|
ok |= !physical;
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
seen_input_ports.insert(ports[i]);
|
||||||
|
if (this->configuration.input_added && configuration.notify_in_constructor)
|
||||||
|
this->configuration.input_added(to_port_info<true>(client, port));
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_free(ports);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput);
|
||||||
|
|
||||||
|
if (ports != nullptr)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (ports[i] != nullptr)
|
||||||
|
{
|
||||||
|
const auto port = jack_port_by_name(client, ports[i]);
|
||||||
|
const auto flags = jack_port_flags(port);
|
||||||
|
|
||||||
|
if (!(flags & 0x20)) // midi 2 check
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool physical = flags & JackPortIsPhysical;
|
||||||
|
bool ok = configuration.track_any;
|
||||||
|
if (configuration.track_hardware)
|
||||||
|
ok |= physical;
|
||||||
|
if (configuration.track_virtual)
|
||||||
|
ok |= !physical;
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
seen_output_ports.insert(ports[i]);
|
||||||
|
if (this->configuration.output_added && configuration.notify_in_constructor)
|
||||||
|
this->configuration.output_added(to_port_info<false>(client, port));
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_free(ports);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_port_callback(jack_port_t* port, bool reg)
|
||||||
|
{
|
||||||
|
auto flags = jack_port_flags(port);
|
||||||
|
std::string name = jack_port_name(port);
|
||||||
|
if (reg)
|
||||||
|
{
|
||||||
|
std::string_view type = jack_port_type(port);
|
||||||
|
if (type != port_type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!(flags & 0x20)) // midi 2 check
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool physical = flags & JackPortIsPhysical;
|
||||||
|
bool ok = configuration.track_any;
|
||||||
|
if (configuration.track_hardware)
|
||||||
|
ok |= physical;
|
||||||
|
if (configuration.track_virtual)
|
||||||
|
ok |= !physical;
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Note: we keep track of the ports as
|
||||||
|
// when disconnecting, jack_port_type and jack_port_flags aren't correctly
|
||||||
|
// set anymore.
|
||||||
|
|
||||||
|
if (flags & JackPortIsOutput)
|
||||||
|
{
|
||||||
|
seen_input_ports.insert(name);
|
||||||
|
if (this->configuration.input_added)
|
||||||
|
this->configuration.input_added(to_port_info<true>(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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (auto it = seen_input_ports.find(name); it != seen_input_ports.end())
|
||||||
|
{
|
||||||
|
if (this->configuration.input_removed)
|
||||||
|
this->configuration.input_removed(to_port_info<true>(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));
|
||||||
|
seen_output_ports.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_callbacks()
|
||||||
|
{
|
||||||
|
initial_callback();
|
||||||
|
|
||||||
|
if (!configuration.has_callbacks())
|
||||||
|
return;
|
||||||
|
|
||||||
|
jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) {
|
||||||
|
auto& self = *(observer_jack*)arg;
|
||||||
|
if (auto port = jack_port_by_id(self.client, p))
|
||||||
|
{
|
||||||
|
self.on_port_callback(port, r != 0);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
jack_set_port_rename_callback(
|
||||||
|
this->client,
|
||||||
|
+[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) {
|
||||||
|
const auto& self = *static_cast<observer_jack*>(arg);
|
||||||
|
|
||||||
|
auto port = jack_port_by_id(self.client, p);
|
||||||
|
if (!port)
|
||||||
|
return;
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; }
|
||||||
|
|
||||||
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
|
{
|
||||||
|
return get_ports<true>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
~observer_jack()
|
||||||
|
{
|
||||||
|
if (client && !configuration.context)
|
||||||
|
{
|
||||||
|
// If we own the client, deactivate it
|
||||||
|
jack_deactivate(this->client);
|
||||||
|
jack_client_close(this->client);
|
||||||
|
this->client = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> seen_input_ports;
|
||||||
|
std::unordered_set<std::string> seen_output_ports;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
#include <libremidi/backends/dummy.hpp>
|
#include <libremidi/backends/dummy.hpp>
|
||||||
#include <libremidi/backends/keyboard/midi_in.hpp>
|
#include <libremidi/backends/keyboard/midi_in.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
struct kbd_backend
|
struct kbd_backend
|
||||||
@@ -13,8 +15,8 @@ struct kbd_backend
|
|||||||
using midi_out_configuration = dummy_configuration;
|
using midi_out_configuration = dummy_configuration;
|
||||||
using midi_observer_configuration = dummy_configuration;
|
using midi_observer_configuration = dummy_configuration;
|
||||||
static const constexpr auto API = libremidi::API::KEYBOARD;
|
static const constexpr auto API = libremidi::API::KEYBOARD;
|
||||||
static const constexpr auto name = "keyboard";
|
static const constexpr std::string_view name = "keyboard";
|
||||||
static const constexpr auto display_name = "Computer keyboard";
|
static const constexpr std::string_view display_name = "Computer keyboard";
|
||||||
|
|
||||||
static inline bool available() noexcept { return true; }
|
static inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,128 @@
|
|||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
|
enum kbd_event
|
||||||
|
{
|
||||||
|
NOTE_0 = 0x0, // C
|
||||||
|
VEL_0 = NOTE_0 + 128, // Set velocity to 0
|
||||||
|
OCT_0 = VEL_0 + 128, // Set octave to 0
|
||||||
|
OCTAVE_PLUS = OCT_0 + 128,
|
||||||
|
OCTAVE_MINUS,
|
||||||
|
VELOCITY_PLUS,
|
||||||
|
VELOCITY_MINUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default map
|
||||||
|
//
|
||||||
|
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
|
||||||
|
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
|
||||||
|
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
|
||||||
|
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
|
||||||
|
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
|
||||||
|
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
|
||||||
|
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
|
||||||
|
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
|
||||||
|
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
|
||||||
|
// | ctrl | | alt | |altgr | | ctrl |
|
||||||
|
// '------' '-----'--------------------------'------' '------'
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
// actually based on macOS virtual codes as it seems
|
||||||
|
// impossible to get raw scan codes.
|
||||||
|
// https://eastmanreference.com/complete-list-of-applescript-key-codes
|
||||||
|
static inline const std::map<int, int>& scancode_map_macos(){
|
||||||
|
static const std::map<int, int> ret{
|
||||||
|
{ 0, NOTE_0}, // C0
|
||||||
|
{ 13, NOTE_0 + 1},
|
||||||
|
{ 1, NOTE_0 + 2},
|
||||||
|
{ 14, NOTE_0 + 3},
|
||||||
|
{ 2, NOTE_0 + 4},
|
||||||
|
{ 3, NOTE_0 + 5},
|
||||||
|
{ 17, NOTE_0 + 6},
|
||||||
|
{ 5, NOTE_0 + 7},
|
||||||
|
{ 16, NOTE_0 + 8},
|
||||||
|
{ 4, NOTE_0 + 9},
|
||||||
|
{ 32, NOTE_0 + 10},
|
||||||
|
{ 38, NOTE_0 + 11},
|
||||||
|
{ 40, NOTE_0 + 12}, // C1
|
||||||
|
{ 31, NOTE_0 + 13},
|
||||||
|
{ 37, NOTE_0 + 14},
|
||||||
|
{ 35, NOTE_0 + 15},
|
||||||
|
{ 41, NOTE_0 + 16},
|
||||||
|
{ 39, NOTE_0 + 17},
|
||||||
|
{ 30, NOTE_0 + 18},
|
||||||
|
{ 42, NOTE_0 + 19},
|
||||||
|
|
||||||
|
{ 50, VEL_0 + int(0)},
|
||||||
|
{ 18, VEL_0 + int(1 * 127 / 12)},
|
||||||
|
{ 19, VEL_0 + int(2 * 127 / 12)},
|
||||||
|
{ 20, VEL_0 + int(3 * 127 / 12)},
|
||||||
|
{ 21, VEL_0 + int(4 * 127 / 12)},
|
||||||
|
{ 23, VEL_0 + int(5 * 127 / 12)},
|
||||||
|
{ 22, VEL_0 + int(6 * 127 / 12)},
|
||||||
|
{ 26, VEL_0 + int(7 * 127 / 12)},
|
||||||
|
{ 28, VEL_0 + int(8 * 127 / 12)},
|
||||||
|
{ 25, VEL_0 + int(9 * 127 / 12)},
|
||||||
|
{ 29, VEL_0 + int(10 * 127 / 12)},
|
||||||
|
{ 27, VEL_0 + int(11 * 127 / 12)},
|
||||||
|
{ 24, VEL_0 + int(127)},
|
||||||
|
|
||||||
|
{ 6, OCTAVE_MINUS },
|
||||||
|
{ 7, OCTAVE_PLUS },
|
||||||
|
{ 8, VELOCITY_MINUS },
|
||||||
|
{ 9, VELOCITY_PLUS},
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use: https://kbdlayout.info/KBDUSX/scancodes
|
||||||
|
// Also valid for windows
|
||||||
|
static inline const std::map<int, int>& scancode_map_linux() {
|
||||||
|
static const std::map<int, int> ret{
|
||||||
|
{ 0x1E, NOTE_0}, // C0
|
||||||
|
{ 0x11, NOTE_0 + 1},
|
||||||
|
{ 0x1F, NOTE_0 + 2},
|
||||||
|
{ 0x12, NOTE_0 + 3},
|
||||||
|
{ 0x20, NOTE_0 + 4},
|
||||||
|
{ 0x21, NOTE_0 + 5},
|
||||||
|
{ 0x14, NOTE_0 + 6},
|
||||||
|
{ 0x22, NOTE_0 + 7},
|
||||||
|
{ 0x15, NOTE_0 + 8},
|
||||||
|
{ 0x23, NOTE_0 + 9},
|
||||||
|
{ 0x16, NOTE_0 + 10},
|
||||||
|
{ 0x24, NOTE_0 + 11},
|
||||||
|
{ 0x25, NOTE_0 + 12}, // C1
|
||||||
|
{ 0x18, NOTE_0 + 13},
|
||||||
|
{ 0x26, NOTE_0 + 14},
|
||||||
|
{ 0x19, NOTE_0 + 15},
|
||||||
|
{ 0x27, NOTE_0 + 16},
|
||||||
|
{ 0x28, NOTE_0 + 17},
|
||||||
|
{ 0x1B, NOTE_0 + 18},
|
||||||
|
{ 0x2B, NOTE_0 + 19},
|
||||||
|
|
||||||
|
{ 0x29, VEL_0 + int(0)},
|
||||||
|
{ 0x02, VEL_0 + int(1 * 127 / 12)},
|
||||||
|
{ 0x03, VEL_0 + int(2 * 127 / 12)},
|
||||||
|
{ 0x04, VEL_0 + int(3 * 127 / 12)},
|
||||||
|
{ 0x05, VEL_0 + int(4 * 127 / 12)},
|
||||||
|
{ 0x06, VEL_0 + int(5 * 127 / 12)},
|
||||||
|
{ 0x07, VEL_0 + int(6 * 127 / 12)},
|
||||||
|
{ 0x08, VEL_0 + int(7 * 127 / 12)},
|
||||||
|
{ 0x09, VEL_0 + int(8 * 127 / 12)},
|
||||||
|
{ 0x0A, VEL_0 + int(9 * 127 / 12)},
|
||||||
|
{ 0x0B, VEL_0 + int(10 * 127 / 12)},
|
||||||
|
{ 0x0C, VEL_0 + int(11 * 127 / 12)},
|
||||||
|
{ 0x0D, VEL_0 + int(127)},
|
||||||
|
|
||||||
|
{ 0x2C, OCTAVE_MINUS },
|
||||||
|
{ 0x2D, OCTAVE_PLUS },
|
||||||
|
{ 0x2E, VELOCITY_MINUS },
|
||||||
|
{ 0x2F, VELOCITY_PLUS},
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
/**
|
/**
|
||||||
* Used to set up keyboard input.
|
* Used to set up keyboard input.
|
||||||
* Your app should pass a function that will give you a callback
|
* Your app should pass a function that will give you a callback
|
||||||
@@ -13,80 +135,20 @@ namespace libremidi
|
|||||||
*/
|
*/
|
||||||
struct kbd_input_configuration
|
struct kbd_input_configuration
|
||||||
{
|
{
|
||||||
|
// Note that on Linux with X11 scancodes should be offset by 8 (an X11 legacy quirk)
|
||||||
|
// ex.: pass QKeyEvent::nativeScanCode() - 8 to this API
|
||||||
using scancode_callback = std::function<void(int)>;
|
using scancode_callback = std::function<void(int)>;
|
||||||
|
|
||||||
// First argument is on key press, second on key release
|
// First argument is on key press, second on key release
|
||||||
std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks
|
std::function<void(scancode_callback, scancode_callback)> set_input_scancode_callbacks
|
||||||
= [](scancode_callback, scancode_callback) {};
|
= [](const scancode_callback&, const scancode_callback&) { };
|
||||||
|
|
||||||
enum kbd_event
|
std::map<int, int> scancode_map
|
||||||
{
|
#if defined(__APPLE__)
|
||||||
NOTE_0 = 0x0, // C
|
= scancode_map_macos()
|
||||||
VEL_0 = NOTE_0 + 128, // Set velocity to 0
|
#else
|
||||||
OCT_0 = VEL_0 + 128, // Set octave to 0
|
= scancode_map_linux()
|
||||||
OCTAVE_PLUS = OCT_0 + 128,
|
#endif
|
||||||
OCTAVE_MINUS,
|
;
|
||||||
VELOCITY_PLUS,
|
|
||||||
VELOCITY_MINUS,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default map
|
|
||||||
//
|
|
||||||
// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
|
|
||||||
// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- |
|
|
||||||
// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
|
|
||||||
// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| |
|
|
||||||
// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| |
|
|
||||||
// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | |
|
|
||||||
// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----|
|
|
||||||
// | -^ | | O-| O+| V-| V+| | | | | | | ----^ |
|
|
||||||
// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
|
|
||||||
// | ctrl | | alt | |altgr | | ctrl |
|
|
||||||
// '------' '-----'--------------------------'------' '------'
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
// Use: https://kbdlayout.info/KBDUSX/scancodes
|
|
||||||
std::map<int, int> scancode_map{
|
|
||||||
{ 0x1E, NOTE_0}, // C0
|
|
||||||
{ 0x11, NOTE_0 + 1},
|
|
||||||
{ 0x1F, NOTE_0 + 2},
|
|
||||||
{ 0x12, NOTE_0 + 3},
|
|
||||||
{ 0x20, NOTE_0 + 4},
|
|
||||||
{ 0x21, NOTE_0 + 5},
|
|
||||||
{ 0x14, NOTE_0 + 6},
|
|
||||||
{ 0x22, NOTE_0 + 7},
|
|
||||||
{ 0x15, NOTE_0 + 8},
|
|
||||||
{ 0x23, NOTE_0 + 9},
|
|
||||||
{ 0x16, NOTE_0 + 10},
|
|
||||||
{ 0x24, NOTE_0 + 11},
|
|
||||||
{ 0x25, NOTE_0 + 12}, // C1
|
|
||||||
{ 0x18, NOTE_0 + 13},
|
|
||||||
{ 0x26, NOTE_0 + 14},
|
|
||||||
{ 0x19, NOTE_0 + 15},
|
|
||||||
{ 0x27, NOTE_0 + 16},
|
|
||||||
{ 0x28, NOTE_0 + 17},
|
|
||||||
{ 0x1B, NOTE_0 + 18},
|
|
||||||
{ 0x2B, NOTE_0 + 19},
|
|
||||||
|
|
||||||
{ 0x29, VEL_0 + int(0)},
|
|
||||||
{ 0x02, VEL_0 + int(1 * 127 / 12)},
|
|
||||||
{ 0x03, VEL_0 + int(2 * 127 / 12)},
|
|
||||||
{ 0x04, VEL_0 + int(3 * 127 / 12)},
|
|
||||||
{ 0x05, VEL_0 + int(4 * 127 / 12)},
|
|
||||||
{ 0x06, VEL_0 + int(5 * 127 / 12)},
|
|
||||||
{ 0x07, VEL_0 + int(6 * 127 / 12)},
|
|
||||||
{ 0x08, VEL_0 + int(7 * 127 / 12)},
|
|
||||||
{ 0x09, VEL_0 + int(8 * 127 / 12)},
|
|
||||||
{ 0x0A, VEL_0 + int(9 * 127 / 12)},
|
|
||||||
{ 0x0B, VEL_0 + int(10 * 127 / 12)},
|
|
||||||
{ 0x0C, VEL_0 + int(11 * 127 / 12)},
|
|
||||||
{ 0x0D, VEL_0 + int(127)},
|
|
||||||
|
|
||||||
{ 0x2C, OCTAVE_MINUS },
|
|
||||||
{ 0x2D, OCTAVE_PLUS },
|
|
||||||
{ 0x2E, VELOCITY_MINUS },
|
|
||||||
{ 0x2F, VELOCITY_PLUS},
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,22 +22,22 @@ public:
|
|||||||
explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)
|
explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
{
|
{
|
||||||
configuration.set_input_scancode_callbacks(
|
|
||||||
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~midi_in_kbd() override { }
|
~midi_in_kbd() override { }
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; }
|
||||||
|
|
||||||
stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; }
|
stdx::error open_port(const input_port&, std::string_view) override
|
||||||
|
{
|
||||||
|
configuration.set_input_scancode_callbacks(
|
||||||
|
[this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); });
|
||||||
|
|
||||||
stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; }
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
stdx::error close_port() override { return stdx::error{}; }
|
stdx::error close_port() override { return stdx::error{}; }
|
||||||
|
|
||||||
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
|
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override
|
timestamp absolute_timestamp() const noexcept override
|
||||||
{
|
{
|
||||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
@@ -45,7 +45,7 @@ public:
|
|||||||
|
|
||||||
void on_keypress(int scancode)
|
void on_keypress(int scancode)
|
||||||
{
|
{
|
||||||
using kevent = kbd_input_configuration::kbd_event;
|
using kevent = libremidi::kbd_event;
|
||||||
|
|
||||||
auto it = configuration.scancode_map.find(scancode);
|
auto it = configuration.scancode_map.find(scancode);
|
||||||
if (it == configuration.scancode_map.end())
|
if (it == configuration.scancode_map.end())
|
||||||
@@ -88,7 +88,7 @@ public:
|
|||||||
|
|
||||||
void on_keyrelease(int scancode)
|
void on_keyrelease(int scancode)
|
||||||
{
|
{
|
||||||
using kevent = kbd_input_configuration::kbd_event;
|
using kevent = libremidi::kbd_event;
|
||||||
|
|
||||||
auto it = configuration.scancode_map.find(scancode);
|
auto it = configuration.scancode_map.find(scancode);
|
||||||
if (it == configuration.scancode_map.end())
|
if (it == configuration.scancode_map.end())
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <libremidi/backends/linux/dylib_loader.hpp>
|
#include <libremidi/backends/linux/dylib_loader.hpp>
|
||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h> // IWYU pragma: export
|
||||||
|
|
||||||
#if defined(SND_LIB_VERSION)
|
#if defined(SND_LIB_VERSION)
|
||||||
#if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)
|
#if __has_include(<alsa/rawmidi.h>) && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6)
|
||||||
@@ -241,6 +241,7 @@ struct libasound
|
|||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_id)
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_id)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client)
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name)
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_card)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client)
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)
|
LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_INIT(snd_seq, close)
|
LIBREMIDI_SYMBOL_INIT(snd_seq, close)
|
||||||
@@ -298,6 +299,7 @@ struct libasound
|
|||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_id)
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_id)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client)
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name)
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_card)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client)
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)
|
LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof)
|
||||||
LIBREMIDI_SYMBOL_DEF(snd_seq, close)
|
LIBREMIDI_SYMBOL_DEF(snd_seq, close)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ namespace libremidi
|
|||||||
class libpipewire
|
class libpipewire
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
decltype(&::pw_get_library_version) get_library_version{};
|
||||||
|
|
||||||
decltype(&::pw_init) init{};
|
decltype(&::pw_init) init{};
|
||||||
decltype(&::pw_deinit) deinit{};
|
decltype(&::pw_deinit) deinit{};
|
||||||
|
|
||||||
@@ -67,12 +69,12 @@ public:
|
|||||||
bool available{true};
|
bool available{true};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
dylib_loader library;
|
dylib_loader m_library;
|
||||||
|
|
||||||
libpipewire()
|
libpipewire()
|
||||||
: library("libpipewire-0.3.so.0")
|
: m_library("libpipewire-0.3.so.0")
|
||||||
{
|
{
|
||||||
if (!library)
|
if (!m_library)
|
||||||
{
|
{
|
||||||
available = false;
|
available = false;
|
||||||
return;
|
return;
|
||||||
@@ -81,52 +83,59 @@ private:
|
|||||||
// in terms of regex:
|
// in terms of regex:
|
||||||
// decltype\(&::([a-z_]+)\) [a-z_]+{};
|
// decltype\(&::([a-z_]+)\) [a-z_]+{};
|
||||||
// \1 = library.symbol<decltype(&::\1)>("\1");
|
// \1 = library.symbol<decltype(&::\1)>("\1");
|
||||||
init = library.symbol<decltype(&::pw_init)>("pw_init");
|
get_library_version
|
||||||
deinit = library.symbol<decltype(&::pw_deinit)>("pw_deinit");
|
= m_library.symbol<decltype(&::pw_get_library_version)>("pw_get_library_version");
|
||||||
|
|
||||||
context_new = library.symbol<decltype(&::pw_context_new)>("pw_context_new");
|
init = m_library.symbol<decltype(&::pw_init)>("pw_init");
|
||||||
context_connect = library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");
|
deinit = m_library.symbol<decltype(&::pw_deinit)>("pw_deinit");
|
||||||
context_destroy = library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
|
|
||||||
|
|
||||||
core_disconnect = library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");
|
context_new = m_library.symbol<decltype(&::pw_context_new)>("pw_context_new");
|
||||||
|
context_connect = m_library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");
|
||||||
|
context_destroy = m_library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
|
||||||
|
|
||||||
|
core_disconnect = m_library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");
|
||||||
|
|
||||||
proxy_add_listener
|
proxy_add_listener
|
||||||
= library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
|
= m_library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
|
||||||
proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");
|
proxy_destroy = m_library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");
|
||||||
|
|
||||||
main_loop_new = library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");
|
main_loop_new = m_library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");
|
||||||
main_loop_destroy = library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");
|
main_loop_destroy
|
||||||
main_loop_quit = library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");
|
= m_library.symbol<decltype(&::pw_main_loop_destroy)>("pw_main_loop_destroy");
|
||||||
main_loop_run = library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");
|
main_loop_quit = m_library.symbol<decltype(&::pw_main_loop_quit)>("pw_main_loop_quit");
|
||||||
|
main_loop_run = m_library.symbol<decltype(&::pw_main_loop_run)>("pw_main_loop_run");
|
||||||
main_loop_get_loop
|
main_loop_get_loop
|
||||||
= library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");
|
= m_library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");
|
||||||
|
|
||||||
properties_new = library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
|
properties_new = m_library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
|
||||||
properties_free = library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
|
properties_free = m_library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
|
||||||
properties_get = library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");
|
properties_get = m_library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");
|
||||||
|
|
||||||
filter_new_simple = library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");
|
filter_new_simple
|
||||||
|
= m_library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");
|
||||||
filter_get_node_id
|
filter_get_node_id
|
||||||
= library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");
|
= m_library.symbol<decltype(&::pw_filter_get_node_id)>("pw_filter_get_node_id");
|
||||||
filter_get_properties
|
filter_get_properties
|
||||||
= library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");
|
= m_library.symbol<decltype(&::pw_filter_get_properties)>("pw_filter_get_properties");
|
||||||
filter_add_port = library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");
|
filter_add_port = m_library.symbol<decltype(&::pw_filter_add_port)>("pw_filter_add_port");
|
||||||
filter_remove_port
|
filter_remove_port
|
||||||
= library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");
|
= m_library.symbol<decltype(&::pw_filter_remove_port)>("pw_filter_remove_port");
|
||||||
filter_update_properties
|
filter_update_properties = m_library.symbol<decltype(&::pw_filter_update_properties)>(
|
||||||
= library.symbol<decltype(&::pw_filter_update_properties)>("pw_filter_update_properties");
|
"pw_filter_update_properties");
|
||||||
filter_update_params
|
filter_update_params
|
||||||
= library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");
|
= m_library.symbol<decltype(&::pw_filter_update_params)>("pw_filter_update_params");
|
||||||
filter_get_time = library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");
|
filter_get_time = m_library.symbol<decltype(&::pw_filter_get_time)>("pw_filter_get_time");
|
||||||
filter_destroy = library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");
|
filter_destroy = m_library.symbol<decltype(&::pw_filter_destroy)>("pw_filter_destroy");
|
||||||
filter_connect = library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");
|
filter_connect = m_library.symbol<decltype(&::pw_filter_connect)>("pw_filter_connect");
|
||||||
filter_get_dsp_buffer
|
filter_get_dsp_buffer
|
||||||
= library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");
|
= m_library.symbol<decltype(&::pw_filter_get_dsp_buffer)>("pw_filter_get_dsp_buffer");
|
||||||
filter_dequeue_buffer
|
filter_dequeue_buffer
|
||||||
= library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");
|
= m_library.symbol<decltype(&::pw_filter_dequeue_buffer)>("pw_filter_dequeue_buffer");
|
||||||
filter_queue_buffer
|
filter_queue_buffer
|
||||||
= library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");
|
= m_library.symbol<decltype(&::pw_filter_queue_buffer)>("pw_filter_queue_buffer");
|
||||||
filter_flush = library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");
|
filter_flush = m_library.symbol<decltype(&::pw_filter_flush)>("pw_filter_flush");
|
||||||
|
|
||||||
|
assert(get_library_version);
|
||||||
|
|
||||||
assert(init);
|
assert(init);
|
||||||
assert(deinit);
|
assert(deinit);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/backends/linux/dylib_loader.hpp>
|
#include <libremidi/backends/linux/dylib_loader.hpp>
|
||||||
#include <libremidi/backends/linux/helpers.hpp>
|
#include <libremidi/backends/linux/helpers.hpp>
|
||||||
|
#include <libremidi/observer_configuration.hpp>
|
||||||
|
|
||||||
#include <libudev.h>
|
#include <libudev.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
@@ -25,12 +27,28 @@ struct libudev
|
|||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, device_get_action)
|
LIBREMIDI_SYMBOL_INIT(udev, device_get_action)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem)
|
LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, device_get_property_value)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, device_new_from_syspath)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, device_get_parent_with_subsystem_devtype)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, device_unref)
|
LIBREMIDI_SYMBOL_INIT(udev, device_unref)
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref)
|
LIBREMIDI_SYMBOL_INIT(udev, monitor_unref)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_new)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_unref)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_is_initialized)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_subsystem)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_sysname)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_scan_devices)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, enumerate_get_list_entry)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_name)
|
||||||
|
LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_next)
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_INIT2(udev, new, create)
|
LIBREMIDI_SYMBOL_INIT2(udev, new, create)
|
||||||
LIBREMIDI_SYMBOL_INIT(udev, unref)
|
LIBREMIDI_SYMBOL_INIT(udev, unref)
|
||||||
}
|
}
|
||||||
@@ -46,12 +64,28 @@ struct libudev
|
|||||||
|
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_action)
|
LIBREMIDI_SYMBOL_DEF(udev, device_get_action)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem)
|
LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, device_new_from_syspath)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, device_get_property_value)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, device_get_parent_with_subsystem_devtype)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, device_unref)
|
LIBREMIDI_SYMBOL_DEF(udev, device_unref)
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref)
|
LIBREMIDI_SYMBOL_DEF(udev, monitor_unref)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_new)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_unref)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_is_initialized)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_subsystem)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_sysname)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_scan_devices)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, enumerate_get_list_entry)
|
||||||
|
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_name)
|
||||||
|
LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_next)
|
||||||
|
|
||||||
LIBREMIDI_SYMBOL_DEF2(udev, new, create)
|
LIBREMIDI_SYMBOL_DEF2(udev, new, create)
|
||||||
LIBREMIDI_SYMBOL_DEF(udev, unref)
|
LIBREMIDI_SYMBOL_DEF(udev, unref)
|
||||||
};
|
};
|
||||||
@@ -89,4 +123,72 @@ struct udev_helper
|
|||||||
udev_monitor* monitor{};
|
udev_monitor* monitor{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct udev_enumerator
|
||||||
|
{
|
||||||
|
explicit udev_enumerator(const udev_helper& h)
|
||||||
|
: helper{h}
|
||||||
|
{
|
||||||
|
auto& udev = helper.udev;
|
||||||
|
auto& instance = helper.instance;
|
||||||
|
enumerator = udev.enumerate_new(instance);
|
||||||
|
assert(enumerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
~udev_enumerator()
|
||||||
|
{
|
||||||
|
auto& udev = helper.udev;
|
||||||
|
udev.enumerate_unref(enumerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
const udev_helper& helper;
|
||||||
|
udev_enumerate* enumerator{};
|
||||||
|
operator udev_enumerate*() const noexcept { return enumerator; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct udev_soundcard_info
|
||||||
|
{
|
||||||
|
container_identifier container;
|
||||||
|
device_identifier path;
|
||||||
|
port_information::port_type type{};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card)
|
||||||
|
{
|
||||||
|
auto& udev = helper.udev;
|
||||||
|
udev_enumerator e{helper};
|
||||||
|
udev.enumerate_add_match_subsystem(e, "sound");
|
||||||
|
udev.enumerate_add_match_sysname(e, ("controlC" + std::to_string(card)).c_str());
|
||||||
|
if (udev.enumerate_scan_devices(e) != 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
for (auto entry = udev.enumerate_get_list_entry(e); entry;
|
||||||
|
entry = udev.list_entry_get_next(entry))
|
||||||
|
{
|
||||||
|
auto path = udev.list_entry_get_name(entry);
|
||||||
|
if (!path)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto dev = udev.device_new_from_syspath(helper.instance, path);
|
||||||
|
if (!dev)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto usb_device
|
||||||
|
= udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
|
||||||
|
|
||||||
|
port_information::port_type type = port_information::port_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);
|
||||||
|
|
||||||
|
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};
|
||||||
|
udev.device_unref(dev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,31 +42,34 @@ struct optionally_owned
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit optionally_owned(T* maybe_existing)
|
explicit optionally_owned(T* maybe_existing)
|
||||||
: storage{.ref = maybe_existing}
|
: m_storage{.ref = maybe_existing}
|
||||||
{
|
{
|
||||||
if (storage.ref)
|
if (m_storage.ref)
|
||||||
{
|
{
|
||||||
ownership = unowned;
|
m_ownership = unowned;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::destroy_at(&storage.ref);
|
std::destroy_at(&m_storage.ref);
|
||||||
std::construct_at<T>(reinterpret_cast<T*>(storage.object));
|
std::construct_at<T>(reinterpret_cast<T*>(m_storage.object));
|
||||||
ownership = owned;
|
m_ownership = owned;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~optionally_owned()
|
~optionally_owned()
|
||||||
{
|
{
|
||||||
if (is_owned())
|
if (is_owned())
|
||||||
std::destroy_at(reinterpret_cast<T*>(storage.object));
|
std::destroy_at(reinterpret_cast<T*>(m_storage.object));
|
||||||
}
|
}
|
||||||
|
|
||||||
T& get() noexcept { return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref); }
|
T& get() noexcept
|
||||||
|
{
|
||||||
|
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
|
||||||
|
}
|
||||||
const T& get() const noexcept
|
const T& get() const noexcept
|
||||||
{
|
{
|
||||||
return *(is_owned() ? reinterpret_cast<T*>(&storage.object) : storage.ref);
|
return *(is_owned() ? reinterpret_cast<T*>(&m_storage.object) : m_storage.ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
optionally_owned(const optionally_owned&) = delete;
|
optionally_owned(const optionally_owned&) = delete;
|
||||||
@@ -74,18 +77,18 @@ public:
|
|||||||
optionally_owned& operator=(const optionally_owned&) = delete;
|
optionally_owned& operator=(const optionally_owned&) = delete;
|
||||||
optionally_owned& operator=(optionally_owned&&) noexcept = delete;
|
optionally_owned& operator=(optionally_owned&&) noexcept = delete;
|
||||||
|
|
||||||
bool is_owned() const noexcept { return ownership == owned; }
|
bool is_owned() const noexcept { return m_ownership == owned; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
alignas(T) unsigned char object[sizeof(T)];
|
alignas(T) unsigned char object[sizeof(T)];
|
||||||
T* ref;
|
T* ref;
|
||||||
} storage;
|
} m_storage;
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
owned,
|
owned,
|
||||||
unowned
|
unowned
|
||||||
} ownership{};
|
} m_ownership{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
template <typename Impl, typename F>
|
template <typename Impl, typename F>
|
||||||
@@ -219,8 +220,6 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
|
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override
|
timestamp absolute_timestamp() const noexcept override
|
||||||
{
|
{
|
||||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
@@ -394,8 +393,6 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
stdx::error set_port_name(std::string_view) override { return stdx::error{}; }
|
|
||||||
|
|
||||||
timestamp absolute_timestamp() const noexcept override
|
timestamp absolute_timestamp() const noexcept override
|
||||||
{
|
{
|
||||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <libremidi/backends/net/midi_out.hpp>
|
#include <libremidi/backends/net/midi_out.hpp>
|
||||||
#include <libremidi/backends/net/observer.hpp>
|
#include <libremidi/backends/net/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi::net
|
namespace libremidi::net
|
||||||
{
|
{
|
||||||
struct backend
|
struct backend
|
||||||
@@ -16,8 +18,8 @@ struct backend
|
|||||||
using midi_out_configuration = libremidi::net::dgram_output_configuration;
|
using midi_out_configuration = libremidi::net::dgram_output_configuration;
|
||||||
using midi_observer_configuration = libremidi::net::net_observer_configuration;
|
using midi_observer_configuration = libremidi::net::net_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::NETWORK;
|
static const constexpr auto API = libremidi::API::NETWORK;
|
||||||
static const constexpr auto name = "network";
|
static const constexpr std::string_view name = "network";
|
||||||
static const constexpr auto display_name = "Network";
|
static const constexpr std::string_view display_name = "Network";
|
||||||
|
|
||||||
static inline bool available() noexcept { return true; }
|
static inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <libremidi/backends/net/midi_out.hpp>
|
#include <libremidi/backends/net/midi_out.hpp>
|
||||||
#include <libremidi/backends/net/observer.hpp>
|
#include <libremidi/backends/net/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi::net_ump
|
namespace libremidi::net_ump
|
||||||
{
|
{
|
||||||
struct backend
|
struct backend
|
||||||
@@ -15,8 +17,8 @@ struct backend
|
|||||||
using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;
|
using midi_out_configuration = libremidi::net_ump::dgram_output_configuration;
|
||||||
using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;
|
using midi_observer_configuration = libremidi::net_ump::net_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::NETWORK_UMP;
|
static const constexpr auto API = libremidi::API::NETWORK_UMP;
|
||||||
static const constexpr auto name = "network (UMP)";
|
static const constexpr std::string_view name = "network (UMP)";
|
||||||
static const constexpr auto display_name = "Network (UMP)";
|
static const constexpr std::string_view display_name = "Network (UMP)";
|
||||||
|
|
||||||
static inline bool available() noexcept { return true; }
|
static inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <libremidi/backends/pipewire/midi_out.hpp>
|
#include <libremidi/backends/pipewire/midi_out.hpp>
|
||||||
#include <libremidi/backends/pipewire/observer.hpp>
|
#include <libremidi/backends/pipewire/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi::pipewire
|
namespace libremidi::pipewire
|
||||||
{
|
{
|
||||||
struct backend
|
struct backend
|
||||||
@@ -18,8 +20,8 @@ struct backend
|
|||||||
using midi_out_configuration = pipewire_output_configuration;
|
using midi_out_configuration = pipewire_output_configuration;
|
||||||
using midi_observer_configuration = pipewire_observer_configuration;
|
using midi_observer_configuration = pipewire_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::PIPEWIRE;
|
static const constexpr auto API = libremidi::API::PIPEWIRE;
|
||||||
static const constexpr auto name = "pipewire";
|
static const constexpr std::string_view name = "pipewire";
|
||||||
static const constexpr auto display_name = "PipeWire";
|
static const constexpr std::string_view display_name = "PipeWire";
|
||||||
|
|
||||||
static inline bool available() noexcept
|
static inline bool available() noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
@@ -79,11 +80,11 @@ struct pipewire_context
|
|||||||
struct graph
|
struct graph
|
||||||
{
|
{
|
||||||
mutable std::mutex mtx;
|
mutable std::mutex mtx;
|
||||||
libremidi::hash_map<uint32_t, node> physical_audio;
|
libremidi::hash_map<uint64_t, node> physical_audio;
|
||||||
libremidi::hash_map<uint32_t, node> physical_midi;
|
libremidi::hash_map<uint64_t, node> physical_midi;
|
||||||
libremidi::hash_map<uint32_t, node> software_audio;
|
libremidi::hash_map<uint64_t, node> software_audio;
|
||||||
libremidi::hash_map<uint32_t, node> software_midi;
|
libremidi::hash_map<uint64_t, node> software_midi;
|
||||||
libremidi::hash_map<uint32_t, port_info> port_cache;
|
libremidi::hash_map<uint64_t, port_info> port_cache;
|
||||||
|
|
||||||
void for_each_port(auto func)
|
void for_each_port(auto func)
|
||||||
{
|
{
|
||||||
@@ -123,7 +124,7 @@ struct pipewire_context
|
|||||||
}
|
}
|
||||||
|
|
||||||
explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)
|
explicit pipewire_context(std::shared_ptr<pipewire_instance> inst)
|
||||||
: global_instance{inst}
|
: global_instance{std::move(inst)}
|
||||||
, owns_main_loop{true}
|
, owns_main_loop{true}
|
||||||
{
|
{
|
||||||
this->main_loop = pw.main_loop_new(nullptr);
|
this->main_loop = pw.main_loop_new(nullptr);
|
||||||
@@ -287,7 +288,7 @@ struct pipewire_context
|
|||||||
spa_hook_remove(&core_listener);
|
spa_hook_remove(&core_listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_t in_port)
|
[[nodiscard]] pw_proxy* link_ports(uint64_t out_port, uint64_t in_port)
|
||||||
{
|
{
|
||||||
auto props = pw.properties_new(
|
auto props = pw.properties_new(
|
||||||
PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT,
|
PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT,
|
||||||
@@ -361,6 +362,8 @@ struct pipewire_context
|
|||||||
return &this->current_graph.physical_audio[nid];
|
return &this->current_graph.physical_audio[nid];
|
||||||
else if (p.format.find("midi") != p.format.npos)
|
else if (p.format.find("midi") != p.format.npos)
|
||||||
return &this->current_graph.physical_midi[nid];
|
return &this->current_graph.physical_midi[nid];
|
||||||
|
else if (p.format.find("UMP") != p.format.npos)
|
||||||
|
return &this->current_graph.physical_midi[nid];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -368,6 +371,8 @@ struct pipewire_context
|
|||||||
return &this->current_graph.software_audio[nid];
|
return &this->current_graph.software_audio[nid];
|
||||||
else if (p.format.find("midi") != p.format.npos)
|
else if (p.format.find("midi") != p.format.npos)
|
||||||
return &this->current_graph.software_midi[nid];
|
return &this->current_graph.software_midi[nid];
|
||||||
|
else if (p.format.find("UMP") != p.format.npos)
|
||||||
|
return &this->current_graph.software_midi[nid];
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
};
|
};
|
||||||
@@ -453,12 +458,12 @@ struct pipewire_filter
|
|||||||
}* port{};
|
}* port{};
|
||||||
|
|
||||||
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop)
|
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop)
|
||||||
: loop{loop}
|
: loop{std::move(loop)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)
|
explicit pipewire_filter(std::shared_ptr<pipewire_context> loop, pw_filter* filter)
|
||||||
: loop{loop}
|
: loop{std::move(loop)}
|
||||||
, filter{filter}
|
, filter{filter}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -498,7 +503,8 @@ struct pipewire_filter
|
|||||||
pw.filter_destroy(this->filter);
|
pw.filter_destroy(this->filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdx::error create_local_port(std::string_view port_name, spa_direction direction)
|
stdx::error
|
||||||
|
create_local_port(std::string_view port_name, spa_direction direction, const char* format)
|
||||||
{
|
{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
this->port = (struct port*)pw.filter_add_port(
|
this->port = (struct port*)pw.filter_add_port(
|
||||||
@@ -507,7 +513,7 @@ struct pipewire_filter
|
|||||||
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
|
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
|
||||||
sizeof(struct port),
|
sizeof(struct port),
|
||||||
pw.properties_new(
|
pw.properties_new(
|
||||||
PW_KEY_FORMAT_DSP, "8 bit raw midi",
|
PW_KEY_FORMAT_DSP, format,
|
||||||
PW_KEY_PORT_NAME, port_name.data(),
|
PW_KEY_PORT_NAME, port_name.data(),
|
||||||
nullptr),
|
nullptr),
|
||||||
nullptr, 0);
|
nullptr, 0);
|
||||||
@@ -517,7 +523,7 @@ struct pipewire_filter
|
|||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_port_buffer(int bytes)
|
void set_port_buffer(int64_t bytes)
|
||||||
{
|
{
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
struct spa_pod_builder builder;
|
struct spa_pod_builder builder;
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
#include <libremidi/detail/semaphore.hpp>
|
#include <libremidi/detail/semaphore.hpp>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <semaphore>
|
|
||||||
#include <stop_token>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
@@ -66,14 +64,14 @@ struct pipewire_helpers
|
|||||||
{
|
{
|
||||||
this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);
|
this->filter = std::make_unique<pipewire_filter>(this->global_context, configuration.filter);
|
||||||
|
|
||||||
pipewire_callback cbs{
|
libremidi::pipewire_callback cbs{
|
||||||
.token = this_instance,
|
.token = this_instance,
|
||||||
.callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void {
|
.callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void {
|
||||||
if (auto pt = p.lock())
|
if (auto pt = p.lock())
|
||||||
self.process(nf);
|
self.process(nf);
|
||||||
|
|
||||||
self.thread_lock.check_client_released();
|
self.thread_lock.check_client_released();
|
||||||
}};
|
}};
|
||||||
configuration.set_process_func(cbs);
|
configuration.set_process_func(cbs);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -197,7 +195,8 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Self>
|
template <typename Self>
|
||||||
stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction)
|
stdx::error create_local_port(
|
||||||
|
Self& self, std::string_view portName, spa_direction direction, const char* format)
|
||||||
{
|
{
|
||||||
assert(this->global_context);
|
assert(this->global_context);
|
||||||
assert(this->filter);
|
assert(this->filter);
|
||||||
@@ -207,7 +206,7 @@ struct pipewire_helpers
|
|||||||
|
|
||||||
if (!this->filter->port)
|
if (!this->filter->port)
|
||||||
{
|
{
|
||||||
auto ret = this->filter->create_local_port(portName.data(), direction);
|
auto ret = this->filter->create_local_port(portName.data(), direction, format);
|
||||||
if (ret != stdx::error{})
|
if (ret != stdx::error{})
|
||||||
{
|
{
|
||||||
self.libremidi_handle_error(self.configuration, "error creating port");
|
self.libremidi_handle_error(self.configuration, "error creating port");
|
||||||
@@ -218,11 +217,11 @@ struct pipewire_helpers
|
|||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_callbacks(const observer_configuration& conf)
|
void add_callbacks(std::string format, const observer_configuration& conf)
|
||||||
{
|
{
|
||||||
assert(global_context);
|
assert(global_context);
|
||||||
global_context->on_port_added = [&conf](const pipewire_context::port_info& port) {
|
global_context->on_port_added = [format, &conf](const pipewire_context::port_info& port) {
|
||||||
if (port.format.find("midi") == std::string::npos)
|
if (port.format.find(format) == std::string::npos)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool unfiltered = conf.track_any;
|
bool unfiltered = conf.track_any;
|
||||||
@@ -243,8 +242,8 @@ struct pipewire_helpers
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) {
|
global_context->on_port_removed = [format, &conf](const pipewire_context::port_info& port) {
|
||||||
if (port.format.find("midi") == std::string::npos)
|
if (port.format.find(format) == std::string::npos)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool unfiltered = conf.track_any;
|
bool unfiltered = conf.track_any;
|
||||||
@@ -437,7 +436,9 @@ struct pipewire_helpers
|
|||||||
// Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer)
|
// Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer)
|
||||||
// is an "output" port from the point of view of pipewire as data will come out of it
|
// is an "output" port from the point of view of pipewire as data will come out of it
|
||||||
template <spa_direction Direction>
|
template <spa_direction Direction>
|
||||||
static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept
|
static auto get_ports(
|
||||||
|
std::string_view format, const observer_configuration& conf,
|
||||||
|
const pipewire_context& ctx) noexcept
|
||||||
-> std::vector<
|
-> std::vector<
|
||||||
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
std::conditional_t<Direction == SPA_DIRECTION_OUTPUT, input_port, output_port>>
|
||||||
{
|
{
|
||||||
@@ -452,7 +453,8 @@ struct pipewire_helpers
|
|||||||
for (auto& port :
|
for (auto& port :
|
||||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||||
{
|
{
|
||||||
ret.push_back(to_port_info<Direction>(port));
|
if (port.format.find(format) != std::string::npos)
|
||||||
|
ret.push_back(to_port_info<Direction>(port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +464,8 @@ struct pipewire_helpers
|
|||||||
for (auto& port :
|
for (auto& port :
|
||||||
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
(Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs))
|
||||||
{
|
{
|
||||||
ret.push_back(to_port_info<Direction>(port));
|
if (port.format.find(format) != std::string::npos)
|
||||||
|
ret.push_back(to_port_info<Direction>(port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const input_port& in_port, std::string_view name) override
|
stdx::error open_port(const input_port& in_port, std::string_view name) override
|
||||||
{
|
{
|
||||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi");
|
||||||
|
err != stdx::error{})
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (auto err = link_ports(*this, in_port); err != stdx::error{})
|
if (auto err = link_ports(*this, in_port); err != stdx::error{})
|
||||||
@@ -59,7 +60,8 @@ public:
|
|||||||
|
|
||||||
stdx::error open_virtual_port(std::string_view name) override
|
stdx::error open_virtual_port(std::string_view name) override
|
||||||
{
|
{
|
||||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{})
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi");
|
||||||
|
err != stdx::error{})
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
start_thread();
|
start_thread();
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
#include <readerwriterqueue.h>
|
#include <readerwriterqueue.h>
|
||||||
|
|
||||||
#include <semaphore>
|
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
class midi_out_pipewire
|
class midi_out_pipewire
|
||||||
@@ -50,7 +48,8 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const output_port& out_port, std::string_view name) override
|
stdx::error open_port(const output_port& out_port, std::string_view name) override
|
||||||
{
|
{
|
||||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi");
|
||||||
|
err != stdx::error{})
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
this->filter->set_port_buffer(configuration.output_buffer_size);
|
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||||
@@ -64,7 +63,8 @@ public:
|
|||||||
|
|
||||||
stdx::error open_virtual_port(std::string_view name) override
|
stdx::error open_virtual_port(std::string_view name) override
|
||||||
{
|
{
|
||||||
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{})
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi");
|
||||||
|
err != stdx::error{})
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
this->filter->set_port_buffer(configuration.output_buffer_size);
|
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||||
@@ -121,8 +121,9 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi);
|
spa_pod_builder_control(&build, static_cast<int32_t>(m.timestamp), SPA_CONTROL_Midi);
|
||||||
int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size());
|
int res
|
||||||
|
= spa_pod_builder_bytes(&build, m.bytes.data(), static_cast<uint32_t>(m.bytes.size()));
|
||||||
|
|
||||||
// Try again next buffer
|
// Try again next buffer
|
||||||
if (res == -ENOSPC)
|
if (res == -ENOSPC)
|
||||||
|
|||||||
@@ -24,19 +24,9 @@ public:
|
|||||||
{
|
{
|
||||||
create_context(*this);
|
create_context(*this);
|
||||||
|
|
||||||
// FIXME notify_in_constructor
|
|
||||||
// FIXME port rename callback
|
// FIXME port rename callback
|
||||||
#if 0
|
|
||||||
// Initialize PipeWire client
|
|
||||||
if (configuration.context)
|
|
||||||
{
|
{
|
||||||
this->client = configuration.context;
|
this->add_callbacks("midi", configuration);
|
||||||
set_callbacks();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
this->add_callbacks(configuration);
|
|
||||||
this->start_thread();
|
this->start_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,27 +46,18 @@ public:
|
|||||||
|
|
||||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
{
|
{
|
||||||
return get_ports<SPA_DIRECTION_OUTPUT>(this->configuration, *this->global_context);
|
return get_ports<SPA_DIRECTION_OUTPUT>("midi", this->configuration, *this->global_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||||
{
|
{
|
||||||
return get_ports<SPA_DIRECTION_INPUT>(this->configuration, *this->global_context);
|
return get_ports<SPA_DIRECTION_INPUT>("midi", this->configuration, *this->global_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
~observer_pipewire()
|
~observer_pipewire()
|
||||||
{
|
{
|
||||||
stop_thread();
|
stop_thread();
|
||||||
destroy_context();
|
destroy_context();
|
||||||
#if 0
|
|
||||||
if (client && !configuration.context)
|
|
||||||
{
|
|
||||||
// If we own the client, deactivate it
|
|
||||||
pipewire_deactivate(this->client);
|
|
||||||
pipewire_client_close(this->client);
|
|
||||||
this->client = nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_set<std::string> seen_input_ports;
|
std::unordered_set<std::string> seen_input_ports;
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/dummy.hpp>
|
||||||
|
#include <libremidi/backends/linux/pipewire.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/midi_in.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/midi_out.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace libremidi::pipewire_ump
|
||||||
|
{
|
||||||
|
struct backend
|
||||||
|
{
|
||||||
|
using midi_in = libremidi::pipewire_ump::midi_in_pipewire;
|
||||||
|
using midi_out = libremidi::pipewire_ump::midi_out_pipewire;
|
||||||
|
using midi_observer = libremidi::pipewire_ump::observer_pipewire;
|
||||||
|
using midi_in_configuration = libremidi::pipewire_ump::input_configuration;
|
||||||
|
using midi_out_configuration = libremidi::pipewire_ump::output_configuration;
|
||||||
|
using midi_observer_configuration = libremidi::pipewire_ump::observer_configuration;
|
||||||
|
static const constexpr auto API = libremidi::API::PIPEWIRE_UMP;
|
||||||
|
static const constexpr std::string_view name = "pipewire_ump";
|
||||||
|
static const constexpr std::string_view display_name = "PipeWire (UMP)";
|
||||||
|
|
||||||
|
static inline bool available() noexcept
|
||||||
|
{
|
||||||
|
static const libpipewire& pw = libpipewire::instance();
|
||||||
|
if (!pw.available)
|
||||||
|
return false;
|
||||||
|
const std::string_view version = pw.get_library_version();
|
||||||
|
if (version.size() >= 3)
|
||||||
|
{
|
||||||
|
if (version[0] > '1' || version[2] >= '4')
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/pipewire/config.hpp>
|
||||||
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
|
namespace libremidi::pipewire_ump
|
||||||
|
{
|
||||||
|
struct input_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
pw_main_loop* context{};
|
||||||
|
pw_filter* filter{};
|
||||||
|
std::function<void(libremidi::pipewire_callback)> set_process_func{};
|
||||||
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct output_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
pw_main_loop* context{};
|
||||||
|
pw_filter* filter{};
|
||||||
|
std::function<void(libremidi::pipewire_callback)> set_process_func{};
|
||||||
|
std::function<void(int64_t)> clear_process_func{};
|
||||||
|
|
||||||
|
int64_t output_buffer_size{65536};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct observer_configuration
|
||||||
|
{
|
||||||
|
std::string client_name = "libremidi client";
|
||||||
|
|
||||||
|
pw_main_loop* context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/pipewire/helpers.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||||
|
|
||||||
|
namespace libremidi::pipewire_ump
|
||||||
|
{
|
||||||
|
class midi_in_pipewire final
|
||||||
|
: public midi2::in_api
|
||||||
|
, public pipewire_helpers
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct
|
||||||
|
: ump_input_configuration
|
||||||
|
, libremidi::pipewire_ump::input_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit midi_in_pipewire(
|
||||||
|
ump_input_configuration&& conf, libremidi::pipewire_ump::input_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
if (auto ret = create_context(*this); ret != stdx::error{})
|
||||||
|
{
|
||||||
|
client_open_ = ret;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto ret = create_filter(*this); ret != stdx::error{})
|
||||||
|
{
|
||||||
|
client_open_ = ret;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_in_pipewire() override
|
||||||
|
{
|
||||||
|
stop_thread();
|
||||||
|
do_close_port();
|
||||||
|
destroy_filter(*this);
|
||||||
|
destroy_context();
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const input_port& in_port, std::string_view name) override
|
||||||
|
{
|
||||||
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP");
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (auto err = link_ports(*this, in_port); err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
start_thread();
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view name) override
|
||||||
|
{
|
||||||
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP");
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
start_thread();
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override
|
||||||
|
{
|
||||||
|
stop_thread();
|
||||||
|
return do_close_port();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(); }
|
||||||
|
|
||||||
|
void process(struct spa_io_position* position)
|
||||||
|
{
|
||||||
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
|
.has_absolute_timestamps = true,
|
||||||
|
.absolute_is_monotonic = true,
|
||||||
|
.has_samples = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(this->filter);
|
||||||
|
assert(this->filter->port);
|
||||||
|
const auto b = pw.filter_dequeue_buffer(this->filter->port);
|
||||||
|
if (!b)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto buf = b->buffer;
|
||||||
|
const auto d = &buf->datas[0];
|
||||||
|
|
||||||
|
if (d->data == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto pod
|
||||||
|
= (spa_pod*)spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size);
|
||||||
|
if (!pod)
|
||||||
|
return;
|
||||||
|
if (!spa_pod_is_sequence(pod))
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct spa_pod_control* c{};
|
||||||
|
SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c)
|
||||||
|
{
|
||||||
|
if (c->type != SPA_CONTROL_UMP)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto data = (uint8_t*)SPA_POD_BODY(&c->value);
|
||||||
|
auto size = SPA_POD_BODY_SIZE(&c->value);
|
||||||
|
|
||||||
|
const auto to_ns = [=, clk = position->clock] {
|
||||||
|
return 1e9 * ((clk.position + c->offset) / (double)clk.rate.denom);
|
||||||
|
};
|
||||||
|
|
||||||
|
m_processing.on_bytes(
|
||||||
|
{(uint32_t*)data, (uint32_t*)(data + size)},
|
||||||
|
m_processing.timestamp<timestamp_info>(to_ns, c->offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.filter_queue_buffer(this->filter->port, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
midi2::input_state_machine m_processing{this->configuration};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/pipewire/helpers.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/midi_out.hpp>
|
||||||
|
|
||||||
|
#include <readerwriterqueue.h>
|
||||||
|
|
||||||
|
namespace libremidi::pipewire_ump
|
||||||
|
{
|
||||||
|
class midi_out_pipewire
|
||||||
|
: public midi2::out_api
|
||||||
|
, public pipewire_helpers
|
||||||
|
, public error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct
|
||||||
|
: libremidi::output_configuration
|
||||||
|
, libremidi::pipewire_ump::output_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
midi_out_pipewire(
|
||||||
|
libremidi::output_configuration&& conf,
|
||||||
|
libremidi::pipewire_ump::output_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
if (auto ret = create_context(*this); ret != stdx::error{})
|
||||||
|
{
|
||||||
|
client_open_ = ret;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto ret = create_filter(*this); ret != stdx::error{})
|
||||||
|
{
|
||||||
|
client_open_ = ret;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client_open_ = stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
~midi_out_pipewire() override
|
||||||
|
{
|
||||||
|
stop_thread();
|
||||||
|
do_close_port();
|
||||||
|
destroy_filter(*this);
|
||||||
|
destroy_context();
|
||||||
|
client_open_ = std::errc::not_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
|
||||||
|
|
||||||
|
stdx::error open_port(const output_port& out_port, std::string_view name) override
|
||||||
|
{
|
||||||
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP");
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||||
|
|
||||||
|
if (auto err = link_ports(*this, out_port); err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
start_thread();
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error open_virtual_port(std::string_view name) override
|
||||||
|
{
|
||||||
|
if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP");
|
||||||
|
err != stdx::error{})
|
||||||
|
return err;
|
||||||
|
|
||||||
|
this->filter->set_port_buffer(configuration.output_buffer_size);
|
||||||
|
|
||||||
|
start_thread();
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error close_port() override
|
||||||
|
{
|
||||||
|
stop_thread();
|
||||||
|
return do_close_port();
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error set_port_name(std::string_view port_name) override
|
||||||
|
{
|
||||||
|
return rename_port(port_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(spa_io_position* pos)
|
||||||
|
{
|
||||||
|
m_process_clock.store(pos->clock.nsec, std::memory_order_relaxed);
|
||||||
|
const auto b = pw.filter_dequeue_buffer(this->filter->port);
|
||||||
|
if (!b)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
const auto buf = b->buffer;
|
||||||
|
const auto d = &buf->datas[0];
|
||||||
|
|
||||||
|
if (d->data == nullptr)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
spa_pod_builder build;
|
||||||
|
spa_zero(build);
|
||||||
|
spa_pod_builder_init(&build, d->data, d->maxsize);
|
||||||
|
|
||||||
|
spa_pod_frame f;
|
||||||
|
spa_pod_builder_push_sequence(&build, &f, 0);
|
||||||
|
|
||||||
|
// for all events
|
||||||
|
while (auto m_ptr = m_queue.peek())
|
||||||
|
{
|
||||||
|
auto& m = *m_ptr;
|
||||||
|
|
||||||
|
spa_pod_builder_control(&build, static_cast<int32_t>(m.timestamp), SPA_CONTROL_UMP);
|
||||||
|
int res = spa_pod_builder_bytes(&build, m.data, cmidi2_ump_get_message_size_bytes(m.data));
|
||||||
|
|
||||||
|
// Try again next buffer
|
||||||
|
if (res == -ENOSPC)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Recycle the memory
|
||||||
|
m_queue.pop();
|
||||||
|
}
|
||||||
|
spa_pod_builder_pop(&build, &f);
|
||||||
|
|
||||||
|
int n_fill_frames = build.state.offset;
|
||||||
|
if (n_fill_frames > 0)
|
||||||
|
{
|
||||||
|
d->chunk->offset = 0;
|
||||||
|
d->chunk->stride = 1;
|
||||||
|
d->chunk->size = n_fill_frames;
|
||||||
|
b->size = n_fill_frames;
|
||||||
|
|
||||||
|
pw.filter_queue_buffer(this->filter->port, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.filter_flush(this->filter->filter, true);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error send_ump(const uint32_t* message, size_t size) override
|
||||||
|
{
|
||||||
|
// FIXME
|
||||||
|
if (size > 4)
|
||||||
|
return std::errc::message_size;
|
||||||
|
|
||||||
|
libremidi::ump m;
|
||||||
|
std::copy_n(message, size, m.data);
|
||||||
|
m.timestamp = 0;
|
||||||
|
m_queue.enqueue(std::move(m));
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
int convert_timestamp(int64_t user) const noexcept
|
||||||
|
{
|
||||||
|
switch (configuration.timestamps)
|
||||||
|
{
|
||||||
|
case timestamp_mode::AudioFrame:
|
||||||
|
return static_cast<int>(user);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// TODO
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override
|
||||||
|
{
|
||||||
|
// FIXME
|
||||||
|
if (size > 4)
|
||||||
|
return std::errc::message_size;
|
||||||
|
|
||||||
|
libremidi::ump m;
|
||||||
|
std::copy_n(message, size, m.data);
|
||||||
|
m.timestamp = convert_timestamp(ts);
|
||||||
|
m_queue.enqueue(std::move(m)); // FIXME actually send them scheudled
|
||||||
|
return stdx::error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
moodycamel::ReaderWriterQueue<libremidi::ump> m_queue;
|
||||||
|
std::atomic_int64_t m_process_clock = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libremidi/backends/pipewire/helpers.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||||
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace libremidi::pipewire_ump
|
||||||
|
{
|
||||||
|
class observer_pipewire final
|
||||||
|
: public observer_api
|
||||||
|
, private pipewire_helpers
|
||||||
|
, private error_handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct
|
||||||
|
: libremidi::observer_configuration
|
||||||
|
, libremidi::pipewire_ump::observer_configuration
|
||||||
|
{
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
explicit observer_pipewire(
|
||||||
|
libremidi::observer_configuration&& conf,
|
||||||
|
libremidi::pipewire_ump::observer_configuration&& apiconf)
|
||||||
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
|
{
|
||||||
|
create_context(*this);
|
||||||
|
|
||||||
|
// FIXME port rename callback
|
||||||
|
{
|
||||||
|
this->add_callbacks("UMP", configuration);
|
||||||
|
this->start_thread();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.notify_in_constructor)
|
||||||
|
{
|
||||||
|
if (configuration.input_added)
|
||||||
|
for (const auto& p : get_input_ports())
|
||||||
|
configuration.input_added(p);
|
||||||
|
|
||||||
|
if (configuration.output_added)
|
||||||
|
for (const auto& p : get_output_ports())
|
||||||
|
configuration.output_added(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; }
|
||||||
|
|
||||||
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
|
{
|
||||||
|
return get_ports<SPA_DIRECTION_OUTPUT>("UMP", this->configuration, *this->global_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||||
|
{
|
||||||
|
return get_ports<SPA_DIRECTION_INPUT>("UMP", this->configuration, *this->global_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
~observer_pipewire()
|
||||||
|
{
|
||||||
|
stop_thread();
|
||||||
|
destroy_context();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> seen_input_ports;
|
||||||
|
std::unordered_set<std::string> seen_output_ports;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <libremidi/backends/winmidi/midi_out.hpp>
|
#include <libremidi/backends/winmidi/midi_out.hpp>
|
||||||
#include <libremidi/backends/winmidi/observer.hpp>
|
#include <libremidi/backends/winmidi/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi::winmidi
|
namespace libremidi::winmidi
|
||||||
{
|
{
|
||||||
struct backend
|
struct backend
|
||||||
@@ -14,8 +16,8 @@ struct backend
|
|||||||
using midi_out_configuration = winmidi::output_configuration;
|
using midi_out_configuration = winmidi::output_configuration;
|
||||||
using midi_observer_configuration = winmidi::observer_configuration;
|
using midi_observer_configuration = winmidi::observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES;
|
static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES;
|
||||||
static const constexpr auto name = "winmidi";
|
static const constexpr std::string_view name = "winmidi";
|
||||||
static const constexpr auto display_name = "Windows MIDI Services";
|
static const constexpr std::string_view display_name = "Windows MIDI Services";
|
||||||
|
|
||||||
static constexpr inline bool available() noexcept { return true; }
|
static constexpr inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,34 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace winrt::Microsoft::Windows::Devices::Midi2
|
||||||
|
{
|
||||||
|
class MidiSession;
|
||||||
|
class MidiEndpointDeviceInformation;
|
||||||
|
class MidiEndpointConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO allow to share midi session and endpoints
|
||||||
namespace libremidi::winmidi
|
namespace libremidi::winmidi
|
||||||
{
|
{
|
||||||
|
|
||||||
struct input_configuration
|
struct input_configuration
|
||||||
{
|
{
|
||||||
|
std::string client_name = "libremidi input";
|
||||||
|
winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct output_configuration
|
struct output_configuration
|
||||||
{
|
{
|
||||||
|
std::string client_name = "libremidi output";
|
||||||
|
winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct observer_configuration
|
struct observer_configuration
|
||||||
{
|
{
|
||||||
|
std::string client_name = "libremidi observer";
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,58 +3,94 @@
|
|||||||
#define NOMINMAX 1
|
#define NOMINMAX 1
|
||||||
#define WIN32_LEAN_AND_MEAN 1
|
#define WIN32_LEAN_AND_MEAN 1
|
||||||
#include <libremidi/detail/midi_api.hpp>
|
#include <libremidi/detail/midi_api.hpp>
|
||||||
|
#include <libremidi/detail/memory.hpp>
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
#include <guiddef.h>
|
#include <guiddef.h>
|
||||||
|
|
||||||
#include <winrt/Windows.Foundation.h>
|
#include <winrt/Windows.Foundation.h>
|
||||||
#include <winrt/Windows.Foundation.Collections.h>
|
#include <winrt/Windows.Foundation.Collections.h>
|
||||||
#include <winrt/Windows.Devices.Enumeration.h>
|
#include <winrt/Windows.Devices.Enumeration.h>
|
||||||
#include <winrt/Windows.Devices.Midi2.h>
|
#include <winrt/Microsoft.Windows.Devices.Midi2.h>
|
||||||
|
#include <libremidi/cmidi2.hpp>
|
||||||
|
|
||||||
|
// MinGW support
|
||||||
|
#if !defined(_MSC_VER)
|
||||||
|
namespace Microsoft::Windows::Devices::Midi2::Initialization
|
||||||
|
{
|
||||||
|
struct IMidiClientInitializer;
|
||||||
|
struct MidiClientInitializerUuid;
|
||||||
|
}
|
||||||
|
|
||||||
namespace midi2 = winrt::Windows::Devices::Midi2;
|
__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::IMidiClientInitializer,
|
||||||
namespace foundation = winrt::Windows::Foundation;
|
0x8087b303, 0xd551, 0xbce2, 0x1e, 0xad, 0xa2, 0x50, 0x0d, 0x50, 0xc5, 0x80);
|
||||||
namespace collections = winrt::Windows::Foundation::Collections;
|
__CRT_UUID_DECL(Microsoft::Windows::Devices::Midi2::Initialization::MidiClientInitializerUuid,
|
||||||
|
0xc3263827, 0xc3b0, 0xbdbd, 0x25, 0x00, 0xce, 0x63, 0xa3, 0xf3, 0xf2, 0xc3);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <init/Microsoft.Windows.Devices.Midi2.Initialization.hpp>
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2;
|
||||||
|
namespace foundation = winrt::Windows::Foundation;
|
||||||
|
|
||||||
namespace libremidi::winmidi
|
namespace libremidi::winmidi
|
||||||
{
|
{
|
||||||
using namespace winrt;
|
using namespace winrt;
|
||||||
using namespace winrt::Windows::Foundation;
|
using namespace winrt::Windows::Foundation;
|
||||||
using namespace winrt::Windows::Devices::Enumeration;
|
using namespace winrt::Windows::Devices::Enumeration;
|
||||||
using namespace winrt::Windows::Storage::Streams;
|
using namespace winrt::Windows::Storage::Streams;
|
||||||
using namespace winrt::Windows::Devices::Midi2;
|
using namespace winrt::Microsoft::Windows::Devices::Midi2;
|
||||||
using namespace Windows::Devices::Enumeration;
|
using namespace Windows::Devices::Enumeration;
|
||||||
|
using namespace ::Microsoft::Windows::Devices::Midi2::Initialization;
|
||||||
|
|
||||||
inline bool ichar_equals(char a, char b)
|
inline bool ichar_equals(char a, char b)
|
||||||
{
|
{
|
||||||
return std::tolower(static_cast<unsigned char>(a)) ==
|
return std::tolower(static_cast<unsigned char>(a)) ==
|
||||||
std::tolower(static_cast<unsigned char>(b));
|
std::tolower(static_cast<unsigned char>(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool iequals(std::string_view lhs, std::string_view rhs)
|
inline bool iequals(std::string_view lhs, std::string_view rhs)
|
||||||
{
|
{
|
||||||
return std::ranges::equal(lhs, rhs, ichar_equals);
|
return std::ranges::equal(lhs, rhs, ichar_equals);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation
|
inline std::pair<
|
||||||
get_port_by_name(const std::string& port) {
|
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointDeviceInformation,
|
||||||
|
winrt::Microsoft::Windows::Devices::Midi2::MidiGroupTerminalBlock>
|
||||||
|
get_port(const std::string& device_name, int group_terminal_block)
|
||||||
|
{
|
||||||
auto eps = MidiEndpointDeviceInformation::FindAll();
|
auto eps = MidiEndpointDeviceInformation::FindAll();
|
||||||
for (const auto& ep : eps)
|
for (const auto& ep : eps)
|
||||||
{
|
{
|
||||||
auto str = to_string(ep.Id());
|
auto str = to_string(ep.EndpointDeviceId());
|
||||||
if (str.empty())
|
if (str.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (iequals(str, port))
|
if (iequals(str, device_name))
|
||||||
return ep;
|
{
|
||||||
|
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||||
|
{
|
||||||
|
if (gp.Number() == group_terminal_block)
|
||||||
|
{
|
||||||
|
return std::make_pair(ep, gp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {nullptr};
|
return {nullptr, nullptr};
|
||||||
}
|
}
|
||||||
|
struct winmidi_shared_data
|
||||||
|
{
|
||||||
|
std::shared_ptr<MidiDesktopAppSdkInitializer> initializer;
|
||||||
|
bool m_ready{false};
|
||||||
|
winmidi_shared_data()
|
||||||
|
: initializer{libremidi::instance<MidiDesktopAppSdkInitializer>()}
|
||||||
|
, m_ready{
|
||||||
|
initializer && initializer->InitializeSdkRuntime()
|
||||||
|
&& initializer->EnsureServiceAvailable()}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace libremidi::winmidi
|
namespace libremidi::winmidi
|
||||||
{
|
{
|
||||||
|
|
||||||
class midi_in_impl final
|
class midi_in_impl final
|
||||||
: public midi2::in_api
|
: public midi2::in_api
|
||||||
, public error_handler
|
, public error_handler
|
||||||
|
, public winmidi_shared_data
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct
|
struct
|
||||||
@@ -24,7 +22,9 @@ public:
|
|||||||
explicit midi_in_impl(
|
explicit midi_in_impl(
|
||||||
libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)
|
libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
, m_session{MidiSession::CreateSession(L"libremidi session")}
|
, m_session{
|
||||||
|
configuration.context ? *configuration.context
|
||||||
|
: MidiSession::Create(to_hstring(configuration.client_name))}
|
||||||
{
|
{
|
||||||
this->client_open_ = stdx::error{};
|
this->client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
@@ -42,15 +42,23 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const input_port& port, std::string_view) override
|
stdx::error open_port(const input_port& port, std::string_view) override
|
||||||
{
|
{
|
||||||
auto ep = get_port_by_name(port.port_name);
|
auto device_id = std::get_if<std::string>(&port.device);
|
||||||
if (!ep)
|
if (!device_id)
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
|
auto [ep, gp] = get_port(*device_id, port.port);
|
||||||
|
if (!ep || !gp)
|
||||||
return std::errc::address_not_available;
|
return std::errc::address_not_available;
|
||||||
|
|
||||||
m_endpoint = m_session.CreateEndpointConnection(ep.Id());
|
m_group_filter = port.port - 1;
|
||||||
|
|
||||||
|
// TODO use a MidiGroupEndpointListener for the filtering
|
||||||
|
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
|
||||||
|
|
||||||
m_revoke_token = m_endpoint.MessageReceived(
|
m_revoke_token = m_endpoint.MessageReceived(
|
||||||
[&](const foundation::IInspectable& sender,
|
[this](
|
||||||
const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
|
const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&,
|
||||||
|
const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
|
||||||
process_message(args);
|
process_message(args);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,7 +67,7 @@ public:
|
|||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg)
|
void process_message(const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg)
|
||||||
{
|
{
|
||||||
static constexpr timestamp_backend_info timestamp_info{
|
static constexpr timestamp_backend_info timestamp_info{
|
||||||
.has_absolute_timestamps = true,
|
.has_absolute_timestamps = true,
|
||||||
@@ -68,6 +76,14 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
const auto& ump = msg.GetMessagePacket();
|
const auto& ump = msg.GetMessagePacket();
|
||||||
|
auto pk = msg.PeekFirstWord();
|
||||||
|
if (m_group_filter >= 0)
|
||||||
|
{
|
||||||
|
int group = cmidi2_ump_get_group(&pk);
|
||||||
|
if (group != m_group_filter)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& b = ump.GetAllWords();
|
const auto& b = ump.GetAllWords();
|
||||||
|
|
||||||
uint32_t ump_space[64];
|
uint32_t ump_space[64];
|
||||||
@@ -91,7 +107,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
MidiSession m_session;
|
MidiSession m_session;
|
||||||
winrt::event_token m_revoke_token{};
|
winrt::event_token m_revoke_token{};
|
||||||
winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
||||||
midi2::input_state_machine m_processing{this->configuration};
|
midi2::input_state_machine m_processing{this->configuration};
|
||||||
|
int m_group_filter = -1;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace libremidi::winmidi
|
|||||||
class midi_out_impl final
|
class midi_out_impl final
|
||||||
: public midi2::out_api
|
: public midi2::out_api
|
||||||
, public error_handler
|
, public error_handler
|
||||||
|
, public winmidi_shared_data
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct
|
struct
|
||||||
@@ -21,7 +22,9 @@ public:
|
|||||||
|
|
||||||
midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)
|
midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
, m_session{MidiSession::CreateSession(L"libremidi session")}
|
, m_session{
|
||||||
|
configuration.context ? *configuration.context
|
||||||
|
: MidiSession::Create(to_hstring(configuration.client_name))}
|
||||||
{
|
{
|
||||||
this->client_open_ = stdx::error{};
|
this->client_open_ = stdx::error{};
|
||||||
}
|
}
|
||||||
@@ -35,11 +38,15 @@ public:
|
|||||||
|
|
||||||
stdx::error open_port(const output_port& port, std::string_view) override
|
stdx::error open_port(const output_port& port, std::string_view) override
|
||||||
{
|
{
|
||||||
auto ep = get_port_by_name(port.port_name);
|
auto device_id = std::get_if<std::string>(&port.device);
|
||||||
if (!ep)
|
if (!device_id)
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
|
||||||
|
auto [ep, gp] = get_port(*device_id, port.port);
|
||||||
|
if (!ep || !gp)
|
||||||
return std::errc::address_not_available;
|
return std::errc::address_not_available;
|
||||||
|
|
||||||
m_endpoint = m_session.CreateEndpointConnection(ep.Id());
|
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
|
||||||
m_endpoint.Open();
|
m_endpoint.Open();
|
||||||
|
|
||||||
return stdx::error{};
|
return stdx::error{};
|
||||||
@@ -83,7 +90,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
MidiSession m_session;
|
MidiSession m_session;
|
||||||
winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <libremidi/backends/winmidi/config.hpp>
|
#include <libremidi/backends/winmidi/config.hpp>
|
||||||
#include <libremidi/backends/winmidi/helpers.hpp>
|
#include <libremidi/backends/winmidi/helpers.hpp>
|
||||||
#include <libremidi/detail/observer.hpp>
|
#include <libremidi/detail/observer.hpp>
|
||||||
;
|
|
||||||
namespace libremidi::winmidi
|
namespace libremidi::winmidi
|
||||||
{
|
{
|
||||||
struct port_info
|
struct port_info
|
||||||
@@ -11,7 +11,9 @@ struct port_info
|
|||||||
hstring name;
|
hstring name;
|
||||||
};
|
};
|
||||||
|
|
||||||
class observer_impl final : public observer_api
|
class observer_impl final
|
||||||
|
: public observer_api
|
||||||
|
, public winmidi_shared_data
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct
|
struct
|
||||||
@@ -20,11 +22,17 @@ public:
|
|||||||
{
|
{
|
||||||
} configuration;
|
} configuration;
|
||||||
|
|
||||||
|
MidiEndpointDeviceWatcher watcher = nullptr;
|
||||||
|
MidiEndpointDeviceWatcher::Added_revoker m_addHandler;
|
||||||
|
MidiEndpointDeviceWatcher::Updated_revoker m_updHandler;
|
||||||
|
MidiEndpointDeviceWatcher::Removed_revoker m_delHandler;
|
||||||
|
std::map<hstring, std::vector<input_port>> m_known_input_devices;
|
||||||
|
std::map<hstring, std::vector<output_port>> m_known_output_devices;
|
||||||
|
std::mutex m_devices_mtx;
|
||||||
|
|
||||||
explicit observer_impl(
|
explicit observer_impl(
|
||||||
libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)
|
libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf)
|
||||||
: configuration{std::move(conf), std::move(apiconf)}
|
: configuration{std::move(conf), std::move(apiconf)}
|
||||||
, session{MidiSession::CreateSession(L"libremidi session")}
|
|
||||||
{
|
{
|
||||||
if (!configuration.has_callbacks())
|
if (!configuration.has_callbacks())
|
||||||
return;
|
return;
|
||||||
@@ -40,26 +48,35 @@ public:
|
|||||||
configuration.output_added(p);
|
configuration.output_added(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if ((watcher = MidiEndpointDeviceWatcher::Create()))
|
||||||
evTokenOnInputAdded_
|
{
|
||||||
= internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); });
|
namespace enumeration = winrt::Windows::Devices::Enumeration;
|
||||||
evTokenOnInputRemoved_
|
auto addHandler = foundation::TypedEventHandler<
|
||||||
= internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); });
|
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationAddedEventArgs>(
|
||||||
evTokenOnOutputAdded_
|
this, &observer_impl::on_device_added);
|
||||||
= internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); });
|
auto updHandler = foundation::TypedEventHandler<
|
||||||
evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved(
|
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationUpdatedEventArgs>(
|
||||||
[this](const port_info& p) { on_output_removed(p); });
|
this, &observer_impl::on_device_updated);
|
||||||
*/
|
auto delHandler = foundation::TypedEventHandler<
|
||||||
|
MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationRemovedEventArgs>(
|
||||||
|
this, &observer_impl::on_device_removed);
|
||||||
|
|
||||||
|
m_addHandler = watcher.Added(winrt::auto_revoke, addHandler);
|
||||||
|
m_updHandler = watcher.Updated(winrt::auto_revoke, updHandler);
|
||||||
|
m_delHandler = watcher.Removed(winrt::auto_revoke, delHandler);
|
||||||
|
|
||||||
|
watcher.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~observer_impl()
|
~observer_impl()
|
||||||
{
|
{
|
||||||
if (!configuration.has_callbacks())
|
if (!configuration.has_callbacks())
|
||||||
return;
|
return;
|
||||||
// internalInPortObserver_.PortAdded(evTokenOnInputAdded_);
|
|
||||||
// internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);
|
m_addHandler.revoke();
|
||||||
// internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);
|
m_updHandler.revoke();
|
||||||
// internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);
|
m_delHandler.revoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override
|
libremidi::API get_current_api() const noexcept override
|
||||||
@@ -67,33 +84,56 @@ public:
|
|||||||
return libremidi::API::WINDOWS_MIDI_SERVICES;
|
return libremidi::API::WINDOWS_MIDI_SERVICES;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool Input>
|
static port_information::port_type code_to_type(std::string_view str) noexcept
|
||||||
auto to_port_info(const DeviceInformation& p) const noexcept
|
|
||||||
-> std::conditional_t<Input, input_port, output_port>
|
|
||||||
{
|
{
|
||||||
|
using enum port_information::port_type;
|
||||||
|
|
||||||
|
if (str.starts_with("KS"))
|
||||||
|
return hardware;
|
||||||
|
if (str == "BLE")
|
||||||
|
return port_information::port_type(hardware | bluetooth);
|
||||||
|
if (str == "VPB" || str == "APP")
|
||||||
|
return software;
|
||||||
|
if (str == "LOOP")
|
||||||
|
return port_information::port_type(software | loopback);
|
||||||
|
if (str.starts_with("NET"))
|
||||||
|
return network;
|
||||||
|
return unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool Input>
|
||||||
|
auto to_port_info(const MidiEndpointDeviceInformation& p, const MidiGroupTerminalBlock& gp)
|
||||||
|
const noexcept -> std::conditional_t<Input, input_port, output_port>
|
||||||
|
{
|
||||||
|
const auto& tinfo = p.GetTransportSuppliedInfo();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
{.client = 0,
|
{.client = 0,
|
||||||
.port = 0,
|
.container = std::bit_cast<libremidi::uuid>(p.ContainerId()),
|
||||||
.manufacturer = "",
|
.device = to_string(p.EndpointDeviceId()),
|
||||||
.device_name = "",
|
.port = gp.Number(),
|
||||||
.port_name = to_string(p.Id()),
|
.manufacturer = to_string(tinfo.ManufacturerName),
|
||||||
.display_name = to_string(p.Name())}};
|
.device_name = to_string(p.Name()),
|
||||||
|
.port_name = to_string(gp.Name()),
|
||||||
|
.display_name = to_string(gp.Name()) + " " + std::to_string(gp.Number()),
|
||||||
|
.type = code_to_type(to_string(p.GetTransportSuppliedInfo().TransportCode))}};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
{
|
{
|
||||||
std::vector<libremidi::input_port> ret;
|
std::vector<libremidi::input_port> ret;
|
||||||
|
|
||||||
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
|
for (const auto& ep : MidiEndpointDeviceInformation::FindAll())
|
||||||
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
|
|
||||||
for (const auto& ep : endpointDevices)
|
|
||||||
{
|
{
|
||||||
if(ep.Name().starts_with(L"Diagnostics")) {
|
if(ep.Name().starts_with(L"Diagnostics")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// FIXME if(has input...)
|
|
||||||
|
|
||||||
ret.emplace_back(to_port_info<true>(ep));
|
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||||
|
{
|
||||||
|
if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockOutput)
|
||||||
|
ret.emplace_back(to_port_info<true>(ep, gp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -103,46 +143,132 @@ public:
|
|||||||
{
|
{
|
||||||
std::vector<libremidi::output_port> ret;
|
std::vector<libremidi::output_port> ret;
|
||||||
|
|
||||||
auto deviceSelector = MidiEndpointConnection::GetDeviceSelector();
|
for (const auto& ep : MidiEndpointDeviceInformation::FindAll())
|
||||||
auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get();
|
|
||||||
for (const auto& ep : endpointDevices)
|
|
||||||
{
|
{
|
||||||
if(ep.Name().starts_with(L"Diagnostics")) {
|
if(ep.Name().starts_with(L"Diagnostics")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// FIXME if(has output...)
|
|
||||||
ret.emplace_back(to_port_info<false>(ep));
|
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||||
|
{
|
||||||
|
if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockInput)
|
||||||
|
ret.emplace_back(to_port_info<false>(ep, gp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_input_added(const DeviceInformation& name)
|
// Note: these callbacks are called from some random thread!
|
||||||
|
void on_device_added(
|
||||||
|
const MidiEndpointDeviceWatcher&, const MidiEndpointDeviceInformationAddedEventArgs& result)
|
||||||
{
|
{
|
||||||
if (configuration.input_added)
|
add_device(result.AddedDevice());
|
||||||
configuration.input_added(to_port_info<true>(name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_input_removed(const DeviceInformation& name)
|
void on_device_updated(
|
||||||
|
const MidiEndpointDeviceWatcher& e,
|
||||||
|
const MidiEndpointDeviceInformationUpdatedEventArgs& result)
|
||||||
{
|
{
|
||||||
|
// OPTIMIZEME
|
||||||
|
remove_device(result.EndpointDeviceId());
|
||||||
|
|
||||||
|
add_device(
|
||||||
|
MidiEndpointDeviceInformation::CreateFromEndpointDeviceId(result.EndpointDeviceId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_device_removed(
|
||||||
|
const MidiEndpointDeviceWatcher&,
|
||||||
|
const MidiEndpointDeviceInformationRemovedEventArgs& result)
|
||||||
|
{
|
||||||
|
remove_device(result.EndpointDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_device(const MidiEndpointDeviceInformation& ep)
|
||||||
|
{
|
||||||
|
for (const auto& gp : ep.GetGroupTerminalBlocks())
|
||||||
|
{
|
||||||
|
MidiGroupTerminalBlockDirection direction = gp.Direction();
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case MidiGroupTerminalBlockDirection::Bidirectional: {
|
||||||
|
if (configuration.input_added)
|
||||||
|
{
|
||||||
|
auto ip = to_port_info<true>(ep, gp);
|
||||||
|
{
|
||||||
|
std::lock_guard _{m_devices_mtx};
|
||||||
|
m_known_input_devices[ep.EndpointDeviceId()].push_back(ip);
|
||||||
|
}
|
||||||
|
configuration.input_added(std::move(ip));
|
||||||
|
}
|
||||||
|
if (configuration.output_added)
|
||||||
|
{
|
||||||
|
auto op = to_port_info<false>(ep, gp);
|
||||||
|
{
|
||||||
|
std::lock_guard _{m_devices_mtx};
|
||||||
|
m_known_output_devices[ep.EndpointDeviceId()].push_back(op);
|
||||||
|
}
|
||||||
|
configuration.output_added(std::move(op));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MidiGroupTerminalBlockDirection::BlockInput:
|
||||||
|
if (configuration.input_added)
|
||||||
|
{
|
||||||
|
auto ip = to_port_info<true>(ep, gp);
|
||||||
|
{
|
||||||
|
std::lock_guard _{m_devices_mtx};
|
||||||
|
m_known_input_devices[ep.EndpointDeviceId()].push_back(ip);
|
||||||
|
}
|
||||||
|
configuration.input_added(std::move(ip));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MidiGroupTerminalBlockDirection::BlockOutput:
|
||||||
|
if (configuration.output_added)
|
||||||
|
{
|
||||||
|
auto op = to_port_info<false>(ep, gp);
|
||||||
|
{
|
||||||
|
std::lock_guard _{m_devices_mtx};
|
||||||
|
m_known_output_devices[ep.EndpointDeviceId()].push_back(op);
|
||||||
|
}
|
||||||
|
configuration.output_added(std::move(op));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void remove_device(hstring eid)
|
||||||
|
{
|
||||||
|
std::vector<input_port> to_remove_in;
|
||||||
|
std::vector<output_port> to_remove_out;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard _{m_devices_mtx};
|
||||||
|
if (auto it = m_known_input_devices.find(eid); it != m_known_input_devices.end())
|
||||||
|
{
|
||||||
|
for (auto& ip : it->second)
|
||||||
|
{
|
||||||
|
to_remove_in.push_back(ip);
|
||||||
|
}
|
||||||
|
m_known_input_devices.erase(it);
|
||||||
|
}
|
||||||
|
if (auto it = m_known_output_devices.find(eid); it != m_known_output_devices.end())
|
||||||
|
{
|
||||||
|
for (auto& op : it->second)
|
||||||
|
{
|
||||||
|
to_remove_out.push_back(op);
|
||||||
|
}
|
||||||
|
m_known_output_devices.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (configuration.input_removed)
|
if (configuration.input_removed)
|
||||||
configuration.input_removed(to_port_info<true>(name));
|
for (auto& port : to_remove_in)
|
||||||
}
|
configuration.input_removed(port);
|
||||||
|
|
||||||
void on_output_added(const DeviceInformation& name)
|
|
||||||
{
|
|
||||||
if (configuration.output_added)
|
|
||||||
configuration.output_added(to_port_info<false>(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_output_removed(const DeviceInformation& name)
|
|
||||||
{
|
|
||||||
if (configuration.output_removed)
|
if (configuration.output_removed)
|
||||||
configuration.output_removed(to_port_info<false>(name));
|
for (auto& port : to_remove_out)
|
||||||
|
configuration.output_removed(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
MidiSession session;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <libremidi/backends/winmm/midi_out.hpp>
|
#include <libremidi/backends/winmm/midi_out.hpp>
|
||||||
#include <libremidi/backends/winmm/observer.hpp>
|
#include <libremidi/backends/winmm/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
// Default for Windows is to add an identifier to the port names; this
|
// Default for Windows is to add an identifier to the port names; this
|
||||||
// flag can be defined (e.g. in your project file) to disable this behaviour.
|
// flag can be defined (e.g. in your project file) to disable this behaviour.
|
||||||
// #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES
|
// #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES
|
||||||
@@ -28,8 +30,8 @@ struct winmm_backend
|
|||||||
using midi_out_configuration = winmm_output_configuration;
|
using midi_out_configuration = winmm_output_configuration;
|
||||||
using midi_observer_configuration = winmm_observer_configuration;
|
using midi_observer_configuration = winmm_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::WINDOWS_MM;
|
static const constexpr auto API = libremidi::API::WINDOWS_MM;
|
||||||
static const constexpr auto name = "winmm";
|
static const constexpr std::string_view name = "winmm";
|
||||||
static const constexpr auto display_name = "Windows Multimedia";
|
static const constexpr std::string_view display_name = "Windows Multimedia";
|
||||||
|
|
||||||
static constexpr inline bool available() noexcept { return true; }
|
static constexpr inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/backends/winmm/error_domain.hpp>
|
#include <libremidi/backends/winmm/error_domain.hpp>
|
||||||
#include <libremidi/detail/midi_api.hpp>
|
#include <libremidi/detail/midi_api.hpp>
|
||||||
#include <algorithm>
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
@@ -70,5 +71,4 @@ inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumbe
|
|||||||
deviceName += " ";
|
deviceName += " ";
|
||||||
deviceName += std::to_string(x);
|
deviceName += std::to_string(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
#include <libremidi/backends/winmm/helpers.hpp>
|
#include <libremidi/backends/winmm/helpers.hpp>
|
||||||
#include <libremidi/detail/observer.hpp>
|
#include <libremidi/detail/observer.hpp>
|
||||||
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <mutex>
|
|
||||||
#include <ranges>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <libremidi/backends/winuwp/midi_out.hpp>
|
#include <libremidi/backends/winuwp/midi_out.hpp>
|
||||||
#include <libremidi/backends/winuwp/observer.hpp>
|
#include <libremidi/backends/winuwp/observer.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
struct winuwp_backend
|
struct winuwp_backend
|
||||||
@@ -14,8 +16,8 @@ struct winuwp_backend
|
|||||||
using midi_out_configuration = winuwp_output_configuration;
|
using midi_out_configuration = winuwp_output_configuration;
|
||||||
using midi_observer_configuration = winuwp_observer_configuration;
|
using midi_observer_configuration = winuwp_observer_configuration;
|
||||||
static const constexpr auto API = libremidi::API::WINDOWS_UWP;
|
static const constexpr auto API = libremidi::API::WINDOWS_UWP;
|
||||||
static const constexpr auto name = "winuwp";
|
static const constexpr std::string_view name = "winuwp";
|
||||||
static const constexpr auto display_name = "Windows UWP";
|
static const constexpr std::string_view display_name = "Windows UWP";
|
||||||
|
|
||||||
static constexpr inline bool available() noexcept { return true; }
|
static constexpr inline bool available() noexcept { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,13 +197,13 @@ public:
|
|||||||
configuration.output_added(p);
|
configuration.output_added(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
evTokenOnInputAdded_
|
evTokenOnInputAdded_ = get_internal_in_port_observer().PortAdded(
|
||||||
= internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); });
|
[this](const port_info& p) { on_input_added(p); });
|
||||||
evTokenOnInputRemoved_
|
evTokenOnInputRemoved_ = get_internal_in_port_observer().PortRemoved(
|
||||||
= internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); });
|
[this](const port_info& p) { on_input_removed(p); });
|
||||||
evTokenOnOutputAdded_
|
evTokenOnOutputAdded_ = get_internal_out_port_observer().PortAdded(
|
||||||
= internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); });
|
[this](const port_info& p) { on_output_added(p); });
|
||||||
evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved(
|
evTokenOnOutputRemoved_ = get_internal_out_port_observer().PortRemoved(
|
||||||
[this](const port_info& p) { on_output_removed(p); });
|
[this](const port_info& p) { on_output_removed(p); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,10 +211,10 @@ public:
|
|||||||
{
|
{
|
||||||
if (!configuration.has_callbacks())
|
if (!configuration.has_callbacks())
|
||||||
return;
|
return;
|
||||||
internalInPortObserver_.PortAdded(evTokenOnInputAdded_);
|
get_internal_in_port_observer().PortAdded(evTokenOnInputAdded_);
|
||||||
internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_);
|
get_internal_in_port_observer().PortRemoved(evTokenOnInputRemoved_);
|
||||||
internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_);
|
get_internal_out_port_observer().PortAdded(evTokenOnOutputAdded_);
|
||||||
internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_);
|
get_internal_out_port_observer().PortRemoved(evTokenOnOutputRemoved_);
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; }
|
||||||
@@ -235,7 +235,7 @@ public:
|
|||||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||||
{
|
{
|
||||||
std::vector<libremidi::input_port> ret;
|
std::vector<libremidi::input_port> ret;
|
||||||
for (auto& port : internalInPortObserver_.get_ports())
|
for (auto& port : get_internal_in_port_observer().get_ports())
|
||||||
ret.push_back(to_port_info<true>(port));
|
ret.push_back(to_port_info<true>(port));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -243,18 +243,20 @@ public:
|
|||||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||||
{
|
{
|
||||||
std::vector<libremidi::output_port> ret;
|
std::vector<libremidi::output_port> ret;
|
||||||
for (auto& port : internalOutPortObserver_.get_ports())
|
for (auto& port : get_internal_out_port_observer().get_ports())
|
||||||
ret.push_back(to_port_info<false>(port));
|
ret.push_back(to_port_info<false>(port));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static observer_winuwp_internal& get_internal_in_port_observer()
|
static observer_winuwp_internal& get_internal_in_port_observer()
|
||||||
{
|
{
|
||||||
|
static observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};
|
||||||
return internalInPortObserver_;
|
return internalInPortObserver_;
|
||||||
}
|
}
|
||||||
|
|
||||||
static observer_winuwp_internal& get_internal_out_port_observer()
|
static observer_winuwp_internal& get_internal_out_port_observer()
|
||||||
{
|
{
|
||||||
|
static observer_winuwp_internal internalOutPortObserver_{MidiOutPort::GetDeviceSelector()};
|
||||||
return internalOutPortObserver_;
|
return internalOutPortObserver_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,10 +285,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static inline observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()};
|
|
||||||
static inline observer_winuwp_internal internalOutPortObserver_{
|
|
||||||
MidiOutPort::GetDeviceSelector()};
|
|
||||||
|
|
||||||
int evTokenOnInputAdded_{-1};
|
int evTokenOnInputAdded_{-1};
|
||||||
int evTokenOnInputRemoved_{-1};
|
int evTokenOnInputRemoved_{-1};
|
||||||
int evTokenOnOutputAdded_{-1};
|
int evTokenOnOutputAdded_{-1};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <libremidi/shared_context.hpp>
|
#include <libremidi/shared_context.hpp>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace libremidi::midi1
|
namespace libremidi::midi1
|
||||||
{
|
{
|
||||||
@@ -66,8 +67,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
explicit client(const client_configuration& conf, shared_configurations ctx)
|
explicit client(const client_configuration& conf, shared_configurations ctx)
|
||||||
: configuration{conf}
|
: m_configuration{conf}
|
||||||
, context{ctx}
|
, m_context{std::move(ctx)}
|
||||||
, m_observer{
|
, m_observer{
|
||||||
observer_configuration{
|
observer_configuration{
|
||||||
.on_error = conf.on_error,
|
.on_error = conf.on_error,
|
||||||
@@ -81,10 +82,10 @@ public:
|
|||||||
.track_hardware = conf.track_hardware,
|
.track_hardware = conf.track_hardware,
|
||||||
.track_virtual = conf.track_virtual,
|
.track_virtual = conf.track_virtual,
|
||||||
.notify_in_constructor = false},
|
.notify_in_constructor = false},
|
||||||
context.observer}
|
m_context.observer}
|
||||||
{
|
{
|
||||||
if (context.context)
|
if (m_context.context)
|
||||||
context.context->start_processing();
|
m_context.context->start_processing();
|
||||||
}
|
}
|
||||||
|
|
||||||
~client()
|
~client()
|
||||||
@@ -92,8 +93,8 @@ public:
|
|||||||
m_inputs.clear();
|
m_inputs.clear();
|
||||||
m_outputs.clear();
|
m_outputs.clear();
|
||||||
|
|
||||||
if (context.context)
|
if (m_context.context)
|
||||||
context.context->stop_processing();
|
m_context.context->stop_processing();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<libremidi::input_port> get_input_ports() const noexcept
|
std::vector<libremidi::input_port> get_input_ports() const noexcept
|
||||||
@@ -116,19 +117,19 @@ public:
|
|||||||
input_configuration{
|
input_configuration{
|
||||||
.on_message
|
.on_message
|
||||||
= [this,
|
= [this,
|
||||||
port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); },
|
port](libremidi::message&& m) { m_configuration.on_message(port, std::move(m)); },
|
||||||
.on_raw_data = {},
|
.on_raw_data = {},
|
||||||
.get_timestamp = {},
|
.get_timestamp = {},
|
||||||
|
|
||||||
.on_error = configuration.on_error,
|
.on_error = m_configuration.on_error,
|
||||||
.on_warning = configuration.on_warning,
|
.on_warning = m_configuration.on_warning,
|
||||||
|
|
||||||
.ignore_sysex = configuration.ignore_sysex,
|
.ignore_sysex = m_configuration.ignore_sysex,
|
||||||
.ignore_timing = configuration.ignore_timing,
|
.ignore_timing = m_configuration.ignore_timing,
|
||||||
.ignore_sensing = configuration.ignore_sensing,
|
.ignore_sensing = m_configuration.ignore_sensing,
|
||||||
|
|
||||||
.timestamps = configuration.timestamps},
|
.timestamps = m_configuration.timestamps},
|
||||||
context.in);
|
m_context.in);
|
||||||
|
|
||||||
res.first->second.open_port(port, name);
|
res.first->second.open_port(port, name);
|
||||||
}
|
}
|
||||||
@@ -141,11 +142,11 @@ public:
|
|||||||
auto res = m_outputs.try_emplace(
|
auto res = m_outputs.try_emplace(
|
||||||
port,
|
port,
|
||||||
output_configuration{
|
output_configuration{
|
||||||
.on_error = configuration.on_error,
|
.on_error = m_configuration.on_error,
|
||||||
.on_warning = configuration.on_warning,
|
.on_warning = m_configuration.on_warning,
|
||||||
|
|
||||||
.timestamps = configuration.timestamps},
|
.timestamps = m_configuration.timestamps},
|
||||||
context.out);
|
m_context.out);
|
||||||
|
|
||||||
res.first->second.open_port(port, name);
|
res.first->second.open_port(port, name);
|
||||||
}
|
}
|
||||||
@@ -190,8 +191,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
client_configuration configuration;
|
client_configuration m_configuration;
|
||||||
shared_configurations context;
|
shared_configurations m_context;
|
||||||
|
|
||||||
std::map<input_port, midi_in> m_inputs;
|
std::map<input_port, midi_in> m_inputs;
|
||||||
std::map<output_port, midi_out> m_outputs;
|
std::map<output_port, midi_out> m_outputs;
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ enum cmidi2_ump_chord_name_tonic_note
|
|||||||
CMIDI2_UMP_CHORD_NAME_G = 7,
|
CMIDI2_UMP_CHORD_NAME_G = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum cmidi2_ump_chor_name_chord_type
|
enum cmidi2_ump_chord_name_chord_type
|
||||||
{
|
{
|
||||||
CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0,
|
CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0,
|
||||||
CMIDI2_UMP_CHORD_TYPE_MAJOR = 1,
|
CMIDI2_UMP_CHORD_TYPE_MAJOR = 1,
|
||||||
@@ -1494,7 +1494,7 @@ static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump)
|
|||||||
{
|
{
|
||||||
return cmidi2_ump_get_32_to_64(ump);
|
return cmidi2_ump_get_32_to_64(ump);
|
||||||
}
|
}
|
||||||
static inline uint32_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump)
|
static inline uint8_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump)
|
||||||
{
|
{
|
||||||
return cmidi2_ump_get_byte_at(ump, 2);
|
return cmidi2_ump_get_byte_at(ump, 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if defined(MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
#define NOMINMAX 1
|
#define NOMINMAX 1
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif
|
#endif
|
||||||
@@ -62,3 +62,9 @@ using midi_bytes = std::vector<unsigned char>;
|
|||||||
#else
|
#else
|
||||||
#define LIBREMIDI_INLINE
|
#define LIBREMIDI_INLINE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_contracts >= 202502L
|
||||||
|
#define LIBREMIDI_PRECONDITION(...) pre(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LIBREMIDI_PRECONDITION(...)
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <libremidi/api.hpp>
|
||||||
#include <libremidi/backends/alsa_raw/config.hpp>
|
#include <libremidi/backends/alsa_raw/config.hpp>
|
||||||
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
#include <libremidi/backends/alsa_raw_ump/config.hpp>
|
||||||
#include <libremidi/backends/alsa_seq/config.hpp>
|
#include <libremidi/backends/alsa_seq/config.hpp>
|
||||||
@@ -8,17 +9,60 @@
|
|||||||
#include <libremidi/backends/coremidi_ump/config.hpp>
|
#include <libremidi/backends/coremidi_ump/config.hpp>
|
||||||
#include <libremidi/backends/emscripten/config.hpp>
|
#include <libremidi/backends/emscripten/config.hpp>
|
||||||
#include <libremidi/backends/jack/config.hpp>
|
#include <libremidi/backends/jack/config.hpp>
|
||||||
|
#include <libremidi/backends/jack_ump/config.hpp>
|
||||||
#include <libremidi/backends/keyboard/config.hpp>
|
#include <libremidi/backends/keyboard/config.hpp>
|
||||||
#include <libremidi/backends/net/config.hpp>
|
#include <libremidi/backends/net/config.hpp>
|
||||||
#include <libremidi/backends/pipewire/config.hpp>
|
#include <libremidi/backends/pipewire/config.hpp>
|
||||||
|
#include <libremidi/backends/pipewire_ump/config.hpp>
|
||||||
#include <libremidi/backends/winmidi/config.hpp>
|
#include <libremidi/backends/winmidi/config.hpp>
|
||||||
#include <libremidi/backends/winmm/config.hpp>
|
#include <libremidi/backends/winmm/config.hpp>
|
||||||
#include <libremidi/backends/winuwp/config.hpp>
|
#include <libremidi/backends/winuwp/config.hpp>
|
||||||
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
|
struct unspecified_configuration
|
||||||
|
{
|
||||||
|
};
|
||||||
struct dummy_configuration
|
struct dummy_configuration
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using input_api_configuration = std::variant<
|
||||||
|
unspecified_configuration, dummy_configuration, alsa_raw_input_configuration,
|
||||||
|
alsa_raw_ump::input_configuration, alsa_seq::input_configuration,
|
||||||
|
alsa_seq_ump::input_configuration, coremidi_input_configuration,
|
||||||
|
coremidi_ump::input_configuration, emscripten_input_configuration, jack_input_configuration,
|
||||||
|
kbd_input_configuration, libremidi::net::dgram_input_configuration,
|
||||||
|
libremidi::net_ump::dgram_input_configuration, pipewire_input_configuration,
|
||||||
|
winmidi::input_configuration, winmm_input_configuration, winuwp_input_configuration,
|
||||||
|
jack_ump::input_configuration, pipewire_ump::input_configuration, libremidi::API>;
|
||||||
|
|
||||||
|
using output_api_configuration = std::variant<
|
||||||
|
unspecified_configuration, dummy_configuration, alsa_raw_output_configuration,
|
||||||
|
alsa_raw_ump::output_configuration, alsa_seq::output_configuration,
|
||||||
|
alsa_seq_ump::output_configuration, coremidi_output_configuration,
|
||||||
|
coremidi_ump::output_configuration, emscripten_output_configuration, jack_output_configuration,
|
||||||
|
libremidi::net::dgram_output_configuration, libremidi::net_ump::dgram_output_configuration,
|
||||||
|
pipewire_output_configuration, winmidi::output_configuration, winmm_output_configuration,
|
||||||
|
winuwp_output_configuration, jack_ump::output_configuration,
|
||||||
|
pipewire_ump::output_configuration, libremidi::API>;
|
||||||
|
|
||||||
|
using observer_api_configuration = std::variant<
|
||||||
|
unspecified_configuration, dummy_configuration, alsa_raw_observer_configuration,
|
||||||
|
alsa_raw_ump::observer_configuration, alsa_seq::observer_configuration,
|
||||||
|
alsa_seq_ump::observer_configuration, coremidi_observer_configuration,
|
||||||
|
coremidi_ump::observer_configuration, emscripten_observer_configuration,
|
||||||
|
jack_observer_configuration, libremidi::net::dgram_observer_configuration,
|
||||||
|
libremidi::net_ump::dgram_observer_configuration, pipewire_observer_configuration,
|
||||||
|
winmidi::observer_configuration, winmm_observer_configuration, winuwp_observer_configuration,
|
||||||
|
jack_ump::observer_configuration, pipewire_ump::observer_configuration, libremidi::API>;
|
||||||
|
|
||||||
|
LIBREMIDI_EXPORT
|
||||||
|
libremidi::API midi_api(const input_api_configuration& conf);
|
||||||
|
LIBREMIDI_EXPORT
|
||||||
|
libremidi::API midi_api(const output_api_configuration& conf);
|
||||||
|
LIBREMIDI_EXPORT
|
||||||
|
libremidi::API midi_api(const observer_api_configuration& conf);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/api.hpp>
|
#include <libremidi/api.hpp>
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
#include <libremidi/configurations.hpp>
|
||||||
#include <libremidi/input_configuration.hpp>
|
#include <libremidi/input_configuration.hpp>
|
||||||
#include <libremidi/observer_configuration.hpp>
|
#include <libremidi/observer_configuration.hpp>
|
||||||
#include <libremidi/output_configuration.hpp>
|
#include <libremidi/output_configuration.hpp>
|
||||||
|
|
||||||
#include <any>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
@@ -16,22 +16,22 @@ class observer;
|
|||||||
|
|
||||||
//! Get the default midi in configuration object for a given API
|
//! Get the default midi in configuration object for a given API
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any midi_in_configuration_for(libremidi::API);
|
input_api_configuration midi_in_configuration_for(libremidi::API);
|
||||||
|
|
||||||
//! Get the default midi out configuration object for a given API
|
//! Get the default midi out configuration object for a given API
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any midi_out_configuration_for(libremidi::API);
|
output_api_configuration midi_out_configuration_for(libremidi::API);
|
||||||
|
|
||||||
//! Get the default observer configuration object for a given API
|
//! Get the default observer configuration object for a given API
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any observer_configuration_for(libremidi::API);
|
observer_api_configuration observer_configuration_for(libremidi::API);
|
||||||
|
|
||||||
//! Get a matching midi in configuration object for a given observer instance.
|
//! Get a matching midi in configuration object for a given observer instance.
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any midi_in_configuration_for(const libremidi::observer&);
|
input_api_configuration midi_in_configuration_for(const libremidi::observer&);
|
||||||
//! Get a matching midi out configuration object for a given observer instance.
|
//! Get a matching midi out configuration object for a given observer instance.
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any midi_out_configuration_for(const libremidi::observer&);
|
output_api_configuration midi_out_configuration_for(const libremidi::observer&);
|
||||||
|
|
||||||
//! Get the default port for midi input (if any) in a given API.
|
//! Get the default port for midi input (if any) in a given API.
|
||||||
//! Note that this will only return a hardware / physical port.
|
//! Note that this will only return a hardware / physical port.
|
||||||
@@ -47,15 +47,15 @@ namespace midi1
|
|||||||
{
|
{
|
||||||
//! Get a default configuration for creating a MIDI 1 input
|
//! Get a default configuration for creating a MIDI 1 input
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any in_default_configuration();
|
input_api_configuration in_default_configuration();
|
||||||
|
|
||||||
//! Get a default configuration for creating a MIDI 1 output
|
//! Get a default configuration for creating a MIDI 1 output
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any out_default_configuration();
|
output_api_configuration out_default_configuration();
|
||||||
|
|
||||||
//! Get a default configuration for creating a MIDI 1 observer
|
//! Get a default configuration for creating a MIDI 1 observer
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any observer_default_configuration();
|
observer_api_configuration observer_default_configuration();
|
||||||
|
|
||||||
//! Get a default MIDI 1 input port for the default API of the system
|
//! Get a default MIDI 1 input port for the default API of the system
|
||||||
inline std::optional<input_port> in_default_port() noexcept
|
inline std::optional<input_port> in_default_port() noexcept
|
||||||
@@ -74,15 +74,15 @@ namespace midi2
|
|||||||
{
|
{
|
||||||
//! Get a default configuration for creating a MIDI 2 input
|
//! Get a default configuration for creating a MIDI 2 input
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any in_default_configuration();
|
input_api_configuration in_default_configuration();
|
||||||
|
|
||||||
//! Get a default configuration for creating a MIDI 2 output
|
//! Get a default configuration for creating a MIDI 2 output
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any out_default_configuration();
|
output_api_configuration out_default_configuration();
|
||||||
|
|
||||||
//! Get a default configuration for creating a MIDI 2 observer
|
//! Get a default configuration for creating a MIDI 2 observer
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
std::any observer_default_configuration();
|
observer_api_configuration observer_default_configuration();
|
||||||
|
|
||||||
//! Get a default MIDI 2 input port for the default API of the system
|
//! Get a default MIDI 2 input port for the default API of the system
|
||||||
inline std::optional<input_port> in_default_port() noexcept
|
inline std::optional<input_port> in_default_port() noexcept
|
||||||
|
|||||||
@@ -7,8 +7,193 @@
|
|||||||
#include <libremidi/error.hpp>
|
#include <libremidi/error.hpp>
|
||||||
#include <libremidi/message.hpp>
|
#include <libremidi/message.hpp>
|
||||||
#include <libremidi/ump.hpp>
|
#include <libremidi/ump.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
static inline void cmidi2_reverse(int64_t v, cmidi2_ump* output)
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
uint32_t u[2];
|
||||||
|
int64_t i;
|
||||||
|
} e0, e1;
|
||||||
|
e0.i = v;
|
||||||
|
e1.u[0] = e0.u[1];
|
||||||
|
e1.u[1] = e0.u[0];
|
||||||
|
memcpy(output, &e1, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline auto u7_to_u16(uint8_t in) -> uint16_t
|
||||||
|
{
|
||||||
|
static constexpr auto ratio = float(std::numeric_limits<uint16_t>::max()) / 127.f;
|
||||||
|
return std::clamp(std::round(in * ratio), 0.f, float(std::numeric_limits<uint16_t>::max()));
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline auto u7_to_u32(uint8_t in) -> uint32_t
|
||||||
|
{
|
||||||
|
static constexpr auto ratio = double(std::numeric_limits<uint32_t>::max()) / 127.;
|
||||||
|
return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits<uint32_t>::max()));
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline auto u16_to_u32(uint16_t in) -> uint32_t
|
||||||
|
{
|
||||||
|
static constexpr auto ratio = double(std::numeric_limits<uint32_t>::max())
|
||||||
|
/ double(std::numeric_limits<uint16_t>::max());
|
||||||
|
return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits<uint32_t>::max()));
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
cmidi2_midi1_channel_voice_to_midi2(const uint8_t* bytes, std::size_t sz, cmidi2_ump* output)
|
||||||
|
{
|
||||||
|
if (sz < 2)
|
||||||
|
return false;
|
||||||
|
if (sz > 3)
|
||||||
|
return false;
|
||||||
|
const auto status = libremidi::message_type(bytes[0] & 0xF0);
|
||||||
|
const int channel = bytes[0] & 0x0F;
|
||||||
|
const int group = 0;
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case libremidi::message_type::NOTE_OFF: {
|
||||||
|
const int64_t msg
|
||||||
|
= cmidi2_ump_midi2_note_off(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0);
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case libremidi::message_type::NOTE_ON: {
|
||||||
|
const int64_t msg
|
||||||
|
= cmidi2_ump_midi2_note_on(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0);
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case libremidi::message_type::POLY_PRESSURE: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_paf(group, channel, bytes[1], u7_to_u32(bytes[2]));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case libremidi::message_type::CONTROL_CHANGE: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_cc(group, channel, bytes[1], u7_to_u32(bytes[2]));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case libremidi::message_type::PROGRAM_CHANGE: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, bytes[1], 0, 0);
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case libremidi::message_type::AFTERTOUCH: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(bytes[1]));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case libremidi::message_type::PITCH_BEND: {
|
||||||
|
const auto pb = bytes[1] + bytes[2] * 0x80;
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(const cmidi2_ump* input, cmidi2_ump* output)
|
||||||
|
{
|
||||||
|
if (cmidi2_ump_get_message_type(input) != CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto group = cmidi2_ump_get_group(input);
|
||||||
|
const auto channel = cmidi2_ump_get_channel(input);
|
||||||
|
const auto status = cmidi2_ump_get_status_code(input);
|
||||||
|
const auto b1 = cmidi2_ump_get_midi1_byte2(input);
|
||||||
|
const auto b2 = cmidi2_ump_get_midi1_byte3(input);
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
// Not in midi 1:
|
||||||
|
case CMIDI2_STATUS_PER_NOTE_RCC:
|
||||||
|
case CMIDI2_STATUS_PER_NOTE_ACC:
|
||||||
|
case CMIDI2_STATUS_RPN:
|
||||||
|
case CMIDI2_STATUS_NRPN:
|
||||||
|
case CMIDI2_STATUS_RELATIVE_RPN:
|
||||||
|
case CMIDI2_STATUS_RELATIVE_NRPN:
|
||||||
|
case CMIDI2_STATUS_PER_NOTE_PITCH_BEND:
|
||||||
|
case CMIDI2_STATUS_PER_NOTE_MANAGEMENT:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case CMIDI2_STATUS_NOTE_OFF: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, b1, 0, u7_to_u16(b2), 0);
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMIDI2_STATUS_NOTE_ON: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, b1, 0, u7_to_u16(b2), 0);
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMIDI2_STATUS_PAF: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_paf(group, channel, b1, u7_to_u32(b2));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMIDI2_STATUS_CC: {
|
||||||
|
int64_t msg = cmidi2_ump_midi2_cc(group, channel, b1, u7_to_u32(b2));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMIDI2_STATUS_PROGRAM: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, b1, 0, 0);
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMIDI2_STATUS_CAF: {
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(b1));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMIDI2_STATUS_PITCH_BEND: {
|
||||||
|
const auto pb = b1 + b2 * 0x80;
|
||||||
|
const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb));
|
||||||
|
cmidi2_reverse(msg, output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
|
static inline libremidi::message midi1_from_ump(libremidi::ump u)
|
||||||
|
{
|
||||||
|
libremidi::message ret;
|
||||||
|
ret.bytes.resize(4);
|
||||||
|
if (auto n = cmidi2_convert_single_ump_to_midi1((uint8_t*)ret.bytes.data(), 4, u.data))
|
||||||
|
{
|
||||||
|
ret.timestamp = u.timestamp;
|
||||||
|
ret.bytes.resize(n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret.bytes.clear();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline libremidi::ump ump_from_midi1(libremidi::message u)
|
||||||
|
{
|
||||||
|
libremidi::ump ret;
|
||||||
|
if (!u.bytes.empty())
|
||||||
|
{
|
||||||
|
ret.timestamp = u.timestamp;
|
||||||
|
cmidi2_midi1_channel_voice_to_midi2(u.bytes.data(), u.bytes.size(), ret.data);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
struct midi1_to_midi2
|
struct midi1_to_midi2
|
||||||
{
|
{
|
||||||
stdx::error
|
stdx::error
|
||||||
@@ -20,11 +205,31 @@ struct midi1_to_midi2
|
|||||||
context.ump = ump;
|
context.ump = ump;
|
||||||
context.ump_num_bytes = sizeof(ump);
|
context.ump_num_bytes = sizeof(ump);
|
||||||
context.ump_proceeded_bytes = 0;
|
context.ump_proceeded_bytes = 0;
|
||||||
|
context.skip_delta_time = true;
|
||||||
|
|
||||||
if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK)
|
switch (cmidi2_convert_midi1_to_ump(&context))
|
||||||
return std::errc::invalid_argument;
|
{
|
||||||
|
case CMIDI2_CONVERSION_RESULT_OK: {
|
||||||
return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp);
|
if (auto n = context.ump_proceeded_bytes; n > 0)
|
||||||
|
return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp);
|
||||||
|
else
|
||||||
|
return std::errc::operation_not_supported;
|
||||||
|
}
|
||||||
|
case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE:
|
||||||
|
return std::errc::no_buffer_space;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_STATUS:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_INPUT:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
default:
|
||||||
|
return std::errc::operation_not_supported;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmidi2_midi_conversion_context context = [] {
|
cmidi2_midi_conversion_context context = [] {
|
||||||
@@ -32,20 +237,45 @@ struct midi1_to_midi2
|
|||||||
cmidi2_midi_conversion_context_initialize(&tmp);
|
cmidi2_midi_conversion_context_initialize(&tmp);
|
||||||
return tmp;
|
return tmp;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
uint32_t ump[65536 / 4];
|
uint32_t ump[65536 / 4];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct midi2_to_midi1
|
struct midi2_to_midi1
|
||||||
{
|
{
|
||||||
stdx::error
|
stdx::error convert(const uint32_t* message, std::size_t size, int64_t timestamp, auto on_midi)
|
||||||
convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi)
|
|
||||||
{
|
{
|
||||||
auto n
|
context.midi1 = static_cast<unsigned char*>(midi);
|
||||||
= cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast<uint32_t*>(message));
|
context.midi1_num_bytes = sizeof(midi);
|
||||||
if (n > 0)
|
context.midi1_proceeded_bytes = 0;
|
||||||
return on_midi(midi, n, timestamp);
|
context.ump = const_cast<uint32_t*>(message);
|
||||||
else
|
context.ump_num_bytes = size * sizeof(uint32_t);
|
||||||
return std::errc::no_buffer_space;
|
context.ump_proceeded_bytes = 0;
|
||||||
|
context.skip_delta_time = true;
|
||||||
|
|
||||||
|
switch (cmidi2_convert_ump_to_midi1(&context))
|
||||||
|
{
|
||||||
|
case CMIDI2_CONVERSION_RESULT_OK: {
|
||||||
|
if (auto n = context.midi1_proceeded_bytes; n > 0)
|
||||||
|
return on_midi(midi, n, timestamp);
|
||||||
|
else
|
||||||
|
return std::errc::operation_not_supported;
|
||||||
|
}
|
||||||
|
case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE:
|
||||||
|
return std::errc::no_buffer_space;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_STATUS:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
case CMIDI2_CONVERSION_RESULT_INVALID_INPUT:
|
||||||
|
return std::errc::invalid_argument;
|
||||||
|
default:
|
||||||
|
return std::errc::operation_not_supported;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmidi2_midi_conversion_context context = [] {
|
cmidi2_midi_conversion_context context = [] {
|
||||||
@@ -53,6 +283,7 @@ struct midi2_to_midi1
|
|||||||
cmidi2_midi_conversion_context_initialize(&tmp);
|
cmidi2_midi_conversion_context_initialize(&tmp);
|
||||||
return tmp;
|
return tmp;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
uint8_t midi[65536];
|
uint8_t midi[65536];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <libremidi/cmidi2.hpp>
|
||||||
|
#include <libremidi/detail/conversion.hpp>
|
||||||
#include <libremidi/detail/midi_in.hpp>
|
#include <libremidi/detail/midi_in.hpp>
|
||||||
|
|
||||||
#include <libremidi/cmidi2.hpp>
|
#include <cmath>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cinttypes>
|
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
@@ -107,12 +109,12 @@ struct input_state_machine : input_state_machine_base<input_configuration>
|
|||||||
{
|
{
|
||||||
message.bytes.clear();
|
message.bytes.clear();
|
||||||
message.timestamp = {};
|
message.timestamp = {};
|
||||||
state = main;
|
m_state = main;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
|
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
|
||||||
{
|
{
|
||||||
return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));
|
return (((bytes.front() == 0xF0) || (m_state == in_sysex)) && (bytes.back() == 0xF7));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to process a byte stream which may contain multiple successive
|
// Function to process a byte stream which may contain multiple successive
|
||||||
@@ -139,22 +141,22 @@ private:
|
|||||||
void on_bytes_multi_segmented(
|
void on_bytes_multi_segmented(
|
||||||
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
|
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
|
||||||
{
|
{
|
||||||
int64_t nBytes = bytes.size();
|
int64_t n_bytes = bytes.size();
|
||||||
int64_t iByte = 0;
|
int64_t i_byte = 0;
|
||||||
|
|
||||||
const bool finished_sysex = has_finished_sysex(bytes);
|
const bool finished_sysex = has_finished_sysex(bytes);
|
||||||
switch (state)
|
switch (m_state)
|
||||||
{
|
{
|
||||||
case in_sysex: {
|
case in_sysex: {
|
||||||
return on_continue_sysex(cb, bytes, finished_sysex);
|
return on_continue_sysex(cb, bytes, finished_sysex);
|
||||||
}
|
}
|
||||||
case main: {
|
case main: {
|
||||||
while (iByte < nBytes)
|
while (i_byte < n_bytes)
|
||||||
{
|
{
|
||||||
int64_t size = 1;
|
int64_t size = 1;
|
||||||
// We are expecting that the next byte in the packet is a status
|
// We are expecting that the next byte in the packet is a status
|
||||||
// byte.
|
// byte.
|
||||||
const auto status = bytes[iByte];
|
const auto status = bytes[i_byte];
|
||||||
if (!(status & 0x80))
|
if (!(status & 0x80))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -170,17 +172,17 @@ private:
|
|||||||
if (configuration.ignore_sysex)
|
if (configuration.ignore_sysex)
|
||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
iByte = nBytes;
|
i_byte = n_bytes;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
size = nBytes - iByte;
|
size = n_bytes - i_byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes[nBytes - 1] != 0xF7)
|
if (bytes[n_bytes - 1] != 0xF7)
|
||||||
{
|
{
|
||||||
// We know per CoreMIDI API there can't be anything else in this packet
|
// We know per CoreMIDI API there can't be anything else in this packet
|
||||||
state = in_sysex;
|
m_state = in_sysex;
|
||||||
message.assign(bytes.begin(), bytes.begin() + size);
|
message.assign(bytes.begin(), bytes.begin() + size);
|
||||||
message.timestamp = timestamp;
|
message.timestamp = timestamp;
|
||||||
return;
|
return;
|
||||||
@@ -192,7 +194,7 @@ private:
|
|||||||
if (configuration.ignore_timing)
|
if (configuration.ignore_timing)
|
||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
iByte += 2;
|
i_byte += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -209,7 +211,7 @@ private:
|
|||||||
if (configuration.ignore_timing)
|
if (configuration.ignore_timing)
|
||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
iByte += 1;
|
i_byte += 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -222,7 +224,7 @@ private:
|
|||||||
if (configuration.ignore_sensing)
|
if (configuration.ignore_sensing)
|
||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
iByte += 1;
|
i_byte += 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -238,14 +240,14 @@ private:
|
|||||||
// Now process the actual bytes of the message
|
// Now process the actual bytes of the message
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
{
|
{
|
||||||
auto begin = bytes.begin() + iByte;
|
auto begin = bytes.begin() + i_byte;
|
||||||
message.assign(begin, begin + size);
|
message.assign(begin, begin + size);
|
||||||
message.timestamp = timestamp;
|
message.timestamp = timestamp;
|
||||||
|
|
||||||
cb(std::move(message));
|
cb(std::move(message));
|
||||||
message.clear();
|
message.clear();
|
||||||
|
|
||||||
iByte += size;
|
i_byte += size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +258,7 @@ private:
|
|||||||
const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)
|
const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)
|
||||||
{
|
{
|
||||||
if (finished_sysex)
|
if (finished_sysex)
|
||||||
state = main;
|
m_state = main;
|
||||||
|
|
||||||
if (configuration.ignore_sysex)
|
if (configuration.ignore_sysex)
|
||||||
{
|
{
|
||||||
@@ -283,7 +285,7 @@ private:
|
|||||||
// SYSEX start
|
// SYSEX start
|
||||||
case 0xF0: {
|
case 0xF0: {
|
||||||
if (!finished_sysex)
|
if (!finished_sysex)
|
||||||
state = in_sysex;
|
m_state = in_sysex;
|
||||||
|
|
||||||
if (!this->configuration.ignore_sysex)
|
if (!this->configuration.ignore_sysex)
|
||||||
{
|
{
|
||||||
@@ -328,7 +330,7 @@ private:
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const bool finished_sysex = has_finished_sysex(bytes);
|
const bool finished_sysex = has_finished_sysex(bytes);
|
||||||
switch (state)
|
switch (m_state)
|
||||||
{
|
{
|
||||||
case in_sysex:
|
case in_sysex:
|
||||||
return on_continue_sysex(cb, bytes, finished_sysex);
|
return on_continue_sysex(cb, bytes, finished_sysex);
|
||||||
@@ -346,7 +348,7 @@ private:
|
|||||||
{
|
{
|
||||||
main,
|
main,
|
||||||
in_sysex
|
in_sysex
|
||||||
} state{main};
|
} m_state{main};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +455,18 @@ private:
|
|||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: {
|
||||||
|
if (this->configuration.midi1_channel_events_to_midi2)
|
||||||
|
{
|
||||||
|
libremidi::ump msg;
|
||||||
|
cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(bytes.data(), msg.data);
|
||||||
|
msg.timestamp = timestamp;
|
||||||
|
cb(std::move(msg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
libremidi::ump msg;
|
libremidi::ump msg;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <semaphore>
|
#include <semaphore>
|
||||||
|
|
||||||
struct semaphore_pair_lock
|
struct semaphore_pair_lock
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#include <libremidi/cmidi2.hpp>
|
#include <libremidi/cmidi2.hpp>
|
||||||
#include <libremidi/error.hpp>
|
#include <libremidi/error.hpp>
|
||||||
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct source_location {
|
|||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
inline auto from_errc(int ret) noexcept
|
inline auto from_errc(int64_t ret) noexcept
|
||||||
{
|
{
|
||||||
return static_cast<std::errc>(-ret);
|
return static_cast<std::errc>(-ret);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,5 +123,10 @@ struct ump_input_configuration
|
|||||||
uint32_t ignore_sensing : 1 = true;
|
uint32_t ignore_sensing : 1 = true;
|
||||||
|
|
||||||
uint32_t timestamps : 3 = timestamp_mode::Absolute;
|
uint32_t timestamps : 3 = timestamp_mode::Absolute;
|
||||||
|
|
||||||
|
//! Upscale MIDI 1 channel events to MIDI 2 channel events.
|
||||||
|
//! Note that this only has an effect on Windows with MIDI Services
|
||||||
|
//! as other platforms already do this by default.
|
||||||
|
uint32_t midi1_channel_events_to_midi2 : 1 = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ const char* libremidi_get_version(void)
|
|||||||
return LIBREMIDI_VERSION;
|
return LIBREMIDI_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
void libremidi_midi1_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||||
{
|
{
|
||||||
if (!cb)
|
if (!cb)
|
||||||
return;
|
return;
|
||||||
libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); });
|
libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
void libremidi_midi2_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))
|
||||||
{
|
{
|
||||||
if (!cb)
|
if (!cb)
|
||||||
return;
|
return;
|
||||||
@@ -276,11 +276,11 @@ int libremidi_midi_in_new(
|
|||||||
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->on_midi1_message.callback)
|
if (c->version == libremidi_midi_configuration::MIDI1 && c->on_midi1_message.callback)
|
||||||
conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) {
|
conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) {
|
||||||
cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size());
|
cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size());
|
||||||
};
|
};
|
||||||
else if (c->on_midi1_raw_data.callback)
|
else if (c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback)
|
||||||
{
|
{
|
||||||
conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span<const uint8_t> msg, int64_t ts) {
|
conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span<const uint8_t> msg, int64_t ts) {
|
||||||
cb.callback(cb.context, ts, msg.data(), msg.size());
|
cb.callback(cb.context, ts, msg.data(), msg.size());
|
||||||
@@ -319,11 +319,11 @@ int libremidi_midi_in_new(
|
|||||||
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
= [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->on_midi2_message.callback)
|
if (c->version == libremidi_midi_configuration::MIDI2 && c->on_midi2_message.callback)
|
||||||
conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) {
|
conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) {
|
||||||
cb.callback(cb.context, msg.timestamp, msg.data, msg.size());
|
cb.callback(cb.context, msg.timestamp, msg.data, msg.size());
|
||||||
};
|
};
|
||||||
else if (c->on_midi2_raw_data.callback)
|
else if (c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback)
|
||||||
{
|
{
|
||||||
conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span<const uint32_t> msg, int64_t ts) {
|
conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span<const uint32_t> msg, int64_t ts) {
|
||||||
cb.callback(cb.context, ts, msg.data(), msg.size());
|
cb.callback(cb.context, ts, msg.data(), msg.size());
|
||||||
|
|||||||
@@ -53,14 +53,13 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name)
|
|||||||
midi2::for_all_backends([&](auto b) { apis.push_back(b.API); });
|
midi2::for_all_backends([&](auto b) { apis.push_back(b.API); });
|
||||||
return apis;
|
return apis;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
libremidi::API midi_api(const std::any& conf)
|
libremidi::API midi_api(const input_api_configuration& conf)
|
||||||
{
|
{
|
||||||
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
||||||
midi_any::for_all_backends([&]<typename T>(T) {
|
midi_any::for_all_backends([&]<typename T>(T) {
|
||||||
if (std::any_cast<typename T::midi_in_configuration>(&conf)
|
if (std::get_if<typename T::midi_in_configuration>(&conf))
|
||||||
|| std::any_cast<typename T::midi_out_configuration>(&conf)
|
|
||||||
|| std::any_cast<typename T::midi_observer_configuration>(&conf))
|
|
||||||
{
|
{
|
||||||
ret = T::API;
|
ret = T::API;
|
||||||
}
|
}
|
||||||
@@ -68,9 +67,34 @@ libremidi::API midi_api(const std::any& conf)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any midi_in_configuration_for(libremidi::API api)
|
libremidi::API midi_api(const output_api_configuration& conf)
|
||||||
{
|
{
|
||||||
std::any ret;
|
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
||||||
|
midi_any::for_all_backends([&]<typename T>(T) {
|
||||||
|
if (std::get_if<typename T::midi_out_configuration>(&conf))
|
||||||
|
{
|
||||||
|
ret = T::API;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
LIBREMIDI_INLINE
|
||||||
|
libremidi::API midi_api(const observer_api_configuration& conf)
|
||||||
|
{
|
||||||
|
libremidi::API ret = libremidi::API::UNSPECIFIED;
|
||||||
|
midi_any::for_all_backends([&]<typename T>(T) {
|
||||||
|
if (std::get_if<typename T::midi_observer_configuration>(&conf))
|
||||||
|
{
|
||||||
|
ret = T::API;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIBREMIDI_INLINE
|
||||||
|
input_api_configuration midi_in_configuration_for(libremidi::API api)
|
||||||
|
{
|
||||||
|
input_api_configuration ret;
|
||||||
midi_any::for_backend(api, [&]<typename T>(T) {
|
midi_any::for_backend(api, [&]<typename T>(T) {
|
||||||
using conf_type = typename T::midi_in_configuration;
|
using conf_type = typename T::midi_in_configuration;
|
||||||
ret = conf_type{};
|
ret = conf_type{};
|
||||||
@@ -79,9 +103,9 @@ std::any midi_in_configuration_for(libremidi::API api)
|
|||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any midi_out_configuration_for(libremidi::API api)
|
output_api_configuration midi_out_configuration_for(libremidi::API api)
|
||||||
{
|
{
|
||||||
std::any ret;
|
output_api_configuration ret;
|
||||||
midi_any::for_backend(api, [&]<typename T>(T) {
|
midi_any::for_backend(api, [&]<typename T>(T) {
|
||||||
using conf_type = typename T::midi_out_configuration;
|
using conf_type = typename T::midi_out_configuration;
|
||||||
ret = conf_type{};
|
ret = conf_type{};
|
||||||
@@ -90,9 +114,9 @@ std::any midi_out_configuration_for(libremidi::API api)
|
|||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any observer_configuration_for(libremidi::API api)
|
observer_api_configuration observer_configuration_for(libremidi::API api)
|
||||||
{
|
{
|
||||||
std::any ret;
|
observer_api_configuration ret;
|
||||||
midi_any::for_backend(api, [&]<typename T>(T) {
|
midi_any::for_backend(api, [&]<typename T>(T) {
|
||||||
using conf_type = typename T::midi_observer_configuration;
|
using conf_type = typename T::midi_observer_configuration;
|
||||||
ret = conf_type{};
|
ret = conf_type{};
|
||||||
@@ -101,13 +125,13 @@ std::any observer_configuration_for(libremidi::API api)
|
|||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any midi_in_configuration_for(const libremidi::observer& obs)
|
input_api_configuration midi_in_configuration_for(const libremidi::observer& obs)
|
||||||
{
|
{
|
||||||
return midi_in_configuration_for(obs.get_current_api());
|
return midi_in_configuration_for(obs.get_current_api());
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any midi_out_configuration_for(const libremidi::observer& obs)
|
output_api_configuration midi_out_configuration_for(const libremidi::observer& obs)
|
||||||
{
|
{
|
||||||
// FIXME reuse context when meaningful, e.g. ALSA, JACK...
|
// FIXME reuse context when meaningful, e.g. ALSA, JACK...
|
||||||
return midi_out_configuration_for(obs.get_current_api());
|
return midi_out_configuration_for(obs.get_current_api());
|
||||||
@@ -144,19 +168,19 @@ catch (const std::exception& e)
|
|||||||
namespace midi1
|
namespace midi1
|
||||||
{
|
{
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any in_default_configuration()
|
input_api_configuration in_default_configuration()
|
||||||
{
|
{
|
||||||
return midi_in_configuration_for(default_api());
|
return midi_in_configuration_for(default_api());
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any out_default_configuration()
|
output_api_configuration out_default_configuration()
|
||||||
{
|
{
|
||||||
return midi_out_configuration_for(default_api());
|
return midi_out_configuration_for(default_api());
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any observer_default_configuration()
|
observer_api_configuration observer_default_configuration()
|
||||||
{
|
{
|
||||||
return observer_configuration_for(default_api());
|
return observer_configuration_for(default_api());
|
||||||
}
|
}
|
||||||
@@ -165,19 +189,19 @@ std::any observer_default_configuration()
|
|||||||
namespace midi2
|
namespace midi2
|
||||||
{
|
{
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any in_default_configuration()
|
input_api_configuration in_default_configuration()
|
||||||
{
|
{
|
||||||
return midi_in_configuration_for(default_api());
|
return midi_in_configuration_for(default_api());
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any out_default_configuration()
|
output_api_configuration out_default_configuration()
|
||||||
{
|
{
|
||||||
return midi_out_configuration_for(default_api());
|
return midi_out_configuration_for(default_api());
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::any observer_default_configuration()
|
observer_api_configuration observer_default_configuration()
|
||||||
{
|
{
|
||||||
return observer_configuration_for(default_api());
|
return observer_configuration_for(default_api());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libremidi/api.hpp>
|
#include <libremidi/api.hpp>
|
||||||
|
#include <libremidi/configurations.hpp>
|
||||||
#include <libremidi/defaults.hpp>
|
#include <libremidi/defaults.hpp>
|
||||||
#include <libremidi/input_configuration.hpp>
|
#include <libremidi/input_configuration.hpp>
|
||||||
#include <libremidi/message.hpp>
|
#include <libremidi/message.hpp>
|
||||||
@@ -68,7 +69,6 @@
|
|||||||
#include <midi/sysex.h>
|
#include <midi/sysex.h>
|
||||||
#include <midi/universal_packet.h>
|
#include <midi/universal_packet.h>
|
||||||
#endif
|
#endif
|
||||||
#include <any>
|
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
@@ -84,7 +84,7 @@ public:
|
|||||||
//! such as jack_observer_configuration, winmm_observer_configuration, etc...
|
//! such as jack_observer_configuration, winmm_observer_configuration, etc...
|
||||||
//! * if no callbacks are passed, no secondary thread will be created unless absolutely necessary
|
//! * if no callbacks are passed, no secondary thread will be created unless absolutely necessary
|
||||||
explicit observer(const observer_configuration& conf = {}) noexcept;
|
explicit observer(const observer_configuration& conf = {}) noexcept;
|
||||||
explicit observer(observer_configuration conf, std::any api_conf);
|
explicit observer(const observer_configuration& conf, observer_api_configuration api_conf);
|
||||||
observer(const observer&) = delete;
|
observer(const observer&) = delete;
|
||||||
observer(observer&& other) noexcept;
|
observer(observer&& other) noexcept;
|
||||||
observer& operator=(const observer&) = delete;
|
observer& operator=(const observer&) = delete;
|
||||||
@@ -98,7 +98,7 @@ public:
|
|||||||
[[nodiscard]] std::vector<libremidi::output_port> get_output_ports() const noexcept;
|
[[nodiscard]] std::vector<libremidi::output_port> get_output_ports() const noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<class observer_api> impl_;
|
std::unique_ptr<class observer_api> m_impl;
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Main class for receiving MIDI 1.0 and 2.0 messages.
|
//! Main class for receiving MIDI 1.0 and 2.0 messages.
|
||||||
@@ -111,15 +111,15 @@ public:
|
|||||||
//! Construct a midi_in object with a configuration object for a specific MIDI 1 back-end
|
//! Construct a midi_in object with a configuration object for a specific MIDI 1 back-end
|
||||||
//! see configuration.hpp for the available configuration types.
|
//! see configuration.hpp for the available configuration types.
|
||||||
//! An exception will be thrown if the requested back-end cannot be opened.
|
//! An exception will be thrown if the requested back-end cannot be opened.
|
||||||
explicit midi_in(input_configuration conf, std::any api_conf);
|
explicit midi_in(const input_configuration& conf, const input_api_configuration& api_conf);
|
||||||
|
|
||||||
//! Construct a midi_in object with the default MIDI 2 back-end for the platform
|
//! Construct a midi_in object with the default MIDI 2 back-end for the platform
|
||||||
explicit midi_in(ump_input_configuration conf) noexcept;
|
explicit midi_in(const ump_input_configuration& conf) noexcept;
|
||||||
|
|
||||||
//! Construct a midi_in object with a configuration object for a specific MIDI 2 back-end
|
//! Construct a midi_in object with a configuration object for a specific MIDI 2 back-end
|
||||||
//! see configuration.hpp for the available configuration types.
|
//! see configuration.hpp for the available configuration types.
|
||||||
//! An exception will be thrown if the requested back-end cannot be opened.
|
//! An exception will be thrown if the requested back-end cannot be opened.
|
||||||
explicit midi_in(ump_input_configuration conf, std::any api_conf);
|
explicit midi_in(const ump_input_configuration& conf, const input_api_configuration& api_conf);
|
||||||
|
|
||||||
midi_in(const midi_in&) = delete;
|
midi_in(const midi_in&) = delete;
|
||||||
midi_in(midi_in&& other) noexcept;
|
midi_in(midi_in&& other) noexcept;
|
||||||
@@ -157,7 +157,7 @@ public:
|
|||||||
timestamp absolute_timestamp() const noexcept;
|
timestamp absolute_timestamp() const noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<class midi_in_api> impl_;
|
std::unique_ptr<class midi_in_api> m_impl;
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Main class for sending MIDI 1.0 and 2.0 messages.
|
//! Main class for sending MIDI 1.0 and 2.0 messages.
|
||||||
@@ -170,7 +170,7 @@ public:
|
|||||||
//! Construct a midi_out object with a configuration object for a specific back-end
|
//! Construct a midi_out object with a configuration object for a specific back-end
|
||||||
//! see configuration.hpp for the available configuration types.
|
//! see configuration.hpp for the available configuration types.
|
||||||
//! An exception will be thrown if the requested back-end cannot be opened.
|
//! An exception will be thrown if the requested back-end cannot be opened.
|
||||||
explicit midi_out(output_configuration conf, std::any api_conf);
|
explicit midi_out(const output_configuration& conf, const output_api_configuration& api_conf);
|
||||||
|
|
||||||
midi_out(const midi_out&) = delete;
|
midi_out(const midi_out&) = delete;
|
||||||
midi_out(midi_out&& other) noexcept;
|
midi_out(midi_out&& other) noexcept;
|
||||||
@@ -260,7 +260,7 @@ public:
|
|||||||
stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const;
|
stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<class midi_out_api> impl_;
|
std::unique_ptr<class midi_out_api> m_impl;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#if defined(__cpp_exceptions)
|
#if defined(__cpp_exceptions)
|
||||||
@@ -121,12 +122,8 @@ struct message
|
|||||||
auto rbegin() noexcept { return bytes.rbegin(); }
|
auto rbegin() noexcept { return bytes.rbegin(); }
|
||||||
auto rend() noexcept { return bytes.rend(); }
|
auto rend() noexcept { return bytes.rend(); }
|
||||||
|
|
||||||
bool uses_channel(int channel) const
|
bool uses_channel(int channel) const LIBREMIDI_PRECONDITION(channel > 0 && channel <= 16)
|
||||||
{
|
{
|
||||||
#if defined(__cpp_exceptions)
|
|
||||||
if (channel <= 0 || channel > 16)
|
|
||||||
throw std::range_error("out of range");
|
|
||||||
#endif
|
|
||||||
return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0);
|
return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,12 +247,12 @@ struct meta_events
|
|||||||
int notated_32nd_notes_per_beat = 96)
|
int notated_32nd_notes_per_beat = 96)
|
||||||
{
|
{
|
||||||
int n = 1;
|
int n = 1;
|
||||||
int powTwo = 0;
|
int pow_two = 0;
|
||||||
|
|
||||||
while (n < denominator)
|
while (n < denominator)
|
||||||
{
|
{
|
||||||
n <<= 1;
|
n <<= 1;
|
||||||
++powTwo;
|
++pow_two;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -263,7 +260,7 @@ struct meta_events
|
|||||||
0x58,
|
0x58,
|
||||||
0x04,
|
0x04,
|
||||||
static_cast<uint8_t>(numerator),
|
static_cast<uint8_t>(numerator),
|
||||||
static_cast<uint8_t>(powTwo),
|
static_cast<uint8_t>(pow_two),
|
||||||
static_cast<uint8_t>(clocks_per_click),
|
static_cast<uint8_t>(clocks_per_click),
|
||||||
static_cast<uint8_t>(notated_32nd_notes_per_beat)};
|
static_cast<uint8_t>(notated_32nd_notes_per_beat)};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||||
make_midi_in(auto base_conf, std::any api_conf, auto backends)
|
make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends)
|
||||||
{
|
{
|
||||||
std::unique_ptr<midi_in_api> ptr;
|
std::unique_ptr<midi_in_api> ptr;
|
||||||
|
|
||||||
assert(base_conf.on_message || base_conf.on_raw_data);
|
assert(base_conf.on_message || base_conf.on_raw_data);
|
||||||
|
|
||||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
|
if (auto conf = std::get_if<typename T::midi_in_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
ptr = libremidi::make<typename T::midi_in>(std::move(base_conf), std::move(*conf));
|
ptr = libremidi::make<typename T::midi_in>(std::move(base_conf), std::move(*conf));
|
||||||
return true;
|
return true;
|
||||||
@@ -117,8 +117,9 @@ make_midi1_in(const input_configuration& base_conf)
|
|||||||
return std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
return std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
|
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi1_in(
|
||||||
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
|
const input_configuration& base_conf, const input_api_configuration& api_conf,
|
||||||
|
libremidi::API api)
|
||||||
{
|
{
|
||||||
if (libremidi::is_midi1(api))
|
if (libremidi::is_midi1(api))
|
||||||
{
|
{
|
||||||
@@ -133,13 +134,13 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, li
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||||
make_midi1_in(const input_configuration& base_conf, const std::any& api_conf)
|
make_midi1_in(const input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||||
{
|
{
|
||||||
if (!api_conf.has_value())
|
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
return make_midi1_in(base_conf);
|
return make_midi1_in(base_conf);
|
||||||
}
|
}
|
||||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||||
{
|
{
|
||||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||||
{
|
{
|
||||||
@@ -161,19 +162,19 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf)
|
|||||||
|
|
||||||
/// MIDI 1 constructors
|
/// MIDI 1 constructors
|
||||||
LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept
|
LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept
|
||||||
: impl_{make_midi1_in(base_conf)}
|
: m_impl{make_midi1_in(base_conf)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
midi_in::midi_in(input_configuration base_conf, std::any api_conf)
|
midi_in::midi_in(const input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||||
: impl_{make_midi1_in(base_conf, api_conf)}
|
: m_impl{make_midi1_in(base_conf, api_conf)}
|
||||||
{
|
{
|
||||||
if (!impl_)
|
if (!m_impl)
|
||||||
{
|
{
|
||||||
error_handler e;
|
error_handler e;
|
||||||
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
||||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
m_impl = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +215,8 @@ make_midi2_in(const ump_input_configuration& base_conf)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(
|
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(
|
||||||
const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api)
|
const ump_input_configuration& base_conf, const input_api_configuration& api_conf,
|
||||||
|
libremidi::API api)
|
||||||
{
|
{
|
||||||
if (is_midi2(api))
|
if (is_midi2(api))
|
||||||
{
|
{
|
||||||
@@ -230,13 +232,13 @@ static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
|
||||||
make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf)
|
make_midi2_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||||
{
|
{
|
||||||
if (!api_conf.has_value())
|
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
return make_midi2_in(base_conf);
|
return make_midi2_in(base_conf);
|
||||||
}
|
}
|
||||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||||
{
|
{
|
||||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||||
{
|
{
|
||||||
@@ -257,45 +259,45 @@ make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// MIDI 2 constructors
|
/// MIDI 2 constructors
|
||||||
LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept
|
LIBREMIDI_INLINE midi_in::midi_in(const ump_input_configuration& base_conf) noexcept
|
||||||
: impl_{make_midi2_in(base_conf)}
|
: m_impl{make_midi2_in(base_conf)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf)
|
midi_in::midi_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf)
|
||||||
: impl_{make_midi2_in(base_conf, api_conf)}
|
: m_impl{make_midi2_in(base_conf, api_conf)}
|
||||||
{
|
{
|
||||||
if (!impl_)
|
if (!m_impl)
|
||||||
{
|
{
|
||||||
error_handler e;
|
error_handler e;
|
||||||
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");
|
||||||
impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
m_impl = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_in::~midi_in() = default;
|
LIBREMIDI_INLINE midi_in::~midi_in() = default;
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept
|
LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept
|
||||||
: impl_{std::move(other.impl_)}
|
: m_impl{std::move(other.m_impl)}
|
||||||
{
|
{
|
||||||
other.impl_
|
other.m_impl
|
||||||
= std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
= std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_in::set_port_name(std::string_view portName)
|
stdx::error midi_in::set_port_name(std::string_view portName)
|
||||||
{
|
{
|
||||||
if(impl_->is_port_open())
|
if (m_impl->is_port_open())
|
||||||
return impl_->set_port_name(portName);
|
return m_impl->set_port_name(portName);
|
||||||
|
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept
|
LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept
|
||||||
{
|
{
|
||||||
this->impl_ = std::move(other.impl_);
|
this->m_impl = std::move(other.m_impl);
|
||||||
other.impl_
|
other.m_impl
|
||||||
= std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
= std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -303,23 +305,23 @@ LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
libremidi::API midi_in::get_current_api() const noexcept
|
libremidi::API midi_in::get_current_api() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->get_current_api();
|
return m_impl->get_current_api();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_in::open_port(const input_port& port, std::string_view portName)
|
stdx::error midi_in::open_port(const input_port& port, std::string_view portName)
|
||||||
{
|
{
|
||||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
|
|
||||||
if (impl_->is_port_open())
|
if (m_impl->is_port_open())
|
||||||
return std::errc::operation_not_supported;
|
return std::errc::operation_not_supported;
|
||||||
|
|
||||||
auto ret = impl_->open_port(port, portName);
|
auto ret = m_impl->open_port(port, portName);
|
||||||
if (ret == stdx::error{})
|
if (ret == stdx::error{})
|
||||||
{
|
{
|
||||||
impl_->connected_ = true;
|
m_impl->connected_ = true;
|
||||||
impl_->port_open_ = true;
|
m_impl->port_open_ = true;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -327,28 +329,28 @@ stdx::error midi_in::open_port(const input_port& port, std::string_view portName
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_in::open_virtual_port(std::string_view portName)
|
stdx::error midi_in::open_virtual_port(std::string_view portName)
|
||||||
{
|
{
|
||||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
|
|
||||||
if (impl_->is_port_open())
|
if (m_impl->is_port_open())
|
||||||
return std::errc::operation_not_supported;
|
return std::errc::operation_not_supported;
|
||||||
|
|
||||||
auto ret = impl_->open_virtual_port(portName);
|
auto ret = m_impl->open_virtual_port(portName);
|
||||||
if (ret == stdx::error{})
|
if (ret == stdx::error{})
|
||||||
impl_->port_open_ = true;
|
m_impl->port_open_ = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_in::close_port()
|
stdx::error midi_in::close_port()
|
||||||
{
|
{
|
||||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
|
|
||||||
auto ret = impl_->close_port();
|
auto ret = m_impl->close_port();
|
||||||
|
|
||||||
impl_->connected_ = false;
|
m_impl->connected_ = false;
|
||||||
impl_->port_open_ = false;
|
m_impl->port_open_ = false;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -356,18 +358,18 @@ stdx::error midi_in::close_port()
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
bool midi_in::is_port_open() const noexcept
|
bool midi_in::is_port_open() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->is_port_open();
|
return m_impl->is_port_open();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
bool midi_in::is_port_connected() const noexcept
|
bool midi_in::is_port_connected() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->is_port_connected();
|
return m_impl->is_port_connected();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
int64_t midi_in::absolute_timestamp() const noexcept
|
int64_t midi_in::absolute_timestamp() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->absolute_timestamp();
|
return m_impl->absolute_timestamp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
||||||
make_midi_out_impl(auto base_conf, std::any api_conf)
|
make_midi_out_impl(auto base_conf, output_api_configuration api_conf)
|
||||||
{
|
{
|
||||||
std::unique_ptr<midi_out_api> ptr;
|
std::unique_ptr<midi_out_api> ptr;
|
||||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))
|
if (auto conf = std::get_if<typename T::midi_out_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
ptr = libremidi::make<typename T::midi_out>(std::move(base_conf), std::move(*conf));
|
ptr = libremidi::make<typename T::midi_out>(std::move(base_conf), std::move(*conf));
|
||||||
return true;
|
return true;
|
||||||
@@ -23,7 +23,8 @@ make_midi_out_impl(auto base_conf, std::any api_conf)
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);
|
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);
|
||||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
if (!ptr)
|
||||||
|
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,13 +59,13 @@ make_midi_out(const output_configuration& base_conf)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>
|
||||||
make_midi_out(const output_configuration& base_conf, const std::any& api_conf)
|
make_midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf)
|
||||||
{
|
{
|
||||||
if (!api_conf.has_value())
|
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
return make_midi_out(base_conf);
|
return make_midi_out(base_conf);
|
||||||
}
|
}
|
||||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||||
{
|
{
|
||||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||||
{
|
{
|
||||||
@@ -85,35 +86,35 @@ make_midi_out(const output_configuration& base_conf, const std::any& api_conf)
|
|||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept
|
LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept
|
||||||
: impl_{make_midi_out(base_conf)}
|
: m_impl{make_midi_out(base_conf)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
midi_out::midi_out(output_configuration base_conf, std::any api_conf)
|
midi_out::midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf)
|
||||||
: impl_{make_midi_out(base_conf, api_conf)}
|
: m_impl{make_midi_out(base_conf, api_conf)}
|
||||||
{
|
{
|
||||||
if (!impl_)
|
if (!m_impl)
|
||||||
{
|
{
|
||||||
error_handler e;
|
error_handler e;
|
||||||
e.libremidi_handle_error(base_conf, "Could not open midi out for the given api");
|
e.libremidi_handle_error(base_conf, "Could not open midi out for the given api");
|
||||||
impl_ = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
m_impl = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_out::~midi_out() = default;
|
LIBREMIDI_INLINE midi_out::~midi_out() = default;
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept
|
LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept
|
||||||
: impl_{std::move(other.impl_)}
|
: m_impl{std::move(other.m_impl)}
|
||||||
{
|
{
|
||||||
other.impl_
|
other.m_impl
|
||||||
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
|
LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
|
||||||
{
|
{
|
||||||
this->impl_ = std::move(other.impl_);
|
this->m_impl = std::move(other.m_impl);
|
||||||
other.impl_
|
other.m_impl
|
||||||
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
= std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -121,8 +122,8 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_out::set_port_name(std::string_view portName) const
|
stdx::error midi_out::set_port_name(std::string_view portName) const
|
||||||
{
|
{
|
||||||
if(impl_->is_port_open())
|
if (m_impl->is_port_open())
|
||||||
return impl_->set_port_name(portName);
|
return m_impl->set_port_name(portName);
|
||||||
|
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
}
|
}
|
||||||
@@ -130,23 +131,23 @@ stdx::error midi_out::set_port_name(std::string_view portName) const
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
libremidi::API midi_out::get_current_api() const noexcept
|
libremidi::API midi_out::get_current_api() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->get_current_api();
|
return m_impl->get_current_api();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const
|
stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const
|
||||||
{
|
{
|
||||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
|
|
||||||
if (impl_->is_port_open())
|
if (m_impl->is_port_open())
|
||||||
return std::errc::operation_not_supported;
|
return std::errc::operation_not_supported;
|
||||||
|
|
||||||
auto ret = impl_->open_port(port, portName);
|
auto ret = m_impl->open_port(port, portName);
|
||||||
if (ret == stdx::error{})
|
if (ret == stdx::error{})
|
||||||
{
|
{
|
||||||
impl_->connected_ = true;
|
m_impl->connected_ = true;
|
||||||
impl_->port_open_ = true;
|
m_impl->port_open_ = true;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -154,40 +155,40 @@ stdx::error midi_out::open_port(const output_port& port, std::string_view portNa
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_out::open_virtual_port(std::string_view portName) const
|
stdx::error midi_out::open_virtual_port(std::string_view portName) const
|
||||||
{
|
{
|
||||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
|
|
||||||
if (impl_->is_port_open())
|
if (m_impl->is_port_open())
|
||||||
return std::errc::operation_not_supported;
|
return std::errc::operation_not_supported;
|
||||||
|
|
||||||
auto ret = impl_->open_virtual_port(portName);
|
auto ret = m_impl->open_virtual_port(portName);
|
||||||
if (ret == stdx::error{})
|
if (ret == stdx::error{})
|
||||||
impl_->port_open_ = true;
|
m_impl->port_open_ = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_out::close_port() const
|
stdx::error midi_out::close_port() const
|
||||||
{
|
{
|
||||||
if (auto err = impl_->is_client_open(); err != stdx::error{})
|
if (auto err = m_impl->is_client_open(); err != stdx::error{})
|
||||||
return std::errc::not_connected;
|
return std::errc::not_connected;
|
||||||
|
|
||||||
auto ret = impl_->close_port();
|
auto ret = m_impl->close_port();
|
||||||
impl_->connected_ = false;
|
m_impl->connected_ = false;
|
||||||
impl_->port_open_ = false;
|
m_impl->port_open_ = false;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
bool midi_out::is_port_open() const noexcept
|
bool midi_out::is_port_open() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->is_port_open();
|
return m_impl->is_port_open();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
bool midi_out::is_port_connected() const noexcept
|
bool midi_out::is_port_connected() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->is_port_connected();
|
return m_impl->is_port_connected();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
@@ -227,13 +228,13 @@ stdx::error midi_out::send_message(const unsigned char* message, size_t size) co
|
|||||||
assert(size > 0);
|
assert(size > 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return impl_->send_message(message, size);
|
return m_impl->send_message(message, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
int64_t midi_out::current_time()
|
int64_t midi_out::current_time()
|
||||||
{
|
{
|
||||||
return impl_->current_time();
|
return m_impl->current_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
@@ -243,7 +244,7 @@ stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message,
|
|||||||
assert(size > 0);
|
assert(size > 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return impl_->schedule_message(ts, message, size);
|
return m_impl->schedule_message(ts, message, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
@@ -254,7 +255,7 @@ stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const
|
|||||||
assert(size <= 4);
|
assert(size <= 4);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return impl_->send_ump(message, size);
|
return m_impl->send_ump(message, size);
|
||||||
}
|
}
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
stdx::error midi_out::send_ump(const libremidi::ump& message) const
|
stdx::error midi_out::send_ump(const libremidi::ump& message) const
|
||||||
@@ -321,6 +322,6 @@ stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t s
|
|||||||
assert(size > 0);
|
assert(size > 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return impl_->schedule_ump(ts, message, size);
|
return m_impl->schedule_ump(ts, message, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,19 @@
|
|||||||
#include <libremidi/backends.hpp>
|
#include <libremidi/backends.hpp>
|
||||||
#include <libremidi/detail/midi_api.hpp>
|
#include <libremidi/detail/midi_api.hpp>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
|
|
||||||
static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf)
|
static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(const auto& base_conf)
|
||||||
{
|
{
|
||||||
for (const auto& api : available_apis())
|
for (const auto& api : available_apis())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
|
if (auto impl = make_observer(base_conf, observer_configuration_for(api)))
|
||||||
return impl_;
|
return impl;
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
@@ -26,8 +28,8 @@ static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_co
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))
|
if (auto impl = make_observer(base_conf, observer_configuration_for(api)))
|
||||||
return impl_;
|
return impl;
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
@@ -37,11 +39,11 @@ static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_co
|
|||||||
return std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
return std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf)
|
LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configuration api_conf)
|
||||||
{
|
{
|
||||||
std::unique_ptr<observer_api> ptr;
|
std::unique_ptr<observer_api> ptr;
|
||||||
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
auto from_api = [&]<typename T>(T& /*backend*/) mutable {
|
||||||
if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))
|
if (auto conf = std::get_if<typename T::midi_observer_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
ptr = libremidi::make<typename T::midi_observer>(std::move(base_conf), std::move(*conf));
|
ptr = libremidi::make<typename T::midi_observer>(std::move(base_conf), std::move(*conf));
|
||||||
return true;
|
return true;
|
||||||
@@ -49,17 +51,19 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf)
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);
|
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);
|
||||||
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
if (!ptr)
|
||||||
|
std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf, std::any api_conf)
|
LIBREMIDI_INLINE std::unique_ptr<observer_api>
|
||||||
|
make_observer(const auto& base_conf, observer_api_configuration api_conf)
|
||||||
{
|
{
|
||||||
if (!api_conf.has_value())
|
if (std::get_if<unspecified_configuration>(&api_conf))
|
||||||
{
|
{
|
||||||
return make_observer(base_conf);
|
return make_observer(base_conf);
|
||||||
}
|
}
|
||||||
else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))
|
else if (auto api_p = std::get_if<libremidi::API>(&api_conf))
|
||||||
{
|
{
|
||||||
if (*api_p == libremidi::API::UNSPECIFIED)
|
if (*api_p == libremidi::API::UNSPECIFIED)
|
||||||
{
|
{
|
||||||
@@ -80,32 +84,33 @@ LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf, std
|
|||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept
|
LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept
|
||||||
: impl_{make_observer(base_conf)}
|
: m_impl{make_observer(base_conf)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf)
|
LIBREMIDI_INLINE
|
||||||
: impl_{make_observer(base_conf, api_conf)}
|
observer::observer(const observer_configuration& base_conf, observer_api_configuration api_conf)
|
||||||
|
: m_impl{make_observer(base_conf, std::move(api_conf))}
|
||||||
{
|
{
|
||||||
if (!impl_)
|
if (!m_impl)
|
||||||
{
|
{
|
||||||
error_handler e;
|
error_handler e;
|
||||||
e.libremidi_handle_error(base_conf, "Could not open observer for the given api");
|
e.libremidi_handle_error(base_conf, "Could not open observer for the given api");
|
||||||
impl_ = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
m_impl = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE observer::observer(observer&& other) noexcept
|
LIBREMIDI_INLINE observer::observer(observer&& other) noexcept
|
||||||
: impl_{std::move(other.impl_)}
|
: m_impl{std::move(other.m_impl)}
|
||||||
{
|
{
|
||||||
other.impl_ = std::make_unique<libremidi::observer_dummy>(
|
other.m_impl = std::make_unique<libremidi::observer_dummy>(
|
||||||
observer_configuration{}, dummy_configuration{});
|
observer_configuration{}, dummy_configuration{});
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE observer& observer::operator=(observer&& other) noexcept
|
LIBREMIDI_INLINE observer& observer::operator=(observer&& other) noexcept
|
||||||
{
|
{
|
||||||
this->impl_ = std::move(other.impl_);
|
this->m_impl = std::move(other.m_impl);
|
||||||
other.impl_ = std::make_unique<libremidi::observer_dummy>(
|
other.m_impl = std::make_unique<libremidi::observer_dummy>(
|
||||||
observer_configuration{}, dummy_configuration{});
|
observer_configuration{}, dummy_configuration{});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -116,18 +121,18 @@ observer::~observer() = default;
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
libremidi::API observer::get_current_api() const noexcept
|
libremidi::API observer::get_current_api() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->get_current_api();
|
return m_impl->get_current_api();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::vector<libremidi::input_port> observer::get_input_ports() const noexcept
|
std::vector<libremidi::input_port> observer::get_input_ports() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->get_input_ports();
|
return m_impl->get_input_ports();
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
std::vector<libremidi::output_port> observer::get_output_ports() const noexcept
|
std::vector<libremidi::output_port> observer::get_output_ports() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->get_output_ports();
|
return m_impl->get_output_ports();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,43 +2,106 @@
|
|||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
#include <libremidi/error.hpp>
|
#include <libremidi/error.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
using client_handle = std::uint64_t;
|
using client_handle = std::uint64_t;
|
||||||
using port_handle = std::uint64_t;
|
using port_handle = std::uint64_t;
|
||||||
|
|
||||||
|
struct uuid
|
||||||
|
{
|
||||||
|
std::array<uint8_t, 16> bytes;
|
||||||
|
|
||||||
|
bool operator==(const uuid& other) const noexcept = default;
|
||||||
|
std::strong_ordering operator<=>(const uuid& other) const noexcept = default;
|
||||||
|
};
|
||||||
|
using container_identifier = std::variant<std::monostate, uuid, std::string, std::uint64_t>;
|
||||||
|
using device_identifier = std::variant<std::monostate, std::string, std::uint64_t>;
|
||||||
|
|
||||||
struct LIBREMIDI_EXPORT port_information
|
struct LIBREMIDI_EXPORT port_information
|
||||||
{
|
{
|
||||||
// Handle to the client object:
|
enum port_type : uint8_t
|
||||||
|
{
|
||||||
|
unknown = 0,
|
||||||
|
|
||||||
|
software = (1 << 1),
|
||||||
|
loopback = (1 << 2),
|
||||||
|
|
||||||
|
hardware = (1 << 3),
|
||||||
|
usb = (1 << 4),
|
||||||
|
bluetooth = (1 << 5),
|
||||||
|
pci = (1 << 6),
|
||||||
|
|
||||||
|
network = (1 << 7),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Handle to the API client object if the API provides one
|
||||||
// ALSA Raw: unused
|
// ALSA Raw: unused
|
||||||
// ALSA Seq: snd_seq_t*
|
// ALSA Seq: snd_seq_t*
|
||||||
// CoreMIDI: MidiClientRef
|
// CoreMIDI: MidiClientRef
|
||||||
// WebMIDI: unused
|
// WebMIDI: unused
|
||||||
// JACK: jack_client_t*
|
// JACK: jack_client_t*
|
||||||
// PipeWire: unused
|
// PipeWire: unused // FIXME: pw_context? pw_main_loop?
|
||||||
// WinMM: unused
|
// WinMM: unused
|
||||||
// WinUWP: unused
|
// WinUWP: unused
|
||||||
client_handle client = static_cast<client_handle>(-1);
|
client_handle client = static_cast<client_handle>(-1);
|
||||||
|
|
||||||
// ALSA Raw: { uint16_t card, device, sub, padding; }
|
/// Container identifier if the API provides one
|
||||||
// ALSA Seq: { uint32_t client, uint32_t port; }
|
// WinMIDI: ContainerID GUID (bit_cast to a winapi or winrt::GUID ;
|
||||||
|
// this is not the string but the binary representation).
|
||||||
|
// ALSA: device id (std::string), e.g. ID_PATH as returned by udev: "pci-0000:00:14.0-usb-0:12:1.0"
|
||||||
|
// CoreMIDI: USBLocationID (int32_t)
|
||||||
|
container_identifier container = std::monostate{};
|
||||||
|
|
||||||
|
/// Device identifier if the API provides one
|
||||||
|
// WinMM: { uint16_t manufacturer_id, uint16_t product_id; }
|
||||||
|
// WinMIDI: EndpointDeviceId (std::string), e.g. "\\?\swd#midisrv#midiu_ksa..."
|
||||||
|
// ALSA: sysfs path (std::string), e.g. "/sys/devices/pci0000:00/0000:00:02.2/0000:02:00.0/sound/card0/controlC0"
|
||||||
|
// CoreMIDI: USBVendorProduct (int32_t)
|
||||||
|
device_identifier device = std::monostate{};
|
||||||
|
|
||||||
|
/// Handle to the port identifier if the API provides one
|
||||||
|
// ALSA Raw: bit_cast to struct { uint16_t card, device, sub, padding; }.
|
||||||
|
// ALSA Seq: bit_cast to struct { uint32_t client, port; }
|
||||||
// CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t)
|
// CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t)
|
||||||
// WebMIDI: unused
|
// WebMIDI: unused
|
||||||
// JACK: jack_port_id_t
|
// JACK: jack_port_id_t
|
||||||
// PipeWire: port.id
|
// PipeWire: port.id
|
||||||
// WinMM: unset, identified by port_name
|
// WinMIDI: uint64_t terminal_block_number; (MidiGroupTerminalBlock::Number(), index is 1-based)
|
||||||
|
// WinMM: port index between 0 and midi{In,Out}GetNumDevs()
|
||||||
// WinUWP: unused
|
// WinUWP: unused
|
||||||
port_handle port = static_cast<port_handle>(-1);
|
port_handle port = static_cast<port_handle>(-1);
|
||||||
|
|
||||||
|
/// User-readable information
|
||||||
|
// CoreMIDI: kMIDIPropertyManufacturer
|
||||||
|
// WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName
|
||||||
|
// WinMM: unavailable
|
||||||
std::string manufacturer{};
|
std::string manufacturer{};
|
||||||
|
|
||||||
|
// CoreMIDI: kMIDIPropertyModel
|
||||||
|
// WinMIDI: MidiEndpointDeviceInformation::Name
|
||||||
|
// WinMM: unavailable
|
||||||
std::string device_name{};
|
std::string device_name{};
|
||||||
|
|
||||||
|
// CoreMIDI: kMIDIPropertyName
|
||||||
|
// WinMIDI: MidiGroupTerminalBlock::Name
|
||||||
|
// WinMM: szPname
|
||||||
std::string port_name{};
|
std::string port_name{};
|
||||||
|
|
||||||
|
// CoreMIDI: kMIDIPropertyDisplayName
|
||||||
|
// Otherwise: the closest to a unique name we can get
|
||||||
std::string display_name{};
|
std::string display_name{};
|
||||||
|
|
||||||
|
/// Port type
|
||||||
|
// CoreMIDI: available
|
||||||
|
// WinMM: unavailable
|
||||||
|
// WinMIDI: available
|
||||||
|
port_type type = port_type::unknown;
|
||||||
|
|
||||||
bool operator==(const port_information& other) const noexcept = default;
|
bool operator==(const port_information& other) const noexcept = default;
|
||||||
std::strong_ordering operator<=>(const port_information& other) const noexcept = default;
|
std::strong_ordering operator<=>(const port_information& other) const noexcept = default;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,858 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libremidi/error_handler.hpp>
|
||||||
|
#include <libremidi/message.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <functional>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace libremidi
|
||||||
|
{
|
||||||
|
static constexpr auto to_underlying(auto e)
|
||||||
|
{
|
||||||
|
return static_cast<std::underlying_type_t<decltype(e)>>(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A clean-room reverse-engineered remote control protocol compatible with many hardware devices.
|
||||||
|
// Thanks https://github.com/NicoG60/TouchMCU !
|
||||||
|
struct remote_control_protocol
|
||||||
|
{
|
||||||
|
enum class device_type : uint8_t
|
||||||
|
{
|
||||||
|
logic_control = 0x10,
|
||||||
|
logic_control_xt = 0x11,
|
||||||
|
mackie_control = 0x14
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class command_to_device : uint8_t
|
||||||
|
{
|
||||||
|
device_query = 0x00,
|
||||||
|
host_connection_reply = 0x02,
|
||||||
|
|
||||||
|
transport_click = 0x0A,
|
||||||
|
lcd_backlight_save = 0x0B,
|
||||||
|
touchless_movable_fader = 0x0C,
|
||||||
|
|
||||||
|
faders_touch_sensitivity = 0x0E,
|
||||||
|
go_offline = 0x0F,
|
||||||
|
update_tc_display = 0x10,
|
||||||
|
update_assignment_display = 0x11,
|
||||||
|
update_lcd = 0x12,
|
||||||
|
firmware_version_request = 0x13,
|
||||||
|
version_reply = 0x14,
|
||||||
|
|
||||||
|
firmware_update = 0x18,
|
||||||
|
|
||||||
|
channel_meter_mode = 0x20,
|
||||||
|
global_lcd_meter_mode = 0x21,
|
||||||
|
|
||||||
|
faders_to_minimum = 0x61,
|
||||||
|
all_leds_off = 0x62,
|
||||||
|
reset = 0x63,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class command_from_device : uint8_t
|
||||||
|
{
|
||||||
|
host_connection_query = 0x01,
|
||||||
|
host_connection_confirmation = 0x03,
|
||||||
|
host_connection_error = 0x04,
|
||||||
|
version_reply = 0x14,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class lcd_meter_mode : uint8_t
|
||||||
|
{
|
||||||
|
horizontal = 0x00,
|
||||||
|
vertical = 0x01,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class fader_sensitivity : uint8_t
|
||||||
|
{
|
||||||
|
sensitivity_0 = 0x00,
|
||||||
|
sensitivity_1 = 0x01,
|
||||||
|
sensitivity_2 = 0x02,
|
||||||
|
sensitivity_3 = 0x03,
|
||||||
|
sensitivity_4 = 0x04,
|
||||||
|
|
||||||
|
sensitivity_default = sensitivity_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
// CC Message:
|
||||||
|
// 0b0LMMVVVV
|
||||||
|
// L: toggle underneath LED
|
||||||
|
// MM: mode as led_ring_mode
|
||||||
|
// VVVV: value
|
||||||
|
enum class led_ring_mode : uint8_t
|
||||||
|
{
|
||||||
|
mode_0 = 0b00, // one led only
|
||||||
|
mode_1 = 0b01, // pan pot
|
||||||
|
mode_2 = 0b10, // fill leds from left
|
||||||
|
mode_3 = 0b11, // fill leds from middle
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class pot : uint8_t
|
||||||
|
{
|
||||||
|
pot_0 = 0x00,
|
||||||
|
pot_1 = 0x01,
|
||||||
|
pot_2 = 0x02,
|
||||||
|
pot_3 = 0x03,
|
||||||
|
pot_4 = 0x04,
|
||||||
|
pot_5 = 0x05,
|
||||||
|
pot_6 = 0x06,
|
||||||
|
pot_7 = 0x07,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class fader : uint8_t
|
||||||
|
{
|
||||||
|
fader_0 = 0x00,
|
||||||
|
fader_1 = 0x01,
|
||||||
|
fader_2 = 0x02,
|
||||||
|
fader_3 = 0x03,
|
||||||
|
fader_4 = 0x04,
|
||||||
|
fader_5 = 0x05,
|
||||||
|
fader_6 = 0x06,
|
||||||
|
fader_7 = 0x07,
|
||||||
|
fader_master = 0x08,
|
||||||
|
};
|
||||||
|
|
||||||
|
// control changes
|
||||||
|
enum class mixer_control : uint8_t
|
||||||
|
{
|
||||||
|
// rotation: CC
|
||||||
|
// 0b00000001 : clockwise
|
||||||
|
// 0b01000001 : counter-clockwise
|
||||||
|
vpot_rotation_0 = 0x10 + 0x00,
|
||||||
|
vpot_rotation_1 = 0x10 + 0x01,
|
||||||
|
vpot_rotation_2 = 0x10 + 0x02,
|
||||||
|
vpot_rotation_3 = 0x10 + 0x03,
|
||||||
|
vpot_rotation_4 = 0x10 + 0x04,
|
||||||
|
vpot_rotation_5 = 0x10 + 0x05,
|
||||||
|
vpot_rotation_6 = 0x10 + 0x06,
|
||||||
|
vpot_rotation_7 = 0x10 + 0x07,
|
||||||
|
|
||||||
|
external_control = 0x2E,
|
||||||
|
|
||||||
|
// led ring: CC
|
||||||
|
vpot_led_0 = 0x30 + 0x00,
|
||||||
|
vpot_led_1 = 0x30 + 0x01,
|
||||||
|
vpot_led_2 = 0x30 + 0x02,
|
||||||
|
vpot_led_3 = 0x30 + 0x03,
|
||||||
|
vpot_led_4 = 0x30 + 0x04,
|
||||||
|
vpot_led_5 = 0x30 + 0x05,
|
||||||
|
vpot_led_6 = 0x30 + 0x06,
|
||||||
|
vpot_led_7 = 0x30 + 0x07,
|
||||||
|
|
||||||
|
jog_wheel = 0x3C,
|
||||||
|
|
||||||
|
timecode_digit_0 = 0x40 + 0x00,
|
||||||
|
timecode_digit_1 = 0x40 + 0x01,
|
||||||
|
timecode_digit_2 = 0x40 + 0x02,
|
||||||
|
timecode_digit_3 = 0x40 + 0x03,
|
||||||
|
timecode_digit_4 = 0x40 + 0x04,
|
||||||
|
timecode_digit_5 = 0x40 + 0x05,
|
||||||
|
timecode_digit_6 = 0x40 + 0x06,
|
||||||
|
timecode_digit_7 = 0x40 + 0x07,
|
||||||
|
timecode_digit_8 = 0x40 + 0x08,
|
||||||
|
timecode_digit_9 = 0x40 + 0x09,
|
||||||
|
|
||||||
|
assignment_digit_0 = 0x4A,
|
||||||
|
assignment_digit_1 = 0x4B,
|
||||||
|
};
|
||||||
|
|
||||||
|
// note events
|
||||||
|
enum class mixer_command : uint8_t
|
||||||
|
{
|
||||||
|
vpot_click_0 = 0x20 + 0x00,
|
||||||
|
vpot_click_1 = 0x20 + 0x01,
|
||||||
|
vpot_click_2 = 0x20 + 0x02,
|
||||||
|
vpot_click_3 = 0x20 + 0x03,
|
||||||
|
vpot_click_4 = 0x20 + 0x04,
|
||||||
|
vpot_click_5 = 0x20 + 0x05,
|
||||||
|
vpot_click_6 = 0x20 + 0x06,
|
||||||
|
vpot_click_7 = 0x20 + 0x07,
|
||||||
|
|
||||||
|
rec_0 = 0x00 + 0x00,
|
||||||
|
rec_1 = 0x00 + 0x01,
|
||||||
|
rec_2 = 0x00 + 0x02,
|
||||||
|
rec_3 = 0x00 + 0x03,
|
||||||
|
rec_4 = 0x00 + 0x04,
|
||||||
|
rec_5 = 0x00 + 0x05,
|
||||||
|
rec_6 = 0x00 + 0x06,
|
||||||
|
rec_7 = 0x00 + 0x07,
|
||||||
|
|
||||||
|
solo_0 = 0x08 + 0x00,
|
||||||
|
solo_1 = 0x08 + 0x01,
|
||||||
|
solo_2 = 0x08 + 0x02,
|
||||||
|
solo_3 = 0x08 + 0x03,
|
||||||
|
solo_4 = 0x08 + 0x04,
|
||||||
|
solo_5 = 0x08 + 0x05,
|
||||||
|
solo_6 = 0x08 + 0x06,
|
||||||
|
solo_7 = 0x08 + 0x07,
|
||||||
|
|
||||||
|
mute_0 = 0x10 + 0x00,
|
||||||
|
mute_1 = 0x10 + 0x01,
|
||||||
|
mute_2 = 0x10 + 0x02,
|
||||||
|
mute_3 = 0x10 + 0x03,
|
||||||
|
mute_4 = 0x10 + 0x04,
|
||||||
|
mute_5 = 0x10 + 0x05,
|
||||||
|
mute_6 = 0x10 + 0x06,
|
||||||
|
mute_7 = 0x10 + 0x07,
|
||||||
|
|
||||||
|
sel_0 = 0x18 + 0x00,
|
||||||
|
sel_1 = 0x18 + 0x01,
|
||||||
|
sel_2 = 0x18 + 0x02,
|
||||||
|
sel_3 = 0x18 + 0x03,
|
||||||
|
sel_4 = 0x18 + 0x04,
|
||||||
|
sel_5 = 0x18 + 0x05,
|
||||||
|
sel_6 = 0x18 + 0x06,
|
||||||
|
sel_7 = 0x18 + 0x07,
|
||||||
|
|
||||||
|
// TODO metering
|
||||||
|
assign_track = 0x28,
|
||||||
|
assign_send = 0x29,
|
||||||
|
assign_pan = 0x2A,
|
||||||
|
assign_plugin = 0x2B,
|
||||||
|
assign_eq = 0x2C,
|
||||||
|
assign_instrument = 0x2D,
|
||||||
|
|
||||||
|
bank_left = 0x2E,
|
||||||
|
bank_right = 0x2F,
|
||||||
|
channel_left = 0x30,
|
||||||
|
channel_right = 0x31,
|
||||||
|
flip = 0x32,
|
||||||
|
global = 0x33,
|
||||||
|
|
||||||
|
name_value_button = 0x34,
|
||||||
|
smpte_beats_button = 0x35,
|
||||||
|
|
||||||
|
f1 = 0x36 + 0x00,
|
||||||
|
f2 = 0x36 + 0x01,
|
||||||
|
f3 = 0x36 + 0x02,
|
||||||
|
f4 = 0x36 + 0x03,
|
||||||
|
f5 = 0x36 + 0x04,
|
||||||
|
f6 = 0x36 + 0x05,
|
||||||
|
f7 = 0x36 + 0x06,
|
||||||
|
f8 = 0x36 + 0x07,
|
||||||
|
|
||||||
|
midi_tracks = 0x3E,
|
||||||
|
inputs = 0x3F,
|
||||||
|
audio_tracks = 0x40,
|
||||||
|
audio_instruments = 0x41,
|
||||||
|
aux = 0x42,
|
||||||
|
busses = 0x43,
|
||||||
|
outputs = 0x44,
|
||||||
|
user = 0x45,
|
||||||
|
|
||||||
|
shift = 0x46,
|
||||||
|
option = 0x47,
|
||||||
|
control = 0x48,
|
||||||
|
alt = 0x49,
|
||||||
|
|
||||||
|
save = 0x50,
|
||||||
|
undo = 0x51,
|
||||||
|
cancel = 0x52,
|
||||||
|
enter = 0x53,
|
||||||
|
|
||||||
|
markers = 0x54,
|
||||||
|
nudge = 0x55,
|
||||||
|
cycle = 0x56,
|
||||||
|
drop = 0x57,
|
||||||
|
replace = 0x58,
|
||||||
|
click = 0x59,
|
||||||
|
solo = 0x5a,
|
||||||
|
|
||||||
|
rewind = 0x5b,
|
||||||
|
forward = 0x5c,
|
||||||
|
stop = 0x5d,
|
||||||
|
play = 0x5e,
|
||||||
|
record = 0x5f,
|
||||||
|
|
||||||
|
up = 0x60,
|
||||||
|
down = 0x61,
|
||||||
|
left = 0x62,
|
||||||
|
right = 0x63,
|
||||||
|
zoom = 0x64,
|
||||||
|
scrub = 0x65,
|
||||||
|
|
||||||
|
user_switch_1 = 0x66,
|
||||||
|
user_switch_2 = 0x67,
|
||||||
|
|
||||||
|
fader_touched_0 = 0x68,
|
||||||
|
fader_touched_1 = 0x69,
|
||||||
|
fader_touched_2 = 0x6a,
|
||||||
|
fader_touched_3 = 0x6b,
|
||||||
|
fader_touched_4 = 0x6c,
|
||||||
|
fader_touched_5 = 0x6d,
|
||||||
|
fader_touched_6 = 0x6e,
|
||||||
|
fader_touched_7 = 0x6f,
|
||||||
|
fader_touched_master = 0x70,
|
||||||
|
|
||||||
|
smpte_led = 0x71,
|
||||||
|
beats_led = 0x72,
|
||||||
|
rude_solo_led = 0x73,
|
||||||
|
|
||||||
|
relay_click = 0x76,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
using arr = std::array<uint8_t, N>;
|
||||||
|
|
||||||
|
device_type type = device_type::mackie_control;
|
||||||
|
|
||||||
|
static libremidi::message make_command_impl(auto&&... data)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
libremidi::message m;
|
||||||
|
m.bytes.reserve((std::ssize(data) + ...));
|
||||||
|
(m.bytes.insert(m.bytes.end(), begin(data), end(data)), ...);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
libremidi::message make_command(command_to_device c, auto&&... data)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
const auto type = to_underlying(this->type);
|
||||||
|
const auto cmd = to_underlying(c);
|
||||||
|
const uint8_t header[6]{0xF0, 0x00, 0x00, 0x66, type, cmd};
|
||||||
|
const uint8_t footer[1]{0xF7};
|
||||||
|
return make_command_impl(header, data..., footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto device_query() { return make_command(command_to_device::device_query); }
|
||||||
|
|
||||||
|
auto response_to_challenge(arr<4> c)
|
||||||
|
{
|
||||||
|
arr<4> r;
|
||||||
|
|
||||||
|
r[0] = 0x7F & (c[0] + (c[1] ^ 0x0A) - c[3]);
|
||||||
|
r[1] = 0x7F & ((c[2] >> 4) ^ (c[0] + c[3]));
|
||||||
|
r[2] = 0x7F & (c[3] - (c[2] << 2) ^ (c[0] | c[1]));
|
||||||
|
r[3] = 0x7F & (c[1] - c[2] + (0xF0 ^ (c[3] << 4)));
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto host_connection_reply(arr<7> serial, arr<4> challenge_code)
|
||||||
|
{
|
||||||
|
const auto res = response_to_challenge(challenge_code);
|
||||||
|
return make_command(command_to_device::host_connection_reply, serial, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transport_click(bool enabled)
|
||||||
|
{
|
||||||
|
return make_command(
|
||||||
|
command_to_device::transport_click, arr<1>{uint8_t(enabled ? 0x01 : 0x00)});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lcd_backlight_save(uint8_t timeout)
|
||||||
|
{
|
||||||
|
// 0: instant off otherwise timeout in minutes
|
||||||
|
return make_command(command_to_device::lcd_backlight_save, arr<1>{timeout});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto touchless_movable_fader(bool enabled)
|
||||||
|
{
|
||||||
|
return make_command(
|
||||||
|
command_to_device::touchless_movable_fader, arr<1>{uint8_t(enabled ? 0x01 : 0x00)});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto faders_touch_sensitivity(uint8_t fader_id, fader_sensitivity sens)
|
||||||
|
{
|
||||||
|
return make_command(
|
||||||
|
command_to_device::faders_touch_sensitivity, arr<2>{fader_id, to_underlying(sens)});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto go_offline() { return make_command(command_to_device::go_offline, arr<1>{0x7F}); }
|
||||||
|
|
||||||
|
auto update_tc_display()
|
||||||
|
{
|
||||||
|
// FIXME 1 .. 10
|
||||||
|
return make_command(command_to_device::update_tc_display, arr<10>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto update_assignment_display()
|
||||||
|
{
|
||||||
|
// FIXME 1 .. 2
|
||||||
|
return make_command(command_to_device::update_assignment_display, arr<2>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto update_lcd(std::string_view txt, int pos)
|
||||||
|
{
|
||||||
|
// FIXME
|
||||||
|
if (pos < 0 || pos >= 112)
|
||||||
|
return libremidi::message{};
|
||||||
|
|
||||||
|
int len = int(std::ssize(txt));
|
||||||
|
|
||||||
|
if (len > (112 - pos))
|
||||||
|
{
|
||||||
|
txt = txt.substr(0, 112 - pos);
|
||||||
|
len = 112 - pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t buf[128];
|
||||||
|
const int N = std::min(len, 112 - pos);
|
||||||
|
for (int i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
buf[i + pos] = charmap_lcd(txt[i]);
|
||||||
|
}
|
||||||
|
buf[55] = '\n';
|
||||||
|
buf[111] = '\n';
|
||||||
|
|
||||||
|
uint8_t cmd_pos = pos;
|
||||||
|
|
||||||
|
return make_command(command_to_device::update_lcd, arr<1>{cmd_pos}, std::span(buf + pos, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto update_lcd(std::string_view txt)
|
||||||
|
{
|
||||||
|
uint8_t buf[112] = {};
|
||||||
|
for (int i = 0; i < std::min(int(std::ssize(txt)), 112); i++)
|
||||||
|
{
|
||||||
|
buf[i] = charmap_lcd(txt[i]);
|
||||||
|
}
|
||||||
|
buf[55] = '\n';
|
||||||
|
buf[111] = '\n';
|
||||||
|
return make_command(command_to_device::update_lcd, arr<1>{0}, std::span(buf, 112));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto firmware_version_request()
|
||||||
|
{
|
||||||
|
return make_command(command_to_device::firmware_version_request, arr<1>{0});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto firmware_update(std::span<uint8_t> firmware)
|
||||||
|
{
|
||||||
|
return make_command(command_to_device::firmware_update, firmware);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto channel_meter_mode(uint8_t fader_id, bool level_meter, bool peak_hold, bool signal_led)
|
||||||
|
{
|
||||||
|
uint8_t mode = 0;
|
||||||
|
|
||||||
|
if (signal_led)
|
||||||
|
mode |= 0b1;
|
||||||
|
if (peak_hold)
|
||||||
|
mode |= 0b10;
|
||||||
|
if (level_meter)
|
||||||
|
mode |= 0b100;
|
||||||
|
|
||||||
|
return make_command(command_to_device::channel_meter_mode, arr<2>{fader_id, mode});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto global_lcd_meter_mode(lcd_meter_mode mode)
|
||||||
|
{
|
||||||
|
return make_command(command_to_device::global_lcd_meter_mode, arr<1>{to_underlying(mode)});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto faders_to_minimum() { return make_command(command_to_device::faders_to_minimum); }
|
||||||
|
|
||||||
|
auto all_leds_off() { return make_command(command_to_device::all_leds_off); }
|
||||||
|
|
||||||
|
auto reset() { return make_command(command_to_device::reset); }
|
||||||
|
|
||||||
|
static auto timecode(int hi, int mi, int si, int framei)
|
||||||
|
{
|
||||||
|
std::vector<libremidi::message> msg;
|
||||||
|
auto h = std::to_string(hi);
|
||||||
|
while (h.size() < 3)
|
||||||
|
h.insert(h.begin(), '0');
|
||||||
|
auto m = std::to_string(mi);
|
||||||
|
while (m.size() < 2)
|
||||||
|
m.insert(m.begin(), '0');
|
||||||
|
auto s = std::to_string(si);
|
||||||
|
while (s.size() < 2)
|
||||||
|
s.insert(s.begin(), '0');
|
||||||
|
auto f = std::to_string(framei);
|
||||||
|
while (f.size() < 3)
|
||||||
|
f.insert(f.begin(), '0');
|
||||||
|
|
||||||
|
using ce = libremidi::channel_events;
|
||||||
|
msg.push_back(ce::control_change(1, 0x49, charmap_7segment(h[0])));
|
||||||
|
msg.push_back(ce::control_change(1, 0x48, charmap_7segment(h[1])));
|
||||||
|
msg.push_back(ce::control_change(1, 0x47, charmap_7segment(h[2])));
|
||||||
|
|
||||||
|
msg.push_back(ce::control_change(1, 0x46, charmap_7segment(m[0])));
|
||||||
|
msg.push_back(ce::control_change(1, 0x45, charmap_7segment(m[1])));
|
||||||
|
|
||||||
|
msg.push_back(ce::control_change(1, 0x44, charmap_7segment(s[0])));
|
||||||
|
msg.push_back(ce::control_change(1, 0x43, charmap_7segment(s[1])));
|
||||||
|
|
||||||
|
msg.push_back(ce::control_change(1, 0x42, charmap_7segment(f[0])));
|
||||||
|
msg.push_back(ce::control_change(1, 0x41, charmap_7segment(f[1])));
|
||||||
|
msg.push_back(ce::control_change(1, 0x40, charmap_7segment(f[2])));
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t charmap_7segment(char c, bool dot)
|
||||||
|
{
|
||||||
|
uint8_t res = charmap_7segment(c);
|
||||||
|
if (dot)
|
||||||
|
res |= 0b00100000;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t charmap_7segment(char c)
|
||||||
|
{
|
||||||
|
// FIXME there are some more characters but what to map them to ? :)
|
||||||
|
if (c >= 'a' && c <= 'z')
|
||||||
|
return c - 'a' + 1;
|
||||||
|
else if (c >= 'A' && c <= 'Z')
|
||||||
|
return c - 'A' + 1;
|
||||||
|
else if (c >= '0' && c <= '9')
|
||||||
|
return c - '0' + 0x30;
|
||||||
|
else
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '[':
|
||||||
|
case '{':
|
||||||
|
return 0x1B;
|
||||||
|
case '\\':
|
||||||
|
case '~': // yen too ?
|
||||||
|
return 0x1C;
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
return 0x1D;
|
||||||
|
case '^':
|
||||||
|
return 0x1E;
|
||||||
|
case '_':
|
||||||
|
return 0x1F;
|
||||||
|
|
||||||
|
case '!':
|
||||||
|
return 0x21;
|
||||||
|
case '"':
|
||||||
|
return 0x22;
|
||||||
|
case '#':
|
||||||
|
return 0x23;
|
||||||
|
case '$':
|
||||||
|
return 0x24;
|
||||||
|
case '%':
|
||||||
|
return 0x25;
|
||||||
|
case '&':
|
||||||
|
return 0x26;
|
||||||
|
case '\'':
|
||||||
|
return 0x27;
|
||||||
|
case '(':
|
||||||
|
return 0x28;
|
||||||
|
case ')':
|
||||||
|
return 0x29;
|
||||||
|
case '*':
|
||||||
|
return 0x2A;
|
||||||
|
case '+':
|
||||||
|
return 0x2B;
|
||||||
|
case ',':
|
||||||
|
return 0x2C;
|
||||||
|
case '-':
|
||||||
|
return 0x2D;
|
||||||
|
case '.':
|
||||||
|
return 0x2E;
|
||||||
|
case '/':
|
||||||
|
return 0x2F;
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
return 0x3A;
|
||||||
|
case ';':
|
||||||
|
return 0x3B;
|
||||||
|
case '<':
|
||||||
|
return 0x3C;
|
||||||
|
case '=':
|
||||||
|
return 0x3D;
|
||||||
|
case '>':
|
||||||
|
return 0x3E;
|
||||||
|
case '?':
|
||||||
|
return 0x3F;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static uint8_t charmap_lcd(char c)
|
||||||
|
{
|
||||||
|
// FIXME there are some more characters but what to map them to ? :)
|
||||||
|
if (c >= 'a' && c <= 'z')
|
||||||
|
return c - 'a' + 0x61;
|
||||||
|
else if (c >= 'A' && c <= 'Z')
|
||||||
|
return c - 'A' + 0x41;
|
||||||
|
else if (c >= '0' && c <= '9')
|
||||||
|
return c - '0' + 0x30;
|
||||||
|
else
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '!':
|
||||||
|
return 0x21;
|
||||||
|
case '"':
|
||||||
|
return 0x22;
|
||||||
|
case '#':
|
||||||
|
return 0x23;
|
||||||
|
case '$':
|
||||||
|
return 0x24;
|
||||||
|
case '%':
|
||||||
|
return 0x25;
|
||||||
|
case '&':
|
||||||
|
return 0x26;
|
||||||
|
case '\'':
|
||||||
|
return 0x27;
|
||||||
|
case '(':
|
||||||
|
return 0x28;
|
||||||
|
case ')':
|
||||||
|
return 0x29;
|
||||||
|
case '*':
|
||||||
|
return 0x2A;
|
||||||
|
case '+':
|
||||||
|
return 0x2B;
|
||||||
|
case ',':
|
||||||
|
return 0x2C;
|
||||||
|
case '-':
|
||||||
|
return 0x2D;
|
||||||
|
case '.':
|
||||||
|
return 0x2E;
|
||||||
|
case '/':
|
||||||
|
return 0x2F;
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
return 0x3A;
|
||||||
|
case ';':
|
||||||
|
return 0x3B;
|
||||||
|
case '<':
|
||||||
|
return 0x3C;
|
||||||
|
case '=':
|
||||||
|
return 0x3D;
|
||||||
|
case '>':
|
||||||
|
return 0x3E;
|
||||||
|
case '?':
|
||||||
|
return 0x3F;
|
||||||
|
|
||||||
|
case '@':
|
||||||
|
return 0x40;
|
||||||
|
case '[':
|
||||||
|
return 0x5B;
|
||||||
|
case '~': // Yen symbol... builtin mojibake?
|
||||||
|
return 0x5C;
|
||||||
|
case ']':
|
||||||
|
return 0x5D;
|
||||||
|
case '^':
|
||||||
|
return 0x5E;
|
||||||
|
case '_':
|
||||||
|
return 0x5F;
|
||||||
|
case '`':
|
||||||
|
return 0x60;
|
||||||
|
case '{':
|
||||||
|
return 0x7B;
|
||||||
|
case '|':
|
||||||
|
return 0x7C;
|
||||||
|
case '}':
|
||||||
|
return 0x7D;
|
||||||
|
case '\u000E':
|
||||||
|
return 0x7E;
|
||||||
|
case '\u000F':
|
||||||
|
return 0x7F;
|
||||||
|
default:
|
||||||
|
return c; // gives access to the bubble first row 0x00 > 0x0F
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rcp_configuration
|
||||||
|
{
|
||||||
|
//! How to send MIDI messages to the device.
|
||||||
|
//! Note: this function *will* be called from different thread,
|
||||||
|
//! thus it has to be thread-safe, for instance
|
||||||
|
//! by storing the message in an event queue.
|
||||||
|
std::function<void(libremidi::message&&)> midi_out;
|
||||||
|
|
||||||
|
std::function<void(libremidi::remote_control_protocol::device_type)> on_connected;
|
||||||
|
std::function<void(libremidi::remote_control_protocol::mixer_command, bool)> on_command;
|
||||||
|
std::function<void(libremidi::remote_control_protocol::mixer_control, int)> on_control;
|
||||||
|
std::function<void(libremidi::remote_control_protocol::fader, uint16_t)> on_fader;
|
||||||
|
|
||||||
|
libremidi::midi_error_callback on_error{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct remote_control_processor : libremidi::error_handler
|
||||||
|
{
|
||||||
|
using rcp = libremidi::remote_control_protocol;
|
||||||
|
rcp_configuration configuration;
|
||||||
|
rcp impl;
|
||||||
|
|
||||||
|
explicit remote_control_processor(rcp_configuration conf)
|
||||||
|
: configuration{std::move(conf)}
|
||||||
|
{
|
||||||
|
assert(configuration.midi_out);
|
||||||
|
|
||||||
|
if (!configuration.on_error)
|
||||||
|
configuration.on_error = [](std::string_view s, auto&&...) {
|
||||||
|
std::fprintf(stderr, "libremidi: rcp error: %s\n", s.data());
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!configuration.on_connected)
|
||||||
|
configuration.on_connected
|
||||||
|
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_connected"); };
|
||||||
|
|
||||||
|
if (!configuration.on_command)
|
||||||
|
configuration.on_command
|
||||||
|
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_command"); };
|
||||||
|
|
||||||
|
if (!configuration.on_control)
|
||||||
|
configuration.on_control
|
||||||
|
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_control"); };
|
||||||
|
|
||||||
|
if (!configuration.on_fader)
|
||||||
|
configuration.on_fader
|
||||||
|
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_fader"); };
|
||||||
|
}
|
||||||
|
|
||||||
|
void start()
|
||||||
|
{
|
||||||
|
current_state = waiting_for_query;
|
||||||
|
configuration.midi_out(impl.device_query());
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_midi(const libremidi::message& message)
|
||||||
|
{
|
||||||
|
switch (message.get_message_type())
|
||||||
|
{
|
||||||
|
case libremidi::message_type::SYSTEM_EXCLUSIVE:
|
||||||
|
if (auto N = message.size(); N >= 7)
|
||||||
|
{
|
||||||
|
const uint8_t* bytes = message.bytes.data();
|
||||||
|
|
||||||
|
// strip 0xF0 & 0xF7
|
||||||
|
bytes += 1;
|
||||||
|
N -= 2;
|
||||||
|
|
||||||
|
// Mackie manufacturer ID check
|
||||||
|
if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x66)
|
||||||
|
{
|
||||||
|
impl.type = static_cast<rcp::device_type>(bytes[3]);
|
||||||
|
|
||||||
|
// strip header
|
||||||
|
bytes += 4;
|
||||||
|
N -= 4;
|
||||||
|
on_rcp_command(std::span(bytes, N));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "Invalid sysex");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case libremidi::message_type::NOTE_ON:
|
||||||
|
configuration.on_command(static_cast<rcp::mixer_command>(message[1]), message[2] > 0);
|
||||||
|
break;
|
||||||
|
case libremidi::message_type::NOTE_OFF:
|
||||||
|
break;
|
||||||
|
case libremidi::message_type::CONTROL_CHANGE:
|
||||||
|
configuration.on_control(static_cast<rcp::mixer_control>(message[1]), message[2]);
|
||||||
|
break;
|
||||||
|
case libremidi::message_type::PITCH_BEND: {
|
||||||
|
uint16_t value = message.bytes[2] * 128 + message.bytes[1];
|
||||||
|
configuration.on_fader(static_cast<rcp::fader>(uint8_t(message.get_channel() - 1)), value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_rcp_command(std::span<const uint8_t> cmd)
|
||||||
|
{
|
||||||
|
if (cmd.empty())
|
||||||
|
{
|
||||||
|
libremidi_handle_error(configuration, "on_rcp_command: empty command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto command = static_cast<rcp::command_from_device>(cmd[0]);
|
||||||
|
cmd = cmd.subspan(1);
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case rcp::command_from_device::host_connection_query: {
|
||||||
|
if (cmd.size() == 11)
|
||||||
|
{
|
||||||
|
current_state = got_query;
|
||||||
|
|
||||||
|
std::array<uint8_t, 7> serial;
|
||||||
|
std::array<uint8_t, 4> challenge;
|
||||||
|
std::copy_n(cmd.data(), 7, serial.begin());
|
||||||
|
std::copy_n(cmd.data() + 7, 4, challenge.begin());
|
||||||
|
|
||||||
|
configuration.midi_out(impl.host_connection_reply(serial, challenge));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
libremidi_handle_error(configuration, "host_connection_query: invalid size");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case rcp::command_from_device::host_connection_confirmation:
|
||||||
|
current_state = connected;
|
||||||
|
configuration.on_connected(impl.type);
|
||||||
|
break;
|
||||||
|
case rcp::command_from_device::host_connection_error:
|
||||||
|
current_state = errored;
|
||||||
|
libremidi_handle_error(configuration, "host_connection_error");
|
||||||
|
break;
|
||||||
|
case rcp::command_from_device::version_reply: {
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_timecode(int h, int m, int s, int f)
|
||||||
|
{
|
||||||
|
for (auto&& m : rcp::timecode(h, m, s, f))
|
||||||
|
configuration.midi_out(std::move(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_lcd(std::string_view v)
|
||||||
|
{
|
||||||
|
auto res = impl.update_lcd(v);
|
||||||
|
if (!res.empty())
|
||||||
|
configuration.midi_out(std::move(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_lcd(std::string_view v, int pos)
|
||||||
|
{
|
||||||
|
auto res = impl.update_lcd(v, pos);
|
||||||
|
if (!res.empty())
|
||||||
|
configuration.midi_out(std::move(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
void command(remote_control_protocol::mixer_command c, bool press)
|
||||||
|
{
|
||||||
|
using ce = libremidi::channel_events;
|
||||||
|
configuration.midi_out(ce::note_on(1, to_underlying(c), press ? 127 : 0));
|
||||||
|
configuration.midi_out(ce::note_off(1, to_underlying(c), press ? 127 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void control(remote_control_protocol::mixer_control c, int value)
|
||||||
|
{
|
||||||
|
using ce = libremidi::channel_events;
|
||||||
|
configuration.midi_out(ce::control_change(1, to_underlying(c), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fader(remote_control_protocol::fader c, uint16_t value)
|
||||||
|
{
|
||||||
|
int idx = to_underlying(c);
|
||||||
|
|
||||||
|
using ce = libremidi::channel_events;
|
||||||
|
configuration.midi_out(ce::pitch_bend(idx + 1, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// State machine
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
waiting_for_query,
|
||||||
|
got_query,
|
||||||
|
connected,
|
||||||
|
errored
|
||||||
|
} current_state{waiting_for_query};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -521,7 +521,7 @@ LIBREMIDI_INLINE
|
|||||||
reader::reader(bool useAbsolute)
|
reader::reader(bool useAbsolute)
|
||||||
: ticksPerBeat(480)
|
: ticksPerBeat(480)
|
||||||
, startingTempo(120)
|
, startingTempo(120)
|
||||||
, useAbsoluteTicks(useAbsolute)
|
, m_useAbsoluteTicks(useAbsolute)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,12 +549,12 @@ try
|
|||||||
return parse_result::invalid;
|
return parse_result::invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t* const dataEnd = dataPtr + size;
|
const uint8_t* const data_end = dataPtr + size;
|
||||||
|
|
||||||
uint32_t headerId = read_checked::read_uint32_be(dataPtr, dataEnd);
|
uint32_t header_id = read_checked::read_uint32_be(dataPtr, data_end);
|
||||||
uint32_t headerLength = read_checked::read_uint32_be(dataPtr, dataEnd);
|
uint32_t header_length = read_checked::read_uint32_be(dataPtr, data_end);
|
||||||
|
|
||||||
if (static_cast<int>(headerId) != str_to_headerid("MThd") || headerLength != 6)
|
if (static_cast<int>(header_id) != str_to_headerid("MThd") || header_length != 6)
|
||||||
{
|
{
|
||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
std::cerr << "couldn't parse header" << std::endl;
|
std::cerr << "couldn't parse header" << std::endl;
|
||||||
@@ -563,7 +563,7 @@ try
|
|||||||
}
|
}
|
||||||
|
|
||||||
format = read_checked::read_uint16_be(
|
format = read_checked::read_uint16_be(
|
||||||
dataPtr, dataEnd); //@tofix format type -> save for later eventually
|
dataPtr, data_end); //@tofix format type -> save for later eventually
|
||||||
if (format > 2)
|
if (format > 2)
|
||||||
{
|
{
|
||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
@@ -571,12 +571,12 @@ try
|
|||||||
#endif
|
#endif
|
||||||
return parse_result::invalid;
|
return parse_result::invalid;
|
||||||
}
|
}
|
||||||
int trackCount = read_checked::read_uint16_be(dataPtr, dataEnd);
|
int track_count = read_checked::read_uint16_be(dataPtr, data_end);
|
||||||
uint16_t timeDivision = read_checked::read_uint16_be(dataPtr, dataEnd);
|
uint16_t time_division = read_checked::read_uint16_be(dataPtr, data_end);
|
||||||
|
|
||||||
// CBB: deal with the SMPTE style time coding
|
// CBB: deal with the SMPTE style time coding
|
||||||
// timeDivision is described here http://www.sonicspot.com/guide/midifiles.html
|
// timeDivision is described here http://www.sonicspot.com/guide/midifiles.html
|
||||||
if (timeDivision & 0x8000)
|
if (time_division & 0x8000)
|
||||||
{
|
{
|
||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
std::cerr << "found SMPTE time frames (unsupported)" << std::endl;
|
std::cerr << "found SMPTE time frames (unsupported)" << std::endl;
|
||||||
@@ -590,18 +590,18 @@ try
|
|||||||
}
|
}
|
||||||
|
|
||||||
startingTempo = 120.0f; // midi default
|
startingTempo = 120.0f; // midi default
|
||||||
ticksPerBeat = float(timeDivision); // ticks per beat (a beat is defined as a quarter note)
|
ticksPerBeat = float(time_division); // ticks per beat (a beat is defined as a quarter note)
|
||||||
|
|
||||||
parse_result result = parse_result::validated;
|
parse_result result = parse_result::validated;
|
||||||
|
|
||||||
for (int i = 0; i < trackCount; ++i)
|
for (int i = 0; i < track_count; ++i)
|
||||||
{
|
{
|
||||||
midi_track track;
|
midi_track track;
|
||||||
|
|
||||||
headerId = read_checked::read_uint32_be(dataPtr, dataEnd);
|
header_id = read_checked::read_uint32_be(dataPtr, data_end);
|
||||||
headerLength = read_checked::read_uint32_be(dataPtr, dataEnd);
|
header_length = read_checked::read_uint32_be(dataPtr, data_end);
|
||||||
|
|
||||||
if (headerId != str_to_headerid("MTrk"))
|
if (header_id != str_to_headerid("MTrk"))
|
||||||
{
|
{
|
||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
std::cerr << "couldn't find track header" << std::endl;
|
std::cerr << "couldn't find track header" << std::endl;
|
||||||
@@ -609,8 +609,8 @@ try
|
|||||||
return parse_result::incomplete;
|
return parse_result::incomplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t available = dataEnd - dataPtr;
|
int64_t available = data_end - dataPtr;
|
||||||
if (available < headerLength)
|
if (available < header_length)
|
||||||
{
|
{
|
||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
std::cerr << "not enough data available" << std::endl;
|
std::cerr << "not enough data available" << std::endl;
|
||||||
@@ -618,35 +618,35 @@ try
|
|||||||
return parse_result::incomplete;
|
return parse_result::incomplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
track.reserve(headerLength / 3);
|
track.reserve(header_length / 3);
|
||||||
|
|
||||||
const uint8_t* const trackEnd = dataPtr + headerLength;
|
const uint8_t* const track_end = dataPtr + header_length;
|
||||||
|
|
||||||
auto runningEvent = message_type::INVALID;
|
auto running_event = message_type::INVALID;
|
||||||
|
|
||||||
std::size_t tickCount = 0;
|
std::size_t tick_count = 0;
|
||||||
|
|
||||||
while (dataPtr < trackEnd)
|
while (dataPtr < track_end)
|
||||||
{
|
{
|
||||||
const auto tick = read_checked::read_variable_length(dataPtr, trackEnd);
|
const auto tick = read_checked::read_variable_length(dataPtr, track_end);
|
||||||
if (useAbsoluteTicks)
|
if (m_useAbsoluteTicks)
|
||||||
{
|
{
|
||||||
tickCount += tick;
|
tick_count += tick;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tickCount = tick;
|
tick_count = tick;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
track_event ev
|
track_event ev
|
||||||
= parse_event(static_cast<int>(tickCount), i, dataPtr, trackEnd, runningEvent);
|
= parse_event(static_cast<int>(tick_count), i, dataPtr, track_end, running_event);
|
||||||
if (!ev.m.empty())
|
if (!ev.m.empty())
|
||||||
{
|
{
|
||||||
if (!ev.m.is_meta_event())
|
if (!ev.m.is_meta_event())
|
||||||
{
|
{
|
||||||
runningEvent = static_cast<message_type>(ev.m.bytes[0]);
|
running_event = static_cast<message_type>(ev.m.bytes[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -654,7 +654,7 @@ try
|
|||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
std::cerr << "could not read event" << std::endl;
|
std::cerr << "could not read event" << std::endl;
|
||||||
#endif
|
#endif
|
||||||
dataPtr = trackEnd;
|
dataPtr = track_end;
|
||||||
result = parse_result::incomplete;
|
result = parse_result::incomplete;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -666,7 +666,7 @@ try
|
|||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
std::cerr << "" << e.what() << std::endl;
|
std::cerr << "" << e.what() << std::endl;
|
||||||
#endif
|
#endif
|
||||||
dataPtr = trackEnd;
|
dataPtr = track_end;
|
||||||
result = parse_result::incomplete;
|
result = parse_result::incomplete;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -684,7 +684,7 @@ try
|
|||||||
|
|
||||||
if (result == parse_result::validated)
|
if (result == parse_result::validated)
|
||||||
{
|
{
|
||||||
if (dataPtr != dataEnd)
|
if (dataPtr != data_end)
|
||||||
{
|
{
|
||||||
#if defined(__LIBREMIDI_DEBUG__)
|
#if defined(__LIBREMIDI_DEBUG__)
|
||||||
std::cerr << "midifile has junk at end: " << std::intptr_t(dataEnd - dataPtr) << std::endl;
|
std::cerr << "midifile has junk at end: " << std::intptr_t(dataEnd - dataPtr) << std::endl;
|
||||||
@@ -706,33 +706,33 @@ catch (const std::exception& e)
|
|||||||
LIBREMIDI_INLINE
|
LIBREMIDI_INLINE
|
||||||
double reader::get_end_time() const noexcept
|
double reader::get_end_time() const noexcept
|
||||||
{
|
{
|
||||||
if (useAbsoluteTicks)
|
if (m_useAbsoluteTicks)
|
||||||
{
|
{
|
||||||
double totalLength = 0.;
|
double total_length = 0.;
|
||||||
for (const auto& t : tracks)
|
for (const auto& t : tracks)
|
||||||
{
|
{
|
||||||
if (!t.empty())
|
if (!t.empty())
|
||||||
{
|
{
|
||||||
const auto& last_event = t.back();
|
const auto& last_event = t.back();
|
||||||
if (last_event.tick > totalLength)
|
if (last_event.tick > total_length)
|
||||||
totalLength = last_event.tick;
|
total_length = last_event.tick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return totalLength;
|
return total_length;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double totalLength = 0.;
|
double total_length = 0.;
|
||||||
for (const auto& t : tracks)
|
for (const auto& t : tracks)
|
||||||
{
|
{
|
||||||
double trackLength = 0.;
|
double track_length = 0.;
|
||||||
for (const auto& e : t)
|
for (const auto& e : t)
|
||||||
trackLength += e.tick;
|
track_length += e.tick;
|
||||||
|
|
||||||
if (trackLength > totalLength)
|
if (track_length > total_length)
|
||||||
totalLength = trackLength;
|
total_length = track_length;
|
||||||
}
|
}
|
||||||
return totalLength;
|
return total_length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public:
|
|||||||
std::vector<midi_track> tracks;
|
std::vector<midi_track> tracks;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool useAbsoluteTicks{};
|
bool m_useAbsoluteTicks{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/api.hpp>
|
#include <libremidi/api.hpp>
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/configurations.hpp>
|
||||||
|
|
||||||
#include <any>
|
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
@@ -24,7 +22,9 @@ public:
|
|||||||
struct shared_configurations
|
struct shared_configurations
|
||||||
{
|
{
|
||||||
std::shared_ptr<shared_context> context;
|
std::shared_ptr<shared_context> context;
|
||||||
std::any observer, in, out;
|
observer_api_configuration observer;
|
||||||
|
input_api_configuration in;
|
||||||
|
output_api_configuration out;
|
||||||
};
|
};
|
||||||
|
|
||||||
LIBREMIDI_EXPORT
|
LIBREMIDI_EXPORT
|
||||||
|
|||||||
+31
-21
@@ -1,14 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <libremidi/config.hpp>
|
#include <libremidi/config.hpp>
|
||||||
|
|
||||||
#include <span>
|
|
||||||
|
|
||||||
#if LIBREMIDI_NI_MIDI2_COMPAT
|
#if LIBREMIDI_NI_MIDI2_COMPAT
|
||||||
#include <midi/universal_packet.h>
|
#include <midi/universal_packet.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace libremidi
|
namespace libremidi
|
||||||
{
|
{
|
||||||
|
namespace midi2
|
||||||
|
{
|
||||||
|
// Imported from cmidi2
|
||||||
|
enum class message_type
|
||||||
|
{
|
||||||
|
// MIDI 2.0 UMP Section 3.
|
||||||
|
UTILITY = 0,
|
||||||
|
SYSTEM = 1,
|
||||||
|
MIDI_1_CHANNEL = 2,
|
||||||
|
SYSEX7 = 3,
|
||||||
|
MIDI_2_CHANNEL = 4,
|
||||||
|
SYSEX8_MDS = 5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
struct ump
|
struct ump
|
||||||
{
|
{
|
||||||
alignas(4) uint32_t data[4] = {};
|
alignas(4) uint32_t data[4] = {};
|
||||||
@@ -62,28 +75,16 @@ struct ump
|
|||||||
|
|
||||||
constexpr std::size_t size() const noexcept
|
constexpr std::size_t size() const noexcept
|
||||||
{
|
{
|
||||||
// Imported from cmidi2
|
switch (midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF))
|
||||||
enum midi2_message_type
|
|
||||||
{
|
{
|
||||||
// MIDI 2.0 UMP Section 3.
|
case midi2::message_type::UTILITY:
|
||||||
UTILITY = 0,
|
case midi2::message_type::SYSTEM:
|
||||||
SYSTEM = 1,
|
case midi2::message_type::MIDI_1_CHANNEL:
|
||||||
MIDI_1_CHANNEL = 2,
|
|
||||||
SYSEX7 = 3,
|
|
||||||
MIDI_2_CHANNEL = 4,
|
|
||||||
SYSEX8_MDS = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (((data[0] & 0xF0000000) >> 28) & 0xF)
|
|
||||||
{
|
|
||||||
case UTILITY:
|
|
||||||
case SYSTEM:
|
|
||||||
case MIDI_1_CHANNEL:
|
|
||||||
return 1;
|
return 1;
|
||||||
case MIDI_2_CHANNEL:
|
case midi2::message_type::MIDI_2_CHANNEL:
|
||||||
case SYSEX7:
|
case midi2::message_type::SYSEX7:
|
||||||
return 2;
|
return 2;
|
||||||
case SYSEX8_MDS:
|
case midi2::message_type::SYSEX8_MDS:
|
||||||
return 4;
|
return 4;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
@@ -109,5 +110,14 @@ struct ump
|
|||||||
constexpr auto cend() const noexcept { return data + size(); }
|
constexpr auto cend() const noexcept { return data + size(); }
|
||||||
constexpr auto cbegin() noexcept { return data; }
|
constexpr auto cbegin() noexcept { return data; }
|
||||||
constexpr auto cend() noexcept { return data + size(); }
|
constexpr auto cend() noexcept { return data + size(); }
|
||||||
|
|
||||||
|
constexpr midi2::message_type get_type() const noexcept
|
||||||
|
{
|
||||||
|
return midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF);
|
||||||
|
}
|
||||||
|
constexpr uint8_t get_group() const noexcept { return (data[0] >> 24) & 0x0F; }
|
||||||
|
constexpr uint8_t get_channel_0_15() const noexcept { return (data[0] >> 16) & 0x0F; }
|
||||||
|
constexpr uint8_t get_channel() const noexcept { return get_channel_0_15() + 1; }
|
||||||
|
constexpr uint8_t get_status_code() const noexcept { return (data[0] >> 16) & 0xF0; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user