summaryrefslogtreecommitdiff
path: root/seq
diff options
context:
space:
mode:
authorJaroslav Kysela <perex@perex.cz>2000-01-04 12:39:00 +0000
committerJaroslav Kysela <perex@perex.cz>2000-01-04 12:39:00 +0000
commitfa88a970787507697bf01d5eba5deb4b27d07dff (patch)
tree3167488e76bfa6efebf12998ba5d6005c225fb51 /seq
parent94179487220b066de77b2dc78919fde47de9d901 (diff)
downloadalsa-utils-fa88a970787507697bf01d5eba5deb4b27d07dff.tar.gz
Moved aconnect and aseqnet sequencer utilities into the alsa-utils package.
Diffstat (limited to 'seq')
-rw-r--r--seq/Makefile.am1
-rw-r--r--seq/aconnect/Makefile.am7
-rw-r--r--seq/aconnect/README.aconnect50
-rw-r--r--seq/aconnect/aconnect.1127
-rw-r--r--seq/aconnect/aconnect.c305
-rw-r--r--seq/aseqnet/Makefile.am7
-rw-r--r--seq/aseqnet/README.aseqnet52
-rw-r--r--seq/aseqnet/aseqnet.178
-rw-r--r--seq/aseqnet/aseqnet.c532
9 files changed, 1159 insertions, 0 deletions
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 <iwai@ww.uni-erlangen.de>.
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 <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/asoundlib.h>
+
+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 <iwai@ww.uni-erlangen.de>.
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 <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/asoundlib.h>
+#include <getopt.h>
+#include <signal.h>
+
+/*
+ * 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;
+}
+