libremidi update to v5.4.3 and added virtual midi out as default
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
namespace libremidi::kdmapi
|
||||
{
|
||||
// KDMAPI does not support MIDI input
|
||||
struct input_configuration
|
||||
{
|
||||
};
|
||||
|
||||
struct output_configuration
|
||||
{
|
||||
// Use the no-buffer variant of SendDirectData for lowest latency
|
||||
// This bypasses OmniMIDI's internal buffer
|
||||
bool use_no_buffer = false;
|
||||
};
|
||||
|
||||
struct observer_configuration
|
||||
{
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
// clang-format off
|
||||
#define NOMINMAX 1
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
// clang-format on
|
||||
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
namespace libremidi::kdmapi
|
||||
{
|
||||
|
||||
using IsKDMAPIAvailable_t = BOOL(WINAPI*)();
|
||||
using InitializeKDMAPIStream_t = BOOL(WINAPI*)();
|
||||
using TerminateKDMAPIStream_t = BOOL(WINAPI*)();
|
||||
using ResetKDMAPIStream_t = VOID(WINAPI*)();
|
||||
using SendDirectData_t = VOID(WINAPI*)(DWORD);
|
||||
using SendDirectDataNoBuf_t = VOID(WINAPI*)(DWORD);
|
||||
using SendDirectLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
|
||||
using SendDirectLongDataNoBuf_t = UINT(WINAPI*)(LPSTR, DWORD);
|
||||
using PrepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
|
||||
using UnprepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT);
|
||||
using ReturnKDMAPIVer_t = BOOL(WINAPI*)(LPDWORD, LPDWORD, LPDWORD, LPDWORD);
|
||||
|
||||
class kdmapi_loader
|
||||
{
|
||||
public:
|
||||
static kdmapi_loader& instance()
|
||||
{
|
||||
static kdmapi_loader loader;
|
||||
return loader;
|
||||
}
|
||||
|
||||
bool is_available() const noexcept { return m_available; }
|
||||
|
||||
HMODULE handle() const noexcept { return m_handle; }
|
||||
|
||||
// KDMAPI function pointers
|
||||
IsKDMAPIAvailable_t IsKDMAPIAvailable{};
|
||||
InitializeKDMAPIStream_t InitializeKDMAPIStream{};
|
||||
TerminateKDMAPIStream_t TerminateKDMAPIStream{};
|
||||
ResetKDMAPIStream_t ResetKDMAPIStream{};
|
||||
SendDirectData_t SendDirectData{};
|
||||
SendDirectDataNoBuf_t SendDirectDataNoBuf{};
|
||||
SendDirectLongData_t SendDirectLongData{};
|
||||
SendDirectLongDataNoBuf_t SendDirectLongDataNoBuf{};
|
||||
PrepareLongData_t PrepareLongData{};
|
||||
UnprepareLongData_t UnprepareLongData{};
|
||||
ReturnKDMAPIVer_t ReturnKDMAPIVer{};
|
||||
|
||||
private:
|
||||
kdmapi_loader()
|
||||
{
|
||||
// Try to get OmniMIDI if it's already loaded
|
||||
m_handle = GetModuleHandleW(L"OmniMIDI");
|
||||
if (!m_handle)
|
||||
{
|
||||
// Try to load it explicitly
|
||||
m_handle = LoadLibraryW(L"OmniMIDI.dll");
|
||||
}
|
||||
|
||||
if (!m_handle)
|
||||
{
|
||||
m_available = false;
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
#endif
|
||||
|
||||
// Load all function pointers
|
||||
IsKDMAPIAvailable
|
||||
= reinterpret_cast<IsKDMAPIAvailable_t>(GetProcAddress(m_handle, "IsKDMAPIAvailable"));
|
||||
InitializeKDMAPIStream = reinterpret_cast<InitializeKDMAPIStream_t>(
|
||||
GetProcAddress(m_handle, "InitializeKDMAPIStream"));
|
||||
TerminateKDMAPIStream = reinterpret_cast<TerminateKDMAPIStream_t>(
|
||||
GetProcAddress(m_handle, "TerminateKDMAPIStream"));
|
||||
ResetKDMAPIStream
|
||||
= reinterpret_cast<ResetKDMAPIStream_t>(GetProcAddress(m_handle, "ResetKDMAPIStream"));
|
||||
SendDirectData
|
||||
= reinterpret_cast<SendDirectData_t>(GetProcAddress(m_handle, "SendDirectData"));
|
||||
SendDirectDataNoBuf
|
||||
= reinterpret_cast<SendDirectDataNoBuf_t>(GetProcAddress(m_handle, "SendDirectDataNoBuf"));
|
||||
SendDirectLongData
|
||||
= reinterpret_cast<SendDirectLongData_t>(GetProcAddress(m_handle, "SendDirectLongData"));
|
||||
SendDirectLongDataNoBuf = reinterpret_cast<SendDirectLongDataNoBuf_t>(
|
||||
GetProcAddress(m_handle, "SendDirectLongDataNoBuf"));
|
||||
PrepareLongData
|
||||
= reinterpret_cast<PrepareLongData_t>(GetProcAddress(m_handle, "PrepareLongData"));
|
||||
UnprepareLongData
|
||||
= reinterpret_cast<UnprepareLongData_t>(GetProcAddress(m_handle, "UnprepareLongData"));
|
||||
ReturnKDMAPIVer
|
||||
= reinterpret_cast<ReturnKDMAPIVer_t>(GetProcAddress(m_handle, "ReturnKDMAPIVer"));
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
// Check if the minimum required functions are available
|
||||
if (!IsKDMAPIAvailable || !InitializeKDMAPIStream || !TerminateKDMAPIStream || !SendDirectData)
|
||||
{
|
||||
m_available = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Call IsKDMAPIAvailable to activate KDMAPI mode in OmniMIDI
|
||||
m_available = IsKDMAPIAvailable() != FALSE;
|
||||
}
|
||||
|
||||
~kdmapi_loader()
|
||||
{
|
||||
}
|
||||
|
||||
kdmapi_loader(const kdmapi_loader&) = delete;
|
||||
kdmapi_loader& operator=(const kdmapi_loader&) = delete;
|
||||
|
||||
HMODULE m_handle{};
|
||||
bool m_available{false};
|
||||
};
|
||||
|
||||
class kdmapi_stream_manager
|
||||
{
|
||||
public:
|
||||
static kdmapi_stream_manager& instance()
|
||||
{
|
||||
static kdmapi_stream_manager mgr;
|
||||
return mgr;
|
||||
}
|
||||
|
||||
bool acquire()
|
||||
{
|
||||
std::lock_guard lock{m_mutex};
|
||||
auto& loader = kdmapi_loader::instance();
|
||||
if (!loader.is_available())
|
||||
return false;
|
||||
|
||||
if (m_rc == 0)
|
||||
{
|
||||
if (!loader.InitializeKDMAPIStream())
|
||||
return false;
|
||||
}
|
||||
++m_rc;
|
||||
return true;
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
std::lock_guard lock{m_mutex};
|
||||
if (m_rc == 0)
|
||||
return;
|
||||
|
||||
--m_rc;
|
||||
if (m_rc == 0)
|
||||
{
|
||||
auto& loader = kdmapi_loader::instance();
|
||||
if (loader.TerminateKDMAPIStream)
|
||||
loader.TerminateKDMAPIStream();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
kdmapi_stream_manager() = default;
|
||||
~kdmapi_stream_manager() = default;
|
||||
kdmapi_stream_manager(const kdmapi_stream_manager&) = delete;
|
||||
kdmapi_stream_manager& operator=(const kdmapi_stream_manager&) = delete;
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::size_t m_rc{0};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/kdmapi/config.hpp>
|
||||
#include <libremidi/backends/dummy.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
|
||||
namespace libremidi::kdmapi
|
||||
{
|
||||
|
||||
using midi_in = libremidi::midi_in_dummy;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/kdmapi/config.hpp>
|
||||
#include <libremidi/backends/kdmapi/helpers.hpp>
|
||||
#include <libremidi/backends/kdmapi/observer.hpp>
|
||||
#include <libremidi/detail/midi_out.hpp>
|
||||
|
||||
namespace libremidi::kdmapi
|
||||
{
|
||||
|
||||
class midi_out final
|
||||
: public midi1::out_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: libremidi::output_configuration
|
||||
, kdmapi::output_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
midi_out(libremidi::output_configuration&& conf, kdmapi::output_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
auto& loader = kdmapi_loader::instance();
|
||||
if (!loader.is_available())
|
||||
{
|
||||
libremidi_handle_error(
|
||||
configuration, "KDMAPI is not available. Is OmniMIDI installed?");
|
||||
this->client_open_ = std::errc::no_such_device;
|
||||
return;
|
||||
}
|
||||
|
||||
this->client_open_ = stdx::error{};
|
||||
}
|
||||
|
||||
~midi_out() override
|
||||
{
|
||||
midi_out::close_port();
|
||||
this->client_open_ = std::errc::not_connected;
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; }
|
||||
|
||||
stdx::error open_port(const output_port& port, std::string_view) override
|
||||
{
|
||||
if (port.api != libremidi::API::KDMAPI)
|
||||
{
|
||||
libremidi_handle_error(configuration, "port is not a KDMAPI port");
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
return do_open();
|
||||
}
|
||||
|
||||
stdx::error do_open()
|
||||
{
|
||||
auto& mgr = kdmapi_stream_manager::instance();
|
||||
if (!mgr.acquire())
|
||||
{
|
||||
libremidi_handle_error(configuration, "failed to initialize KDMAPI stream");
|
||||
return std::errc::io_error;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error close_port() override
|
||||
{
|
||||
if (connected_)
|
||||
{
|
||||
auto& mgr = kdmapi_stream_manager::instance();
|
||||
mgr.release();
|
||||
connected_ = false;
|
||||
}
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
stdx::error send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
if (!connected_)
|
||||
return std::errc::not_connected;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
libremidi_handle_warning(configuration, "message argument is empty!");
|
||||
return std::errc::invalid_argument;
|
||||
}
|
||||
|
||||
auto& loader = kdmapi_loader::instance();
|
||||
|
||||
if (message[0] == 0xF0)
|
||||
{
|
||||
// SysEx message
|
||||
return send_sysex(message, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Channel or system message
|
||||
if (size > 3)
|
||||
{
|
||||
libremidi_handle_warning(
|
||||
configuration, "message size is greater than 3 bytes (and not sysex)!");
|
||||
return std::errc::message_size;
|
||||
}
|
||||
|
||||
// Pack MIDI bytes into DWORD
|
||||
DWORD packet = 0;
|
||||
std::copy_n(message, size, reinterpret_cast<unsigned char*>(&packet));
|
||||
|
||||
// Send the message
|
||||
if (configuration.use_no_buffer && loader.SendDirectDataNoBuf)
|
||||
loader.SendDirectDataNoBuf(packet);
|
||||
else
|
||||
loader.SendDirectData(packet);
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
private:
|
||||
stdx::error send_sysex(const unsigned char* message, size_t size)
|
||||
{
|
||||
auto& loader = kdmapi_loader::instance();
|
||||
|
||||
if (configuration.use_no_buffer && loader.SendDirectLongDataNoBuf)
|
||||
{
|
||||
// Use the no-buffer variant - it takes raw data directly
|
||||
loader.SendDirectLongDataNoBuf(
|
||||
const_cast<LPSTR>(reinterpret_cast<const char*>(message)), static_cast<DWORD>(size));
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
// Standard path with MIDIHDR
|
||||
buffer_.assign(message, message + size);
|
||||
|
||||
MIDIHDR sysex{};
|
||||
sysex.lpData = reinterpret_cast<LPSTR>(buffer_.data());
|
||||
sysex.dwBufferLength = static_cast<DWORD>(size);
|
||||
sysex.dwFlags = 0;
|
||||
|
||||
if (loader.PrepareLongData)
|
||||
{
|
||||
UINT result = loader.PrepareLongData(&sysex, sizeof(MIDIHDR));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
libremidi_handle_error(configuration, "error preparing sysex header");
|
||||
return std::errc::io_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (loader.SendDirectLongData)
|
||||
{
|
||||
UINT result = loader.SendDirectLongData(&sysex, sizeof(MIDIHDR));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
libremidi_handle_error(configuration, "error sending sysex message");
|
||||
// Still try to unprepare
|
||||
}
|
||||
}
|
||||
|
||||
if (loader.UnprepareLongData)
|
||||
{
|
||||
loader.UnprepareLongData(&sysex, sizeof(MIDIHDR));
|
||||
}
|
||||
|
||||
return stdx::error{};
|
||||
}
|
||||
|
||||
std::vector<char> buffer_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/kdmapi/config.hpp>
|
||||
#include <libremidi/backends/kdmapi/helpers.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
namespace libremidi::kdmapi
|
||||
{
|
||||
|
||||
class observer final : public observer_api
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: libremidi::observer_configuration
|
||||
, kdmapi::observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer(
|
||||
libremidi::observer_configuration&& conf, kdmapi::observer_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
|
||||
auto& loader = kdmapi_loader::instance();
|
||||
if (!loader.is_available())
|
||||
return;
|
||||
|
||||
// KDMAPI provides a single virtual output port
|
||||
if (configuration.notify_in_constructor)
|
||||
{
|
||||
if (configuration.output_added)
|
||||
{
|
||||
auto ports = get_output_ports();
|
||||
for (const auto& port : ports)
|
||||
configuration.output_added(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~observer() = default;
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; }
|
||||
|
||||
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
// KDMAPI does not provide input ports
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
auto& loader = kdmapi_loader::instance();
|
||||
if (!loader.is_available())
|
||||
return {};
|
||||
|
||||
// Get KDMAPI version for display name
|
||||
std::string display_name = "OmniMIDI";
|
||||
if (loader.ReturnKDMAPIVer)
|
||||
{
|
||||
DWORD major{}, minor{}, build{}, rev{};
|
||||
if (loader.ReturnKDMAPIVer(&major, &minor, &build, &rev))
|
||||
{
|
||||
display_name += " (KDMAPI ";
|
||||
display_name += std::to_string(major);
|
||||
display_name += ".";
|
||||
display_name += std::to_string(minor);
|
||||
display_name += ".";
|
||||
display_name += std::to_string(build);
|
||||
display_name += ")";
|
||||
}
|
||||
}
|
||||
|
||||
return {libremidi::output_port{
|
||||
{.api = libremidi::API::KDMAPI,
|
||||
.client = 0,
|
||||
.port = 0,
|
||||
.manufacturer = "KeppySoftware",
|
||||
.device_name = "OmniMIDI",
|
||||
.port_name = "OmniMIDI",
|
||||
.display_name = std::move(display_name)},
|
||||
}};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user