2.0 init
[external/alsa-utils.git] / amidi / amidi.c
1 /*
2  *  amidi.c - read from/write to RawMIDI ports
3  *
4  *  Copyright (c) Clemens Ladisch <clemens@ladisch.de>
5  *
6  *
7  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
20  */
21
22 #define _GNU_SOURCE
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <getopt.h>
29 #include <errno.h>
30 #include <signal.h>
31 #include <sys/types.h>
32 #include <sys/poll.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <alsa/asoundlib.h>
37 #include "aconfig.h"
38 #include "version.h"
39
40 static int do_device_list, do_rawmidi_list;
41 static char *port_name = "default";
42 static char *send_file_name;
43 static char *receive_file_name;
44 static char *send_hex;
45 static char *send_data;
46 static int send_data_length;
47 static int receive_file;
48 static int dump;
49 static int timeout;
50 static int stop;
51 static snd_rawmidi_t *input, **inputp;
52 static snd_rawmidi_t *output, **outputp;
53
54 static void error(const char *format, ...)
55 {
56         va_list ap;
57
58         va_start(ap, format);
59         vfprintf(stderr, format, ap);
60         va_end(ap);
61         putc('\n', stderr);
62 }
63
64 static void usage(void)
65 {
66         printf(
67                 "Usage: amidi options\n"
68                 "\n"
69                 "-h, --help             this help\n"
70                 "-V, --version          print current version\n"
71                 "-l, --list-devices     list all hardware ports\n"
72                 "-L, --list-rawmidis    list all RawMIDI definitions\n"
73                 "-p, --port=name        select port by name\n"
74                 "-s, --send=file        send the contents of a (.syx) file\n"
75                 "-r, --receive=file     write received data into a file\n"
76                 "-S, --send-hex=\"...\"   send hexadecimal bytes\n"
77                 "-d, --dump             print received data as hexadecimal bytes\n"
78                 "-t, --timeout=seconds  exits when no data has been received\n"
79                 "                       for the specified duration\n"
80                 "-a, --active-sensing   don't ignore active sensing bytes\n");
81 }
82
83 static void version(void)
84 {
85         puts("amidi version " SND_UTIL_VERSION_STR);
86 }
87
88 static void *my_malloc(size_t size)
89 {
90         void *p = malloc(size);
91         if (!p) {
92                 error("out of memory");
93                 exit(EXIT_FAILURE);
94         }
95         return p;
96 }
97
98 static void list_device(snd_ctl_t *ctl, int card, int device)
99 {
100         snd_rawmidi_info_t *info;
101         const char *name;
102         const char *sub_name;
103         int subs, subs_in, subs_out;
104         int sub;
105         int err;
106
107         snd_rawmidi_info_alloca(&info);
108         snd_rawmidi_info_set_device(info, device);
109
110         snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
111         err = snd_ctl_rawmidi_info(ctl, info);
112         if (err >= 0)
113                 subs_in = snd_rawmidi_info_get_subdevices_count(info);
114         else
115                 subs_in = 0;
116
117         snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);
118         err = snd_ctl_rawmidi_info(ctl, info);
119         if (err >= 0)
120                 subs_out = snd_rawmidi_info_get_subdevices_count(info);
121         else
122                 subs_out = 0;
123
124         subs = subs_in > subs_out ? subs_in : subs_out;
125         if (!subs)
126                 return;
127
128         for (sub = 0; sub < subs; ++sub) {
129                 snd_rawmidi_info_set_stream(info, sub < subs_in ?
130                                             SND_RAWMIDI_STREAM_INPUT :
131                                             SND_RAWMIDI_STREAM_OUTPUT);
132                 snd_rawmidi_info_set_subdevice(info, sub);
133                 err = snd_ctl_rawmidi_info(ctl, info);
134                 if (err < 0) {
135                         error("cannot get rawmidi information %d:%d:%d: %s\n",
136                               card, device, sub, snd_strerror(err));
137                         return;
138                 }
139                 name = snd_rawmidi_info_get_name(info);
140                 sub_name = snd_rawmidi_info_get_subdevice_name(info);
141                 if (sub == 0 && sub_name[0] == '\0') {
142                         printf("%c%c  hw:%d,%d    %s",
143                                sub < subs_in ? 'I' : ' ',
144                                sub < subs_out ? 'O' : ' ',
145                                card, device, name);
146                         if (subs > 1)
147                                 printf(" (%d subdevices)", subs);
148                         putchar('\n');
149                         break;
150                 } else {
151                         printf("%c%c  hw:%d,%d,%d  %s\n",
152                                sub < subs_in ? 'I' : ' ',
153                                sub < subs_out ? 'O' : ' ',
154                                card, device, sub, sub_name);
155                 }
156         }
157 }
158
159 static void list_card_devices(int card)
160 {
161         snd_ctl_t *ctl;
162         char name[32];
163         int device;
164         int err;
165
166         sprintf(name, "hw:%d", card);
167         if ((err = snd_ctl_open(&ctl, name, 0)) < 0) {
168                 error("cannot open control for card %d: %s", card, snd_strerror(err));
169                 return;
170         }
171         device = -1;
172         for (;;) {
173                 if ((err = snd_ctl_rawmidi_next_device(ctl, &device)) < 0) {
174                         error("cannot determine device number: %s", snd_strerror(err));
175                         break;
176                 }
177                 if (device < 0)
178                         break;
179                 list_device(ctl, card, device);
180         }
181         snd_ctl_close(ctl);
182 }
183
184 static void device_list(void)
185 {
186         int card, err;
187
188         card = -1;
189         if ((err = snd_card_next(&card)) < 0) {
190                 error("cannot determine card number: %s", snd_strerror(err));
191                 return;
192         }
193         if (card < 0) {
194                 error("no sound card found");
195                 return;
196         }
197         puts("Dir Device    Name");
198         do {
199                 list_card_devices(card);
200                 if ((err = snd_card_next(&card)) < 0) {
201                         error("cannot determine card number: %s", snd_strerror(err));
202                         break;
203                 }
204         } while (card >= 0);
205 }
206
207 static void rawmidi_list(void)
208 {
209         snd_output_t *output;
210         snd_config_t *config;
211         int err;
212
213         if ((err = snd_config_update()) < 0) {
214                 error("snd_config_update failed: %s", snd_strerror(err));
215                 return;
216         }
217         if ((err = snd_output_stdio_attach(&output, stdout, 0)) < 0) {
218                 error("snd_output_stdio_attach failed: %s", snd_strerror(err));
219                 return;
220         }
221         if (snd_config_search(snd_config, "rawmidi", &config) >= 0) {
222                 puts("RawMIDI list:");
223                 snd_config_save(config, output);
224         }
225         snd_output_close(output);
226 }
227
228 static void load_file(void)
229 {
230         int fd;
231         off_t length;
232
233         fd = open(send_file_name, O_RDONLY);
234         if (fd == -1) {
235                 error("cannot open %s - %s", send_file_name, strerror(errno));
236                 return;
237         }
238         length = lseek(fd, 0, SEEK_END);
239         if (length == (off_t)-1) {
240                 error("cannot determine length of %s: %s", send_file_name, strerror(errno));
241                 goto _error;
242         }
243         send_data = my_malloc(length);
244         lseek(fd, 0, SEEK_SET);
245         if (read(fd, send_data, length) != length) {
246                 error("cannot read from %s: %s", send_file_name, strerror(errno));
247                 goto _error;
248         }
249         if (length >= 4 && !memcmp(send_data, "MThd", 4)) {
250                 error("%s is a Standard MIDI File; use aplaymidi to send it", send_file_name);
251                 goto _error;
252         }
253         send_data_length = length;
254         goto _exit;
255 _error:
256         free(send_data);
257         send_data = NULL;
258 _exit:
259         close(fd);
260 }
261
262 static int hex_value(char c)
263 {
264         if ('0' <= c && c <= '9')
265                 return c - '0';
266         if ('A' <= c && c <= 'F')
267                 return c - 'A' + 10;
268         if ('a' <= c && c <= 'f')
269                 return c - 'a' + 10;
270         error("invalid character %c", c);
271         return -1;
272 }
273
274 static void parse_data(void)
275 {
276         const char *p;
277         int i, value;
278
279         send_data = my_malloc(strlen(send_hex)); /* guesstimate */
280         i = 0;
281         value = -1; /* value is >= 0 when the first hex digit of a byte has been read */
282         for (p = send_hex; *p; ++p) {
283                 int digit;
284                 if (isspace((unsigned char)*p)) {
285                         if (value >= 0) {
286                                 send_data[i++] = value;
287                                 value = -1;
288                         }
289                         continue;
290                 }
291                 digit = hex_value(*p);
292                 if (digit < 0) {
293                         send_data = NULL;
294                         return;
295                 }
296                 if (value < 0) {
297                         value = digit;
298                 } else {
299                         send_data[i++] = (value << 4) | digit;
300                         value = -1;
301                 }
302         }
303         if (value >= 0)
304                 send_data[i++] = value;
305         send_data_length = i;
306 }
307
308 /*
309  * prints MIDI commands, formatting them nicely
310  */
311 static void print_byte(unsigned char byte)
312 {
313         static enum {
314                 STATE_UNKNOWN,
315                 STATE_1PARAM,
316                 STATE_1PARAM_CONTINUE,
317                 STATE_2PARAM_1,
318                 STATE_2PARAM_2,
319                 STATE_2PARAM_1_CONTINUE,
320                 STATE_SYSEX
321         } state = STATE_UNKNOWN;
322         int newline = 0;
323
324         if (byte >= 0xf8)
325                 newline = 1;
326         else if (byte >= 0xf0) {
327                 newline = 1;
328                 switch (byte) {
329                 case 0xf0:
330                         state = STATE_SYSEX;
331                         break;
332                 case 0xf1:
333                 case 0xf3:
334                         state = STATE_1PARAM;
335                         break;
336                 case 0xf2:
337                         state = STATE_2PARAM_1;
338                         break;
339                 case 0xf4:
340                 case 0xf5:
341                 case 0xf6:
342                         state = STATE_UNKNOWN;
343                         break;
344                 case 0xf7:
345                         newline = state != STATE_SYSEX;
346                         state = STATE_UNKNOWN;
347                         break;
348                 }
349         } else if (byte >= 0x80) {
350                 newline = 1;
351                 if (byte >= 0xc0 && byte <= 0xdf)
352                         state = STATE_1PARAM;
353                 else
354                         state = STATE_2PARAM_1;
355         } else /* b < 0x80 */ {
356                 int running_status = 0;
357                 newline = state == STATE_UNKNOWN;
358                 switch (state) {
359                 case STATE_1PARAM:
360                         state = STATE_1PARAM_CONTINUE;
361                         break;
362                 case STATE_1PARAM_CONTINUE:
363                         running_status = 1;
364                         break;
365                 case STATE_2PARAM_1:
366                         state = STATE_2PARAM_2;
367                         break;
368                 case STATE_2PARAM_2:
369                         state = STATE_2PARAM_1_CONTINUE;
370                         break;
371                 case STATE_2PARAM_1_CONTINUE:
372                         running_status = 1;
373                         state = STATE_2PARAM_2;
374                         break;
375                 default:
376                         break;
377                 }
378                 if (running_status)
379                         fputs("\n  ", stdout);
380         }
381         printf("%c%02X", newline ? '\n' : ' ', byte);
382 }
383
384 static void sig_handler(int dummy)
385 {
386         stop = 1;
387 }
388
389 static void add_send_hex_data(const char *str)
390 {
391         int length;
392         char *s;
393
394         length = (send_hex ? strlen(send_hex) + 1 : 0) + strlen(str) + 1;
395         s = my_malloc(length);
396         if (send_hex) {
397                 strcpy(s, send_hex);
398                 strcat(s, " ");
399         } else {
400                 s[0] = '\0';
401         }
402         strcat(s, str);
403         free(send_hex);
404         send_hex = s;
405 }
406
407 int main(int argc, char *argv[])
408 {
409         static const char short_options[] = "hVlLp:s:r:S::dt:a";
410         static const struct option long_options[] = {
411                 {"help", 0, NULL, 'h'},
412                 {"version", 0, NULL, 'V'},
413                 {"list-devices", 0, NULL, 'l'},
414                 {"list-rawmidis", 0, NULL, 'L'},
415                 {"port", 1, NULL, 'p'},
416                 {"send", 1, NULL, 's'},
417                 {"receive", 1, NULL, 'r'},
418                 {"send-hex", 2, NULL, 'S'},
419                 {"dump", 0, NULL, 'd'},
420                 {"timeout", 1, NULL, 't'},
421                 {"active-sensing", 0, NULL, 'a'},
422                 { }
423         };
424         int c, err, ok = 0;
425         int ignore_active_sensing = 1;
426         int do_send_hex = 0;
427
428         while ((c = getopt_long(argc, argv, short_options,
429                                 long_options, NULL)) != -1) {
430                 switch (c) {
431                 case 'h':
432                         usage();
433                         return 0;
434                 case 'V':
435                         version();
436                         return 0;
437                 case 'l':
438                         do_device_list = 1;
439                         break;
440                 case 'L':
441                         do_rawmidi_list = 1;
442                         break;
443                 case 'p':
444                         port_name = optarg;
445                         break;
446                 case 's':
447                         send_file_name = optarg;
448                         break;
449                 case 'r':
450                         receive_file_name = optarg;
451                         break;
452                 case 'S':
453                         do_send_hex = 1;
454                         if (optarg)
455                                 add_send_hex_data(optarg);
456                         break;
457                 case 'd':
458                         dump = 1;
459                         break;
460                 case 't':
461                         timeout = atoi(optarg);
462                         break;
463                 case 'a':
464                         ignore_active_sensing = 0;
465                         break;
466                 default:
467                         error("Try `amidi --help' for more information.");
468                         return 1;
469                 }
470         }
471         if (do_send_hex) {
472                 /* data for -S can be specified as multiple arguments */
473                 if (!send_hex && !argv[optind]) {
474                         error("Please specify some data for --send-hex.");
475                         return 1;
476                 }
477                 for (; argv[optind]; ++optind)
478                         add_send_hex_data(argv[optind]);
479         } else {
480                 if (argv[optind]) {
481                         error("%s is not an option.", argv[optind]);
482                         return 1;
483                 }
484         }
485
486         if (do_rawmidi_list)
487                 rawmidi_list();
488         if (do_device_list)
489                 device_list();
490         if (do_rawmidi_list || do_device_list)
491                 return 0;
492
493         if (!send_file_name && !receive_file_name && !send_hex && !dump) {
494                 error("Please specify at least one of --send, --receive, --send-hex, or --dump.");
495                 return 1;
496         }
497         if (send_file_name && send_hex) {
498                 error("--send and --send-hex cannot be specified at the same time.");
499                 return 1;
500         }
501
502         if (send_file_name)
503                 load_file();
504         else if (send_hex)
505                 parse_data();
506         if ((send_file_name || send_hex) && !send_data)
507                 return 1;
508
509         if (receive_file_name) {
510                 receive_file = creat(receive_file_name, 0666);
511                 if (receive_file == -1) {
512                         error("cannot create %s: %s", receive_file_name, strerror(errno));
513                         return -1;
514                 }
515         } else {
516                 receive_file = -1;
517         }
518
519         if (receive_file_name || dump)
520                 inputp = &input;
521         else
522                 inputp = NULL;
523         if (send_data)
524                 outputp = &output;
525         else
526                 outputp = NULL;
527
528         if ((err = snd_rawmidi_open(inputp, outputp, port_name, SND_RAWMIDI_NONBLOCK)) < 0) {
529                 error("cannot open port \"%s\": %s", port_name, snd_strerror(err));
530                 goto _exit2;
531         }
532
533         if (inputp)
534                 snd_rawmidi_read(input, NULL, 0); /* trigger reading */
535
536         if (send_data) {
537                 if ((err = snd_rawmidi_nonblock(output, 0)) < 0) {
538                         error("cannot set blocking mode: %s", snd_strerror(err));
539                         goto _exit;
540                 }
541                 if ((err = snd_rawmidi_write(output, send_data, send_data_length)) < 0) {
542                         error("cannot send data: %s", snd_strerror(err));
543                         goto _exit;
544                 }
545         }
546
547         if (inputp) {
548                 int read = 0;
549                 int npfds, time = 0;
550                 struct pollfd *pfds;
551
552                 timeout *= 1000;
553                 npfds = snd_rawmidi_poll_descriptors_count(input);
554                 pfds = alloca(npfds * sizeof(struct pollfd));
555                 snd_rawmidi_poll_descriptors(input, pfds, npfds);
556                 signal(SIGINT, sig_handler);
557                 for (;;) {
558                         unsigned char buf[256];
559                         int i, length;
560                         unsigned short revents;
561
562                         err = poll(pfds, npfds, 200);
563                         if (stop || (err < 0 && errno == EINTR))
564                                 break;
565                         if (err < 0) {
566                                 error("poll failed: %s", strerror(errno));
567                                 break;
568                         }
569                         if (err == 0) {
570                                 time += 200;
571                                 if (timeout && time >= timeout)
572                                         break;
573                                 continue;
574                         }
575                         if ((err = snd_rawmidi_poll_descriptors_revents(input, pfds, npfds, &revents)) < 0) {
576                                 error("cannot get poll events: %s", snd_strerror(errno));
577                                 break;
578                         }
579                         if (revents & (POLLERR | POLLHUP))
580                                 break;
581                         if (!(revents & POLLIN))
582                                 continue;
583                         err = snd_rawmidi_read(input, buf, sizeof(buf));
584                         if (err == -EAGAIN)
585                                 continue;
586                         if (err < 0) {
587                                 error("cannot read from port \"%s\": %s", port_name, snd_strerror(err));
588                                 break;
589                         }
590                         length = 0;
591                         for (i = 0; i < err; ++i)
592                                 if (!ignore_active_sensing || buf[i] != 0xfe)
593                                         buf[length++] = buf[i];
594                         if (length == 0)
595                                 continue;
596                         read += length;
597                         time = 0;
598                         if (receive_file != -1)
599                                 write(receive_file, buf, length);
600                         if (dump) {
601                                 for (i = 0; i < length; ++i)
602                                         print_byte(buf[i]);
603                                 fflush(stdout);
604                         }
605                 }
606                 if (isatty(fileno(stdout)))
607                         printf("\n%d bytes read\n", read);
608         }
609
610         ok = 1;
611 _exit:
612         if (inputp)
613                 snd_rawmidi_close(input);
614         if (outputp)
615                 snd_rawmidi_close(output);
616 _exit2:
617         if (receive_file != -1)
618                 close(receive_file);
619         return !ok;
620 }