/*
**	TXPUT -- Store voice(s) and/or performance(s) into DX7 or TX816
*/

#include <stdio.h>
#include <midi.h>
#include <dx7voice.h>
#include <mpuvar.h>

#d DEFCHAN	0
#d e		MidiError
#d MIN		0
#d MAX		31
#d EDIT		77
#d ALL		99

#d MAX1LEN	(TX1PLEN + DX7VOXLEN)	/* need room for either */

u_char	P64[6 + TX64PTOTLEN];	/* for assembling single-perf update */
u_char	V32[6 + DX732VTOTLEN];	/* for assembling single-voice update */
int	Chan	= DEFCHAN;	/* Midi channel to be used */

main(argc, argv)
char *argv[];
{
	int i, n;

	av0 = argv[0];
	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch (argv[i][1]) {
		case 'c':		/* set channel */
		    n = atoi(&argv[i][2]) - 1;
		    if (n < 0 || 15 < n) {
			e("%s: bad chan number (%d)\n", av0, Chan);
			use();
		    }
		    if (n != Chan) {
			if (*P64) {
			    P64[2] = DX7_SXSS_BD | Chan;
			    pvsend(P64, 6, &P64[6], TX64PTOTLEN);
			    *P64 = 0;
			}
			if (*V32) {
			    V32[2] = DX7_SXSS_BD | Chan;
			    pvsend(V32, 6, &V32[6], DX732VTOTLEN);
			    *V32 = 0;
			}
			Chan = n;
		    }
		    break;
		case 'p':		/* load performance(s) */
		    put(argv[i], "performance",
		     TX_SXF_64P, TX64PTOTLEN, TX_SXF_1P, TX1PLEN);
		    break;
		case 'v':		/* load voice(s) */
		    put(argv[i], "voice",
		     DX7_SXF_32V, DX732VTOTLEN, DX7_SXF_1V, DX7VOXLEN);
		    break;
		default:
		    use();
		}
	    }
	}
	if (*P64) {
	    P64[2] = DX7_SXSS_BD | Chan;
	    pvsend(P64, 6, &P64[6], TX64PTOTLEN);
	}
	if (*V32) {
	    V32[2] = DX7_SXSS_BD | Chan;
	    pvsend(V32, 6, &V32[6], DX732VTOTLEN);
	}
	exit(0);
}

put(arg, vorp, afmt, alen, efmt, elen)
char	*arg, *vorp;
{
	register char *cp;
	int fh;

	for (cp = arg; *cp && *cp != '='; cp++);
	if (*cp != '=') {
	    e("%s: missing '=' in %s\n", av0, arg);
	    use();
	}
	*cp++ = '\0';
	if (*cp == '-' && cp[1] == '\0')
	    fh = 0;
	else if ((fh = open(cp, 0)) < 0) {
	    perror(cp);
	    use();
	}
	if (strcmp(&arg[2], "edit") == 0
	 || (arg[2] == '0' && arg[3] == '\0'))
	    pvput(fh, efmt, elen);
	else if (strcmp(&arg[2], "all") == 0)
	    pvput(fh, afmt, alen);
	else if ('0' <= arg[2] && arg[2] <= '9')
	    pvup(fh, vorp, atoi(&arg[2]) - 1, afmt, alen, efmt, elen);
	else {
	    e("%s: bad %s spec in %s=%s\n", av0, vorp, arg, cp);
	    use();
	}
}

pvup(fh, vorp, n, afmt, alen, efmt, elen)
char	*vorp;
{
	u_char buf[6 + MAX1LEN];

	if (n < 0 || n >= (*vorp == 'p'? 64 : 32)) {
	    e("%s: %s number %d is out of range\n", av0, vorp, n);
	    return;
	}
	if (!pvfile(fh, efmt, elen, buf))
	    return;
	if (*vorp == 'p') {
	    if (*P64 == 0 && pvget(afmt, alen, P64) == 0)
		return;
	    if (!tx1p64(&buf[6], n, &P64[6]))
		e("tx1p64() failed\n");
	} else {
	    if (*V32 == 0 && pvget(afmt, alen, V32) == 0)
		return;
	    if (!tx1v32(&buf[6], n, &V32[6]))
		e("tx1v32() failed\n");
	}
}

pvget(fmt, dlen, buf)
u_char	*buf;
{
	u_char cbuf[16], *cp;
	int lhi, llo, mfh, len, i;

	if ((mfh = open(MidiDevice, 2)) == -1) {
	    e("%s: ", av0);
	    perror(MidiDevice);
	    return(0);
	}
	lhi = dlen / 128;
	llo = dlen % 128;
	MpuSetTrack(mfh, MPU_TR_COM);
	cp = cbuf;
	*cp++ = MPU_RESET;
	*cp++ = MPU_MIDI_THRU_OFF;
	*cp++ = MPU_SEND_SYSTEM_MESSAGE;
	*cp++ = SX_CMD;
	*cp++ = ID_YAMAHA;
	*cp++ = DX7_SXSS_DR | Chan;
	*cp++ = fmt;
	*cp++ = SX_EOB;
	*cp++ = MPU_EXCLUSIVE_TO_HOST_ON;
	*cp++ = MPU_SEND_MEASURE_END_OFF;
	if ((i = write(mfh, cbuf, cp - cbuf)) != cp - cbuf) {
	    e("write(mfh, cbuf, %d) returned %d\n", cp - cbuf, i);
	    perror(MidiDevice);
	    return(0);
	}
	MpuSetTrack(mfh, 0);
	for (;;) {
	    while (read(mfh, buf, 1) == 1 && *buf != SX_CMD);
	    if (read(mfh, &buf[1], 5) != 5) {		/* get header */
		e("%s: EOF in bulk header\n", av0);
		close(mfh);
		return(0);
	    }
	    if (buf[1] == ID_YAMAHA
	     && buf[3] == fmt
	     && buf[4] == lhi
	     && buf[5] == llo)
		break;
	    e("%s: naughty header for bulk dump:", av0);
	    e(" %x %x %x %x %x\n", buf[1], buf[2], buf[3], buf[4], buf[5]);
	    e("Wanted: %x, xx, %x, %x %x\n", ID_YAMAHA, fmt, lhi, llo);
	    e("Scanning past wrong dump\n");
	}
	len = dlen;
	for (cp = &buf[6]; (i = read(mfh, cp, len)) > 0; cp += i)
	    if ((len -= i) <= 0)
		break;
	if (len > 0) {
	    e("%s: expected %d bytes, got %d\n", av0, dlen, dlen - len);
	    close(mfh);
	    return(0);
	}
	read(mfh, cbuf, 2);		/* get rid of check-sum & SX_EOB */;
	close(mfh);
	return(1);
}

pvput(fh, fmt, dlen)
{
	u_char buf[6 + TX64PTOTLEN];

	if (pvfile(fh, fmt, dlen, buf) == 0)
	    return;
	buf[2] = DX7_SXSS_BD | Chan;
	pvsend(buf, 6, &buf[6], dlen);
}

pvsend(cmd, clen, data, dlen)
u_char	*cmd, *data;
{
	char buf[4];
	int mfh;

#ifdef	TEST
	if ((mfh = creat("txput.test", 0644)) == -1) {
	    e("%s: ", av0);
	    perror("txput.test");
	    exit(1);
	}
#else
	if ((mfh = open(MidiDevice, 2)) == -1) {
	    e("%s: ", av0);
	    perror(MidiDevice);
	    exit(1);
	}
	MpuSetTrack(mfh, MPU_TR_COM);
	buf[0] = MPU_RESET;
	buf[1] = MPU_MIDI_THRU_OFF;
	buf[2] = MPU_SEND_SYSTEM_MESSAGE;
	write(mfh, buf, 3);
#endif
	write(mfh, cmd, clen);
	write(mfh, data, dlen);
	buf[0] = CheckSum(data, dlen);
	buf[1] = SX_EOB;
	write(mfh, buf, 2);
#ifndef	TEST
	MpuSetTrack(mfh, 0);
#endif
	close(mfh);
}

pvfile(fh, fmt, len, cp)
u_char	*cp;
{
	u_char buf[2];
	int lhi, llo;

	lhi = len / 128;
	llo = len % 128;
	while (read(fh, cp, 1) == 1 && *cp != SX_CMD);
	if (read(fh, &cp[1], 5) != 5) {
	    e("%s: EOF in file header?\n", av0);
	    return(0);
	}
	if (cp[1] != ID_YAMAHA
	 || (cp[2] != Chan && cp[2] != 0)     /* TX816 likes 0 */
	 || cp[3] != fmt
	 || cp[4] != lhi || cp[5] != llo) {
	    e("%s: naughty file header:", av0);
	    e(" %x %x %x %x %x\n", cp[1], cp[2], cp[3], cp[4], cp[5]);
	    return(0);
	}
	if (read(fh, &cp[6], len) != len) {
	    e("%s: EOF in file body?\n", av0);
	    return(0);
	}
	if (read(fh, buf, 2) != 2) {
	    e("%s: EOF in checksum/SX_EOB?\n", av0);
	    return(0);
	}
	if (buf[0] != (llo = CheckSum(&cp[6], len)))
	    e("%s: Bad checksum (%d should be %d)\n", av0, buf[0], llo);
	if (buf[1] != SX_EOB)
	    e("%s: Missing SX_EOB\n", av0);
	return(1);
}

use()
{
	e("Usage: %s [-c#] [-pNUM=FILE ...] [-vNUM=FILE ...]\n", av0);
	e(" -c# send data on midi channel # default is %d\n", DEFCHAN+1);
	e("NUM is either a decimal number, 1-32, 'edit', or 'all'\n");
	e("FILE is a filename, '-' represents stdin\n");
	e("Arguments are processed left to right; for example:\n");
	e("% %s -c9 -v1=v.1.9 -v2=v.2.9 -c10 -vall=v..10 -pall=p..10\n", av0);
	exit(2);
}
