/*
**	MIXPLAY -- Mix recorded & "live" MIDI data & play it.
**	P. Langston, 10/87
*/
#include	<stdio.h>
#include	<signal.h>
#include	<midi.h>
#include	<mpuvar.h>
#include	<mpureg.h>
#include	<sys/time.h>
#include	<sys/ioctl.h>

#define	MAXMPUS	MPUS_PER_BOARD	/* how many MPUs we've got */
#define	MAXTPM	8		/* how many tracks each MPU can have */
#define	MAXINP	64		/* how many simultaneous inputs we can have */

int	Tapesync = 0;		/* if true, put MPU in FSK INT SYNC mode */
int	Record	= -1;		/* MPU #, record (overdub) while playing */
int	Debug	= 0;		/* if set, output is to a tty */
int	Midifh;			/* file descriptor for /dev/mpu */
int	Fpipe	= 0;		/* is at least one source a pipe? */
int	Ninp	= 0;		/* number of sources */

struct	mpustr	{
	int	fd;		/* file descriptor for the MPU device */
	int	trax;		/* how many tracks we've assigned */
	int	mode;		/* mode in which to start MPU */
	char	name[32];	/* device file name */
} Mpu[MAXMPUS];

struct	inpstr	{		/* track info */
	FILE	*fp;		/* input file pointer */
	int	fd;		/* file descriptors for Fp[] */
	int	lm;		/* 1 ==> "live" MIDI, 0 ==> time-tagged MPU */
	int	mpu;		/* which MPU this track uses */
	int	trak;		/* which track on this MPU */
} Inp[MAXINP];

#define	TBITSLOC	1	/* (in both sequences) */
char	Sinit[]	= {		/* default tape sync initialization */
	MPU_ACTIVE_TRACKS, 0, MPU_CLEAR_PLAY_COUNTERS,
	MPU_MIDI_THRU_OFF, MPU_BENDER_ON, MPU_FSK_CLOCK,
	0,
};
char	Ninit[]	= {		/* default "normal" initialization */
	MPU_ACTIVE_TRACKS, 0, MPU_CLEAR_PLAY_COUNTERS,
	MPU_MIDI_THRU_OFF, MPU_BENDER_ON,
	0,
};

main(argc, argv)
char	*argv[];
{
	char *init_file = NULL, *arg;
	int i, Wait=0;
	int inraw, mpu;
	void interrupt();

/****/setbuf(stderr, 0);	/* THOSE ASSHOLES! */
	av0 = argv[0];
	signal(SIGINT, interrupt);
	for (i = MAXMPUS; --i >= 0; ) {
	    Mpu[i].fd = -1;
	    sprintf(Mpu[i].name, "/dev/mpu%d", i);
	}
	for (i = MAXINP; --i >= 0; Inp[i].fd = -1);
	inraw = 0;
	openmpu(mpu = 0);
	for (i = 1; i < argc; i++) {
	    arg = argv[i];
	    if (arg[0] == '-') {
		switch (arg[1]) {
		case '-': in(&arg[2], mpu, inraw); break;
		case 'b': MpuSet(MPU_METRO_MEAS), MpuSet(atoi(&arg[2])); break;
		case 'c': init_file = &arg[2]; break;
		case 'D': Debug = 1; break;
		case 'd': openmpu(mpu = atoi(&arg[2])); break;
		case 'M': MpuSet(MPU_METRO_ACC); break;
		case 'm': MpuSet(MPU_METRO_NO_ACC); break;
		case 'R': inraw = 1; break;
		case 'r': if (Record >= 0) syntax(1);
			Mpu[Record = mpu].mode = MPU_START_OVERDUB; break;
		case 'S': Tapesync = 1; break;
		case 'T': inraw = 0; break;
		case 't': MpuSet(MPU_TEMPO), MpuSet(atoi(&arg[2])); break;
		case 'w': Wait = atoi(&arg[2]); break;
		case 'x': MpuSet(MPU_EXCLUSIVE_TO_HOST_ON),
			MpuSet(MPU_SEND_MEASURE_END_OFF); break;
		default : syntax(1);
		}
	    } else
		in(arg, mpu, inraw);
	}
/****/fprintf(stderr, "mpu=%d, (%s)\n", mpu, Mpu[mpu].name);
	if (Wait || Fpipe)
	    sleep(Wait? Wait : 2); /* let input pipes fill up a little */
	if (midiinit(init_file))
	    Error(init_file);
	splay();
	if (Record >= 0)
	    for(;;)
		record(fileno(stdout));
	done();
}

openmpu(mpu)			/* open appropriate /dev/mpu? */
{
	if (mpu < 0 || mpu >= MAXMPUS) {
	    fprintf(stderr,
	     "mpu number, %d, out of range (0-%d).\n", mpu, MAXMPUS - 1);
	    syntax(1);
	}
	if ((Mpu[mpu].fd = open(Mpu[mpu].name, 2)) < 0) {
/****/fprintf(stderr, "openmpu()  ");
	    perror(Mpu[mpu].name);
	    syntax(1);
	}
	Mpu[mpu].mode = MPU_START_PLAY;
}

in(file, mpu, lflg)		/* associate input data with mpu track */
char	*file;
{
	struct mpustr *mp;
	struct inpstr *ip;

	if (mpu < 0 || mpu > MAXMPUS) {
	    fprintf(stderr,
	     "mpu number, %d, out of range (0-%d).\n", mpu, MAXMPUS - 1);
	    syntax(1);
	}
	if (Ninp >= MAXINP) {
	    fprintf(stderr, "Too many input sources, max is %d.\n", MAXINP);
	    syntax(1);
	}
	mp = &Mpu[mpu];
	if (mp->trax >= MAXTPM) {
	    fprintf(stderr,
	     "Too many tracks for mpu%d, max is %d.\n", mpu, MAXTPM);
	    syntax(1);
	}
	ip = &Inp[Ninp++];
	if (file && *file)
	    ip->fp = sopen(file, "r");
	else
	    ip->fp = stdin;
	if (!ip->fp) {
/****/fprintf(stderr, "in(%s, %d, %d)  ", file, mpu, lflg);
	    perror(file);
	    ip->fd = -1;		/* ip->fp = (FILE *) 0; */
	} else {
	    ip->fd = fileno(ip->fp);
	    Fpipe += isapipe(ip->fd);
	    ip->lm = lflg;
#ifdef	UNDEF
	    if (lflg)
		fseek(ip->fp, 0, 2);	/* seek to end of live files */
#endif
	    ip->mpu = mpu;
	    ip->trak = mp->trax++;
	}
}

done()
{
	int i;

	for (i = 0; i < MAXMPUS; i++)
	    if (Mpu[i].fd >= 0)
		close(Mpu[i].fd);
	exit(0);
}

Error(s)
{
	MidiError("%s: ", av0);
/****/fprintf(stderr, "Error   ");
	perror(s);
	exit(1);
}

midiinit(file)
char *file;
{
	register int i, n;
	char buf[1024], *iseq;
	int d, trackbits[MAXMPUS], islen;
	struct inpstr *ip;
	struct mpustr *mp;
	FILE *ifp;

	for (i = MAXMPUS; --i >= 0; trackbits[i] = 0);
	for (i = 0; i < Ninp; i++) {
	    ip = &Inp[i];
	    mp = &Mpu[ip->mpu];
	    if (!ip->lm) {
		trackbits[ip->mpu] |= (1 << ip->trak);
		if ((n = read(ip->fd, buf, sizeof buf)) < sizeof buf) {
		    sclose(ip->fp);
		    ip->fp = (FILE *) 0;
		    ip->fd = -1;
		}
		if (n > 0
		 && (MpuSetTrack(mp->fd, ip->trak) == -1
		  || write(mp->fd, buf, n) != n)) {
/****/fprintf(stderr, "midiinit() MpuSetTrack() || write() failed   ");
		    perror(mp->name);
		    return(-1);
		}
	    }
	}
	/* send initialization sequence */
	if (file) {
	    if (!(ifp = sopen(file,"r")))
		Error(file);
	    for (iseq = buf; fscanf(ifp, "%x", &d) == 1; *iseq++ = d);
	    islen = iseq - buf;
	    iseq = buf;
	    sclose(ifp);
	} else if (Tapesync) {		/* default tape sync initialization */
	    iseq = Sinit;
	    islen = sizeof Sinit;
	} else {			/* default "normal" initialization */
	    iseq = Ninit;
	    islen = sizeof Ninit;
	}
	for (i = 0; i < MAXMPUS; i++) {
	    if (Mpu[i].fd < 0)
		continue;
	    if (!file) {
		iseq[TBITSLOC] = trackbits[i];
		iseq[islen - 1] = Mpu[i].mode;
	    }
	    MpuSetTrack(Mpu[i].fd, MPU_TR_COM);
	    write(Mpu[i].fd, iseq, islen);
	    if (MpuSetTrack(Mpu[i].fd, 0) == -1) {
/****/fprintf(stderr, "midiinit() MpuSetTrack() failed   ");
		perror(Mpu[i].name);
		return(-1);
	    }
	}
	return(0);
}

syntax(i)
{
	MidiError("Usage: %s [files or stdin]\n", av0);
	MidiError(
	 "flags:\n\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s",
	 "--	read stdin for data\n",
	 "-bN	set # beats/measure to N\n",
	 "-cfile	use contents of \"file\" to initialize MPU\n",
	 "-dN	following data sources are for /dev/mpuN\n",
	 "-M	metronome on, with accent\n",
	 "-m	metronome on, no accent\n",
	 "-R	following data sources are \"raw\".\n",
	 "-r	record (writing the recording to stdout)\n",
	 "-S	synchronize with FSK signal at TAPE IN jack\n",
	 "-T	following data sources are time-tagged.\n",
	 "-tN	set tempo to N (beats/minute, default=100)\n",
	 "-wN	wait for N seconds before starting\n",
	 "-x	enable recording system exclusive data\n",
	 0);
	exit(i);
}

record(ofh)		/* copy any data from Midifh to ofh */
{
	char c;
	int n, fh;

	if (Record < 0)
	    return;
	if (iwait(0, 0)) {	/* input arrived from keyboard; quit */
	    getchar();
	    done();
	    /*NOTREACHED*/
	}
	fh = Mpu[Record].fd;
	if (iwait(fh, 0) && (n = read(fh, &c, 1)) > 0) {
	    if (Debug)
		printf("0x%02x\n", c);
	    else if (write(ofh, &c, n) != n)
		perror("write");
	}
}

splay()		/* simultaneously play all files in "Fp", "Fd" & "Lm" */
{
	int n, in, dataleft;
	char s[512];
	struct inpstr *ip;
	struct mpustr *mp;

	do {
	    dataleft = 0;
	    for (in = 0; in < Ninp; in++) {
		ip = &Inp[in];
		if (ip->fd < 0)
		    continue;
		dataleft++;
		if (ip->lm) {
		    if (iwait(ip->fd, 0) > 0)
			livedata(ip);
		} else {
		    mp = &Mpu[ip->mpu];
		    MpuSetTrack(mp->fd, ip->trak);
		    if ((n = read(ip->fd, s, sizeof s)) > 0) {
			if (write(mp->fd, s, n) != n)
/****/{
/****/fprintf(stderr, "splay() write(%d, %x, %d) failed   ", mp->fd, s, n);
			    perror(mp->name);
/****/}
		    } else {
			sclose(ip->fp);
			ip->fp = (FILE *)0;
			ip->fd = -1;
			putTCIP(mp->fd);
		    }
		}
	    }
	    if (Record >= 0)
		record(fileno(stdout));
	} while (dataleft);
}

livedata(ip)		/* read & write out "live" data */
struct	inpstr *ip;
{
	register int c, n, ifh;
	u_char buf[64], *cp, *bep;		/* not big enough for SysEx */
	static int status;
	struct mpustr *mp;

	ifh = ip->fd;
	mp = &Mpu[ip->mpu];
	while (iwait(ifh, 0) && (n = read(ifh, buf, 1)) == 1) {
	    cp = buf;
	    c = *cp;
	    n = statproc(&status, c);
	    MpuSetTrack(mp->fd, MPU_TR_COM);
	    if (n < 0) {				/* Sys Excl */
		*cp++ = MPU_SEND_SYSTEM_MESSAGE;
		*cp++ = c;				/* SX_CMD */
		mwrite(mp->fd, buf, cp - buf);
		for (cp = buf; read(ifh, cp, 1) >= 0; ) {
		    c = *cp++;
		    if (cp - buf >= sizeof buf) {
			mwrite(mp->fd, buf, cp - buf);
			cp = buf;
		    }
		    if (c == SX_EOB)
			break;
		}
	    } else {
		*cp++ = MPU_WANT_TO_SEND_DATA + ip->trak;
		*cp++ = c;
		for (bep = &buf[n+1]; cp < bep; cp += read(ifh, cp, bep - cp));
	    }
	    if (cp > buf)
		mwrite(mp->fd, buf, cp - buf);
	}
	if (n == 0) {				/* EOF */
	    sclose(ip->fp);
	    ip->fp = (FILE *)0;
	    ip->fd = -1;
/****	    ioctl(mp->fd, MPU_IOC_PURGE, &ip->trak);	/****/
	}
}

mwrite(fh, buf, len)		/* write out midi data to fh */
u_char	*buf;
{
	register int i;

	if (Debug) {
	    for (i = 0; i < len; i++)
		fprintf(stderr, " %02x", buf[i] & 0xFF);
	    fprintf(stderr, "\n");
	} else
	    if ((i = write(fh, buf, len)) != len)
		perror("mixplay:mwrite");
	return(i);
}

void
interrupt()	/* user must ^C again to really quit, I think */
{
	int i;

	for (i = 0; i < MAXMPUS; i++)
	    if (Mpu[i].fd >= 0)
		ioctl(Mpu[i].fd, MPU_IOC_PURGE, 0);
	done();
}

iwait(f, timeout)
unsigned long timeout;  /* in seconds */
/*
 * Wait until 'f' is ready for reading, or 'timeout'.
 * Return '>=0' when 'f' is readable, '0' if timeout, '-1' on error.
 * Example: 'iwait(f,0)' polls a file descriptor
 * without blocking and returns true if it's readable;
 * 'iwait(0,0)' returns true when standard input is available.
 */
{
	int readfd;
	struct timeval t;

	readfd = (1 << f);
	t.tv_sec = timeout;
	t.tv_usec = 0;
	return(select(f + 1,  &readfd, (int *)0, (int *)0, &t));
}
