174 lines
4.2 KiB
C++
174 lines
4.2 KiB
C++
|
|
#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_;
|
||
|
|
};
|
||
|
|
|
||
|
|
}
|