aboutsummaryrefslogtreecommitdiff
path: root/lib/timidity/interface/server_c.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/timidity/interface/server_c.c')
-rw-r--r--lib/timidity/interface/server_c.c1599
1 files changed, 1599 insertions, 0 deletions
diff --git a/lib/timidity/interface/server_c.c b/lib/timidity/interface/server_c.c
new file mode 100644
index 0000000000..2c41daefad
--- /dev/null
+++ b/lib/timidity/interface/server_c.c
@@ -0,0 +1,1599 @@
+/*
+ TiMidity++ -- MIDI to WAVE converter and player
+ Copyright (C) 1999-2004 Masanao Izumo <iz@onicos.co.jp>
+ Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ server_c.c - TiMidity server written by Masanao Izumo <iz@onicos.co.jp>
+ Mon Apr 5 1999: Initial created.
+
+ Launch TiMidity server: (example)
+ % timidity -ir 7777
+
+ Protcol note:
+ The protocol is based on OSS interface.
+ TiMidity server has 2 TCP/IP connection. They are control port and
+ data port.
+ Control port:
+ ASCII text protocol like FTP control port. See command_help for
+ control protocol in this source code.
+ Data port:
+ One way Binary stream data from client to server. The format of
+ stream is same as OSS sequencer stream.
+
+ TODO:
+ Protocol specification to be documented.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#ifndef NO_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#include <signal.h>
+
+#ifdef HAVE_SYS_SOUNDCARD_H
+#include <sys/soundcard.h>
+#else
+#include "server_defs.h"
+#endif /* HAVE_SYS_SOUNDCARD_H */
+
+#include "timidity.h"
+#include "common.h"
+#include "controls.h"
+#include "instrum.h"
+#include "playmidi.h"
+#include "readmidi.h"
+#include "recache.h"
+#include "output.h"
+#include "aq.h"
+#include "timer.h"
+
+/* #define DEBUG_DUMP_SEQ 1 */
+#define MIDI_COMMAND_PER_SEC 100
+#define DEFAULT_LOW_TIMEAT 0.4
+#define DEFAULT_HIGH_TIMEAT 0.6
+#define DONT_STOP_AUDIO 1
+#define DEFAULT_TIMEBASE 100 /* HZ? */
+#define MAXTICKDIFF 150
+#define SIG_TIMEOUT_SEC 3
+
+
+static int cmd_help(int argc, char **argv);
+static int cmd_open(int argc, char **argv);
+static int cmd_close(int argc, char **argv);
+static int cmd_timebase(int argc, char **argv);
+static int cmd_reset(int argc, char **argv);
+static int cmd_patch(int argc, char **argv);
+static int cmd_quit(int argc, char **argv);
+static int cmd_queue(int argc, char **argv);
+static int cmd_maxqueue(int argc, char **argv);
+static int cmd_time(int argc, char **argv);
+static int cmd_nop(int argc, char **argv);
+static int cmd_autoreduce(int argc, char **argv);
+static int cmd_setbuf(int argc, char **argv);
+
+struct
+{
+ char *cmd, *help;
+ int minarg, maxarg;
+ int (* proc)(int argc, char **argv); /* argv[0] is command name
+ * argv[1..argc-1] is the arg.
+ * return: 0=success, -1=fatal-error,
+ * 1=connection-closed
+ */
+} cmd_table[] =
+{
+ {"HELP",
+ "HELP\tDisplay this message",
+ 1, 1, cmd_help},
+ {"OPEN",
+ "OPEN {lsb|msb}\n"
+ "\tOpen data connection\n"
+ "\tlsb: The byte order of data stream is LSB\n"
+ "\tmsb: The byte order of data stream is MSB",
+ 2, 2, cmd_open},
+ {"CLOSE",
+ "CLOSE\tShutdown current data connection",
+ 1, 1, cmd_close},
+ {"TIMEBASE",
+ "TIMEBASE [timebase]\n\tSet time base",
+ 1, 2, cmd_timebase},
+ {"RESET",
+ "RESET\tInitialize all of MIDI status",
+ 1, 1, cmd_reset},
+ {"PATCH",
+ "PATCH {drumset|bank} <bank-no> <prog-no>\n\tLoad specified patch",
+ 4, 4, cmd_patch},
+ {"QUIT",
+ "QUIT\tClose control connection",
+ 1, 1, cmd_quit},
+ {"QUEUE",
+ "QUEUE\tTiMidity tells the time of audio buffer queue in second",
+ 1, 1, cmd_queue},
+ {"MAXQUEUE",
+ "MAXQUEUE\n"
+ "\tTiMidity tells the maxmum time of audio buffer queue in second",
+ 1, 1, cmd_maxqueue},
+ {"TIME",
+ "TIME\tTiMidity tells the current playing time in second",
+ 1, 1, cmd_time},
+ {"NOP",
+ "NOP\tDo nothing",
+ 1, 1, cmd_nop},
+ {"AUTOREDUCE",
+ "AUTOREDUCE {on|off} [msec]\n\tEnable/Disable auto voice reduction",
+ 2, 3, cmd_autoreduce},
+ {"SETBUF",
+ "SETBUF low hi\n\tSpecify low/hi sec of buffer queue",
+ 3, 3, cmd_setbuf},
+
+ {NULL, NULL, 0, 0, NULL} /* terminate */
+};
+
+/*
+TEMPO [<tempo>]\n\
+ Change the tempo. If the argument is omitted, TiMidity tells the\n\
+ current tempo.\n\
+KEYSHIFT <{+|-}offs>\n\
+ Change the base key. (0 to reset)\n\
+SPEED <{+|-}offs>\n\
+ Change the play speed. (0 to reset)\n\
+MODE {gs|xg|gm}\n\
+ Specify default MIDI system mode\n\
+SYNTH [gus|awe]\n\
+ Specify Synth type emulation. (no argument to default)\n\
+*/
+
+
+static int ctl_open(int using_stdin, int using_stdout);
+static void ctl_close(void);
+static int ctl_read(int32 *valp);
+static int cmsg(int type, int verbosity_level, char *fmt, ...);
+static void ctl_event(CtlEvent *e);
+static void ctl_pass_playing_list(int n, char *args[]);
+
+/**********************************/
+/* export the interface functions */
+
+#define ctl server_control_mode
+
+ControlMode ctl=
+{
+ "remote interface", 'r',
+ 1,0,0,
+ 0,
+ ctl_open,
+ ctl_close,
+ ctl_pass_playing_list,
+ ctl_read,
+ cmsg,
+ ctl_event
+};
+
+
+struct fd_read_buffer
+{
+ char buff[BUFSIZ];
+ /* count: beginning of read pointer
+ * size: end of read pointer
+ * fd: file descripter for input
+ */
+ int count, size, fd;
+};
+static int fdgets(char *buff, size_t buff_size, struct fd_read_buffer *p);
+static int fdputs(char *s, int fd);
+static uint32 data2long(uint8* data);
+static uint16 data2short(uint8* data);
+static int do_control_command_nonblock(void);
+
+static struct fd_read_buffer control_fd_buffer;
+static int data_fd = -1, control_fd = -1;
+static int data_port, control_port;
+static int is_lsb_data = 1;
+static int curr_timebase = DEFAULT_TIMEBASE;
+static int32 sample_correction;
+static int32 sample_increment;
+static int32 sample_correction;
+static int32 sample_cum;
+static int32 curr_event_samples, event_time_offset;
+static int32 curr_tick, tick_offs;
+static double start_time;
+static int tmr_running;
+
+static int is_system_prefix = 0;
+static struct sockaddr_in control_client;
+static double low_time_at = 0.3;
+static double high_time_at = 0.5;
+static FILE *outfp;
+
+/*ARGSUSED*/
+static int ctl_open(int using_stdin, int using_stdout)
+{
+ ctl.opened = 1;
+ ctl.flags &= ~(CTLF_LIST_RANDOM|CTLF_LIST_SORT);
+ if(using_stdout)
+ outfp = stderr;
+ else
+ outfp = stdout;
+ return 0;
+}
+
+static void ctl_close(void)
+{
+ if(!ctl.opened)
+ return;
+ if(data_fd != -1)
+ {
+ close(data_fd);
+ data_fd = -1;
+ }
+ if(control_fd != -1)
+ {
+ close(control_fd);
+ control_fd = -1;
+ }
+}
+
+/*ARGSUSED*/
+static int ctl_read(int32 *valp)
+{
+ if(data_fd != -1)
+ do_control_command_nonblock();
+ return RC_NONE;
+}
+
+static int cmsg(int type, int verbosity_level, char *fmt, ...)
+{
+ va_list ap;
+
+ if((type==CMSG_TEXT || type==CMSG_INFO || type==CMSG_WARNING) &&
+ ctl.verbosity < verbosity_level)
+ return 0;
+
+ if(outfp == NULL)
+ outfp = stderr;
+
+ va_start(ap, fmt);
+ vfprintf(outfp, fmt, ap);
+ fputs(NLS, outfp);
+ fflush(outfp);
+ va_end(ap);
+
+ return 0;
+}
+
+static void ctl_event(CtlEvent *e)
+{
+}
+
+static int pasv_open(int *port)
+{
+ int sfd;
+ struct sockaddr_in server;
+
+ if((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+ {
+ perror("socket");
+ return -1;
+ }
+
+ memset(&server, 0, sizeof(server));
+ server.sin_port = htons(*port);
+ server.sin_family = AF_INET;
+ server.sin_addr.s_addr = htonl(INADDR_ANY);
+
+#ifdef SO_REUSEADDR
+ {
+ int on = 1;
+ setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (caddr_t)&on, sizeof(on));
+ }
+#endif /* SO_REUSEADDR */
+
+ ctl.cmsg(CMSG_INFO, VERB_DEBUG, "Bind TCP/IP port=%d", *port);
+ if(bind(sfd, (struct sockaddr *)&server, sizeof(server)) < 0)
+ {
+ perror("bind");
+ close(sfd);
+ return -1;
+ }
+ if(*port == 0)
+ {
+ int len = sizeof(server);
+ if(getsockname(sfd, (struct sockaddr *)&server, &len) < 0)
+ {
+ perror("getsockname");
+ close(sfd);
+ return -1;
+ }
+ *port = ntohs(server.sin_port);
+ }
+
+ /* Set it up to wait for connections. */
+ if(listen(sfd, 1) < 0)
+ {
+ perror("listen");
+ close(sfd);
+ return -1;
+ }
+
+ return sfd;
+}
+
+static RETSIGTYPE sig_timeout(int sig)
+{
+ signal(SIGALRM, sig_timeout); /* For SysV base */
+ /* Expect EINTR */
+}
+
+static void doit(void);
+static int send_status(int status, char *message, ...);
+static void compute_sample_increment(void);
+static void server_reset(void);
+
+static void ctl_pass_playing_list(int n, char *args[])
+{
+ int sock;
+
+ if(n != 2 && n != 1)
+ {
+ fprintf(stderr, "Usage: timidity -ir control-port [data-port]\n");
+ return;
+ }
+
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN); /* Handle broken pipe */
+#endif /* SIGPIPE */
+
+ control_port = atoi(args[0]);
+ if(n == 2)
+ data_port = atoi(args[1]);
+ else
+ data_port = 0;
+
+ if (control_port) {
+ sock = pasv_open(&control_port);
+ if(sock == -1)
+ return;
+ }
+ opt_realtime_playing = 1; /* Enable loading patch while playing */
+ allocate_cache_size = 0; /* Don't use pre-calclated samples */
+/* aq_set_soft_queue(-1.0, 0.0); */
+ alarm(0);
+ signal(SIGALRM, sig_timeout);
+
+ play_mode->close_output();
+ while(1)
+ {
+ int addrlen;
+
+ addrlen = sizeof(control_client);
+ memset(&control_client, 0, addrlen);
+
+ if (control_port) {
+ if((control_fd = accept(sock,
+ (struct sockaddr *)&control_client,
+ &addrlen)) < 0)
+ {
+ if(errno == EINTR)
+ continue;
+ perror("accept");
+ close(sock);
+ return;
+ }
+ }
+ else control_fd = 0;
+
+ if(play_mode->open_output() < 0)
+ {
+ ctl.cmsg(CMSG_FATAL, VERB_NORMAL,
+ "Couldn't open %s (`%c')",
+ play_mode->id_name, play_mode->id_character);
+ send_status(510, "Couldn't open %s (`%c')",
+ play_mode->id_name, play_mode->id_character);
+ if (control_port) {
+ close(control_fd);
+ control_fd = 1;
+ }
+ continue;
+ }
+
+ server_reset();
+
+ ctl.cmsg(CMSG_INFO, VERB_NOISY, "Connected");
+ doit();
+ ctl.cmsg(CMSG_INFO, VERB_NOISY, "Connection closed");
+
+ play_mode->close_output();
+
+ if(control_fd != -1 && control_port)
+ {
+ close(control_fd);
+ control_fd = -1;
+ }
+ if(data_fd != -1)
+ {
+ close(data_fd);
+ data_fd = -1;
+ }
+ free_instruments(0);
+ free_global_mblock();
+ if (!control_port)
+ break;
+ }
+}
+
+#define MAX_GETCMD_PARAMS 8
+/* return:
+ * 0: success
+ * 1: error
+ *-1: fatal error (will be close the connection)
+ */
+static int control_getcmd(char **params, int *nparams)
+{
+ static char buff[BUFSIZ];
+ int n;
+
+ /* read line */
+ n = fdgets(buff, sizeof(buff), &control_fd_buffer);
+ if(n == -1)
+ {
+ perror("read");
+ return -1;
+ }
+ if(n == 0)
+ return 1;
+ if((params[0] = strtok(buff, " \t\r\n\240")) == NULL)
+ return 0;
+ *nparams = 0;
+ while(params[*nparams] && *nparams < MAX_GETCMD_PARAMS)
+ params[++(*nparams)] = strtok(NULL," \t\r\n\240");
+ return 0;
+}
+
+static int send_status(int status, char *message, ...)
+{
+ va_list ap;
+ char buff[BUFSIZ];
+
+ va_start(ap, message);
+ sprintf(buff, "%03d ", status);
+ vsnprintf(buff + 4, sizeof(buff) - 5, message, ap);
+ va_end(ap);
+ strncat(buff, "\n", BUFSIZ - strlen(buff) - 1);
+ buff[BUFSIZ-1] = '\0'; /* force terminate */
+ if(write(control_fd, buff, strlen(buff)) == -1)
+ return -1;
+ return 0;
+}
+
+static void seq_play_event(MidiEvent *ev)
+{
+ if(tmr_running)
+ ev->time = curr_event_samples;
+ else
+ {
+ if(IS_STREAM_TRACE)
+ {
+ event_time_offset += play_mode->rate / MIDI_COMMAND_PER_SEC;
+ ev->time = curr_event_samples;
+ }
+ else
+ {
+ double past_time = get_current_calender_time() - start_time;
+ if(play_mode->flag & PF_PCM_STREAM)
+ past_time += high_time_at;
+ ev->time = (int32)(past_time * play_mode->rate);
+ }
+ }
+ ev->time += event_time_offset;
+ play_event(ev);
+}
+
+static void tmr_reset(void)
+{
+ curr_event_samples =
+ event_time_offset =
+ sample_cum = 0;
+ playmidi_tmr_reset();
+ curr_timebase = DEFAULT_TIMEBASE;
+ curr_tick = tick_offs = 0;
+ start_time = get_current_calender_time();
+}
+
+static void compute_sample_increment(void)
+{
+ double a;
+ a = (double)current_play_tempo * (double)play_mode->rate
+ * (65536.0/500000.0) / (double)curr_timebase,
+ sample_correction = (int32)(a) & 0xFFFF;
+ sample_increment = (int32)(a) >> 16;
+}
+
+static void add_tick(int tick)
+{
+ int32 samples_to_do;
+ MidiEvent ev;
+
+ samples_to_do = sample_increment * tick;
+ sample_cum += sample_correction * tick;
+ if(sample_cum & 0xFFFF0000)
+ {
+ samples_to_do += ((sample_cum >> 16) & 0xFFFF);
+ sample_cum &= 0x0000FFFF;
+ }
+ curr_event_samples += samples_to_do;
+ curr_tick += tick;
+ ev.type = ME_NONE;
+ seq_play_event(&ev);
+}
+
+static int tick2sample(int tick)
+{
+ int32 samples, cum;
+
+ samples = sample_increment * tick;
+ cum = sample_correction * tick;
+ if(cum & 0xFFFF0000)
+ samples += ((sample_cum >> 16) & 0xFFFF);
+ return samples;
+}
+
+int time2tick(double sec)
+{
+ return (int)(sec * curr_timebase);
+}
+
+
+static void stop_playing(void)
+{
+ if(upper_voices)
+ {
+ MidiEvent ev;
+ ev.type = ME_EOT;
+ ev.a = 0;
+ ev.b = 0;
+ seq_play_event(&ev);
+ aq_flush(0);
+ }
+}
+
+static int do_control_command(void);
+static int do_control_command_nonblock(void);
+static int do_sequencer(void);
+static void do_chn_voice(uint8 *);
+static void do_chn_common(uint8 *);
+static void do_timing(uint8 *);
+static void do_sysex(uint8 *, int len);
+static void do_extended(uint8 *);
+static void do_timeout(void);
+static void server_seq_sync(double tm);
+
+static uint8 data_buffer[BUFSIZ];
+static int data_buffer_len;
+
+static void doit(void)
+{
+ memset(&control_fd_buffer, 0, sizeof(control_fd_buffer));
+ control_fd_buffer.fd = control_fd;
+
+ send_status(220, "TiMidity++ %s%s ready",
+ (strcmp(timidity_version, "current")) ? "v" : "",
+ timidity_version);
+
+/* while(data_fd != -1 && control_fd != -1) */
+ while(control_fd != -1)
+ {
+ fd_set fds;
+ int n, maxfd;
+
+ if(data_fd == -1)
+ {
+ if(do_control_command())
+ break;
+ }
+ else
+ {
+ long usec;
+
+ FD_ZERO(&fds);
+ FD_SET(control_fd, &fds);
+ FD_SET(data_fd, &fds);
+ if(control_fd > data_fd)
+ maxfd = control_fd;
+ else
+ maxfd = data_fd;
+
+ if(data_fd != -1)
+ {
+ double wait_time;
+ int32 filled;
+
+ filled = aq_filled();
+ if(!tmr_running && filled <= 0)
+ usec = -1;
+ else
+ {
+ wait_time = (double)filled / play_mode->rate
+ - low_time_at;
+ if(wait_time <= 0)
+ usec = 0;
+ else
+ usec = (long)(wait_time * 1000000);
+ }
+ }
+ else
+ usec = -1;
+
+ if(usec >= 0)
+ {
+ struct timeval timeout;
+ timeout.tv_sec = usec / 1000000;
+ timeout.tv_usec = usec % 1000000;
+ n = select(maxfd + 1, &fds, NULL, NULL, &timeout);
+ }
+ else
+ n = select(maxfd + 1, &fds, NULL, NULL, NULL);
+
+ if(n < 0)
+ {
+ perror("select");
+ break;
+ }
+
+ if(n == 0)
+ {
+ if(ctl.verbosity >= VERB_DEBUG)
+ {
+ putchar(',');
+ fflush(stdout);
+ }
+ do_timeout();
+ continue;
+ }
+
+ if(control_fd != -1 && FD_ISSET(control_fd, &fds))
+ {
+ if(do_control_command())
+ {
+ close(control_fd);
+ control_fd = -1;
+ }
+ }
+ else if(data_fd != -1 && FD_ISSET(data_fd, &fds))
+ {
+ if(do_sequencer())
+ {
+ close(data_fd);
+ data_fd = -1;
+ send_status(403, "Data connection is closed");
+ }
+ }
+ }
+ }
+
+ if(data_fd != -1)
+ stop_playing();
+}
+
+static void do_timeout(void)
+{
+ double fill_time;
+
+ if(data_fd == -1 || !IS_STREAM_TRACE)
+ return;
+ aq_add(NULL, 0);
+ fill_time = high_time_at - (double)aq_filled() / play_mode->rate;
+ if(fill_time <= 0)
+ return;
+
+ if(tmr_running)
+ add_tick(time2tick(fill_time));
+ else
+ {
+ MidiEvent ev;
+ event_time_offset += (int32)(fill_time *
+ play_mode->rate);
+ ev.time = curr_event_samples + event_time_offset;
+ ev.type = ME_NONE;
+ play_event(&ev);
+ }
+}
+
+/* -1=error, 0=success, 1=connection-closed */
+static int data_flush(int discard)
+{
+ fd_set fds;
+ char buff[BUFSIZ];
+ struct timeval timeout;
+ int n;
+
+ while(1)
+ {
+ FD_ZERO(&fds);
+ FD_SET(data_fd, &fds);
+ timeout.tv_sec = 0;
+ if(discard)
+ timeout.tv_usec = 100000;
+ else
+ timeout.tv_usec = 0;
+ if((n = select(data_fd + 1, &fds, NULL, NULL, &timeout)) < 0)
+ {
+ perror("select");
+ return -1;
+ }
+ if(n == 0)
+ break;
+ if(discard)
+ {
+ if((n = read(data_fd, buff, sizeof(buff))) < 0)
+ {
+ perror("read");
+ return -1;
+ }
+ if(n == 0)
+ return 1;
+ }
+ else
+ {
+ int status;
+ if((status = do_sequencer()) != 0)
+ return status;
+ }
+ }
+ return 0;
+}
+
+static void server_seq_sync(double tm)
+{
+ double t;
+ aq_soft_flush();
+ t = (double)aq_filled() / play_mode->rate;
+ if(t > tm)
+ usleep((unsigned long)((t - tm) * 1000000));
+}
+
+static void server_reset(void)
+{
+ playmidi_stream_init();
+ if(free_instruments_afterwards)
+ free_instruments(0);
+ data_buffer_len = 0;
+ do_sysex(NULL, 0); /* Initialize SysEx buffer */
+ low_time_at = DEFAULT_LOW_TIMEAT;
+ high_time_at = DEFAULT_HIGH_TIMEAT;
+ reduce_voice_threshold = 0; /* Disable auto reduction voice */
+ compute_sample_increment();
+ tmr_reset();
+ tmr_running = 0;
+ start_time = get_current_calender_time();
+}
+
+/* -1=error, 0=success, 1=connection-closed */
+static int do_control_command(void)
+{
+ int status;
+ char *params[MAX_GETCMD_PARAMS];
+ int nparams;
+ int i;
+
+ if((status = control_getcmd(params, &nparams)) == -1)
+ return -1;
+ if(status == 1)
+ {
+ send_status(500, "Error");
+ return 1;
+ }
+
+ if(nparams == 0 || *params == NULL || **params == '\0')
+ return 0;
+
+ for(i = 0; cmd_table[i].cmd; i++)
+ if(strcasecmp(params[0], cmd_table[i].cmd) == 0)
+ {
+ if(nparams < cmd_table[i].minarg)
+ return send_status(501, "'%s': Arguments is too few",
+ params[0]);
+ if(nparams > cmd_table[i].maxarg)
+ return send_status(501, "'%s': Arguments is too many",
+ params[0]);
+ return cmd_table[i].proc(nparams, params);
+ }
+ return send_status(500, "'%s': command not understood.", params[0]);
+}
+
+static int cmd_help(int argc, char **argv)
+{
+ int i;
+
+ if(send_status(200, "Help ok"))
+ return -1;
+
+ for(i = 0; cmd_table[i].cmd; i++)
+ {
+ if(fdputs(cmd_table[i].help, control_fd))
+ return -1;
+ if(fdputs("\n", control_fd))
+ return -1;
+ }
+ return fdputs(".\n", control_fd);
+}
+
+static int cmd_open(int argc, char **argv)
+{
+ int sock;
+ struct sockaddr_in in;
+ int addrlen;
+ int port;
+
+ if(data_fd != -1)
+ return send_status(125, "Data connection is already opened");
+
+ if(strcasecmp(argv[1], "lsb") == 0)
+ is_lsb_data = 1;
+ else if(strcasecmp(argv[1], "msb") == 0)
+ is_lsb_data = 0;
+ else
+ return send_status(502, "OPEN: Invalid argument: %s", argv[1]);
+
+ port = data_port;
+ if((sock = pasv_open(&port)) == -1)
+ return send_status(511, "Can't open data connection");
+
+ addrlen = sizeof(in);
+ memset(&in, 0, addrlen);
+ send_status(200, "%d is ready acceptable", port);
+
+ alarm(SIG_TIMEOUT_SEC);
+ data_fd = accept(sock, (struct sockaddr *)&in, &addrlen);
+ alarm(0);
+
+ if(data_fd < 0)
+ {
+ send_status(512, "Accept error");
+ close(sock);
+ return 0;
+ }
+ close(sock);
+
+ if(control_client.sin_addr.s_addr != in.sin_addr.s_addr)
+ return send_status(513, "Security violation: Address mismatch");
+
+ send_status(200, "Ready data connection");
+ data_buffer_len = 0;
+ do_sysex(NULL, 0); /* Initialize SysEx buffer */
+ tmr_reset();
+
+ return 0;
+}
+
+static int cmd_close(int argc, char **argv)
+{
+ if(data_fd != -1)
+ {
+ close(data_fd);
+ data_fd = -1;
+ return send_status(302, "Data connection is closed");
+ }
+ return send_status(302, "Data connection is already closed");
+}
+
+static int cmd_queue(int argc, char **argv)
+{
+ int32 qsamples;
+
+ aq_add(NULL, 0); /* Update software queue */
+ if(!aq_fill_buffer_flag)
+ qsamples = aq_soft_filled() + aq_filled();
+ else
+ qsamples = 0;
+ return send_status(200, "%f sec", (double)qsamples / play_mode->rate);
+}
+
+static int cmd_maxqueue(int argc, char **argv)
+{
+ return send_status(200, "%f sec",
+ (double)aq_get_dev_queuesize() / play_mode->rate);
+}
+
+static int cmd_time(int argc, char **argv)
+{
+ return send_status(200, "%f sec", (double)aq_samples());
+}
+
+static int cmd_quit(int argc, char **argv)
+{
+ send_status(200, "Bye");
+ return 1;
+}
+
+static int cmd_timebase(int argc, char **argv)
+{
+ int i;
+
+ if(argc == 1)
+ return send_status(200, "%d OK", curr_timebase);
+ i = atoi(argv[1]);
+ if(i < 1)
+ i = 1;
+ else if(i > 1000)
+ i = 1000;
+ if(i != curr_timebase)
+ {
+ curr_timebase = i;
+ compute_sample_increment();
+ tick_offs = curr_tick;
+ start_time = get_current_calender_time();
+ }
+ return send_status(200, "OK");
+}
+
+static int cmd_patch(int argc, char **argv)
+{
+ int dr, bank, prog;
+
+ if(strcasecmp(argv[1], "drumset") == 0)
+ dr = 1;
+ else if(strcasecmp(argv[1], "bank") == 0)
+ dr = 0;
+ else
+ return send_status(502, "PATCH: Invalid argument: %s", argv[1]);
+
+ bank = atoi(argv[2]);
+ prog = atoi(argv[3]);
+ if(bank < 0 || bank > 127 || prog < 0 || prog > 127)
+ return send_status(502, "PATCH: Invalid argument");
+ if(play_midi_load_instrument(dr, bank, prog) == NULL)
+ return send_status(514, "PATCH: Can't load the patch");
+ return send_status(200, "OK");
+}
+
+static int cmd_reset(int argc, char **argv)
+{
+ int status;
+
+ if(data_fd >= 0)
+ {
+ stop_playing();
+ if((status = data_flush(1)) != 0)
+ return status;
+ }
+ server_reset();
+ return send_status(200, "OK");
+}
+
+static int cmd_autoreduce(int argc, char **argv)
+{
+ if(strcasecmp(argv[1], "on") == 0)
+ {
+ if(argc == 3)
+ reduce_voice_threshold = atoi(argv[2]);
+ else
+ reduce_voice_threshold = -1;
+ }
+ else if(strcasecmp(argv[1], "off") == 0)
+ reduce_voice_threshold = 0;
+ else
+ return send_status(502, "AUTOREDUCE: Invalid argument: %s",
+ argv[1]);
+ return send_status(200, "OK");
+}
+
+static int cmd_setbuf(int argc, char **argv)
+{
+ low_time_at = atof(argv[1]);
+ high_time_at = atof(argv[1]);
+ return send_status(200, "OK");
+}
+
+static int cmd_nop(int argc, char **argv)
+{
+ return send_status(200, "NOP OK");
+}
+
+static int do_control_command_nonblock(void)
+{
+ struct timeval timeout;
+ int n;
+ fd_set fds;
+
+ if(control_fd == -1)
+ return 1;
+ FD_ZERO(&fds);
+ FD_SET(control_fd, &fds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ n = select(control_fd + 1, &fds, NULL, NULL, &timeout);
+ if(n > 0 && FD_ISSET(control_fd, &fds))
+ return do_control_command();
+ return 0;
+}
+
+static int fdgets(char *buff, size_t buff_size, struct fd_read_buffer *p)
+{
+ int n, len, count, size, fd;
+ char *buff_endp = buff + buff_size - 1, *pbuff, *beg;
+
+ fd = p->fd;
+ if(buff_size == 0)
+ return 0;
+ else if(buff_size == 1) /* buff == buff_endp */
+ {
+ *buff = '\0';
+ return 0;
+ }
+
+ len = 0;
+ count = p->count;
+ size = p->size;
+ pbuff = p->buff;
+ beg = buff;
+ do
+ {
+ if(count == size)
+ {
+ if((n = read(fd, pbuff, BUFSIZ)) <= 0)
+ {
+ *buff = '\0';
+ if(n == 0)
+ {
+ p->count = p->size = 0;
+ return buff - beg;
+ }
+ return -1; /* < 0 error */
+ }
+ count = p->count = 0;
+ size = p->size = n;
+ }
+ *buff++ = pbuff[count++];
+ } while(*(buff - 1) != '\n' && buff != buff_endp);
+ *buff = '\0';
+ p->count = count;
+ return buff - beg;
+}
+
+static int fdputs(char *s, int fd)
+{
+ if(write(fd, s, strlen(s)) == -1)
+ return -1;
+ return 0;
+}
+
+#ifdef LITTLE_ENDIAN
+static uint32 data2long(uint8* data)
+{
+ uint32 x;
+ memcpy(&x, data, sizeof(x));
+ if(!is_lsb_data)
+ x = XCHG_LONG(x);
+ return x;
+}
+static uint16 data2short(uint8* data)
+{
+ uint16 x;
+ memcpy(&x, data, sizeof(x));
+ if(!is_lsb_data)
+ x = XCHG_SHORT(x);
+ return x;
+}
+#else
+static uint32 data2long(uint8* data)
+{
+ uint32 x;
+ memcpy(&x, data, sizeof(x));
+ if(is_lsb_data)
+ x = XCHG_LONG(x);
+ return x;
+}
+static uint16 data2short(uint8* data)
+{
+ uint16 x;
+ memcpy(&x, data, sizeof(x));
+ if(is_lsb_data)
+ x = XCHG_SHORT(x);
+ return x;
+}
+#endif
+
+static int do_sequencer(void)
+{
+ int n, offset;
+ MidiEvent ev;
+
+ n = read(data_fd, data_buffer + data_buffer_len,
+ sizeof(data_buffer) - data_buffer_len);
+
+ if(n <= 0)
+ {
+ stop_playing();
+ return n;
+ }
+
+#ifdef DEBUG_DUMP_SEQ
+ {
+ int i;
+ for(i = 0; i < n; i++)
+ printf("%02x", data_buffer[data_buffer_len + i]);
+ putchar('\n');
+ }
+#endif /* DEBUG_DUMP_SEQ */
+
+ data_buffer_len += n;
+ offset = 0;
+ while(offset < data_buffer_len)
+ {
+ int cmd;
+ cmd = data_buffer[offset];
+
+#define READ_NEEDBUF(x) if(offset + x > data_buffer_len) goto done;
+#define READ_ADVBUF(x) offset += x;
+ switch(cmd)
+ {
+ case EV_CHN_VOICE:
+ READ_NEEDBUF(8);
+ do_chn_voice(data_buffer + offset);
+ READ_ADVBUF(8);
+ break;
+ case EV_CHN_COMMON:
+ READ_NEEDBUF(8);
+ do_chn_common(data_buffer + offset);
+ READ_ADVBUF(8);
+ break;
+ case EV_TIMING:
+ READ_NEEDBUF(8);
+ do_timing(data_buffer + offset);
+ READ_ADVBUF(8);
+ break;
+ case EV_SYSEX:
+ READ_NEEDBUF(8);
+ do_sysex(data_buffer + offset + 2, 6);
+ READ_ADVBUF(8);
+ break;
+ case SEQ_MIDIPUTC:
+ if(is_system_prefix)
+ {
+ READ_NEEDBUF(4);
+ do_sysex(data_buffer + offset + 1, 1);
+ if(data_buffer[offset + 1] == 0xf7)
+ is_system_prefix = 0; /* End SysEX */
+ READ_ADVBUF(4);
+ break;
+ }
+ READ_NEEDBUF(2);
+ switch(data_buffer[offset + 1] & 0xf0)
+ {
+ case MIDI_NOTEON:
+ READ_NEEDBUF(12);
+ ev.channel = data_buffer[offset + 1] & 0x0f;
+ ev.a = data_buffer[offset + 5] & 0x7f;
+ ev.b = data_buffer[offset + 9] & 0x7f;
+ if(ev.b != 0)
+ ev.type = ME_NOTEON;
+ else
+ ev.type = ME_NOTEOFF;
+ seq_play_event(&ev);
+ READ_ADVBUF(12);
+ break;
+
+ case MIDI_NOTEOFF:
+ READ_NEEDBUF(12);
+ ev.type = ME_NOTEOFF;
+ ev.channel = data_buffer[offset + 1] & 0x0f;
+ ev.a = data_buffer[offset + 5] & 0x7f;
+ ev.b = data_buffer[offset + 9] & 0x7f;
+ seq_play_event(&ev);
+ READ_ADVBUF(12);
+ break;
+
+ case MIDI_KEY_PRESSURE:
+ READ_NEEDBUF(12);
+ ev.type = ME_KEYPRESSURE;
+ ev.channel = data_buffer[offset + 1] & 0x0f;
+ ev.a = data_buffer[offset + 5] & 0x7f;
+ ev.b = data_buffer[offset + 9] & 0x7f;
+ seq_play_event(&ev);
+ READ_ADVBUF(12);
+ break;
+
+ case MIDI_CTL_CHANGE:
+ READ_NEEDBUF(12);
+ if(convert_midi_control_change(data_buffer[offset + 1] & 0x0f,
+ data_buffer[offset + 5],
+ data_buffer[offset + 9],
+ &ev))
+ seq_play_event(&ev);
+ READ_ADVBUF(12);
+ break;
+
+ case MIDI_PGM_CHANGE:
+ READ_NEEDBUF(8);
+ ev.type = ME_PROGRAM;
+ ev.channel = data_buffer[offset + 1] & 0x0f;
+ ev.a = data_buffer[offset + 5] & 0x7f;
+ ev.b = 0;
+ seq_play_event(&ev);
+ READ_ADVBUF(8);
+ break;
+
+ case MIDI_CHN_PRESSURE:
+ READ_NEEDBUF(8);
+ ev.type = ME_CHANNEL_PRESSURE;
+ ev.channel = data_buffer[offset + 1] & 0x0f;
+ ev.a = data_buffer[offset + 5] & 0x7f;
+ ev.b = 0;
+ seq_play_event(&ev);
+ READ_ADVBUF(8);
+ break;
+
+ case MIDI_PITCH_BEND:
+ READ_NEEDBUF(12);
+ ev.type = ME_PITCHWHEEL;
+ ev.channel = data_buffer[offset + 1] & 0x0f;
+ ev.a = data_buffer[offset + 5] & 0x7f;
+ ev.b = data_buffer[offset + 9] & 0x7f;
+ seq_play_event(&ev);
+ READ_ADVBUF(12);
+ break;
+
+ case MIDI_SYSTEM_PREFIX:
+ READ_NEEDBUF(4);
+ do_sysex(data_buffer + offset + 1, 1);
+ is_system_prefix = 1; /* Start SysEX */
+ READ_ADVBUF(4);
+ break;
+
+ default:
+ ctl.cmsg(CMSG_ERROR, VERB_NORMAL,
+ "Undefined SEQ_MIDIPUTC 0x%02x",
+ data_buffer[offset + 1]);
+ send_status(402, "Undefined SEQ_MIDIPUTC 0x%02x",
+ data_buffer[offset + 1]);
+ return 1;
+ }
+ break;
+
+ case SEQ_FULLSIZE:
+ /* WARNING: This data may be devided into some socket fragments. */
+ offset = data_buffer_len;
+ ctl.cmsg(CMSG_WARNING, VERB_NORMAL,
+ "SEQ_FULLSIZE is received. This command is not safety.");
+ break;
+
+ case SEQ_EXTENDED:
+ READ_NEEDBUF(8);
+ do_extended(data_buffer + offset);
+ READ_ADVBUF(8);
+ break;
+
+ case EV_SEQ_LOCAL:
+ case SEQ_PRIVATE:
+ READ_NEEDBUF(8);
+ /* Ignore */
+ READ_ADVBUF(8);
+ break;
+
+ default:
+ ctl.cmsg(CMSG_ERROR, VERB_NORMAL,
+ "Undefined data 0x%02x", data_buffer[offset - 1]);
+ send_status(401, "Wrong data is recieved (seqcmd=0x%02x)",
+ cmd);
+ stop_playing();
+ return 1;
+ }
+#undef READ_NEEDBUF
+#undef READ_ADVBUF
+ }
+
+ done:
+ if(offset)
+ {
+ data_buffer_len -= offset;
+ memmove(data_buffer, data_buffer + offset, data_buffer_len);
+ }
+ return 0;
+}
+
+
+static void do_chn_voice(uint8 *data)
+{
+ int type, chn, note, parm;
+ MidiEvent ev;
+
+ type = data[2];
+ chn = data[3] % MAX_CHANNELS;
+ note = data[4] & 0x7f;
+ parm = data[5] & 0x7f;
+
+ ev.channel = chn;
+ ev.a = note;
+ ev.b = parm;
+ switch(type)
+ {
+ case MIDI_NOTEON:
+ if(parm > 0)
+ {
+ ev.type = ME_NOTEON;
+ seq_play_event(&ev);
+ break;
+ }
+ /* FALLTHROUGH */
+ case MIDI_NOTEOFF:
+ ev.type = ME_NOTEOFF;
+ seq_play_event(&ev);
+ break;
+ case MIDI_KEY_PRESSURE:
+ ev.type = ME_KEYPRESSURE;
+ seq_play_event(&ev);
+ break;
+ }
+}
+
+static void do_chn_common(uint8 *data)
+{
+ int type, chn, p1, p2, w14;
+ MidiEvent ev;
+
+ type = data[2];
+ chn = data[3] % MAX_CHANNELS;
+ p1 = data[4] & 0x7f;
+ p2 = data[5] & 0x7f;
+ w14 = data2short(data + 6) & 0x3fff;
+
+ if(type == 0xff) /* Meta event */
+ {
+ /* This event is special event for timidity. (not OSS compatible) */
+ switch(data[3])
+ {
+ case 0x2f: /* End of midi */
+ stop_playing();
+ tmr_reset();
+ break;
+
+ case 0x7f: /* Sequencer-Specific Meta-Event */
+ switch(data[4])
+ {
+ case 0x01: /* MIDI Reset */
+ ev.type = ME_RESET;
+ ev.a = DEFAULT_SYSTEM_MODE;
+ ev.b = 0;
+ seq_play_event(&ev);
+ break;
+
+ case 0x02: { /* Used to sync. */
+ double target_time, queue_time, sleep_time;
+ if(w14 == 0)
+ {
+ aq_flush(0); /* wait until playout */
+ send_status(301, "0 Sync OK");
+ break;
+ }
+
+ aq_soft_flush();
+ target_time = (double)w14 / curr_timebase;
+ queue_time = (double)aq_filled() / play_mode->rate;
+ sleep_time = queue_time - target_time;
+ if(sleep_time > 0)
+ {
+ send_status(301, "%g Sync OK", sleep_time);
+ usleep((unsigned long)(sleep_time * 1000000));
+ }
+ else
+ send_status(301, "0 Sync OK");
+ }
+ break;
+ }
+ }
+ return;
+ }
+
+ ev.channel = chn;
+ switch(type)
+ {
+ case MIDI_CTL_CHANGE:
+ if(convert_midi_control_change(chn, p1, w14, &ev))
+ seq_play_event(&ev);
+ break;
+ case MIDI_PGM_CHANGE:
+ ev.type = ME_PROGRAM;
+ ev.a = p1;
+ ev.b = 0;
+ seq_play_event(&ev);
+ break;
+ case MIDI_CHN_PRESSURE:
+ ev.type = ME_CHANNEL_PRESSURE;
+ ev.a = p1;
+ ev.b = p2;
+ seq_play_event(&ev);
+ break;
+ case MIDI_PITCH_BEND:
+ ev.type = ME_PITCHWHEEL;
+ ev.a = w14 & 0x7f;
+ ev.b = (w14>>7) & 0x7f;
+ seq_play_event(&ev);
+ break;
+ }
+}
+
+
+static void do_timing(uint8 *data)
+{
+ int32 val;
+
+ val = data2long(data + 4);
+ switch(data[1])
+ {
+ case TMR_START:
+ tmr_running = 1;
+ tmr_reset();
+ event_time_offset = (int32)(play_mode->rate * high_time_at);
+ break;
+
+ case TMR_STOP:
+ tmr_running = 0;
+ break;
+
+ case TMR_CONTINUE:
+ if(!tmr_running)
+ {
+ tmr_running = 1;
+ tick_offs = curr_tick;
+ start_time = get_current_calender_time();
+ }
+ break;
+
+ case TMR_TEMPO:
+#if 0 /* What should TMR_TEMPO work ? */
+ if(val < 8)
+ val = 8;
+ else if(val > 250)
+ val = 250;
+ current_play_tempo = 60 * 1000000 / val;
+ compute_sample_increment();
+#endif
+ break;
+
+ case TMR_WAIT_ABS:
+
+/*
+ printf("## TMR_WAIT_ABS: %d %d %d %g\n",
+ curr_tick,
+ curr_tick - (time2tick(get_current_calender_time()
+ - start_time) + tick_offs),
+ event_time_offset,
+ (double)aq_filled() / play_mode->rate);
+ */
+
+ val -= curr_tick;
+ /*FALLTHROUGH*/
+ case TMR_WAIT_REL:
+ if(val <= 0)
+ break;
+ add_tick(val);
+ break;
+
+#if 0
+ case TMR_ECHO:
+ case TMR_SPP:
+#endif
+ default:
+/* printf("## TMR=0x%02x is not supported\n", data[1]); */
+ break;
+ }
+}
+
+
+static void do_sysex(uint8 *data, int n)
+{
+ static uint8 sysexbuf[BUFSIZ];
+ static int buflen;
+ static int fillflag;
+ int i;
+
+ if(data == NULL)
+ {
+ buflen = fillflag = 0;
+ is_system_prefix = 0;
+ return;
+ }
+
+ for(i = 0; i < n; i++)
+ {
+ /* SysEx := /\xf0([^\xf7]*\xf7)/ */
+ if(!fillflag)
+ {
+ if(data[i] == 0xf0)
+ {
+ fillflag = 1;
+ continue;
+ }
+ }
+ if(fillflag)
+ {
+ if(buflen < sizeof(sysexbuf))
+ sysexbuf[buflen++] = data[i];
+ if(data[i] == 0xf7)
+ {
+ MidiEvent ev;
+ if(parse_sysex_event(sysexbuf, buflen, &ev))
+ seq_play_event(&ev);
+ buflen = 0;
+ fillflag = 0;
+ }
+ }
+ }
+}
+
+static void do_extended(uint8 *data)
+{
+ int value;
+ MidiEvent ev;
+
+ value = data[5] | data[6] * 256;
+ switch(data[1])
+ {
+ case SEQ_CONTROLLER:
+ switch(data[4])
+ {
+ case CTRL_PITCH_BENDER: /* ?? */
+ break;
+ case CTRL_PITCH_BENDER_RANGE: /* ?? */
+ ev.channel = data[3] % MAX_CHANNELS;
+ ev.b = 0;
+ ev.a = 0;
+
+ /* LSB */
+ ev.type = ME_NRPN_LSB;
+ seq_play_event(&ev);
+
+ /* MSB */
+ ev.type = ME_NRPN_MSB;
+ seq_play_event(&ev);
+
+ /* Data entry */
+ ev.type = ME_DATA_ENTRY_MSB;
+ ev.a = value / 100; /* ?? */
+ seq_play_event(&ev);
+
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+
+/*
+ * interface_<id>_loader();
+ */
+ControlMode *interface_r_loader(void)
+{
+ return &ctl;
+}