/*
**	TXGET -- Get voice(s) and/or performance(s) from DX7 or TX816
*/

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

#d DEFCHAN	0
#d e		MidiError
#d EDIT		-1
#d MIN		0		/* default range for voices & perfs */
#d MAX		31
#d ALL		98

int	Chan	= DEFCHAN;	/* Midi channel to be used */
int	Mfh	= -1;		/* file handle for Midi device */
int	Iflg	= 0;		/* ignore wrong sys.excl. dumps */
FILE	*Ofp	= 0;		/* default is to make a file */

int	pget(), vget();

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

	av0 = argv[0];
	if ((Mfh = open(MidiDevice, 2)) == -1) {
	    e("%s: ", av0);
	    perror(MidiDevice);
	    exit(1);
	}
	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch (argv[i][1]) {
		case '-':			/* use stdout */
		    Ofp = stdout;
		    break;
		case 'c':			/* set channel */
		    Chan = atoi(&argv[i][2]) - 1;
		    if (Chan < 0 || 15 < Chan)
			use();
		    break;
		case 'i':			/* ignore wrong dumps */
		    Iflg++;
		    break;
		case 'p':			/* get performance(s) */
		    get(argv[i], "performance", pget);
		    break;
		case 'v':			/* get voice(s) */
		    get(argv[i], "voice", vget);
		    break;
		default:
		    use();
		}
	    } else {
		use();
		exit(2);
	    }
	}
	exit(0);
}

get(arg, vorp, func)
char	*arg, *vorp;
int	(*func)();
{
	register char *cp;
	register int i, n;

	if (strcmp(&arg[2], "each") == 0)
	    for (n = MIN; n <= MAX; n++)
		func(n);
	else if (strcmp(&arg[2], "all") == 0)
	    func(ALL);
	else if (strcmp(&arg[2], "edit") == 0)
	    func(EDIT);
	else {
	    cp = &arg[2];
	    n = atoi(cp);
	    i = n - 1;
	    while (*cp && *cp != '-')
		cp++;
	    if (*cp++ == '-')
		n = atoi(cp);
	    while (i < n)
		func(i++);
	}
}

unsigned char Load[] = {
	MPU_WANT_TO_SEND_DATA,
	0,			/* CH_PRG | chan goes here */
	0,			/* voice # goes here */
};
unsigned char Suck[] = {
	MPU_RESET, MPU_MIDI_THRU_OFF, MPU_SEND_SYSTEM_MESSAGE,
	SX_CMD, ID_YAMAHA,
	0,			/* (DX7_SXSS_DR<<4) | chan goes here */
	0,			/* DX7_SXF_1V, DX7_SXF_32V, */
				/* TX_SXF_1P, or TX_SXF_64P goes here */
	SX_EOB,
	MPU_EXCLUSIVE_TO_HOST_ON, MPU_SEND_MEASURE_END_OFF,
};

pget(n)
{
	unsigned char buf[6 + TX64PTOTLEN + 2], *cp;
	int i, len, totlen, fmt;
	FILE *fopen(), *fp;

	if (!(fp = Ofp)) {
	    if (n == ALL)
		sprintf(buf, "p..%d", Chan + 1);
	    else
		sprintf(buf, "p.%d.%d", n + 1, Chan + 1);
	    if (!(fp = fopen(buf, "w"))) {
		perror(buf);
		return;
	    }
	}
	MpuSetTrack(Mfh, MPU_TR_COM);
	if (n == EDIT) {
	    fmt = TX_SXF_1P;
	    totlen = 6 + TX1PLEN + 2;
	} else if (n == ALL) {
	    fmt = TX_SXF_64P;
	    totlen = 6 + TX64PTOTLEN + 2;
	} else {
	    Load[1] = CH_PRG | Chan;
	    Load[2] = n;
	    write(Mfh, Load, sizeof Load);
	    fmt = TX_SXF_1P;
	    totlen = 6 + TX1PLEN + 2;
	}
	Suck[5] = (DX7_SXSS_DR<<4) | Chan;
	Suck[6] = fmt;
	write(Mfh, Suck, sizeof Suck);
	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 header for performance %d\n", av0, n);
		return;
	    }
	    if (buf[1] == ID_YAMAHA
	     && (buf[2] == Chan || buf[2] == 0)	/* TX816 likes 0 */
	     && buf[3] == fmt
	     && ((buf[4] == 0x00 && buf[5] == 0x5E)
	      || (buf[4] == 0x20 && buf[5] == 0x00)))
		break;
	    e("%s: naughty header for performance %d:", av0, n + 1);
	    e(" %x %x %x %x %x\n", buf[1], buf[2], buf[3], buf[4], buf[5]);
	    e("Wanted: %x, %x(or 0), %x, %x %x (or %x %x)\n",
	     ID_YAMAHA, Chan, fmt, 0x00, 0x5E, 0x20, 0x00);
	    if (!Iflg) {
		e("Giving up (try -i option?)\n");
		return;
	    }
	    e("Scanning past wrong dump\n");
	}
	len = totlen - 6;
	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, totlen, totlen - len);
	    return;
	}
	if (write(fileno(fp), buf, totlen) != totlen)
	    perror("write");
	if (fp != Ofp)
	    fclose(fp);
}

vget(n)
{
	unsigned char buf[6 + DX732VTOTLEN + 2], *cp;
	int i, len, totlen, fmt;
	FILE *fopen(), *fp;

	if (!(fp = Ofp)) {
	    if (n == ALL)
		sprintf(buf, "v..%d", Chan + 1);
	    else
		sprintf(buf, "v.%d.%d", n + 1, Chan + 1);
	    if (!(fp = fopen(buf, "w"))) {
		perror(buf);
		return;
	    }
	}
	MpuSetTrack(Mfh, MPU_TR_COM);
	if (n == EDIT) {
	    fmt = DX7_SXF_1V;
	    totlen = 6 + DX7VOXLEN + 2;
	} else if (n == ALL) {
	    fmt = DX7_SXF_32V;
	    totlen = 6 + DX732VTOTLEN + 2;
	} else {
	    Load[1] = CH_PRG | Chan;
	    Load[2] = n;
	    write(Mfh, Load, sizeof Load);
	    fmt = DX7_SXF_1V;
	    totlen = 6 + DX7VOXLEN + 2;
	}
	Suck[5] = (DX7_SXSS_DR<<4) | Chan;
	Suck[6] = fmt;
	write(Mfh, Suck, sizeof Suck);
	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 header for voice %d\n", av0, n);
		return;
	    }
	    if (buf[1] == ID_YAMAHA
	     && (buf[2] == Chan || buf[2] == 0)	/* TX816 likes 0 */
	     && buf[3] == fmt
	     && ((buf[4] == 0x01 && buf[5] == 0x1B)
	      || (buf[4] == 0x20 && buf[5] == 0x00)))
		break;
	    e("%s: naughty header for voice %d:", av0, n + 1);
	    e(" %x %x %x %x %x\n", buf[1], buf[2], buf[3], buf[4], buf[5]);
	    e("Wanted: %x, %x(or 0), %x, %x %x (or %x %x)\n",
	     ID_YAMAHA, Chan, fmt, 0x01, 0x1B, 0x20, 0x00);
	    if (!Iflg) {
		e("Giving up (try -i option?)\n");
		return;
	    }
	    e("Scanning past wrong dump\n");
	}
	len = totlen - 6;
	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, totlen, totlen - len);
	    return;
	}
	if (write(fileno(fp), buf, totlen) != totlen)
	    perror("write");
	if (fp != Ofp)
	    fclose(fp);
}

use()
{
	e("Usage: %s [--] [-c#] [-i] [POPT ...] [VOPT ...]\n", av0);
	e(" --  send output to stdout instead of default file(s)\n");
	e(" -c# read voices from midi channel #.  default is %d\n", DEFCHAN+1);
	e(" -i  ignore other sysex dumps that may precede the one we want\n");
	e("The POPTs are:\n");
	e(" -p# output specified performance, (# usually in the range 1-32)\n");
	e(" -p#-# output specified performances (in the style of -peach)\n");
	e(" -pall output performances 1-32 as half a 64-performance dump\n");
	e(" -peach output performances 1-32 as 32 1-perf dumps (-p1-32)\n");
	e(" -pedit output the performance in the edit buffer (-p0)\n");
	e("The VOPTs are:\n");
	e(" -v# output specified voice, (# usually in the range 1-32)\n");
	e(" -v#-# output specified range of voices (in the style of -veach)\n");
	e(" -vall output voices 1 through 32 as a 32-voice dump\n");
	e(" -veach output voices 1-32 as 32 1-voice dumps (-v1-32)\n");
	e(" -vedit output the voice in the edit buffer (-v0)\n");
	e("default file names are:\n");
	e("\t'v.V.C'\tfor single voice bulk dumps,\n");
	e("\t'v..C'\tfor 32-voice bulk dumps,\n");
	e("\t'p.P.C'\tfor single performance bulk dumps,\n");
	e("\t'p..C'\tfor 64-performance bulk dumps,\n");
	e("where 'C' is channel, 'V' is voice number (0 for edit buffer),\n");
	e("and 'P' is perf number (0 for edit buffer).\n");
	exit(2);
}
