1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2018 The RetroArch team
3 *
4 * RetroArch is free software: you can redistribute it and/or modify it under the terms
5 * of the GNU General Public License as published by the Free Software Found-
6 * ation, either version 3 of the License, or (at your option) any later version.
7 *
8 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10 * PURPOSE. See the GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License along with RetroArch.
13 * If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 #include <windows.h>
17
18 #include <libretro.h>
19 #include <lists/string_list.h>
20 #include <string/stdstring.h>
21
22 #include "../../verbosity.h"
23 #include "../midi_driver.h"
24
25 #define WINMM_MIDI_BUF_CNT 3
26 #define WINMM_MIDI_BUF_LEN 1024
27
28 typedef struct
29 {
30 MIDIHDR header;
31 DWORD data[WINMM_MIDI_BUF_LEN];
32 } winmm_midi_buffer_t;
33
34 typedef struct
35 {
36 uint8_t data[WINMM_MIDI_BUF_LEN * 4];
37 midi_event_t events[WINMM_MIDI_BUF_LEN];
38 int rd_idx;
39 int wr_idx;
40 } winmm_midi_queue_t;
41
42 typedef struct
43 {
44 HMIDIIN in_dev;
45 HMIDISTRM out_dev;
46 winmm_midi_queue_t in_queue;
47 winmm_midi_buffer_t out_bufs[WINMM_MIDI_BUF_CNT];
48 int out_buf_idx;
49 double tick_dur;
50 } winmm_midi_t;
51
52 static void winmm_midi_free(void *p);
53
winmm_midi_queue_read(winmm_midi_queue_t * q,midi_event_t * ev)54 static bool winmm_midi_queue_read(winmm_midi_queue_t *q, midi_event_t *ev)
55 {
56 unsigned i;
57 midi_event_t *src_ev = NULL;
58
59 if (q->rd_idx == q->wr_idx)
60 return false;
61
62 if (ev->data_size < q->events[q->rd_idx].data_size)
63 {
64 #ifdef DEBUG
65 RARCH_ERR("[MIDI]: Input queue read failed (event data too small).\n");
66 #endif
67 return false;
68 }
69
70 src_ev = &q->events[q->rd_idx];
71
72 for (i = 0; i < src_ev->data_size; ++i)
73 ev->data[i] = src_ev->data[i];
74
75 ev->data_size = src_ev->data_size;
76 ev->delta_time = src_ev->delta_time;
77
78 if (q->rd_idx + 1 == WINMM_MIDI_BUF_LEN)
79 q->rd_idx = 0;
80 else
81 ++q->rd_idx;
82
83 return true;
84 }
85
winmm_midi_queue_write(winmm_midi_queue_t * q,const midi_event_t * ev)86 static bool winmm_midi_queue_write(winmm_midi_queue_t *q, const midi_event_t *ev)
87 {
88 int wr_avail;
89 unsigned i;
90 int rd_idx = q->rd_idx;
91 midi_event_t *dest_ev = NULL;
92
93 if (q->wr_idx >= rd_idx)
94 wr_avail = WINMM_MIDI_BUF_LEN - q->wr_idx + rd_idx;
95 else
96 wr_avail = rd_idx - q->wr_idx - 1;
97
98 if (wr_avail < 1)
99 {
100 #ifdef DEBUG
101 RARCH_ERR("[MIDI]: Input queue overflow.\n");
102 #endif
103 return false;
104 }
105
106 dest_ev = &q->events[q->wr_idx];
107 if (ev->data_size > 4)
108 {
109 #ifdef DEBUG
110 RARCH_ERR("[MIDI]: Input queue write failed (event too big).\n");
111 #endif
112 return false;
113 }
114
115 for (i = 0; i < ev->data_size; ++i)
116 dest_ev->data[i] = ev->data[i];
117
118 dest_ev->data_size = ev->data_size;
119 dest_ev->delta_time = ev->delta_time;
120
121 if (q->wr_idx + 1 == WINMM_MIDI_BUF_LEN)
122 q->wr_idx = 0;
123 else
124 ++q->wr_idx;
125
126 return true;
127 }
128
winmm_midi_queue_init(winmm_midi_queue_t * q)129 static void winmm_midi_queue_init(winmm_midi_queue_t *q)
130 {
131 unsigned i, j;
132
133 for (i = j = 0; i < WINMM_MIDI_BUF_LEN; ++i, j += 4)
134 {
135 q->events[i].data = &q->data[j];
136 q->events[i].delta_time = 0;
137 }
138
139 q->rd_idx = 0;
140 q->wr_idx = 0;
141 }
142
winmm_midi_input_callback(HMIDIIN dev,UINT msg,DWORD_PTR q,DWORD_PTR par1,DWORD_PTR par2)143 static void CALLBACK winmm_midi_input_callback(HMIDIIN dev, UINT msg,
144 DWORD_PTR q, DWORD_PTR par1, DWORD_PTR par2)
145 {
146 uint8_t data[3];
147 midi_event_t event;
148 winmm_midi_queue_t *queue = (winmm_midi_queue_t*)q;
149
150 (void)dev;
151
152 if (msg == MIM_OPEN)
153 winmm_midi_queue_init(queue);
154 else if (msg == MIM_DATA)
155 {
156 data[0] = (uint8_t)(par1 & 0xFF);
157 data[1] = (uint8_t)((par1 >> 8) & 0xFF);
158 data[2] = (uint8_t)((par1 >> 16) & 0xFF);
159
160 event.data = data;
161 event.data_size = midi_driver_get_event_size(data[0]);
162 event.delta_time = 0;
163
164 if (!winmm_midi_queue_write(queue, &event))
165 {
166 #ifdef DEBUG
167 RARCH_ERR("[MIDI]: Input event dropped.\n");
168 #endif
169 }
170 }
171 else if (msg == MIM_LONGDATA)
172 {
173 #ifdef DEBUG
174 RARCH_WARN("[MIDI]: SysEx input not implemented, event dropped.\n");
175 #endif
176 }
177 }
178
winmm_midi_open_input_device(const char * dev_name,winmm_midi_queue_t * queue)179 static HMIDIIN winmm_midi_open_input_device(const char *dev_name,
180 winmm_midi_queue_t *queue)
181 {
182 unsigned i;
183 UINT dev_count = midiInGetNumDevs();
184 HMIDIIN dev = NULL;
185
186 for (i = 0; i < dev_count; ++i)
187 {
188 MIDIINCAPSA caps;
189 MMRESULT mmr = midiInGetDevCapsA((UINT)i, &caps, sizeof(caps));
190 if (mmr == MMSYSERR_NOERROR)
191 {
192 if (string_is_equal(caps.szPname, dev_name))
193 {
194 mmr = midiInOpen(&dev, (UINT)i, (DWORD_PTR)winmm_midi_input_callback,
195 (DWORD_PTR)queue, CALLBACK_FUNCTION);
196 if (mmr != MMSYSERR_NOERROR)
197 RARCH_ERR("[MIDI]: midiInOpen failed with error %d.\n", mmr);
198 break;
199 }
200 }
201 else
202 RARCH_WARN("[MIDI]: midiInGetDevCapsA failed with error %d.\n", mmr);
203 }
204
205 return dev;
206 }
207
winmm_midi_open_output_device(const char * dev_name)208 static HMIDISTRM winmm_midi_open_output_device(const char *dev_name)
209 {
210 unsigned i;
211 UINT dev_count = midiOutGetNumDevs();
212 HMIDISTRM dev = NULL;
213
214 for (i = 0; i < dev_count; ++i)
215 {
216 MIDIOUTCAPSA caps;
217 MMRESULT mmr = midiOutGetDevCapsA(i, &caps, sizeof(caps));
218 if (mmr == MMSYSERR_NOERROR)
219 {
220 if (string_is_equal(caps.szPname, dev_name))
221 {
222 mmr = midiStreamOpen(&dev, &i, 1, 0, 0, CALLBACK_NULL);
223 if (mmr != MMSYSERR_NOERROR)
224 RARCH_ERR("[MIDI]: midiStreamOpen failed with error %d.\n", mmr);
225 break;
226 }
227 }
228 else
229 RARCH_WARN("[MIDI]: midiOutGetDevCapsA failed with error %d.\n", mmr);
230 }
231
232 return dev;
233 }
234
winmm_midi_init_clock(HMIDISTRM out_dev,double * tick_dur)235 static bool winmm_midi_init_clock(HMIDISTRM out_dev, double *tick_dur)
236 {
237 MIDIPROPTIMEDIV division;
238 MIDIPROPTEMPO tempo;
239 MMRESULT mmr;
240
241 tempo.cbStruct = sizeof(tempo);
242 mmr = midiStreamProperty(out_dev, (LPBYTE)&tempo,
243 MIDIPROP_GET | MIDIPROP_TEMPO);
244 if (mmr != MMSYSERR_NOERROR)
245 {
246 RARCH_ERR("[MIDI]: Current tempo unavailable (error %d).\n", mmr);
247 return false;
248 }
249
250 division.dwTimeDiv = 3;
251 while (tempo.dwTempo / division.dwTimeDiv > 320)
252 division.dwTimeDiv *= 2;
253
254 division.cbStruct = sizeof(division);
255 mmr = midiStreamProperty(out_dev, (LPBYTE)&division,
256 MIDIPROP_SET | MIDIPROP_TIMEDIV);
257 if (mmr != MMSYSERR_NOERROR)
258 {
259 RARCH_ERR("[MIDI]: Time division change failed (error %d).\n", mmr);
260 return false;
261 }
262
263 *tick_dur = (double)tempo.dwTempo / (double)division.dwTimeDiv;
264 #ifdef DEBUG
265 RARCH_LOG("[MIDI]: Tick duration %f us.\n", *tick_dur);
266 #endif
267
268 return true;
269 }
270
winmm_midi_init_output_buffers(HMIDISTRM dev,winmm_midi_buffer_t * bufs)271 static bool winmm_midi_init_output_buffers(HMIDISTRM dev,
272 winmm_midi_buffer_t *bufs)
273 {
274 unsigned i;
275
276 for (i = 0; i < WINMM_MIDI_BUF_CNT; ++i)
277 {
278 MMRESULT mmr;
279
280 bufs[i].header.dwBufferLength = sizeof(DWORD) * WINMM_MIDI_BUF_LEN;
281 bufs[i].header.dwBytesRecorded = 0;
282 bufs[i].header.dwFlags = 0;
283 bufs[i].header.lpData = (LPSTR)bufs[i].data;
284
285 mmr = midiOutPrepareHeader((HMIDIOUT)dev, &bufs[i].header, sizeof(MIDIHDR));
286 if (mmr != MMSYSERR_NOERROR)
287 {
288 RARCH_ERR("[MIDI]: midiOutPrepareHeader failed with error %d.\n", mmr);
289
290 while (--i <= 0)
291 midiOutUnprepareHeader((HMIDIOUT)dev, &bufs[i].header, sizeof(MIDIHDR));
292
293 return false;
294 }
295 }
296
297 return true;
298 }
299
winmm_midi_free_output_buffers(HMIDISTRM dev,winmm_midi_buffer_t * bufs)300 static void winmm_midi_free_output_buffers(HMIDISTRM dev, winmm_midi_buffer_t *bufs)
301 {
302 unsigned i;
303
304 for (i = 0; i < WINMM_MIDI_BUF_CNT; ++i)
305 {
306 MMRESULT mmr = midiOutUnprepareHeader(
307 (HMIDIOUT)dev, &bufs[i].header, sizeof(MIDIHDR));
308 if (mmr != MMSYSERR_NOERROR)
309 RARCH_ERR("[MIDI]: midiOutUnprepareHeader failed with error %d.\n", mmr);
310 }
311 }
312
winmm_midi_write_short_event(winmm_midi_buffer_t * buf,const uint8_t * data,size_t data_size,DWORD delta_time)313 static bool winmm_midi_write_short_event(winmm_midi_buffer_t *buf,
314 const uint8_t *data, size_t data_size, DWORD delta_time)
315 {
316 DWORD i = buf->header.dwBytesRecorded / sizeof(DWORD);
317
318 if (buf->header.dwBytesRecorded + sizeof(DWORD) * 3 >
319 sizeof(DWORD) * WINMM_MIDI_BUF_LEN)
320 return false;
321
322 buf->data[i++] = delta_time;
323 buf->data[i++] = 0;
324 buf->data[i] = MEVT_F_SHORT << 24;
325 if (data_size == 0)
326 buf->data[i] |= MEVT_NOP;
327 else
328 {
329 buf->data[i] |= MEVT_SHORTMSG << 24 | data[0];
330 if (data_size > 1)
331 buf->data[i] |= data[1] << 8;
332 if (data_size > 2)
333 buf->data[i] |= data[2] << 16;
334 }
335
336 buf->header.dwBytesRecorded += sizeof(DWORD) * 3;
337
338 return true;
339 }
340
winmm_midi_write_long_event(winmm_midi_buffer_t * buf,const uint8_t * data,size_t data_size,DWORD delta_time)341 static bool winmm_midi_write_long_event(winmm_midi_buffer_t *buf,
342 const uint8_t *data, size_t data_size, DWORD delta_time)
343 {
344 DWORD i = buf->header.dwBytesRecorded / sizeof(DWORD);
345
346 if (buf->header.dwBytesRecorded + sizeof(DWORD) * 3 + data_size >
347 sizeof(DWORD) * WINMM_MIDI_BUF_LEN)
348 return false;
349
350 buf->data[i++] = delta_time;
351 buf->data[i++] = 0;
352 buf->data[i++] = MEVT_F_LONG << 24 | MEVT_LONGMSG << 24 | data_size;
353
354 memcpy(&buf->data[i], data, data_size);
355 buf->header.dwBytesRecorded += sizeof(DWORD) * 3 + data_size;
356
357 return true;
358 }
359
winmm_midi_get_avail_inputs(struct string_list * inputs)360 static bool winmm_midi_get_avail_inputs(struct string_list *inputs)
361 {
362 unsigned i;
363 union string_list_elem_attr attr = {0};
364 UINT dev_count = midiInGetNumDevs();
365
366 for (i = 0; i < dev_count; ++i)
367 {
368 MIDIINCAPSA caps;
369 MMRESULT mmr = midiInGetDevCapsA((UINT)i, &caps, sizeof(caps));
370 if (mmr != MMSYSERR_NOERROR)
371 {
372 RARCH_ERR("[MIDI]: midiInGetDevCapsA failed with error %d.\n", mmr);
373 return false;
374 }
375
376 if (!string_list_append(inputs, caps.szPname, attr))
377 {
378 RARCH_ERR("[MIDI]: string_list_append failed.\n");
379 return false;
380 }
381 }
382
383 return true;
384 }
385
winmm_midi_get_avail_outputs(struct string_list * outputs)386 static bool winmm_midi_get_avail_outputs(struct string_list *outputs)
387 {
388 unsigned i;
389 union string_list_elem_attr attr = {0};
390 UINT dev_count = midiOutGetNumDevs();
391
392 for (i = 0; i < dev_count; ++i)
393 {
394 MIDIOUTCAPSA caps;
395 MMRESULT mmr = midiOutGetDevCapsA((UINT)i, &caps, sizeof(caps));
396 if (mmr != MMSYSERR_NOERROR)
397 {
398 RARCH_ERR("[MIDI]: midiOutGetDevCapsA failed with error %d.\n", mmr);
399 return false;
400 }
401
402 if (!string_list_append(outputs, caps.szPname, attr))
403 {
404 RARCH_ERR("[MIDI]: string_list_append failed.\n");
405 return false;
406 }
407 }
408
409 return true;
410 }
411
winmm_midi_init(const char * input,const char * output)412 static void *winmm_midi_init(const char *input, const char *output)
413 {
414 MMRESULT mmr;
415 bool err = false;
416 winmm_midi_t *d = (winmm_midi_t*)calloc(sizeof(winmm_midi_t), 1);
417
418 if (!d)
419 {
420 RARCH_ERR("[MIDI]: Out of memory.\n");
421 return NULL;
422 }
423
424 if (input)
425 {
426 d->in_dev = winmm_midi_open_input_device(input, &d->in_queue);
427 if (!d->in_dev)
428 err = true;
429 else
430 {
431 mmr = midiInStart(d->in_dev);
432 if (mmr != MMSYSERR_NOERROR)
433 {
434 RARCH_ERR("[MIDI]: midiInStart failed with error %d.\n", mmr);
435 err = true;
436 }
437 }
438 }
439
440 if (output)
441 {
442 d->out_dev = winmm_midi_open_output_device(output);
443 if (!d->out_dev)
444 err = true;
445 else if (!winmm_midi_init_clock(d->out_dev, &d->tick_dur))
446 err = true;
447 else if (!winmm_midi_init_output_buffers(d->out_dev, d->out_bufs))
448 err = true;
449 else
450 {
451 mmr = midiStreamRestart(d->out_dev);
452 if (mmr != MMSYSERR_NOERROR)
453 {
454 RARCH_ERR("[MIDI]: midiStreamRestart failed with error %d.\n", mmr);
455 err = true;
456 }
457 }
458 }
459
460 if (err)
461 {
462 winmm_midi_free(d);
463 return NULL;
464 }
465
466 return d;
467 }
468
winmm_midi_free(void * p)469 static void winmm_midi_free(void *p)
470 {
471 winmm_midi_t *d = (winmm_midi_t*)p;
472
473 if (!d)
474 return;
475
476 if (d->in_dev)
477 {
478 midiInStop(d->in_dev);
479 midiInClose(d->in_dev);
480 }
481
482 if (d->out_dev)
483 {
484 winmm_midi_free_output_buffers(d->out_dev, d->out_bufs);
485 midiStreamClose(d->out_dev);
486 }
487
488 free(d);
489 }
490
winmm_midi_set_input(void * p,const char * input)491 static bool winmm_midi_set_input(void *p, const char *input)
492 {
493 winmm_midi_t *d = (winmm_midi_t*)p;
494 HMIDIIN new_dev = NULL;
495
496 if (input)
497 {
498 new_dev = winmm_midi_open_input_device(input, &d->in_queue);
499 if (!new_dev)
500 return false;
501 }
502
503 if (d->in_dev)
504 {
505 midiInStop(d->in_dev);
506 midiInClose(d->in_dev);
507 }
508
509 d->in_dev = new_dev;
510 if (d->in_dev)
511 {
512 MMRESULT mmr = midiInStart(d->in_dev);
513 if (mmr != MMSYSERR_NOERROR)
514 {
515 RARCH_ERR("[MIDI]: midiInStart failed with error %d.\n", mmr);
516 return false;
517 }
518 }
519
520 return true;
521 }
522
winmm_midi_set_output(void * p,const char * output)523 static bool winmm_midi_set_output(void *p, const char *output)
524 {
525 winmm_midi_t *d = (winmm_midi_t*)p;
526 HMIDISTRM new_dev = NULL;
527
528 if (output)
529 {
530 new_dev = winmm_midi_open_output_device(output);
531 if (!new_dev)
532 return false;
533 }
534
535 if (d->out_dev)
536 {
537 midiStreamStop(d->out_dev);
538 winmm_midi_free_output_buffers(d->out_dev, d->out_bufs);
539 midiStreamClose(d->out_dev);
540 }
541
542 d->out_dev = new_dev;
543 if (d->out_dev)
544 {
545 MMRESULT mmr;
546 if (!winmm_midi_init_output_buffers(d->out_dev, d->out_bufs))
547 return false;
548
549 d->out_buf_idx = 0;
550
551 mmr = midiStreamRestart(d->out_dev);
552 if (mmr != MMSYSERR_NOERROR)
553 {
554 RARCH_ERR("[MIDI]: midiStreamRestart failed with error %d.\n", mmr);
555 return false;
556 }
557 }
558
559 return true;
560 }
561
winmm_midi_read(void * p,midi_event_t * event)562 static bool winmm_midi_read(void *p, midi_event_t *event)
563 {
564 winmm_midi_t *d = (winmm_midi_t*)p;
565
566 return winmm_midi_queue_read(&d->in_queue, event);
567 }
568
winmm_midi_write(void * p,const midi_event_t * event)569 static bool winmm_midi_write(void *p, const midi_event_t *event)
570 {
571 winmm_midi_t *d = (winmm_midi_t*)p;
572 winmm_midi_buffer_t *buf = &d->out_bufs[d->out_buf_idx];
573 DWORD delta_time;
574
575 if (buf->header.dwFlags & MHDR_INQUEUE)
576 return false;
577
578 if (buf->header.dwFlags & MHDR_DONE)
579 {
580 buf->header.dwBytesRecorded = 0;
581 buf->header.dwFlags ^= MHDR_DONE;
582 }
583
584 delta_time = (DWORD)((double)event->delta_time / d->tick_dur);
585 if (event->data_size < 4)
586 return winmm_midi_write_short_event(buf, event->data,
587 event->data_size, delta_time);
588
589 return winmm_midi_write_long_event(buf, event->data,
590 event->data_size, delta_time);
591 }
592
winmm_midi_flush(void * p)593 static bool winmm_midi_flush(void *p)
594 {
595 winmm_midi_t *d = (winmm_midi_t*)p;
596 winmm_midi_buffer_t *buf = &d->out_bufs[d->out_buf_idx];
597
598 if (buf->header.dwBytesRecorded)
599 {
600 MMRESULT mmr = midiStreamOut(
601 d->out_dev, &buf->header, sizeof(buf->header));
602
603 if (mmr != MMSYSERR_NOERROR)
604 {
605 #ifdef DEBUG
606 RARCH_ERR("[MIDI]: midiStreamOut failed with error %d.\n", mmr);
607 #endif
608 /* Core sent MIDI message not understood by the MIDI driver
609 * Make this buffer available to be used again */
610 buf->header.dwFlags |= MHDR_DONE;
611 buf->header.dwFlags &= ~MHDR_INQUEUE;
612 return false;
613 }
614
615 if (++d->out_buf_idx == WINMM_MIDI_BUF_CNT)
616 d->out_buf_idx = 0;
617 }
618
619 return true;
620 }
621
622 midi_driver_t midi_winmm = {
623 "winmm",
624 winmm_midi_get_avail_inputs,
625 winmm_midi_get_avail_outputs,
626 winmm_midi_init,
627 winmm_midi_free,
628 winmm_midi_set_input,
629 winmm_midi_set_output,
630 winmm_midi_read,
631 winmm_midi_write,
632 winmm_midi_flush
633 };
634