MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
WinMmMidiBackend.cpp
Go to the documentation of this file.
2
3#ifdef MAYAFLUX_PLATFORM_WINDOWS
4
6
7#pragma comment(lib, "winmm.lib")
8
9namespace MayaFlux::Core {
10
11namespace {
12 constexpr auto C = Journal::Component::Core;
13 constexpr auto X = Journal::Context::InputBackend;
14}
15
16WinMmMidiBackend::WinMmMidiBackend()
17 : WinMmMidiBackend(Config {})
18{
19}
20
21WinMmMidiBackend::WinMmMidiBackend(Config config)
22 : m_config(std::move(config))
23{
24}
25
26WinMmMidiBackend::~WinMmMidiBackend()
27{
28 if (m_initialized.load())
29 shutdown();
30}
31
32// ===================================================================================
33// IInputBackend: Lifecycle
34// ===================================================================================
35
36bool WinMmMidiBackend::initialize()
37{
38 if (m_initialized.load()) {
39 MF_WARN(C, X, "WinMmMidiBackend already initialized");
40 return true;
41 }
42
43 m_initialized.store(true);
44 refresh_devices();
45
46 MF_INFO(C, X, "WinMmMidiBackend initialized with {} port(s)",
47 m_enumerated_devices.size());
48 return true;
49}
50
51void WinMmMidiBackend::start()
52{
53 if (!m_initialized.load()) {
54 MF_ERROR(C, X, "Cannot start WinMmMidiBackend: not initialized");
55 return;
56 }
57 if (m_running.load()) {
58 MF_WARN(C, X, "WinMmMidiBackend already running");
59 return;
60 }
61
62 if (m_config.auto_open_inputs) {
63 std::vector<uint32_t> to_open;
64 {
65 std::lock_guard lock(m_devices_mutex);
66 for (const auto& [id, info] : m_enumerated_devices)
67 to_open.push_back(id);
68 }
69 for (uint32_t id : to_open)
70 open_device(id);
71 }
72
73 m_running.store(true);
74 MF_INFO(C, X, "WinMmMidiBackend started with {} open port(s)",
75 get_open_devices().size());
76}
77
78void WinMmMidiBackend::stop()
79{
80 if (!m_running.load())
81 return;
82
83 std::vector<uint32_t> to_close;
84 {
85 std::lock_guard lock(m_devices_mutex);
86 for (const auto& [id, _] : m_open_devices)
87 to_close.push_back(id);
88 }
89 for (uint32_t id : to_close)
90 close_device(id);
91
92 m_running.store(false);
93 MF_INFO(C, X, "WinMmMidiBackend stopped");
94}
95
96void WinMmMidiBackend::shutdown()
97{
98 if (!m_initialized.load())
99 return;
100
101 stop();
102
103 {
104 std::lock_guard lock(m_devices_mutex);
105 m_enumerated_devices.clear();
106 }
107
108 m_initialized.store(false);
109 MF_INFO(C, X, "WinMmMidiBackend shutdown complete");
110}
111
112// ===================================================================================
113// IInputBackend: Device Management
114// ===================================================================================
115
116std::vector<InputDeviceInfo> WinMmMidiBackend::get_devices() const
117{
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);
123 return result;
124}
125
126size_t WinMmMidiBackend::refresh_devices()
127{
128 UINT count = midiInGetNumDevs();
129 MF_INFO(C, X, "midiInGetNumDevs returned {}", count);
130 std::vector<InputDeviceInfo> newly_added;
131
132 {
133 std::lock_guard lock(m_devices_mutex);
134 for (UINT i = 0; i < count; ++i) {
135 MIDIINCAPS caps {};
136 if (midiInGetDevCaps(i, &caps, sizeof(caps)) != MMSYSERR_NOERROR)
137 continue;
138
139 std::string name(caps.szPname);
140 if (!port_matches_filter(name))
141 continue;
142
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());
145
146 MIDIPortInfo info {};
147 info.id = dev_id;
148 info.name = name;
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);
155
156 m_enumerated_devices[dev_id] = info;
157
158 if (is_new)
159 newly_added.push_back(info);
160 }
161 }
162
163 for (const auto& info : newly_added) {
164 MF_INFO(C, X, "MIDI port found: '{}'", info.name);
165 notify_device_change(info, true);
166 }
167
168 std::lock_guard lock(m_devices_mutex);
169 return m_enumerated_devices.size();
170}
171
172bool WinMmMidiBackend::open_device(uint32_t device_id)
173{
174 std::lock_guard lock(m_devices_mutex);
175
176 if (m_open_devices.count(device_id)) {
177 MF_WARN(C, X, "MIDI port {} already open", device_id);
178 return true;
179 }
180
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);
184 return false;
185 }
186
187 auto state = std::make_shared<MIDIPortState>();
188 state->info = it->second;
189 state->device_id = device_id;
190 {
191 std::lock_guard cb_lock(m_callback_mutex);
192 state->input_callback = m_input_callback;
193 }
194
195 MMRESULT res = midiInOpen(
196 &state->handle,
197 state->info.winmm_device_id,
198 reinterpret_cast<DWORD_PTR>(&WinMmMidiBackend::midi_callback),
199 reinterpret_cast<DWORD_PTR>(state.get()),
200 CALLBACK_FUNCTION);
201
202 if (res != MMSYSERR_NOERROR) {
203 MF_ERROR(C, X, "midiInOpen failed for '{}': error {}", it->second.name, res);
204 return false;
205 }
206
207 if (m_config.enable_sysex)
208 queue_sysex_buffers(*state);
209
210 state->active.store(true);
211 midiInStart(state->handle);
212
213 m_open_devices.insert_or_assign(device_id, state);
214 MF_INFO(C, X, "Opened MIDI port {}: '{}'", device_id, state->info.name);
215 return true;
216}
217
218void WinMmMidiBackend::close_device(uint32_t device_id)
219{
220 std::shared_ptr<MIDIPortState> state;
221 {
222 std::lock_guard lock(m_devices_mutex);
223 auto it = m_open_devices.find(device_id);
224 if (it == m_open_devices.end())
225 return;
226 state = std::move(it->second);
227 m_open_devices.erase(it);
228 }
229
230 state->active.store(false);
231 midiInStop(state->handle);
232 midiInReset(state->handle);
233
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));
238 }
239 }
240
241 midiInClose(state->handle);
242 MF_INFO(C, X, "Closed MIDI port {}: '{}'", device_id, state->info.name);
243}
244
245bool WinMmMidiBackend::is_device_open(uint32_t device_id) const
246{
247 std::lock_guard lock(m_devices_mutex);
248 return m_open_devices.count(device_id) > 0;
249}
250
251std::vector<uint32_t> WinMmMidiBackend::get_open_devices() const
252{
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);
258 return result;
259}
260
261// ===================================================================================
262// IInputBackend: Callbacks
263// ===================================================================================
264
265void WinMmMidiBackend::set_input_callback(InputCallback callback)
266{
267 std::lock_guard lock(m_callback_mutex);
268 m_input_callback = std::move(callback);
269}
270
271void WinMmMidiBackend::set_device_callback(DeviceCallback callback)
272{
273 std::lock_guard lock(m_callback_mutex);
274 m_device_callback = std::move(callback);
275}
276
277std::string WinMmMidiBackend::get_version() const
278{
279 return "WinMM";
280}
281
282// ===================================================================================
283// Private
284// ===================================================================================
285
286bool WinMmMidiBackend::port_matches_filter(const std::string& name) const
287{
288 if (m_config.input_port_filters.empty())
289 return true;
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;
293 });
294}
295
296uint32_t WinMmMidiBackend::find_or_assign_device_id(UINT winmm_id)
297{
298 for (const auto& [id, info] : m_enumerated_devices) {
299 if (info.winmm_device_id == winmm_id)
300 return id;
301 }
302 return m_next_device_id++;
303}
304
305void WinMmMidiBackend::queue_sysex_buffers(MIDIPortState& state)
306{
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];
310 hdr = {};
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));
316 }
317}
318
319void WinMmMidiBackend::notify_device_change(const InputDeviceInfo& info, bool connected)
320{
321 std::lock_guard lock(m_callback_mutex);
322 if (m_device_callback)
323 m_device_callback(info, connected);
324}
325
326void CALLBACK WinMmMidiBackend::midi_callback(
327 HMIDIIN /*handle*/, UINT msg,
328 DWORD_PTR user_data, DWORD_PTR param1, DWORD_PTR param2)
329{
330 auto* state = reinterpret_cast<MIDIPortState*>(user_data);
331 if (!state || !state->active.load() || !state->input_callback)
332 return;
333
334 switch (msg) {
335 case MIM_DATA: {
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));
340 break;
341 }
342 case MIM_LONGDATA: {
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));
350 }
351 midiInAddBuffer(reinterpret_cast<HMIDIIN>(hdr->dwUser), hdr, sizeof(MIDIHDR));
352 break;
353 }
354 default:
355 break;
356 }
357}
358
359} // namespace MayaFlux::Core
360
361#endif // MAYAFLUX_PLATFORM_WINDOWS
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
size_t count
@ InputBackend
Input device backend (HID, MIDI, OSC)
@ Core
Core engine, backend, subsystems.
@ CALLBACK
Use callback for custom transition.
void shutdown()
Release stored references.
Definition Forma.cpp:168
void stop()
Stop all Portal::Graphics operations.
Definition Graphics.cpp:69