3#ifdef MAYAFLUX_PLATFORM_WINDOWS
7#pragma comment(lib, "winmm.lib")
16WinMmMidiBackend::WinMmMidiBackend()
17 : WinMmMidiBackend(Config {})
21WinMmMidiBackend::WinMmMidiBackend(Config config)
22 : m_config(
std::move(config))
26WinMmMidiBackend::~WinMmMidiBackend()
28 if (m_initialized.load())
36bool WinMmMidiBackend::initialize()
38 if (m_initialized.load()) {
39 MF_WARN(C, X,
"WinMmMidiBackend already initialized");
43 m_initialized.store(
true);
46 MF_INFO(C, X,
"WinMmMidiBackend initialized with {} port(s)",
47 m_enumerated_devices.size());
51void WinMmMidiBackend::start()
53 if (!m_initialized.load()) {
54 MF_ERROR(C, X,
"Cannot start WinMmMidiBackend: not initialized");
57 if (m_running.load()) {
58 MF_WARN(C, X,
"WinMmMidiBackend already running");
62 if (m_config.auto_open_inputs) {
63 std::vector<uint32_t> to_open;
65 std::lock_guard lock(m_devices_mutex);
66 for (
const auto& [
id, info] : m_enumerated_devices)
67 to_open.push_back(id);
69 for (uint32_t
id : to_open)
73 m_running.store(
true);
74 MF_INFO(C, X,
"WinMmMidiBackend started with {} open port(s)",
75 get_open_devices().size());
78void WinMmMidiBackend::stop()
80 if (!m_running.load())
83 std::vector<uint32_t> to_close;
85 std::lock_guard lock(m_devices_mutex);
86 for (
const auto& [
id, _] : m_open_devices)
87 to_close.push_back(id);
89 for (uint32_t
id : to_close)
92 m_running.store(
false);
93 MF_INFO(C, X,
"WinMmMidiBackend stopped");
96void WinMmMidiBackend::shutdown()
98 if (!m_initialized.load())
104 std::lock_guard lock(m_devices_mutex);
105 m_enumerated_devices.clear();
108 m_initialized.store(
false);
109 MF_INFO(C, X,
"WinMmMidiBackend shutdown complete");
116std::vector<InputDeviceInfo> WinMmMidiBackend::get_devices()
const
118 std::lock_guard lock(m_devices_mutex);
119 std::vector<InputDeviceInfo> result;
120 result.reserve(m_enumerated_devices.size());
121 for (
const auto& [
id, info] : m_enumerated_devices)
122 result.push_back(info);
126size_t WinMmMidiBackend::refresh_devices()
128 UINT
count = midiInGetNumDevs();
130 std::vector<InputDeviceInfo> newly_added;
133 std::lock_guard lock(m_devices_mutex);
134 for (UINT i = 0; i <
count; ++i) {
136 if (midiInGetDevCaps(i, &caps,
sizeof(caps)) != MMSYSERR_NOERROR)
139 std::string name(caps.szPname);
140 if (!port_matches_filter(name))
143 uint32_t dev_id = find_or_assign_device_id(i);
144 bool is_new = (m_enumerated_devices.find(dev_id) == m_enumerated_devices.end());
146 MIDIPortInfo info {};
149 info.winmm_device_id = i;
150 info.backend_type = InputType::MIDI;
151 info.is_connected =
true;
152 info.is_input =
true;
153 info.is_output =
false;
154 info.port_number =
static_cast<uint8_t
>(i);
156 m_enumerated_devices[dev_id] = info;
159 newly_added.push_back(info);
163 for (
const auto& info : newly_added) {
164 MF_INFO(C, X,
"MIDI port found: '{}'", info.name);
165 notify_device_change(info,
true);
168 std::lock_guard lock(m_devices_mutex);
169 return m_enumerated_devices.size();
172bool WinMmMidiBackend::open_device(uint32_t device_id)
174 std::lock_guard lock(m_devices_mutex);
176 if (m_open_devices.count(device_id)) {
177 MF_WARN(C, X,
"MIDI port {} already open", device_id);
181 auto it = m_enumerated_devices.find(device_id);
182 if (it == m_enumerated_devices.end()) {
183 MF_ERROR(C, X,
"MIDI port {} not found", device_id);
187 auto state = std::make_shared<MIDIPortState>();
188 state->info = it->second;
189 state->device_id = device_id;
191 std::lock_guard cb_lock(m_callback_mutex);
192 state->input_callback = m_input_callback;
195 MMRESULT res = midiInOpen(
197 state->info.winmm_device_id,
198 reinterpret_cast<DWORD_PTR
>(&WinMmMidiBackend::midi_callback),
199 reinterpret_cast<DWORD_PTR
>(state.get()),
202 if (res != MMSYSERR_NOERROR) {
203 MF_ERROR(C, X,
"midiInOpen failed for '{}': error {}", it->second.name, res);
207 if (m_config.enable_sysex)
208 queue_sysex_buffers(*state);
210 state->active.store(
true);
211 midiInStart(state->handle);
213 m_open_devices.insert_or_assign(device_id, state);
214 MF_INFO(C, X,
"Opened MIDI port {}: '{}'", device_id, state->info.name);
218void WinMmMidiBackend::close_device(uint32_t device_id)
220 std::shared_ptr<MIDIPortState> state;
222 std::lock_guard lock(m_devices_mutex);
223 auto it = m_open_devices.find(device_id);
224 if (it == m_open_devices.end())
226 state = std::move(it->second);
227 m_open_devices.erase(it);
230 state->active.store(
false);
231 midiInStop(state->handle);
232 midiInReset(state->handle);
234 if (m_config.enable_sysex) {
235 for (
size_t i = 0; i < k_sysex_buf_count; ++i) {
236 if (state->sysex_headers[i].dwFlags & MHDR_PREPARED)
237 midiInUnprepareHeader(state->handle, &state->sysex_headers[i],
sizeof(MIDIHDR));
241 midiInClose(state->handle);
242 MF_INFO(C, X,
"Closed MIDI port {}: '{}'", device_id, state->info.name);
245bool WinMmMidiBackend::is_device_open(uint32_t device_id)
const
247 std::lock_guard lock(m_devices_mutex);
248 return m_open_devices.count(device_id) > 0;
251std::vector<uint32_t> WinMmMidiBackend::get_open_devices()
const
253 std::lock_guard lock(m_devices_mutex);
254 std::vector<uint32_t> result;
255 result.reserve(m_open_devices.size());
256 for (
const auto& [
id, _] : m_open_devices)
257 result.push_back(id);
265void WinMmMidiBackend::set_input_callback(InputCallback callback)
267 std::lock_guard lock(m_callback_mutex);
268 m_input_callback = std::move(callback);
271void WinMmMidiBackend::set_device_callback(DeviceCallback callback)
273 std::lock_guard lock(m_callback_mutex);
274 m_device_callback = std::move(callback);
277std::string WinMmMidiBackend::get_version()
const
286bool WinMmMidiBackend::port_matches_filter(
const std::string& name)
const
288 if (m_config.input_port_filters.empty())
290 return std::ranges::any_of(m_config.input_port_filters,
291 [&name](
const std::string& f) {
292 return name.find(f) != std::string::npos;
296uint32_t WinMmMidiBackend::find_or_assign_device_id(UINT winmm_id)
298 for (
const auto& [
id, info] : m_enumerated_devices) {
299 if (info.winmm_device_id == winmm_id)
302 return m_next_device_id++;
305void WinMmMidiBackend::queue_sysex_buffers(MIDIPortState& state)
307 for (
size_t i = 0; i < k_sysex_buf_count; ++i) {
308 state.sysex_bufs[i].resize(k_sysex_buf_size);
309 MIDIHDR& hdr = state.sysex_headers[i];
311 hdr.lpData =
reinterpret_cast<LPSTR
>(state.sysex_bufs[i].data());
312 hdr.dwBufferLength = k_sysex_buf_size;
313 hdr.dwUser =
reinterpret_cast<DWORD_PTR
>(state.handle);
314 midiInPrepareHeader(state.handle, &hdr,
sizeof(MIDIHDR));
315 midiInAddBuffer(state.handle, &hdr,
sizeof(MIDIHDR));
319void WinMmMidiBackend::notify_device_change(
const InputDeviceInfo& info,
bool connected)
321 std::lock_guard lock(m_callback_mutex);
322 if (m_device_callback)
323 m_device_callback(info, connected);
326void CALLBACK WinMmMidiBackend::midi_callback(
328 DWORD_PTR user_data, DWORD_PTR param1, DWORD_PTR param2)
330 auto* state =
reinterpret_cast<MIDIPortState*
>(user_data);
331 if (!state || !state->active.load() || !state->input_callback)
336 uint8_t status =
static_cast<uint8_t
>(param1 & 0xFF);
337 uint8_t d1 =
static_cast<uint8_t
>((param1 >> 8) & 0xFF);
338 uint8_t d2 =
static_cast<uint8_t
>((param1 >> 16) & 0xFF);
339 state->input_callback(InputValue::make_midi(status, d1, d2, state->device_id));
343 auto* hdr =
reinterpret_cast<MIDIHDR*
>(param1);
344 if (hdr->dwBytesRecorded > 0) {
345 std::vector<uint8_t> bytes(
346 reinterpret_cast<uint8_t*
>(hdr->lpData),
347 reinterpret_cast<uint8_t*
>(hdr->lpData) + hdr->dwBytesRecorded);
348 state->input_callback(
349 InputValue::make_bytes(std::move(bytes), state->device_id, InputType::MIDI));
351 midiInAddBuffer(
reinterpret_cast<HMIDIIN
>(hdr->dwUser), hdr,
sizeof(MIDIHDR));
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
@ InputBackend
Input device backend (HID, MIDI, OSC)
@ Core
Core engine, backend, subsystems.
@ CALLBACK
Use callback for custom transition.
void stop()
Stop all Portal::Graphics operations.