/*
**	TAB2MPU -- Convert tab to mpu format
**	psl 3/88
*/
#include	<stdio.h>
#include	<midi.h>

#define	MAXSTRINGS	16	/* that ought to hold them */
#define	KVEL		0x40	/* default volume */
#define	TIECHAR		'('

int	Cps		= 30;	/* MPU clocks / note, (sixteenths) */
int	Acps		= 24;	/* articulated Cps (sixteenths, 80% duty) */
int	Tuning[MAXSTRINGS];	/* How the instrument is tuned */
int	Chan[MAXSTRINGS];	/* channels for each string */
int	Nut[MAXSTRINGS];	/* string lengths (e.g. banjo Nut[4] = 5) */
int	Nstrings	= 0;
int	Kvel		= KVEL;	/* how loud we're playing (key velocity) */
int	Nn[] = { 9, 11, 0, 2, 4, 5, 7, };
double	Artic	= 0.8;		/* articulation, note duty cycle */
long	Lastwhen;

extern	double	atof();

main(argc, argv)
char	*argv[];
{
	register int i;
	FILE *ifp;

	ifp = stdin;
	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch (argv[i][1]) {
		default:
		    fprintf(stderr, "Usage: %s [file or stdin]\n", argv[0]);
		    fprintf(stderr, "See tab(5) for tab language details.\n");
		    exit(2);
		}
	    } else if (!(ifp = fopen(argv[i], "r")))
		perror(argv[i]);
	    else {
		detab(ifp, stdout);
		fclose(ifp);
	    }
	}
	if (ifp == stdin)
	    detab(ifp, stdout);
	exit(0);
}

detab(ifp, ofp)
FILE	*ifp, *ofp;
{
	char *cp, buf[256], a[9][64];
	int nr, nf, s, fret[MAXSTRINGS], a0;
	long when, offtime;

	when = Lastwhen = offtime = 0L;
	for (nr = 1; fgets(buf, sizeof buf, ifp); nr++) {
	    nf = sscanf(buf, "%s%s%s%s%s%s%s%s%s",
	     a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]);
	    if (nf <= 0)
		continue;
	    if (a[0][0] == '#') {
		if (strcmp(a[0], "#ARTIC") == 0) {
		    Artic = atof(a[1]);
		    if (Artic <= 0. || Artic > 1.) {
			fprintf(stderr, "Error in: %s", buf);
			fprintf(stderr, "Articulation range is 0.1 to 1.0\n");
			Artic = 0.8;
		    }
		    Acps = Cps * Artic + 0.5;
		} else if (strcmp(a[0], "#CHANS") == 0) {
		    getnums(nr, nf, a, Chan, "channel", 1, 16, -1);
		} else if (strcmp(a[0], "#NUT") == 0) {
		    getnums(nr, nf, a, Nut, "nut", 0, 22, 0);
		} else if (strcmp(a[0], "#SPEED") == 0) {
		    Cps = (2 * MPU_CLOCK_PERIOD) / atoi(a[1]);
		    Acps = Cps * Artic + 0.5;
		} else if (strcmp(a[0], "#TUNING") == 0) {
		    Nstrings = gettuning(nr, nf, a);
		    for (s = Nstrings; --s >= 0; fret[s] = -1);
		} else if (strcmp(a[0], "#VOL") == 0
		        || strcmp(a[0], "#VOLUME") == 0
		        || strcmp(a[0], "#VEL") == 0
		        || strcmp(a[0], "#VELOCITY") == 0) {
		    Kvel = atoi(a[1]);
		}
		continue;
	    }
	    if (Nstrings == 0) {
		fprintf(stderr, "Line %d: data before '#TUNING' spec\n", nr);
		exit(1);
	    }
	    a0 = (*buf > ' ')? 1 : 0;		/* skip finger info */
	    if (nf - a0 < Nstrings) {
		fprintf(stderr, "Line %d: missing data\n", nr);
		exit(1);
	    }
	    for (s = Nstrings; --s >= 0; ) {
		cp = a[a0 + Nstrings - s - 1];
		if (*cp != TIECHAR && fret[s] != -1) {
		    pluck(ofp, offtime, s, fret[s], 0);		/* key-off */
		    fret[s] = -1;
		}
	    }
	    for (s = Nstrings; --s >= 0; ) {
		cp = a[a0 + Nstrings - s - 1];
		if ('0' <= *cp && *cp <= '9')
		    pluck(ofp, when, s, fret[s] = atoi(cp), Kvel); /* key-on */
	    }
	    offtime = when + Acps;
	    when += Cps;
	}
	for (s = Nstrings; --s >= 0; )
	    if (fret[s] != -1)
		pluck(ofp, offtime, s, fret[s], 0);		/* key-off */
	pluck(ofp, when, -1, -1, -1);	/* TCWME */
}

pluck(ofp, when, s, f, v)
FILE	*ofp;
long	when;
{
	int dt;

	dt = when - Lastwhen;
	while (dt >= MPU_CLOCK_PERIOD) {
	    putc(RT_TCIP, ofp);
	    dt -= MPU_CLOCK_PERIOD;
	}
	putc(dt, ofp);			/* timing byte */
	if (s >= 0 && f >= 0 && v >= 0) {
	    putc(CH_KEY_ON | Chan[s], ofp);
	    if (f == 0)
		putc(Tuning[s], ofp);
	    else
		putc(Tuning[s] + f - Nut[s], ofp);
	    putc(v, ofp);
	} else
	    putc(RT_TCWME, ofp);
	Lastwhen = when;
}

gettuning(nr, nf, a)
char	a[][64];
{
	register char *cp;
	register int n, i;
	int t[MAXSTRINGS];

	for (n = 0; n < nf - 1 && n < MAXSTRINGS; n++) {
	    cp = a[n + 1];
	    i = -1;
	    if ('0' <= *cp && *cp <= '9')
		i = strtol(cp, 0, 0);
	    else if ('A' <= *cp && *cp <= 'G')
		i = pitch2num(nr, cp);
	    if (i > -1)
		t[n] = i;
	    else {
		fprintf(stderr, "Line %d: Note format error in %s", nr, cp);
		exit(1);
	    }
	}
	for (i = n; --i >= 0; Tuning[i] = t[n - i - 1]);
	return(n);
}

pitch2num(nr, buf)
char	*buf;
{
	register char *cp = buf;
	register int n, o;

	if (*cp == 'R' || *cp == '-')
	    return(-1);
	if (*cp < 'A' || *cp > 'G') {
	    fprintf(stderr, "Line %d: bad note format in %s\n", nr, buf);
	    return(-1);
	}
	n = Nn[*cp++ - 'A'];
	while (*cp == 'b') {
	    --n;
	    cp++;
	}
	while (*cp == '#') {
	    n++;
	    cp++;
	}
	if (*cp == '-')
	    o = -(*++cp - '0');
	else
	    o = *cp - '0';
	if (o < -1 || o > 8) {
	    fprintf(stderr, "Line %d: octave out of range in: %s\n", nr, buf);
	    return(-1);
	}
	return(12 + 12 * o + n);
}

getnums(nr, nf, a, array, name, lo, hi, delta)
char	a[][64], *name;
int	array[];
{
	register char *cp;
	register int n, i;
	int x[MAXSTRINGS];

	for (n = 0; n < nf - 1 && n < MAXSTRINGS; n++) {
	    cp = a[n + 1];
	    i = strtol(cp, 0, 0);
	    if (lo <= i && i <= hi)
		x[n] = i + delta;
	    else {
		fprintf(stderr, "Line %d: %s spec error (range is %d:%d)\n",
		 nr, name, lo, hi);
		exit(1);
	    }
	}
	for (i = n; --i >= 0; array[i] = x[n - i - 1]);
	return(n);
}
