#pragma once #include #include #include #include #include #include #include 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(); else check_new_ports(); } ~observer_winmm() { } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; } std::vector get_input_ports() const noexcept override { return get_port_list(); } std::vector get_output_ports() const noexcept override { return get_port_list(); } protected: template void check_new_ports() { auto currInputPortList = get_port_list(); 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(); 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 auto get_port_list() const noexcept -> std::vector> { std::vector> 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 inputPortList; std::vector outputPortList; }; } #if __has_include() && __cpp_lib_jthread >= 201911L #include 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(); std::this_thread::sleep_for(this->configuration.poll_period); } }); } private: std::jthread thread; }; } #else #include #include 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(); 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(); }}); } ~observer_manual() { } }; } namespace libremidi { template <> inline std::unique_ptr make( libremidi::observer_configuration&& conf, libremidi::winmm_observer_configuration&& api) { if (api.manual_poll) return std::make_unique(std::move(conf), std::move(api)); else return std::make_unique(std::move(conf), std::move(api)); } }