#pragma once #include #include #include #include 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(&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(reinterpret_cast(message)), static_cast(size)); return stdx::error{}; } // Standard path with MIDIHDR buffer_.assign(message, message + size); MIDIHDR sysex{}; sysex.lpData = reinterpret_cast(buffer_.data()); sysex.dwBufferLength = static_cast(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 buffer_; }; }