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 <alsa/asoundlib.h>
17
18 #include <libretro.h>
19 #include <verbosity.h>
20 #include <lists/string_list.h>
21 #include <string/stdstring.h>
22
23 #include "../midi_driver.h"
24
25 typedef struct
26 {
27 snd_seq_t *seq;
28 snd_seq_addr_t in;
29 snd_seq_addr_t in_dest;
30 snd_seq_addr_t out;
31 snd_seq_addr_t out_src;
32 int out_queue;
33 snd_seq_real_time_t out_ev_time; /* time of the last output event */
34 } alsa_midi_t;
35
36 static const snd_seq_event_type_t alsa_midi_ev_map[8] =
37 {
38 SND_SEQ_EVENT_NOTEOFF,
39 SND_SEQ_EVENT_NOTEON,
40 SND_SEQ_EVENT_KEYPRESS,
41 SND_SEQ_EVENT_CONTROLLER,
42 SND_SEQ_EVENT_PGMCHANGE,
43 SND_SEQ_EVENT_CHANPRESS,
44 SND_SEQ_EVENT_PITCHBEND,
45 SND_SEQ_EVENT_SYSEX
46 };
47
alsa_midi_get_avail_ports(struct string_list * ports,unsigned caps)48 static bool alsa_midi_get_avail_ports(struct string_list *ports, unsigned caps)
49 {
50 int r;
51 snd_seq_t *seq;
52 snd_seq_client_info_t *client_info;
53 snd_seq_port_info_t *port_info;
54 union string_list_elem_attr attr = {0};
55
56 snd_seq_client_info_alloca(&client_info);
57 snd_seq_port_info_alloca(&port_info);
58
59 r = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
60 if (r < 0)
61 {
62 RARCH_ERR("[MIDI]: snd_seq_open failed with error %d.\n", r);
63 return false;
64 }
65
66 snd_seq_client_info_set_client(client_info, -1);
67
68 while (snd_seq_query_next_client(seq, client_info) == 0)
69 {
70 int client = snd_seq_client_info_get_client(client_info);
71
72 snd_seq_port_info_set_client(port_info, client);
73 snd_seq_port_info_set_port(port_info, -1);
74
75 while (snd_seq_query_next_port(seq, port_info) == 0)
76 {
77 unsigned port_caps = snd_seq_port_info_get_capability(port_info);
78 unsigned port_type = snd_seq_port_info_get_type(port_info);
79
80 if ((port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) &&
81 (port_caps & caps) == caps)
82 {
83 const char *port_name = snd_seq_port_info_get_name(port_info);
84
85 if (!string_list_append(ports, port_name, attr))
86 {
87 RARCH_ERR("[MIDI]: string_list_append failed.\n");
88 snd_seq_close(seq);
89
90 return false;
91 }
92 }
93 }
94 }
95
96 snd_seq_close(seq);
97
98 return true;
99 }
100
alsa_midi_get_port(snd_seq_t * seq,const char * name,unsigned caps,snd_seq_addr_t * addr)101 static bool alsa_midi_get_port(snd_seq_t *seq, const char *name, unsigned caps,
102 snd_seq_addr_t *addr)
103 {
104 snd_seq_client_info_t *client_info;
105 snd_seq_port_info_t *port_info;
106
107 snd_seq_client_info_alloca(&client_info);
108 snd_seq_port_info_alloca(&port_info);
109
110 snd_seq_client_info_set_client(client_info, -1);
111 while (snd_seq_query_next_client(seq, client_info) == 0)
112 {
113 int client_id = snd_seq_client_info_get_client(client_info);
114
115 snd_seq_port_info_set_client(port_info, client_id);
116 snd_seq_port_info_set_port(port_info, -1);
117
118 while (snd_seq_query_next_port(seq, port_info) == 0)
119 {
120 unsigned port_caps = snd_seq_port_info_get_capability(port_info);
121 unsigned type = snd_seq_port_info_get_type(port_info);
122
123 if ((type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) && (port_caps & caps) == caps)
124 {
125 const char *port_name = snd_seq_port_info_get_name(port_info);
126
127 if (string_is_equal(port_name, name))
128 {
129 addr->client = client_id;
130 addr->port = snd_seq_port_info_get_port(port_info);
131
132 return true;
133 }
134 }
135 }
136 }
137
138 return false;
139 }
140
alsa_midi_get_avail_inputs(struct string_list * inputs)141 static bool alsa_midi_get_avail_inputs(struct string_list *inputs)
142 {
143 return alsa_midi_get_avail_ports(inputs, SND_SEQ_PORT_CAP_READ |
144 SND_SEQ_PORT_CAP_SUBS_READ);
145 }
146
alsa_midi_get_avail_outputs(struct string_list * outputs)147 static bool alsa_midi_get_avail_outputs(struct string_list *outputs)
148 {
149 return alsa_midi_get_avail_ports(outputs, SND_SEQ_PORT_CAP_WRITE |
150 SND_SEQ_PORT_CAP_SUBS_WRITE);
151 }
152
alsa_midi_free(void * p)153 static void alsa_midi_free(void *p)
154 {
155 alsa_midi_t *d = (alsa_midi_t*)p;
156
157 if (d)
158 {
159 if (d->seq)
160 snd_seq_close(d->seq);
161 free(d);
162 }
163 }
164
alsa_midi_set_input(void * p,const char * input)165 static bool alsa_midi_set_input(void *p, const char *input)
166 {
167 int r;
168 snd_seq_port_subscribe_t *sub;
169 alsa_midi_t *d = (alsa_midi_t*)p;
170
171 if (!input)
172 {
173 if (d->in_dest.port >= 0)
174 {
175 snd_seq_delete_simple_port(d->seq, d->in_dest.port);
176 d->in_dest.port = -1;
177 }
178
179 return true;
180 }
181
182 if (!alsa_midi_get_port(d->seq, input, SND_SEQ_PORT_CAP_READ |
183 SND_SEQ_PORT_CAP_SUBS_READ, &d->in))
184 return false;
185
186 r = snd_seq_create_simple_port(d->seq, "in", SND_SEQ_PORT_CAP_WRITE |
187 SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION);
188 if (r < 0)
189 {
190 RARCH_ERR("[MIDI]: snd_seq_create_simple_port failed with error %d.\n", r);
191 return false;
192 }
193
194 d->in_dest.client = snd_seq_client_id(d->seq);
195 d->in_dest.port = r;
196
197 snd_seq_port_subscribe_alloca(&sub);
198 snd_seq_port_subscribe_set_sender(sub, &d->in);
199 snd_seq_port_subscribe_set_dest(sub, &d->in_dest);
200 r = snd_seq_subscribe_port(d->seq, sub);
201 if (r < 0)
202 RARCH_ERR("[MIDI]: snd_seq_subscribe_port failed with error %d.\n", r);
203
204 return r >= 0;
205 }
206
alsa_midi_set_output(void * p,const char * output)207 static bool alsa_midi_set_output(void *p, const char *output)
208 {
209 int r;
210 alsa_midi_t *d = (alsa_midi_t*)p;
211
212 if (!output)
213 {
214 if (d->out_queue >= 0)
215 {
216 snd_seq_stop_queue(d->seq, d->out_queue, NULL);
217 snd_seq_free_queue(d->seq, d->out_queue);
218 d->out_queue = -1;
219 }
220 if (d->out_src.port >= 0)
221 {
222 snd_seq_delete_simple_port(d->seq, d->out_src.port);
223 d->out_src.port = -1;
224 }
225
226 return true;
227 }
228
229 if (!alsa_midi_get_port(d->seq, output, SND_SEQ_PORT_CAP_WRITE |
230 SND_SEQ_PORT_CAP_SUBS_WRITE, &d->out))
231 return false;
232
233 r = snd_seq_create_simple_port(d->seq, "out", SND_SEQ_PORT_CAP_READ |
234 SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION);
235 if (r < 0)
236 {
237 RARCH_ERR("[MIDI]: snd_seq_create_simple_port failed with error %d.\n", r);
238 return false;
239 }
240
241 d->out_src.client = snd_seq_client_id(d->seq);
242 d->out_src.port = r;
243
244 r = snd_seq_connect_to(d->seq, d->out_src.port, d->out.client, d->out.port);
245 if (r < 0)
246 {
247 RARCH_ERR("[MIDI]: snd_seq_connect_to failed with error %d.\n", r);
248 return false;
249 }
250
251 d->out_queue = snd_seq_alloc_queue(d->seq);
252 if (d->out_queue < 0)
253 {
254 RARCH_ERR("[MIDI]: snd_seq_alloc_queue failed with error %d.\n", d->out_queue);
255 return false;
256 }
257
258 r = snd_seq_start_queue(d->seq, d->out_queue, NULL);
259 if (r < 0)
260 {
261 RARCH_ERR("[MIDI]: snd_seq_start_queue failed with error %d.\n", r);
262 return false;
263 }
264
265 return true;
266 }
267
alsa_midi_init(const char * input,const char * output)268 static void *alsa_midi_init(const char *input, const char *output)
269 {
270 int r;
271 bool err = false;
272 alsa_midi_t *d = (alsa_midi_t*)calloc(sizeof(alsa_midi_t), 1);
273
274 if (!d)
275 {
276 RARCH_ERR("[MIDI]: Out of memory.\n");
277 return NULL;
278 }
279
280 d->in_dest.port = -1;
281 d->out_src.port = -1;
282 d->out_queue = -1;
283
284 r = snd_seq_open(&d->seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
285 if (r < 0)
286 {
287 RARCH_ERR("[MIDI]: snd_seq_open failed with error %d.\n", r);
288 err = true;
289 }
290 else if (!alsa_midi_set_input(d, input))
291 err = true;
292 else if (!alsa_midi_set_output(d, output))
293 err = true;
294
295 if (err)
296 {
297 alsa_midi_free(d);
298 d = NULL;
299 }
300
301 return d;
302 }
303
alsa_midi_read(void * p,midi_event_t * event)304 static bool alsa_midi_read(void *p, midi_event_t *event)
305 {
306 int r;
307 snd_seq_event_t *ev;
308 alsa_midi_t *d = (alsa_midi_t*)p;
309
310 r = snd_seq_event_input(d->seq, &ev);
311 if (r < 0)
312 {
313 #ifdef DEBUG
314 if (r != -EAGAIN)
315 RARCH_ERR("[MIDI]: snd_seq_event_input failed with error %d.\n", r);
316 #endif
317 return false;
318 }
319
320 if (ev->type == SND_SEQ_EVENT_NOTEOFF)
321 {
322 event->data[0] = 0x80 | ev->data.note.channel;
323 event->data[1] = ev->data.note.note;
324 event->data[2] = ev->data.note.velocity;
325 event->data_size = 3;
326 }
327 else if (ev->type == SND_SEQ_EVENT_NOTEON)
328 {
329 event->data[0] = 0x90 | ev->data.note.channel;
330 event->data[1] = ev->data.note.note;
331 event->data[2] = ev->data.note.velocity;
332 event->data_size = 3;
333 }
334 else if (ev->type == SND_SEQ_EVENT_KEYPRESS)
335 {
336 event->data[0] = 0xA0 | ev->data.note.channel;
337 event->data[1] = ev->data.note.note;
338 event->data[2] = ev->data.note.velocity;
339 event->data_size = 3;
340 }
341 else if (ev->type == SND_SEQ_EVENT_CONTROLLER)
342 {
343 event->data[0] = 0xB0 | ev->data.control.channel;
344 event->data[1] = ev->data.control.param;
345 event->data[2] = ev->data.control.value;
346 event->data_size = 3;
347 }
348 else if (ev->type == SND_SEQ_EVENT_PGMCHANGE)
349 {
350 event->data[0] = 0xC0 | ev->data.control.channel;
351 event->data[1] = ev->data.control.value;
352 event->data_size = 2;
353 }
354 else if (ev->type == SND_SEQ_EVENT_CHANPRESS)
355 {
356 event->data[0] = 0xD0 | ev->data.control.channel;
357 event->data[1] = ev->data.control.value;
358 event->data_size = 2;
359 }
360 else if (ev->type == SND_SEQ_EVENT_PITCHBEND)
361 {
362 event->data[0] = 0xE0 | ev->data.control.channel;
363 event->data[1] = ev->data.control.value & 127;
364 event->data[2] = ev->data.control.value >> 7;
365 event->data_size = 3;
366 }
367 else if (ev->type == SND_SEQ_EVENT_SYSEX)
368 {
369 if (ev->data.ext.len <= event->data_size)
370 {
371 size_t i;
372 uint8_t *ev_data = (uint8_t*)ev->data.ext.ptr;
373
374 for (i = 0; i < ev->data.ext.len; ++i)
375 event->data[i] = ev_data[i];
376
377 event->data_size = ev->data.ext.len;
378 }
379 #ifdef DEBUG
380 else
381 {
382 RARCH_ERR("[MIDI]: SysEx event too big.\n");
383 r = -1;
384 }
385 #endif
386 }
387 else
388 r = -1;
389
390 event->delta_time = 0;
391 snd_seq_free_event(ev);
392
393 return r >= 0;
394 }
395
alsa_midi_write(void * p,const midi_event_t * event)396 static bool alsa_midi_write(void *p, const midi_event_t *event)
397 {
398 int r;
399 snd_seq_event_t ev;
400 alsa_midi_t *d = (alsa_midi_t*)p;
401
402 ev.type = alsa_midi_ev_map[(event->data[0] >> 4) & 7];
403 ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
404 ev.queue = d->out_queue;
405 ev.time.time.tv_sec = d->out_ev_time.tv_sec + event->delta_time / 1000000;
406 ev.time.time.tv_nsec = d->out_ev_time.tv_nsec +
407 (event->delta_time % 1000000) * 1000;
408
409 if (ev.time.time.tv_nsec >= 1000000000)
410 {
411 ev.time.time.tv_sec += 1;
412 ev.time.time.tv_nsec -= 1000000000;
413 }
414 ev.source.port = d->out_src.port;
415 ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS;
416
417 if (ev.type == SND_SEQ_EVENT_NOTEOFF || ev.type == SND_SEQ_EVENT_NOTEON ||
418 ev.type == SND_SEQ_EVENT_KEYPRESS)
419 {
420 ev.data.note.channel = event->data[0] & 0x0F;
421 ev.data.note.note = event->data[1];
422 ev.data.note.velocity = event->data[2];
423 }
424 else if (ev.type == SND_SEQ_EVENT_CONTROLLER)
425 {
426 ev.data.control.channel = event->data[0] & 0x0F;
427 ev.data.control.param = event->data[1];
428 ev.data.control.value = event->data[2];
429 }
430 else if (ev.type == SND_SEQ_EVENT_PGMCHANGE ||
431 ev.type == SND_SEQ_EVENT_CHANPRESS)
432 {
433 ev.data.control.channel = event->data[0] & 0x0F;
434 ev.data.control.value = event->data[1];
435 }
436 else if (ev.type == SND_SEQ_EVENT_PITCHBEND)
437 {
438 ev.data.control.channel = event->data[0] & 0x0F;
439 ev.data.control.value = (event->data[1] | (event->data[2] << 7)) - 0x2000;
440 }
441 else if (ev.type == SND_SEQ_EVENT_SYSEX)
442 {
443 ev.flags |= SND_SEQ_EVENT_LENGTH_VARIABLE;
444 ev.data.ext.ptr = event->data;
445 ev.data.ext.len = event->data_size;
446 }
447
448 r = snd_seq_event_output(d->seq, &ev);
449 #ifdef DEBUG
450 if (r < 0)
451 RARCH_ERR("[MIDI]: snd_seq_event_output failed with error %d.\n", r);
452 #endif
453
454 d->out_ev_time.tv_sec = ev.time.time.tv_sec;
455 d->out_ev_time.tv_nsec = ev.time.time.tv_nsec;
456
457 return r >= 0;
458 }
459
alsa_midi_flush(void * p)460 static bool alsa_midi_flush(void *p)
461 {
462 int r;
463 alsa_midi_t *d = (alsa_midi_t*)p;
464
465 r = snd_seq_drain_output(d->seq);
466 #ifdef DEBUG
467 if (r < 0)
468 RARCH_ERR("[MIDI]: snd_seq_drain_output failed with error %d.\n", r);
469 #endif
470
471 return r == 0;
472 }
473
474 midi_driver_t midi_alsa = {
475 "alsa",
476 alsa_midi_get_avail_inputs,
477 alsa_midi_get_avail_outputs,
478 alsa_midi_init,
479 alsa_midi_free,
480 alsa_midi_set_input,
481 alsa_midi_set_output,
482 alsa_midi_read,
483 alsa_midi_write,
484 alsa_midi_flush
485 };
486