very, very simple WP with midi CC out
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <libremidi/config.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
struct winmm_input_configuration
|
||||
{
|
||||
int sysex_buffer_size = 1024;
|
||||
int sysex_buffer_count = 4;
|
||||
};
|
||||
|
||||
struct winmm_output_configuration
|
||||
{
|
||||
};
|
||||
|
||||
struct poll_parameters
|
||||
{
|
||||
std::function<void()> callback;
|
||||
};
|
||||
|
||||
struct winmm_observer_configuration
|
||||
{
|
||||
std::chrono::milliseconds poll_period{100};
|
||||
std::function<void(const poll_parameters&)> manual_poll;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
#define NOMINMAX 1
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#include <libremidi/detail/midi_api.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// clang-format off
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
// clang-format on
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
// Convert a nullptr-terminated wide string or ANSI-encoded string to UTF-8.
|
||||
inline std::string ConvertToUTF8(const TCHAR* str)
|
||||
{
|
||||
std::string u8str;
|
||||
const WCHAR* wstr = L"";
|
||||
#if defined(UNICODE) || defined(_UNICODE)
|
||||
wstr = str;
|
||||
#else
|
||||
// Convert from ANSI encoding to wide string
|
||||
int wlength = MultiByteToWideChar(CP_ACP, 0, str, -1, nullptr, 0);
|
||||
std::wstring wstrtemp;
|
||||
if (wlength)
|
||||
{
|
||||
wstrtemp.assign(wlength - 1, 0);
|
||||
MultiByteToWideChar(CP_ACP, 0, str, -1, &wstrtemp[0], wlength);
|
||||
wstr = &wstrtemp[0];
|
||||
}
|
||||
#endif
|
||||
// Convert from wide string to UTF-8
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
|
||||
if (length)
|
||||
{
|
||||
u8str.assign(static_cast<std::string::size_type>(length - 1), 0);
|
||||
/*length =*/WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &u8str[0], length, nullptr, nullptr);
|
||||
}
|
||||
return u8str;
|
||||
}
|
||||
|
||||
// Next functions add the portNumber to the name so that
|
||||
// the device's names are sure to be listed with individual names
|
||||
// even when they have the same brand name
|
||||
inline void MakeUniqueInPortName(std::string& deviceName, unsigned int portNumber)
|
||||
{
|
||||
int x = 1;
|
||||
for (unsigned int i = 0; i < portNumber; i++)
|
||||
{
|
||||
MIDIINCAPS deviceCaps;
|
||||
midiInGetDevCaps(i, &deviceCaps, sizeof(MIDIINCAPS));
|
||||
auto stringName = ConvertToUTF8(deviceCaps.szPname);
|
||||
if (deviceName == stringName)
|
||||
{
|
||||
x++;
|
||||
}
|
||||
}
|
||||
deviceName += " ";
|
||||
deviceName += std::to_string(x);
|
||||
}
|
||||
|
||||
inline void MakeUniqueOutPortName(std::string& deviceName, unsigned int portNumber)
|
||||
{
|
||||
int x = 1;
|
||||
for (unsigned int i = 0; i < portNumber; i++)
|
||||
{
|
||||
MIDIOUTCAPS deviceCaps;
|
||||
midiOutGetDevCaps(i, &deviceCaps, sizeof(MIDIOUTCAPS));
|
||||
auto stringName = ConvertToUTF8(deviceCaps.szPname);
|
||||
if (deviceName == stringName)
|
||||
{
|
||||
x++;
|
||||
}
|
||||
}
|
||||
deviceName += " ";
|
||||
deviceName += std::to_string(x);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/winmm/config.hpp>
|
||||
#include <libremidi/backends/winmm/helpers.hpp>
|
||||
#include <libremidi/backends/winmm/observer.hpp>
|
||||
#include <libremidi/detail/midi_in.hpp>
|
||||
#include <libremidi/detail/midi_stream_decoder.hpp>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
class midi_in_winmm final
|
||||
: public midi1::in_api
|
||||
, public error_handler
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: input_configuration
|
||||
, winmm_input_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit midi_in_winmm(input_configuration&& conf, winmm_input_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
// We'll issue a warning here if no devices are available but not
|
||||
// throw an error since the user can plugin something later.
|
||||
if (midiInGetNumDevs() == 0)
|
||||
{
|
||||
warning(
|
||||
configuration, "midi_in_winmm::initialize: no MIDI input devices currently available.");
|
||||
}
|
||||
|
||||
if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400))
|
||||
{
|
||||
warning(
|
||||
configuration,
|
||||
"midi_in_winmm::initialize: InitializeCriticalSectionAndSpinCount failed.");
|
||||
}
|
||||
}
|
||||
|
||||
~midi_in_winmm() override
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
midi_in_winmm::close_port();
|
||||
|
||||
DeleteCriticalSection(&(this->_mutex));
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmm: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmm: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_in_winmm: set_port_name unsupported");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
|
||||
|
||||
bool do_open(unsigned int portNumber)
|
||||
{
|
||||
MMRESULT result = midiInOpen(
|
||||
&this->inHandle, portNumber, std::bit_cast<DWORD_PTR>(&midiInputCallback),
|
||||
std::bit_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_in_winmm::open_port: error creating Windows MM MIDI input port.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate and init the sysex buffers.
|
||||
const auto bufferCount = static_cast<std::size_t>(configuration.sysex_buffer_count);
|
||||
this->sysexBuffer.resize(bufferCount);
|
||||
for (std::size_t i = 0; i < bufferCount; ++i)
|
||||
{
|
||||
this->sysexBuffer[i] = new MIDIHDR;
|
||||
this->sysexBuffer[i]->lpData = new char[configuration.sysex_buffer_size];
|
||||
this->sysexBuffer[i]->dwBufferLength = static_cast<DWORD>(configuration.sysex_buffer_size);
|
||||
this->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator
|
||||
this->sysexBuffer[i]->dwFlags = 0;
|
||||
|
||||
result = midiInPrepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
midiInClose(this->inHandle);
|
||||
this->inHandle = nullptr;
|
||||
error<driver_error>(
|
||||
configuration,
|
||||
"midi_in_winmm::open_port: error starting Windows MM MIDI input port "
|
||||
"(PrepareHeader).");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register the buffer.
|
||||
result = midiInAddBuffer(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
midiInClose(this->inHandle);
|
||||
this->inHandle = nullptr;
|
||||
error<driver_error>(
|
||||
configuration,
|
||||
"midi_in_winmm::open_port: error starting Windows MM MIDI input port "
|
||||
"(AddBuffer).");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = midiInStart(this->inHandle);
|
||||
midi_start_timestamp = std::chrono::steady_clock::now();
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
midiInClose(this->inHandle);
|
||||
this->inHandle = nullptr;
|
||||
error<driver_error>(
|
||||
configuration, "midi_in_winmm::open_port: error starting Windows MM MIDI input port.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool open_port(const input_port& p, std::string_view) override
|
||||
{
|
||||
observer_winmm obs{{}, winmm_observer_configuration{}};
|
||||
auto ports = obs.get_input_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);
|
||||
}
|
||||
error<invalid_parameter_error>(
|
||||
configuration, "midi_in_winmm::open_port: port not found: " + p.port_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
{
|
||||
if (connected_)
|
||||
{
|
||||
EnterCriticalSection(&(this->_mutex));
|
||||
midiInReset(this->inHandle);
|
||||
midiInStop(this->inHandle);
|
||||
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>(configuration.sysex_buffer_count); ++i)
|
||||
{
|
||||
MMRESULT res{};
|
||||
|
||||
int wait_count = 5;
|
||||
while (
|
||||
((res = midiInUnprepareHeader(this->inHandle, this->sysexBuffer[i], sizeof(MIDIHDR)))
|
||||
== MIDIERR_STILLPLAYING)
|
||||
&& wait_count-- >= 0)
|
||||
{
|
||||
Sleep(1);
|
||||
}
|
||||
|
||||
if (res != MMSYSERR_NOERROR)
|
||||
{
|
||||
warning(
|
||||
configuration,
|
||||
"midi_in_winmm::open_port: error closing Windows MM MIDI input "
|
||||
"port (midiInUnprepareHeader).");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete[] this->sysexBuffer[i]->lpData;
|
||||
delete[] this->sysexBuffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
midiInClose(this->inHandle);
|
||||
this->inHandle = nullptr;
|
||||
LeaveCriticalSection(&(this->_mutex));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
timestamp absolute_timestamp() const noexcept override
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - midi_start_timestamp)
|
||||
.count();
|
||||
}
|
||||
|
||||
static constexpr int bytes_for_message(uint8_t status)
|
||||
{
|
||||
if (status < 0xC0)
|
||||
return 3;
|
||||
else if (status < 0xE0)
|
||||
return 2;
|
||||
else if (status < 0xF0)
|
||||
return 3;
|
||||
else if (status == 0xF1)
|
||||
return 2;
|
||||
else if (status == 0xF2)
|
||||
return 3;
|
||||
else if (status == 0xF3)
|
||||
return 2;
|
||||
else if (status == 0xF8)
|
||||
return 1;
|
||||
else if (status == 0xFE)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void CALLBACK midiInputCallback(
|
||||
HMIDIIN /*hmin*/, UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage,
|
||||
DWORD_PTR timestamp)
|
||||
{
|
||||
if (inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR)
|
||||
return;
|
||||
|
||||
auto& self = *reinterpret_cast<midi_in_winmm*>(instancePtr);
|
||||
static constexpr timestamp_backend_info timestamp_info{
|
||||
.has_absolute_timestamps = true,
|
||||
.absolute_is_monotonic = false,
|
||||
.has_samples = false,
|
||||
};
|
||||
|
||||
const auto to_ns = [timestamp] { return timestamp * 1'000'000; };
|
||||
|
||||
if (inputStatus == MIM_DATA)
|
||||
{
|
||||
// Channel or system message
|
||||
uint8_t message[sizeof(DWORD_PTR)];
|
||||
memcpy(message, &midiMessage, sizeof(DWORD_PTR));
|
||||
|
||||
// Make sure the first byte is a status byte.
|
||||
if (message[0] & 0x80)
|
||||
{
|
||||
self.m_processing.on_bytes(
|
||||
{message, message + bytes_for_message(message[0])},
|
||||
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sysex message ( MIM_LONGDATA or MIM_LONGERROR )
|
||||
const auto* sysex = reinterpret_cast<MIDIHDR*>(midiMessage);
|
||||
if(inputStatus == MIM_LONGERROR)
|
||||
{
|
||||
self.m_processing.message.bytes.clear();
|
||||
self.m_processing.state = self.m_processing.main;
|
||||
}
|
||||
else if (!self.configuration.ignore_sysex)
|
||||
{
|
||||
if(sysex->dwBytesRecorded > 0)
|
||||
{
|
||||
const auto sysex_bytes = reinterpret_cast<uint8_t*>(sysex->lpData);
|
||||
|
||||
self.m_processing.on_bytes(
|
||||
{sysex_bytes, sysex_bytes + sysex->dwBytesRecorded},
|
||||
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// The WinMM API requires that the sysex buffer be requeued after
|
||||
// input of each sysex message. Even if we are ignoring sysex
|
||||
// messages, we still need to requeue the buffer in case the user
|
||||
// decides to not ignore sysex messages in the future. However,
|
||||
// it seems that WinMM calls this function with an empty sysex
|
||||
// buffer when an application closes and in this case, we should
|
||||
// avoid requeueing it, else the computer suddenly reboots after
|
||||
// one or two minutes.
|
||||
if (self.sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0)
|
||||
{
|
||||
EnterCriticalSection(&(self._mutex));
|
||||
MMRESULT result
|
||||
= midiInAddBuffer(self.inHandle, self.sysexBuffer[sysex->dwUser], sizeof(MIDIHDR));
|
||||
LeaveCriticalSection(&(self._mutex));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
#if defined(__LIBREMIDI_DEBUG__)
|
||||
std::cerr << "\nmidi_in::midiInputCallback: error sending sysex to "
|
||||
"Midi device!!\n\n";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HMIDIIN inHandle; // Handle to Midi Input Device
|
||||
|
||||
std::vector<LPMIDIHDR> sysexBuffer;
|
||||
// [Patrice] see
|
||||
// https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
|
||||
CRITICAL_SECTION _mutex;
|
||||
std::chrono::steady_clock::time_point midi_start_timestamp;
|
||||
|
||||
midi1::input_state_machine m_processing{this->configuration};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/winmm/config.hpp>
|
||||
#include <libremidi/backends/winmm/helpers.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)}
|
||||
{
|
||||
// We'll issue a warning here if no devices are available but not
|
||||
// throw an error since the user can plug something in later.
|
||||
if (midiOutGetNumDevs() == 0)
|
||||
{
|
||||
warning(
|
||||
configuration,
|
||||
"midi_out_winmm::initialize: no MIDI output devices currently "
|
||||
"available.");
|
||||
}
|
||||
}
|
||||
|
||||
~midi_out_winmm() override
|
||||
{
|
||||
// Close a connection if it exists.
|
||||
midi_out_winmm::close_port();
|
||||
}
|
||||
|
||||
bool open_virtual_port(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmm: open_virtual_port unsupported");
|
||||
return false;
|
||||
}
|
||||
void set_client_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmm: set_client_name unsupported");
|
||||
}
|
||||
void set_port_name(std::string_view) override
|
||||
{
|
||||
warning(configuration, "midi_out_winmm: set_port_name unsupported");
|
||||
}
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
|
||||
|
||||
[[nodiscard]] bool do_open(unsigned int portNumber)
|
||||
{
|
||||
MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL);
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration,
|
||||
"midi_out_winmm::open_port: error creating Windows MM MIDI output "
|
||||
"port.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool 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);
|
||||
}
|
||||
error<invalid_parameter_error>(
|
||||
configuration, "midi_out_winmm::open_port: port not found: " + p.port_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
void close_port() override
|
||||
{
|
||||
if (this->outHandle)
|
||||
midiOutClose(this->outHandle);
|
||||
|
||||
this->outHandle = nullptr;
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
void send_message(const unsigned char* message, size_t size) override
|
||||
{
|
||||
if (!connected_)
|
||||
return;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
warning(configuration, "midi_out_winmm::send_message: message argument is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_winmm::send_message: error preparing sysex header.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the message.
|
||||
result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_winmm::send_message: error sending sysex message.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
warning(
|
||||
configuration,
|
||||
"midi_out_winmm::send_message: message size is greater than 3 bytes "
|
||||
"(and not sysex)!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
error<driver_error>(
|
||||
configuration, "midi_out_winmm::send_message: error sending MIDI message.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
HMIDIOUT outHandle; // Handle to Midi Output Device
|
||||
std::vector<char> buffer;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
#pragma once
|
||||
#include <libremidi/backends/winmm/config.hpp>
|
||||
#include <libremidi/backends/winmm/helpers.hpp>
|
||||
#include <libremidi/detail/observer.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
|
||||
namespace libremidi
|
||||
{
|
||||
|
||||
class observer_winmm : public observer_api
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: observer_configuration
|
||||
, winmm_observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer_winmm(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
|
||||
: configuration{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
if (!configuration.has_callbacks())
|
||||
return;
|
||||
|
||||
if (configuration.notify_in_constructor)
|
||||
check_new_ports<true>();
|
||||
else
|
||||
check_new_ports<false>();
|
||||
}
|
||||
|
||||
~observer_winmm() { }
|
||||
|
||||
libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; }
|
||||
|
||||
std::vector<input_port> get_input_ports() const noexcept override
|
||||
{
|
||||
return get_port_list<true>();
|
||||
}
|
||||
|
||||
std::vector<output_port> get_output_ports() const noexcept override
|
||||
{
|
||||
return get_port_list<false>();
|
||||
}
|
||||
|
||||
protected:
|
||||
template <bool Notify>
|
||||
void check_new_ports()
|
||||
{
|
||||
auto currInputPortList = get_port_list<true>();
|
||||
|
||||
if constexpr (Notify)
|
||||
{
|
||||
compare_port_lists_and_notify_clients(
|
||||
inputPortList, currInputPortList, configuration.input_added,
|
||||
configuration.input_removed);
|
||||
}
|
||||
inputPortList = std::move(currInputPortList);
|
||||
|
||||
auto currOutputPortList = get_port_list<false>();
|
||||
|
||||
if constexpr (Notify)
|
||||
{
|
||||
compare_port_lists_and_notify_clients(
|
||||
outputPortList, currOutputPortList, configuration.output_added,
|
||||
configuration.output_removed);
|
||||
}
|
||||
outputPortList = std::move(currOutputPortList);
|
||||
}
|
||||
|
||||
void compare_port_lists_and_notify_clients(
|
||||
const auto& prevList, const auto& currList, const auto& portAddedFunc,
|
||||
const auto& portRemovedFunc)
|
||||
{
|
||||
if (portAddedFunc)
|
||||
{
|
||||
for (const auto& port : currList)
|
||||
{
|
||||
auto iter
|
||||
= std::ranges::find(prevList, port.display_name, &port_information::display_name);
|
||||
if (iter == prevList.end())
|
||||
portAddedFunc(port);
|
||||
}
|
||||
}
|
||||
|
||||
if (portRemovedFunc)
|
||||
{
|
||||
for (const auto& port : prevList)
|
||||
{
|
||||
auto iter
|
||||
= std::ranges::find(currList, port.display_name, &port_information::display_name);
|
||||
if (iter == currList.end())
|
||||
portRemovedFunc(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input_port to_in_port_info(std::size_t index) const noexcept
|
||||
{
|
||||
MIDIINCAPS deviceCaps;
|
||||
midiInGetDevCaps(index, &deviceCaps, sizeof(MIDIINCAPS));
|
||||
|
||||
auto rawName = ConvertToUTF8(deviceCaps.szPname);
|
||||
auto portName = rawName;
|
||||
MakeUniqueInPortName(portName, index);
|
||||
return {
|
||||
{.client = 0,
|
||||
.port = index,
|
||||
.manufacturer = "",
|
||||
.device_name = "",
|
||||
.port_name = rawName,
|
||||
.display_name = portName}};
|
||||
}
|
||||
|
||||
output_port to_out_port_info(std::size_t index) const noexcept
|
||||
{
|
||||
MIDIOUTCAPS deviceCaps;
|
||||
midiOutGetDevCaps(index, &deviceCaps, sizeof(MIDIOUTCAPS));
|
||||
|
||||
auto rawName = ConvertToUTF8(deviceCaps.szPname);
|
||||
auto portName = rawName;
|
||||
MakeUniqueOutPortName(portName, index);
|
||||
return {
|
||||
{.client = 0,
|
||||
.port = index,
|
||||
.manufacturer = "",
|
||||
.device_name = "",
|
||||
.port_name = rawName,
|
||||
.display_name = portName}};
|
||||
}
|
||||
|
||||
template <bool Input>
|
||||
auto get_port_list() const noexcept
|
||||
-> std::vector<std::conditional_t<Input, input_port, output_port>>
|
||||
{
|
||||
std::vector<std::conditional_t<Input, input_port, output_port>> portList;
|
||||
|
||||
if constexpr (Input)
|
||||
{
|
||||
std::size_t nDevices = midiInGetNumDevs();
|
||||
for (std::size_t i = 0; i < nDevices; ++i)
|
||||
{
|
||||
portList.push_back(to_in_port_info(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::size_t nDevices = midiOutGetNumDevs();
|
||||
for (std::size_t i = 0; i < nDevices; ++i)
|
||||
{
|
||||
portList.push_back(to_out_port_info(i));
|
||||
}
|
||||
}
|
||||
return portList;
|
||||
}
|
||||
|
||||
static constexpr bool INPUT = true;
|
||||
static constexpr bool OUTPUT = false;
|
||||
|
||||
std::vector<input_port> inputPortList;
|
||||
std::vector<output_port> outputPortList;
|
||||
};
|
||||
}
|
||||
|
||||
#if __has_include(<stop_token>) && __cpp_lib_jthread >= 201911L
|
||||
#include <stop_token>
|
||||
namespace libremidi::winmm
|
||||
{
|
||||
class observer_threaded final : public observer_winmm
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: observer_configuration
|
||||
, winmm_observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
|
||||
: observer_winmm{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
thread = std::jthread([this](std::stop_token tk) {
|
||||
while (!tk.stop_requested())
|
||||
{
|
||||
check_new_ports<true>();
|
||||
std::this_thread::sleep_for(this->configuration.poll_period);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::jthread thread;
|
||||
};
|
||||
}
|
||||
#else
|
||||
#include <atomic>
|
||||
#include <semaphore>
|
||||
namespace libremidi::winmm
|
||||
{
|
||||
class observer_threaded final : public observer_winmm
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: observer_configuration
|
||||
, winmm_observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer_threaded(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
|
||||
: observer_winmm{std::move(conf), std::move(apiconf)}
|
||||
, sema{0}
|
||||
{
|
||||
thread = std::thread([this] {
|
||||
while (!stop_flag.test(std::memory_order_acquire))
|
||||
{
|
||||
check_new_ports<true>();
|
||||
std::this_thread::sleep_for(this->configuration.poll_period);
|
||||
}
|
||||
sema.release();
|
||||
});
|
||||
}
|
||||
|
||||
~observer_threaded()
|
||||
{
|
||||
stop_flag.test_and_set();
|
||||
sema.acquire();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
private:
|
||||
std::thread thread;
|
||||
std::atomic_flag stop_flag = ATOMIC_FLAG_INIT;
|
||||
std::binary_semaphore sema;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace libremidi::winmm
|
||||
{
|
||||
class observer_manual final : public observer_winmm
|
||||
{
|
||||
public:
|
||||
struct
|
||||
: observer_configuration
|
||||
, winmm_observer_configuration
|
||||
{
|
||||
} configuration;
|
||||
|
||||
explicit observer_manual(observer_configuration&& conf, winmm_observer_configuration&& apiconf)
|
||||
: observer_winmm{std::move(conf), std::move(apiconf)}
|
||||
{
|
||||
this->configuration.manual_poll({.callback = [this] { this->check_new_ports<true>(); }});
|
||||
}
|
||||
|
||||
~observer_manual() { }
|
||||
};
|
||||
}
|
||||
namespace libremidi
|
||||
{
|
||||
template <>
|
||||
inline std::unique_ptr<observer_api> make<observer_winmm>(
|
||||
libremidi::observer_configuration&& conf, libremidi::winmm_observer_configuration&& api)
|
||||
{
|
||||
if (api.manual_poll)
|
||||
return std::make_unique<winmm::observer_manual>(std::move(conf), std::move(api));
|
||||
else
|
||||
return std::make_unique<winmm::observer_threaded>(std::move(conf), std::move(api));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user