Files
2024-10-28 19:24:18 +01:00

165 lines
4.3 KiB
C++

#pragma once
#include <libremidi/backends/winmm/config.hpp>
#include <libremidi/backends/winmm/helpers.hpp>
#include <libremidi/backends/winmm/observer.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
{
class midi_out_winmm final
: public midi1::out_api
, public error_handler
{
public:
struct
: output_configuration
, winmm_output_configuration
{
} configuration;
midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
this->client_open_ = stdx::error{};
}
~midi_out_winmm() override
{
// Close a connection if it exists.
midi_out_winmm::close_port();
this->client_open_ = std::errc::not_connected;
}
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
[[nodiscard]] stdx::error do_open(unsigned int portNumber)
{
MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL);
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(
configuration,
"error creating Windows MM MIDI output "
"port.");
return from_mmerr(result);
}
return stdx::error{};
}
stdx::error open_port(const output_port& p, std::string_view) override
{
observer_winmm obs{{}, winmm_observer_configuration{}};
auto ports = obs.get_output_ports();
// First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2
for (auto& port : ports)
{
if (p.display_name == port.display_name)
return do_open(port.port);
}
// If nothing is found, try to check with the raw name
for (auto& port : ports)
{
if (p.port_name == port.port_name)
return do_open(port.port);
}
libremidi_handle_error(
configuration, "port not found: " + p.port_name);
return std::errc::invalid_argument;
}
stdx::error close_port() override
{
if (this->outHandle)
midiOutClose(this->outHandle);
this->outHandle = nullptr;
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;
}
if (message[0] == 0xF0)
{ // Sysex message
buffer.assign(message, message + size);
// FIXME this can be made asynchronous... see Chrome source.
// But need to know whe buffers are freed.
// Create and prepare MIDIHDR structure.
MIDIHDR sysex{};
sysex.lpData = (LPSTR)buffer.data();
sysex.dwBufferLength = size;
sysex.dwFlags = 0;
auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(
configuration, "error preparing sysex header.");
return from_mmerr(result);
}
// Send the message.
result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(
configuration, "error sending sysex message.");
return from_mmerr(result);
}
// Unprepare the buffer and MIDIHDR.
// FIXME yuck
while (MIDIERR_STILLPLAYING
== midiOutUnprepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)))
Sleep(1);
}
else
{ // Channel or system message.
// Make sure the message size isn't too big.
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 double word.
DWORD packet;
std::copy_n(message, size, (unsigned char*)&packet);
// Send the message immediately.
auto result = midiOutShortMsg(this->outHandle, packet);
if (result != MMSYSERR_NOERROR)
{
libremidi_handle_error(
configuration, "error sending MIDI message.");
return from_mmerr(result);
}
}
return stdx::error{};
}
private:
HMIDIOUT outHandle; // Handle to Midi Output Device
std::vector<char> buffer;
};
}