alsa-utils/seq/aseqnet/aseqnet.c
Andrea Piras 0e21f4d864 added an option to aseqnet to optionally set the midi process name
This option allows to run multiple instances of aseqnet without having
to double check the assigned port number, since each one can get spawned
with a unique name.

Fixes: https://github.com/alsa-project/alsa-utils/pull/95
Signed-off-by: Andrea Piras <andrea.piras.85@gmail.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2021-06-14 12:00:44 +02:00

623 lines
13 KiB
C

/*
* 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, char *name);
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'},
{"name", 1, NULL, 'n'},
{"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;
char *name = NULL;
#ifdef ENABLE_NLS
setlocale(LC_ALL, "");
textdomain(PACKAGE);
#endif
while ((c = getopt_long(argc, argv, "p:s:d:n:,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 'n':
name = 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, name);
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 # : specify 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(_(" -n,--name value : use a specific midi process name\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, char* name)
{
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 (name)
snd_seq_set_client_name(handle, name);
else {
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) {
ssize_t wrlen = write(netfd[i], writebuf, cur_wrlen);
if (wrlen != (ssize_t)cur_wrlen)
fprintf(stderr, "write error: %s", wrlen < 0 ? strerror(errno) : "short");
}
}
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;
}