Files

272 lines
6.5 KiB
C++
Raw Permalink Normal View History

2024-10-09 02:01:23 +02:00
#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));
}
}