PageRenderTime 403ms CodeModel.GetById 91ms app.highlight 161ms RepoModel.GetById 108ms app.codeStats 1ms

/alsa-utils-1.0.25/amidi/amidi.c

#
C | 620 lines | 553 code | 43 blank | 24 comment | 129 complexity | 13f049462e65449a97a338932370727a MD5 | raw file
  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
 40static int do_device_list, do_rawmidi_list;
 41static char *port_name = "default";
 42static char *send_file_name;
 43static char *receive_file_name;
 44static char *send_hex;
 45static char *send_data;
 46static int send_data_length;
 47static int receive_file;
 48static int dump;
 49static int timeout;
 50static int stop;
 51static snd_rawmidi_t *input, **inputp;
 52static snd_rawmidi_t *output, **outputp;
 53
 54static 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
 64static 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
 83static void version(void)
 84{
 85	puts("amidi version " SND_UTIL_VERSION_STR);
 86}
 87
 88static 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
 98static 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
159static 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
184static 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
207static 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
228static 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
262static 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
274static 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 */
311static 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
384static void sig_handler(int dummy)
385{
386	stop = 1;
387}
388
389static 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
407int 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}