| /* |
| * network server/client for ALSA sequencer |
| * ver.0.1 |
| * |
| * Copyright (C) 1999-2000 Takashi Iwai |
| * |
| * 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 <stdio.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <netinet/in.h> |
| #include <sys/socket.h> |
| #include <netdb.h> |
| #include <locale.h> |
| #include <alsa/asoundlib.h> |
| #include <getopt.h> |
| #include <signal.h> |
| #include <assert.h> |
| #include "aconfig.h" |
| #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 const 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) |
| { |
| 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 initialize 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 initialize 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) |
| { |
| int i; |
| if (verbose) |
| fprintf(stderr, _("closing files..\n")); |
| for (i = 0; i < max_connection; i++) { |
| if (netfd[i] >= 0) |
| close(netfd[i]); |
| } |
| if (sockfd >= 0) |
| close(sockfd); |
| } |
| |
| |
| /* |
| * initialize 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); |
| } |
| |
| |
| /* |
| * initialize network server |
| */ |
| static void init_server(int port) |
| { |
| int i; |
| int curstate = 1; |
| struct sockaddr_in addr; |
| |
| memset(&addr, 0, sizeof(addr)); |
| |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = INADDR_ANY; |
| addr.sin_port = htons(port); |
| |
| sockfd = socket(AF_INET, 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.. */ |
| |
| 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_in 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++; |
| } |
| |
| /* |
| * initialize network client |
| */ |
| static void init_client(char *server, int port) |
| { |
| struct sockaddr_in addr; |
| struct hostent *host; |
| int curstate = 1; |
| int fd; |
| |
| if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){ |
| perror("create socket"); |
| exit(1); |
| } |
| if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) { |
| perror("setsockopt"); |
| exit(1); |
| } |
| if ((host = gethostbyname(server)) == NULL){ |
| fprintf(stderr, _("can't get address %s\n"), server); |
| exit(1); |
| } |
| addr.sin_port = htons(port); |
| addr.sin_family = AF_INET; |
| memcpy(&addr.sin_addr, host->h_addr, host->h_length); |
| if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
| perror("connect"); |
| exit(1); |
| } |
| 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) |
| { |
| switch (ev->type) { |
| case SND_SEQ_EVENT_CONTROLLER: |
| 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"), |
| ev->data.control.channel, ev->data.control.value); |
| break; |
| case SND_SEQ_EVENT_NOTEON: |
| 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"), |
| 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; |
| } |
| |