diff --git a/Makefile.am b/Makefile.am index 2d4168b..59e2f0a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ INCLUDES=-I$(top_srcdir)/include -SUBDIRS1=include alsactl alsamixer amixer aplay utils +SUBDIRS1=include alsactl alsamixer amixer aplay seq utils if COND_XAMIXER2 SUBDIRS2=$(SUBDIRS1) xamixer2 else diff --git a/configure.in b/configure.in index cb08207..e503880 100644 --- a/configure.in +++ b/configure.in @@ -51,4 +51,4 @@ if test "x$GTK_LIBS" = "x"; then fi AC_OUTPUT(Makefile alsactl/Makefile alsamixer/Makefile amixer/Makefile aplay/Makefile \ include/Makefile utils/Makefile utils/alsa-utils.spec xamixer2/Makefile - gamix/Makefile) + gamix/Makefile seq/Makefile seq/aconnect/Makefile seq/aseqnet/Makefile) diff --git a/seq/Makefile.am b/seq/Makefile.am new file mode 100644 index 0000000..5f0087e --- /dev/null +++ b/seq/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=aconnect aseqnet diff --git a/seq/aconnect/Makefile.am b/seq/aconnect/Makefile.am new file mode 100644 index 0000000..a1d2359 --- /dev/null +++ b/seq/aconnect/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = -I$(top_srcdir)/include +LDADD = -lasound +EXTRA_DIST = README.aconnect + +bin_PROGRAMS = aconnect +aconnect_SOURCES = aconnect.c +man_MANS = aconnect.1 diff --git a/seq/aconnect/README.aconnect b/seq/aconnect/README.aconnect new file mode 100644 index 0000000..a2839ab --- /dev/null +++ b/seq/aconnect/README.aconnect @@ -0,0 +1,50 @@ +================================================================ + aconnect - control subscriptions + ver.0.1.3 + Copyright (C) 1999-2000 Takashi Iwai +================================================================ + +aconnect is a utility to control subscriptions of two ports as the +third "manager" client. + +For example, the following connects two ports, from 64:0 to 65:0. + + % aconnect 64:0 65:0 + +To disconnect the existing subscription, use -d option. + + % aconnect -d 64:0 65:0 + +To see which port is available as input port, run the following +command: + + % aconnect -i + client 0: 'System' [group=system] [type=kernel] + 0 'Timer ' [group=system] + 1 'Announce ' [group=system] + client 64: '0: MIDI Synth' [group=] [type=kernel] + 0 'card 0: synth-midi: 0' [group=device] + +Similary, to see the output ports, use -o flag. + + % aconnect -o + client 64: '0: MIDI Synth' [group=] [type=kernel] + 0 'card 0: synth-midi: 0' [group=device] + client 65: 'AWE Wave Table Synth : 0' [group=device] [type=kernel] + 0 'Emu8000 port 0 ' [group=device] + 1 'Emu8000 port 1 ' [group=device] + 2 'Emu8000 port 2 ' [group=device] + 3 'Emu8000 port 3 ' [group=device] + +Some ports may have permission for its own group. +In such a case, change the group of aconnect to the appropriate one by +using -g option. + +The option -l together with -i or -o shows subscribers for each port. + +Ports are connected exclusively when the option -e is specified. + +For modifying time-stamp with a queue, use -r or -t option followed by +a queue index which updates the time-stamp. Former uses real-time queue, +while the latter uses tick queue. The queue must be used (not necessarily +owned) by the receiver client. diff --git a/seq/aconnect/aconnect.1 b/seq/aconnect/aconnect.1 new file mode 100644 index 0000000..b47b4c2 --- /dev/null +++ b/seq/aconnect/aconnect.1 @@ -0,0 +1,127 @@ +.TH aconnect 1 "January 1, 2000" +.LO 1 +.SH NAME +aconnect \- ALSA sequencer connection manager + +.SH SYNOPSIS +.B aconnect +[\-d] [-options] sender receiver +.br +.B aconnect +\-i|-o [-options] + +.SH DESCRIPTION +.B aconnect +is a utility to connect and disconnect two existing ports on ALSA sequencer +system. +The ports with the arbitrary subscription permission, such as created +by +.B aseqview(1), +can be connected to any (MIDI) device ports using +.B aconnect. +For example, to connect from port 64:0 to 65:0, run as follows: +.IP "" 4 +% aconnect 64:0 65:0 +.PP +The connection is one-way, and the whole data to the sender port (64:0) +is redirected to the receiver port (65:0). When another port (e.g. 65:1) +is attached to the same sender port, the data is sent to both receiver +ports. +For disconnection, use +.B \-d +option. +.IP "" 4 +% aconnect -d 64:0 65:0 +.PP +Another function of +.B aconnect +is to list the present ports +on the given condition. +The input ports, which may become +.I sender +ports, can be listed with +.B \-i +option. +.IP "" 4 +% aconnect -i +.br +client 0: 'System' [group=system] [type=kernel] +.in +4 +0 'Timer ' [group=system] +.br +1 'Announce ' [group=system] +.in -4 +client 64: '0: MIDI Synth' [group=] [type=kernel] +.in +4 +0 'card 0: synth-midi: 0' [group=device] +.in -4 +.PP +Similary, to see the output ports, use +.B \-o +flag. + +.SH OPTIONS +.SS CONNNECTION MANAGEMENT +.TP +.B \-d, --disconnect +Disconnect the given subscription. +.TP +.B \-e, --exclusive +Connect ports with exclusvie mode. +Both sender and receiver ports can be no longer connected by any other ports. +.TP +.B \-r, --real queue +Convert time-stamps of event packets to the current value of the given +.I real-time +queue. +This is option is, however, not so useful, since +the receiver port must use (not necessarily own) the specified queue. +.TP +.B \-t, --tick queue +Like +.B -r +option, but +time-stamps are converted to the current value of the given +.I tick +queue. +.TP +.B \-g, --group name +Specify the group name that +.B aconnect +uses. +Some ports may have special permissions, so that only the same group +may subscribe to them. In such a case, +.B aconnect +can fake the group name +with this option. + +.SS LIST PORTS +.TP +.B \-i, --input +List existing input (readable) ports. +This option is exclusive to +.B \-o. +.TP +.B \-o, --output +List existing output (writable) ports. +This option is exclusive to +.B \-i. +.TP +.B \-l, --list +List the current connection status. The connected and connecting ports +from/to each port are listed together. +The suffix flag +.B [ex] +means the connection is exclusive. +The suffix flag +.B [real:#] +and +.B [tick:#] +mean the connection includes real-time and tick conversion on the listed +queue, respectively. + +.SH "SEE ALSO" +aseqnet(1), aseqview(1) + +.SH AUTHOR +Takashi Iwai . diff --git a/seq/aconnect/aconnect.c b/seq/aconnect/aconnect.c new file mode 100644 index 0000000..935e88c --- /dev/null +++ b/seq/aconnect/aconnect.c @@ -0,0 +1,305 @@ +/* + * connect / disconnect two subscriber ports + * ver.0.1.3 + * + * Copyright (C) 1999 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 +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(void) +{ + fprintf(stderr, "aconnect - ALSA sequencer connection manager\n"); + fprintf(stderr, "Copyright (C) 1999-2000 Takashi Iwai\n"); + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " * Connection/disconnection betwen two ports\n"); + fprintf(stderr, " aconnect [-options] sender receiver\n"); + fprintf(stderr, " sender, receiver = client:port pair\n"); + fprintf(stderr, " -d,--disconnect disconnect\n"); + fprintf(stderr, " -e,--exclusive exclusive connection\n"); + fprintf(stderr, " -r,--real # convert real-time-stamp on queue\n"); + fprintf(stderr, " -t,--tick # convert tick-time-stamp on queue\n"); + fprintf(stderr, " -g,--group name set the group name\n"); + fprintf(stderr, " * List connected ports (no subscription action)\n"); + fprintf(stderr, " aconnect -i|-o [-options]\n"); + fprintf(stderr, " -i,--input list input (readable) ports\n"); + fprintf(stderr, " -o,--output list output (writable) ports\n"); + fprintf(stderr, " -g,--group name specify the group name\n"); + fprintf(stderr, " -l,--list list current connections of each port\n"); +} + +/* + * parse command line to client:port + */ +static int parse_address(snd_seq_addr_t *addr, char *arg) +{ + char *p; + + if (! isdigit(*arg)) + return -1; + if ((p = strpbrk(arg, ":.")) == NULL) + return -1; + addr->client = atoi(arg); + addr->port = atoi(p + 1); + return 0; +} + +/* + * check permission (capability) of specified port + */ +static int check_permission(snd_seq_port_info_t *pinfo, char *group, int perm) +{ + if ((pinfo->capability & perm) == perm && + ! (pinfo->capability & SND_SEQ_PORT_CAP_NO_EXPORT)) + return 1; + if (*group && strcmp(pinfo->group, group) == 0 && + (pinfo->cap_group & perm) == perm && + ! (pinfo->cap_group & SND_SEQ_PORT_CAP_NO_EXPORT)) + return 1; + return 0; +} + +/* + * list subscribers of specified type + */ +static void list_each_subs(snd_seq_t *seq, snd_seq_query_subs_t *subs, int type, char *msg) +{ + subs->type = type; + subs->index = 0; + while (snd_seq_query_port_subscribers(seq, subs) >= 0) { + if (subs->index == 0) + printf("\t%s: ", msg); + else + printf(", "); + printf("%d:%d", subs->addr.client, subs->addr.port); + if (subs->exclusive) + printf("[ex]"); + if (subs->convert_time) + printf("[%s:%d]", + (subs->realtime ? "real" : "tick"), + subs->queue); + subs->index++; + } + if (subs->index) + printf("\n"); +} + +/* + * list subscribers + */ +static void list_subscribers(snd_seq_t *seq, int client, int port) +{ + snd_seq_query_subs_t subs; + memset(&subs, 0, sizeof(subs)); + subs.client = client; + subs.port = port; + list_each_subs(seq, &subs, SND_SEQ_QUERY_SUBS_READ, "Connecting To"); + list_each_subs(seq, &subs, SND_SEQ_QUERY_SUBS_WRITE, "Connected From"); +} + +/* + * list all ports + */ +static void list_ports(snd_seq_t *seq, char *group, int perm, int list_subs) +{ + snd_seq_client_info_t cinfo; + snd_seq_port_info_t pinfo; + int client_printed; + + cinfo.client = -1; + cinfo.name[0] = 0; + cinfo.group[0] = 0; + while (snd_seq_query_next_client(seq, &cinfo) >= 0) { + /* reset query info */ + pinfo.client = cinfo.client; + pinfo.port = -1; + pinfo.name[0] = 0; + strncpy(pinfo.group, group, sizeof(pinfo.group)); + client_printed = 0; + while (snd_seq_query_next_port(seq, &pinfo) >= 0) { + if (check_permission(&pinfo, group, perm)) { + if (! client_printed) { + printf("client %d: '%s' [group=%s] [type=%s]\n", + cinfo.client, cinfo.name, cinfo.group, + (cinfo.type == USER_CLIENT ? "user" : "kernel")); + client_printed = 1; + } + printf(" %3d '%-16s' [group=%s]\n", pinfo.port, pinfo.name, pinfo.group); + if (list_subs) + list_subscribers(seq, pinfo.client, pinfo.port); + } + /* reset query names */ + pinfo.name[0] = 0; + strncpy(pinfo.group, group, sizeof(pinfo.group)); + } + /* reset query names */ + cinfo.name[0] = 0; + cinfo.group[0] = 0; + } +} + + +enum { + SUBSCRIBE, UNSUBSCRIBE, LIST_INPUT, LIST_OUTPUT +}; + +static struct option long_option[] = { + {"disconnect", 0, NULL, 'd'}, + {"input", 0, NULL, 'i'}, + {"output", 0, NULL, 'o'}, + {"group", 1, NULL, 'g'}, + {"real", 1, NULL, 'r'}, + {"tick", 1, NULL, 't'}, + {"exclusive", 0, NULL, 'e'}, + {"list", 0, NULL, 'l'}, + {NULL, 0, NULL, 0}, +}; + +int main(int argc, char **argv) +{ + int c; + snd_seq_t *seq; + int queue = 0, convert_time = 0, convert_real = 0, exclusive = 0; + int command = SUBSCRIBE; + char *group = ""; + int client; + int list_subs = 0; + snd_seq_client_info_t cinfo; + snd_seq_port_subscribe_t subs; + + while ((c = getopt_long(argc, argv, "diog:r:t:el", long_option, NULL)) != -1) { + switch (c) { + case 'd': + command = UNSUBSCRIBE; + break; + case 'i': + command = LIST_INPUT; + break; + case 'o': + command = LIST_OUTPUT; + break; + case 'g': + group = optarg; + break; + case 'e': + exclusive = 1; + break; + case 'r': + queue = atoi(optarg); + convert_time = 1; + convert_real = 1; + break; + case 't': + queue = atoi(optarg); + convert_time = 1; + convert_real = 0; + break; + case 'l': + list_subs = 1; + break; + default: + usage(); + exit(1); + } + } + + if (snd_seq_open(&seq, SND_SEQ_OPEN) < 0) { + fprintf(stderr, "can't open sequencer\n"); + return 1; + } + + if (command == LIST_INPUT) { + list_ports(seq, group, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, list_subs); + snd_seq_close(seq); + return 0; + } else if (command == LIST_OUTPUT) { + list_ports(seq, group, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, list_subs); + snd_seq_close(seq); + return 0; + } + + if (optind + 2 > argc) { + snd_seq_close(seq); + usage(); + exit(1); + } + + if ((client = snd_seq_client_id(seq)) < 0) { + snd_seq_close(seq); + fprintf(stderr, "can't get client id\n"); + return 1; + } + + /* set client info */ + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.client = client; + cinfo.type = USER_CLIENT; + strcpy(cinfo.name, "ALSA Connector"); + strncpy(cinfo.group, group, sizeof(cinfo.group) - 1); + if (snd_seq_set_client_info(seq, &cinfo) < 0) { + snd_seq_close(seq); + fprintf(stderr, "can't set client info\n"); + return 0; + } + + /* set subscription */ + memset(&subs, 0, sizeof(subs)); + if (parse_address(&subs.sender, argv[optind]) < 0) { + fprintf(stderr, "invalid sender address %s\n", argv[optind]); + return 1; + } + if (parse_address(&subs.dest, argv[optind + 1]) < 0) { + fprintf(stderr, "invalid destination address %s\n", argv[optind + 1]); + return 1; + } + subs.queue = queue; + subs.exclusive = exclusive; + subs.convert_time = convert_time; + subs.realtime = convert_real; + + if (command == UNSUBSCRIBE) { + if (snd_seq_get_port_subscription(seq, &subs) < 0) { + snd_seq_close(seq); + fprintf(stderr, "No subscription is found\n"); + return 1; + } + if (snd_seq_unsubscribe_port(seq, &subs) < 0) { + snd_seq_close(seq); + fprintf(stderr, "Disconnection failed (%s)\n", snd_strerror(errno)); + return 1; + } + } else { + if (snd_seq_get_port_subscription(seq, &subs) == 0) { + snd_seq_close(seq); + fprintf(stderr, "Connection is already subscribed\n"); + return 1; + } + if (snd_seq_subscribe_port(seq, &subs) < 0) { + snd_seq_close(seq); + fprintf(stderr, "Connection failed (%s)\n", snd_strerror(errno)); + return 1; + } + } + + snd_seq_close(seq); + + return 0; +} diff --git a/seq/aseqnet/Makefile.am b/seq/aseqnet/Makefile.am new file mode 100644 index 0000000..8ebc0c0 --- /dev/null +++ b/seq/aseqnet/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = -I$(top_srcdir)/include +LDADD = -lasound +EXTRA_DIST = README.aseqnet + +bin_PROGRAMS = aseqnet +aseqnet_SOURCES = aseqnet.c +man_MANS = aseqnet.1 diff --git a/seq/aseqnet/README.aseqnet b/seq/aseqnet/README.aseqnet new file mode 100644 index 0000000..65d7067 --- /dev/null +++ b/seq/aseqnet/README.aseqnet @@ -0,0 +1,52 @@ +================================================================ + ALSA sequencer connectors over network + ver.0.1 + Copyright (C) 1999-2000 Takashi Iwai +================================================================ + +* ASEQNET + +aseqnet is a sequencer client which sends/receives events over +network. Suppose two hosts (hostA and hostB) connected by network. +You need to run ALSA system on both hosts. Then, start aseqnet as a +server on hostA: + + hostA% aseqnet + sequencer opened: 128:0 + +A user client 128 with port 0 was opened. (The client number may +vary.) At next, start client on hostB. The argument is the hostname +where server is running. + + hostB% aseqnet hostA + sequencer opened: 132:0 + +Now events sent to hostA:128:0 is transferred to hostB:132:0, and vice +versa. + +You can connect these ports arbitrary to other sequencer ports. +For example, connect hostB:132:0 to a MIDI output device 65:0. The +aconnect utility can be used for this: + + hostB% aconnect 132:0 65:0 + +Events to hostA:128:0 will be delivered indirectly to hostB:65:0. +You'll hear MIDI sounds as following: + + hostA% pmidi -p 128:0 foo.mid + +The multiple clients may exist simultaneously. If hostC is connected +as a client to hostA, events from from hostA are sent to all connected +network clients, hostB and hostC. However, only one connection is +allowed from a client to a server. + +To disconnect network, stop all clients before server by ctrl-C or +sending signal to them. The server will automatically quit. + +The available options are: + + -p port : specify the TCP port number or TCP service name. + Default value is 9009 (I don't know it's allowed..) + -s addr : explicit read-subscription to the given address + (client:addr). + -d addr : explicit write-subscription to the given address. diff --git a/seq/aseqnet/aseqnet.1 b/seq/aseqnet/aseqnet.1 new file mode 100644 index 0000000..f53a3b1 --- /dev/null +++ b/seq/aseqnet/aseqnet.1 @@ -0,0 +1,78 @@ +.TH aseqnet 1 "January 1, 2000" +.LO 1 +.SH NAME +aseqnet \- ALSA sequencer connectors over network + +.SH SYNOPSIS +.B aseqnet +[remotehost] + +.SH DESCRIPTION +.B aseqnet +is an ALSA sequencer client which sends and receives event packets +over network. +Suppose two hosts connected by network, +.I hostA +as a server +and +.I hostB +as a client. +The ALSA sequencer system must be running on both hosts. +For creating the server port, run the following on hostA: +.IP "" 4 +hostA% aseqnet +.br +sequencer opened: 128:0 +.PP +Then a user client 128 with port 0 was opened on hostA. +(The client number may vary.) +For creating the (network-)client port, run +.B aseqnet +with the hostname of the server: +.IP "" 4 +hostB% aseqnet hostA +.br +sequencer opened: 132:0 +.PP +Now all events sent to hostA:128:0 are transferred to hostB:132:0, and vice +versa. +.PP +The ports created by +.B aseqnet +can be connected arbitrary to other sequencer ports via +.B aconnect(1). +For example, to connect hostB:132:0 to a MIDI output device 65:0: +.IP "" 4 +hostB% aconnect 132:0 65:0 +.PP +Then events to hostA:128:0 will be delivered to hostB:65:0. +The following command plays MIDI on +.I hostB. +.IP "" 4 +hostA% pmidi -p 128:0 foo.mid +.PP +The multiple clients may exist simultaneously. If +.I hostC +is connected as a client to hostA, events from from hostA are sent +to all connected network clients, i.e. hostB and hostC. +However, only one connection is allowed from a client to a server. +.PP +To disconnect network, stop all clients before server by ctrl-C or +sending signal to them. The server will automatically quit. + +.SH OPTIONS +.TP +.B \-p port +Specify the TCP port number or TCP service name. +.TP +.B \-s addr +Subscribe to the given address for read automatically. +.TP +.B \-d addr +Subscribe to the given address for write automatically. + +.SH "SEE ALSO" +aconnect(1), pmidi(1) + +.SH AUTHOR +Takashi Iwai . diff --git a/seq/aseqnet/aseqnet.c b/seq/aseqnet/aseqnet.c new file mode 100644 index 0000000..37ea64a --- /dev/null +++ b/seq/aseqnet/aseqnet.c @@ -0,0 +1,532 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * prototypes + */ +static void usage(void); +static void init_buf(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 9009 + +/* + * 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 int seqfd, 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; + + +/* + * main routine + */ + +static struct option long_option[] = { + {"port", 1, NULL, 'p'}, + {"source", 1, NULL, 's'}, + {"dest", 1, NULL, 'd'}, + {"help", 0, NULL, 'h'}, + {NULL, 0, NULL, 0}, +}; + +int main(int argc, char **argv) +{ + int c; + int port = DEFAULT_PORT; + char *source = NULL, *dest = NULL; + + while ((c = getopt_long(argc, argv, "p:s:d:", 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; + 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_server(port); + } else { + server_mode = 0; + max_connection = 1; + init_client(argv[optind], port); + } + + do_loop(); + + close_files(); + + return 0; +} + + +/* + * print usage + */ +static void usage(void) +{ + fprintf(stderr, "aseqnet - network client/server on ALSA sequencer\n"); + fprintf(stderr, " Copyright (C) 1999 Takashi Iwai\n"); + fprintf(stderr, "usage:\n"); + fprintf(stderr, " server mode: aseqnet [-options]\n"); + fprintf(stderr, " client mode: aseqnet [-options] server_host\n"); + fprintf(stderr, "options:\n"); + fprintf(stderr, " -p,--port # : sepcify TCP port (digit or service name)\n"); + fprintf(stderr, " -s,--source addr : read from given addr (client:port)\n"); + fprintf(stderr, " -d,--dest addr : write to given addr (client:port)\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; +} + +/* + * parse client:port argument + */ +static int parse_addr(snd_seq_addr_t *addr, char *arg) +{ + char *p; + + if (! isdigit(*arg)) + return -1; + if ((p = strpbrk(arg, ":.")) == NULL) + return -1; + addr->client = atoi(arg); + addr->port = atoi(p + 1); + return 0; +} + + +/* + * close all files + */ +static void close_files(void) +{ + int i; +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; + + if (snd_seq_open(&handle, SND_SEQ_OPEN) < 0) { + perror("snd_seq_open"); + exit(1); + } + seqfd = snd_seq_file_descriptor(handle); + snd_seq_block_mode(handle, 0); + + /* 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); + } + fprintf(stderr, "sequencer opened: %d:%d\n", + snd_seq_client_id(handle), seq_port); + + /* explicit subscriptions */ + if (source) { + /* read subscription */ + if (parse_addr(&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 (parse_addr(&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, "%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; + 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) { + fprintf(stderr, "can't create a socket\n"); + exit(1); + } + + if (bind(sockfd, &addr, sizeof(addr)) < 0) { + fprintf(stderr, "can't bind address\n"); + exit(1); + } + + if (listen(sockfd, 5) < 0) { + fprintf(stderr, "can't listen on socket\n"); + 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; + int 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) { + fprintf(stderr, "can't accept\n"); + exit(1); + } + 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 fd; + + if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){ + fprintf(stderr, "can't create socket\n"); + 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, &addr, sizeof(addr)) < 0) { + fprintf(stderr,"can't connect\n"); + exit(1); + } + fprintf(stderr, "ok.. connected\n"); + netfd[0] = fd; + cur_connected = 1; +} + +/* + * set file descriptor + */ +static void set_fd(int fd, fd_set *p, int *width) +{ + FD_SET(fd, p); + if (fd >= *width) + *width = fd + 1; +} + +/* + * event loop + */ +static void do_loop(void) +{ + fd_set rfd; + int i, rc, width; + + for (;;) { + FD_ZERO(&rfd); + width = 0; + set_fd(seqfd, &rfd, &width); + if (server_mode) + set_fd(sockfd, &rfd, &width); + for (i = 0; i < max_connection; i++) { + if (netfd[i] >= 0) + set_fd(netfd[i], &rfd, &width); + } + rc = select(width, &rfd, NULL, NULL, NULL); + if (rc <= 0) + exit(1); + if (server_mode) { + if (FD_ISSET(sockfd, &rfd)) + start_connection(); + } + if (FD_ISSET(seqfd, &rfd)) { + if (copy_local_to_remote()) + break; + } + for (i = 0; i < max_connection; i++) { + if (netfd[i] < 0) + continue; + if (FD_ISSET(netfd[i], &rfd)) { + 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; +} + +/* + * 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_free_event(ev); + continue; + } + if (snd_seq_ev_is_variable(ev)) { + int len; + len = sizeof(snd_seq_event_t) + ev->data.ext.len; + buf = get_writebuf(len); + memcpy(buf, ev, sizeof(snd_seq_event_t)); + memcpy(buf + sizeof(snd_seq_event_t), ev->data.ext.ptr, ev->data.ext.len); + } else { + buf = get_writebuf(sizeof(snd_seq_event_t)); + memcpy(buf, ev, sizeof(snd_seq_event_t)); + } + } + 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) { + fprintf(stderr, "disconnected\n"); + return 1; + } + + while (count > 0) { + ev = snd_seq_create_event(); + if (ev == NULL) { + fprintf(stderr, "can't malloc\n"); + exit(1); + } + memcpy(ev, buf, sizeof(snd_seq_event_t)); + buf += sizeof(snd_seq_event_t); + count -= sizeof(snd_seq_event_t); + if (snd_seq_ev_is_variable(ev) && ev->data.ext.len > 0) { + ev->data.ext.ptr = malloc(ev->data.ext.len); + if (ev->data.ext.ptr == NULL) { + fprintf(stderr, "can't malloc\n"); + exit(1); + } + memcpy(ev->data.ext.ptr, buf, ev->data.ext.len); + 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); + snd_seq_event_output(handle, ev); + snd_seq_free_event(ev); + } + + snd_seq_flush_output(handle); + return 0; +} +