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