/* * network server/client for ALSA sequencer * ver.0.1 * * Copyright (C) 1999-2000 Takashi Iwai * Modified by Rob van der Putten, Leiden, Holland, * rob at sput dot nl. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* RvdP, don't include */ /* #include "../../include/aconfig.h.in" */ /* #include "../../include/gettext.h" */ /* * prototypes */ static void usage(void); static void init_buf(void); static void init_pollfds(void); static void close_files(void); static void init_seq(char *source, char *dest); static int get_port(char *service); static void sigterm_exit(int sig); static void init_server(int port); static void init_client(char *server, int port); static void do_loop(void); static int copy_local_to_remote(void); static int copy_remote_to_local(int fd); /* * default TCP port number */ #define DEFAULT_PORT 40002 /* * local input buffer */ static char *readbuf; static int max_rdlen; static char *writebuf; static int cur_wrlen, max_wrlen; #define MAX_BUF_EVENTS 200 #define MAX_CONNECTION 10 static snd_seq_t *handle; static struct pollfd *seqifds = NULL; static struct pollfd *seqofds = NULL; static struct pollfd *pollfds = NULL; static int seqifds_count = 0; static int seqofds_count = 0; static int pollfds_count = 0; static int sockfd, netfd[MAX_CONNECTION] = {[0 ... MAX_CONNECTION-1] = -1}; static int max_connection; static int cur_connected; static int seq_port; static int server_mode; static int verbose = 0; static int info = 0; /* * main routine */ static struct option long_option[] = { {"port", 1, NULL, 'p'}, {"source", 1, NULL, 's'}, {"dest", 1, NULL, 'd'}, {"help", 0, NULL, 'h'}, {"verbose", 0, NULL, 'v'}, {"info", 0, NULL, 'i'}, {NULL, 0, NULL, 0}, }; int main(int argc, char **argv) { int c; int port = DEFAULT_PORT; char *source = NULL, *dest = NULL; /* #ifdef ENABLE_NLS setlocale(LC_ALL, ""); textdomain(PACKAGE); #endif */ while ((c = getopt_long(argc, argv, "p:s:d:vi", long_option, NULL)) != -1) { switch (c) { case 'p': if (isdigit(*optarg)) port = atoi(optarg); else port = get_port(optarg); break; case 's': source = optarg; break; case 'd': dest = optarg; break; case 'v': verbose++; break; case 'i': info++; break; default: usage(); exit(1); } } signal(SIGINT, sigterm_exit); signal(SIGTERM, sigterm_exit); init_buf(); init_seq(source, dest); if (optind >= argc) { server_mode = 1; max_connection = MAX_CONNECTION; init_pollfds(); init_server(port); } else { server_mode = 0; max_connection = 1; init_pollfds(); init_client(argv[optind], port); } do_loop(); close_files(); return 0; } /* * print usage */ static void usage(void) { /* RvdP, NoNLS */ printf("aseqnet - network client/server on ALSA sequencer\n"); printf(" Copyright (C) 1999 Takashi Iwai\n"); printf("usage:\n"); printf(" server mode: aseqnet [-options]\n"); printf(" client mode: aseqnet [-options] server_host\n"); printf("options:\n"); printf(" -p,--port # : sepcify TCP port (digit or service name)\n"); printf(" -s,--source addr : read from given addr (client:port)\n"); printf(" -d,--dest addr : write to given addr (client:port)\n"); printf(" -v, --verbose : print verbose messages\n"); printf(" -i, --info : print certain received events\n"); } /* * allocate and initialise buffers */ static void init_buf(void) { max_wrlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t); max_rdlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t); writebuf = malloc(max_wrlen); readbuf = malloc(max_rdlen); if (writebuf == NULL || readbuf == NULL) { fprintf(stderr, "can't malloc\n"); exit(1); } memset(writebuf, 0, max_wrlen); memset(readbuf, 0, max_rdlen); cur_wrlen = 0; } /* * allocate and initialise poll array */ static void init_pollfds(void) { pollfds_count = seqifds_count + seqofds_count + 1 + max_connection; pollfds = (struct pollfd *)calloc(pollfds_count, sizeof(struct pollfd)); assert(pollfds); } /* * close all files */ static void close_files(void) { /* RvdP, No NLS */ int i; if (verbose) /* fprintf(stderr, _("closing files..\n")); */ fprintf(stderr, "closing files..\n"); for (i = 0; i < max_connection; i++) { if (netfd[i] >= 0) close(netfd[i]); } if (sockfd >= 0) close(sockfd); } /* * initialise sequencer */ static void init_seq(char *source, char *dest) { snd_seq_addr_t addr; int err, counti, counto; if (snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) { perror("snd_seq_open"); exit(1); } if (seqifds) free(seqifds); if (seqofds) free(seqofds); counti = seqifds_count = snd_seq_poll_descriptors_count(handle, POLLIN); assert(counti > 0); counto = seqofds_count = snd_seq_poll_descriptors_count(handle, POLLOUT); assert(counto > 0); seqifds = (struct pollfd *)calloc(counti, sizeof(struct pollfd)); assert(seqifds); seqofds = (struct pollfd *)calloc(counto, sizeof(struct pollfd)); assert(seqofds); err = snd_seq_poll_descriptors(handle, seqifds, counti, POLLIN); assert(err == counti); err = snd_seq_poll_descriptors(handle, seqofds, counto, POLLOUT); assert(err == counto); snd_seq_nonblock(handle, 1); /* set client info */ if (server_mode) snd_seq_set_client_name(handle, "Net Server"); else snd_seq_set_client_name(handle, "Net Client"); /* create a port */ seq_port = snd_seq_create_simple_port(handle, "Network", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC); if (seq_port < 0) { perror("create seq port"); exit(1); } if (verbose) fprintf(stderr, "sequencer opened: %d:%d\n", snd_seq_client_id(handle), seq_port); /* explicit subscriptions */ if (source) { /* read subscription */ if (snd_seq_parse_address(handle, &addr, source) < 0) { fprintf(stderr, "invalid source address %s\n", source); exit(1); } if (snd_seq_connect_from(handle, seq_port, addr.client, addr.port)) { perror("read subscription"); exit(1); } } if (dest) { /* write subscription */ if (snd_seq_parse_address(handle, &addr, dest) < 0) { fprintf(stderr, "invalid destination address %s\n", dest); exit(1); } if (snd_seq_connect_to(handle, seq_port, addr.client, addr.port)) { perror("write subscription"); exit(1); } } } /* * convert from string to TCP port number */ static int get_port(char *service) { struct servent *sp; if ((sp = getservbyname(service, "tcp")) == NULL){ fprintf(stderr, "service '%s' is not found in /etc/services\n", service); return -1; } return sp->s_port; } /* * signal handler */ static void sigterm_exit(int sig) { close_files(); exit(1); } /* * initialise network server */ static void init_server(int port) { /* * RvdP, changed to support IPv6 * Dual stack only! */ int i; int curstate = 1; int ipv6only = 0; int nodelay = 1; int quickack = 1; struct sockaddr_in6 addr; memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; inet_pton(AF_INET6, "::", &(addr.sin6_addr)); addr.sin6_port = htons(port); sockfd = socket(PF_INET6, SOCK_STREAM, 0); if (sockfd < 0) { perror("create socket"); exit(1); } setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)); /* the return value is ignored.. */ /* Force dual stack */ setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only)); /* the return value is ignored.. */ /* Nagle and quickack */ if ((setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay))) < 0) { perror("Error setsockopt tcp_nodelay"); } if ((setsockopt(sockfd, IPPROTO_TCP, TCP_QUICKACK, &quickack, sizeof(quickack))) < 0) { perror("Error setsockopt tcp_quickack"); } if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("can't bind"); exit(1); } if (listen(sockfd, 5) < 0) { perror("can't listen"); exit(1); } cur_connected = 0; for (i = 0; i < max_connection; i++) netfd[i] = -1; } /* * start connection on server */ static void start_connection(void) { struct sockaddr_in6 addr; int i; socklen_t addr_len; for (i = 0; i < max_connection; i++) { if (netfd[i] < 0) break; } if (i >= max_connection) { fprintf(stderr, "too many connections!\n"); exit(1); } memset(&addr, 0, sizeof(addr)); addr_len = sizeof(addr); netfd[i] = accept(sockfd, (struct sockaddr *)&addr, &addr_len); if (netfd[i] < 0) { perror("accept"); exit(1); } if (verbose) fprintf(stderr, "accepted[%d]\n", netfd[i]); cur_connected++; } /* * initialise network client */ static void init_client(char *server, int port) { /* * RvdP, changed to support IPv6 */ struct addrinfo hints; struct addrinfo *result, *rp; int curstate = 1; int nodelay = 1; int quickack = 1; int fd, s; char portstr[8]; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* hints.ai_protocol = IPPROTO_TCP; */ hints.ai_flags = 0; memset(portstr, 0, 8); snprintf(portstr, 6, "%d", port); s = getaddrinfo(server, portstr, &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(1); } for (rp = result; rp != NULL; rp = rp->ai_next) { fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd == -1) continue; if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1) break; close(fd); } if (rp == NULL) { fprintf(stderr, "Could not connect\n"); exit(1); } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) { perror("setsockopt"); exit(1); } /* RvdP, nagle and quickack */ if ((setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay))) < 0) { perror("Error setsockopt tcp_nodelay"); } if ((setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &quickack, sizeof(quickack))) < 0) { perror("Error setsockopt tcp_quickack"); } if (verbose) fprintf(stderr, "ok.. connected\n"); netfd[0] = fd; cur_connected = 1; } /* * event loop */ static void do_loop(void) { int i, rc, width; int seqifd_ptr, sockfd_ptr = -1, netfd_ptr; for (;;) { memset(pollfds, 0, pollfds_count * sizeof(struct pollfd)); seqifd_ptr = 0; memcpy(pollfds, seqifds, sizeof(*seqifds)*(width = seqifds_count)); if (server_mode) { sockfd_ptr = width; pollfds[width].fd = sockfd; pollfds[width].events = POLLIN; width++; } netfd_ptr = width; for (i = 0; i < max_connection; i++) { if (netfd[i] >= 0) { pollfds[width].fd = netfd[i]; pollfds[width].events = POLLIN; width++; } } do { rc = poll(pollfds, width, -1); } while (rc <= 0 && errno == EINTR); if (rc <= 0) { perror("poll"); exit(1); } if (server_mode) { if (pollfds[sockfd_ptr].revents & (POLLIN|POLLOUT)) start_connection(); } for (i = 0; i < seqifds_count; i++) if (pollfds[seqifd_ptr + i].revents & (POLLIN|POLLOUT)) { if (copy_local_to_remote()) return; break; } for (i = 0; i < max_connection; i++) { if (netfd[i] < 0) continue; if (pollfds[netfd_ptr + i].revents & (POLLIN|POLLOUT)) { if (copy_remote_to_local(netfd[i])) { netfd[i] = -1; cur_connected--; if (cur_connected <= 0) return; } } } } } /* * flush write buffer - send data to the socket */ static void flush_writebuf(void) { if (cur_wrlen) { int i; for (i = 0; i < max_connection; i++) { if (netfd[i] >= 0) write(netfd[i], writebuf, cur_wrlen); } cur_wrlen = 0; } } /* * get space from write buffer */ static char *get_writebuf(int len) { char *buf; if (cur_wrlen + len >= max_wrlen) flush_writebuf(); buf = writebuf + cur_wrlen; cur_wrlen += len; return buf; } static void print_event(snd_seq_event_t *ev) { /* RvdP, No NLS */ switch (ev->type) { case SND_SEQ_EVENT_CONTROLLER: /* printf(_("Channel %2d: Control event : %5d\n"), */ printf("Channel %2d: Control event : %5d\n", ev->data.control.channel, ev->data.control.value); break; case SND_SEQ_EVENT_PITCHBEND: /* printf(_("Channel %2d: Pitchbender : %5d\n"), */ printf("Channel %2d: Pitchbender : %5d\n", ev->data.control.channel, ev->data.control.value); break; case SND_SEQ_EVENT_NOTEON: /* printf(_("Channel %2d: Note On event : %5d\n"), */ printf("Channel %2d: Note On event : %5d\n", ev->data.control.channel, ev->data.note.note); break; case SND_SEQ_EVENT_NOTEOFF: /* printf(_("Channel %2d: Note Off event: %5d\n"), */ printf("Channel %2d: Note Off event: %5d\n", ev->data.control.channel, ev->data.note.note); break; } } #define EVENT_PACKET_SIZE 32 /* * copy events from sequencer to port(s) */ static int copy_local_to_remote(void) { int rc; snd_seq_event_t *ev; char *buf; while ((rc = snd_seq_event_input(handle, &ev)) >= 0 && ev) { if (ev->type >= SND_SEQ_EVENT_CLIENT_START && ! snd_seq_ev_is_variable_type(ev)) { snd_seq_free_event(ev); continue; } if (snd_seq_ev_is_variable(ev)) { int len; len = EVENT_PACKET_SIZE + ev->data.ext.len; buf = get_writebuf(len); memcpy(buf, ev, sizeof(snd_seq_event_t)); memcpy(buf + EVENT_PACKET_SIZE, ev->data.ext.ptr, ev->data.ext.len); } else { buf = get_writebuf(EVENT_PACKET_SIZE); memcpy(buf, ev, EVENT_PACKET_SIZE); } if (info) print_event(ev); snd_seq_free_event(ev); } flush_writebuf(); return 0; } /* * copy events from a port to sequencer */ static int copy_remote_to_local(int fd) { int count; char *buf; snd_seq_event_t *ev; count = read(fd, readbuf, MAX_BUF_EVENTS * sizeof(snd_seq_event_t)); buf = readbuf; if (count == 0) { if (verbose) fprintf(stderr, "disconnected\n"); return 1; } while (count > 0) { ev = (snd_seq_event_t*)buf; buf += EVENT_PACKET_SIZE; count -= EVENT_PACKET_SIZE; if (snd_seq_ev_is_variable(ev) && ev->data.ext.len > 0) { ev->data.ext.ptr = buf; buf += ev->data.ext.len; count -= ev->data.ext.len; } snd_seq_ev_set_direct(ev); snd_seq_ev_set_source(ev, seq_port); snd_seq_ev_set_subs(ev); if (info) print_event(ev); snd_seq_event_output(handle, ev); } snd_seq_drain_output(handle); return 0; }