/*
**	CCC -- Chord Chart Compiler
**	psl 12/85
*/
#include	<stdio.h>
#include	"midi.h"

#define	MAXCH		256
#define	NAMELEN		16
#define	MAXV		8

#define	BAR_PERIOD	(2 * MPU_CLOCK_PERIOD)

#define	MINOCT	-1
#define	MAXOCT	9

char	Noteval[]	= { 9, 11, 0, 2, 4, 5, 7, };
char	Ebuf[128];
int	Step		= BAR_PERIOD / 4;	/* defaults to quarter notes */
int	Style		= 'n';		/* legato, normal, or staccato */
int	Numchords	= 1;		/* "-" already defined */
long	Time		= 0;

struct	chstr	{
	char	name[NAMELEN];
	char	key[MAXV];
} Ch[MAXCH]	= {
	{ "-",	{ 0, }, },		/* to allow "-" to be used as a rest */
};

char	*peel(), *copy();

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

	if (argc == 1)
	    process(stdin);
	else {
	    for (i = 1; i < argc; i++) {
		if ((ifp = fopen(argv[i], "r")) == NULL)
		    perror(argv[i]);
		else {
		    process(ifp);
		    fclose(ifp);
		}
	    }
	}
}

process(ifp)
FILE	*ifp;
{
	register char *bp, *cp;
	register int v, chnum, lastchnum, m, n, dur;
	char buf[512];
	FILE *fp;

	while (fgets(buf, sizeof buf, ifp) != NULL) {
	    bp = peel(cp = buf);
	    if (*cp == '\n' || *cp == '#') {
		continue;
	    } else if (wrdcmp(cp, "Include") == 0) {
		if (*bp++ != '"')
		    syntax("Include must use quotes (\")");
		for (cp = bp; *cp && *cp != '"'; cp++);
		if (*cp != '"')
		    syntax("Include must use quotes (\")");
		*cp = '\0';
		if ((fp = fopen(bp, "r")) == NULL) {
		    perror(bp);
		    exit(1);
		}
		process(fp);
		fclose(fp);
	    } else if (wrdcmp(cp, "Style") == 0) {
		Style = *bp;
	    } else if (wrdcmp(cp, "Quantum") == 0) {
		if (wrdcmp(bp, "whole") == 0)
		    Step = BAR_PERIOD;
		else if (wrdcmp(bp, "half") == 0)
		    Step = BAR_PERIOD / 2;
		else if (wrdcmp(bp, "quarter") == 0)
		    Step = BAR_PERIOD / 4;
		else if (wrdcmp(bp, "eighth") == 0)
		    Step = BAR_PERIOD / 8;
		else if (wrdcmp(bp, "sixteenth") == 0)
		    Step = BAR_PERIOD / 16;
		else
		    syntax("Unrecognized quantum arg");
	    } else if (wrdcmp(cp, "Chord") == 0) {
		bp = peel(cp = bp);
		if ((chnum = Numchords++) >= MAXCH)
		    syntax("Too many chords defined");
		copy(cp, Ch[chnum].name);
		for (v = 0; v < MAXV && (bp = peel(cp = bp)) && *cp; v++)
		    Ch[chnum].key[v] = keynum(cp);
	    } else {
		dur = (Style == 's'? Step / 4 : Step);
		dur = (Style == 'n'? (4 * Step) / 5 : dur);
		lastchnum = -1;
		while (*cp) {
		    if (*cp == '/') {
			if ((chnum = lastchnum) == -1)
			    syntax("Line beginning with '/'");
		    } else {
			if ((chnum = find(cp)) < 0) {
			    sprintf(Ebuf, "Undefined chord: %s", cp);
			    syntax(Ebuf);
			}
		    }
		    for (v = 0; v < MAXV; v++) {
			n = Ch[chnum].key[v];
			if (Style == 'l' && lastchnum != -1) {
			    if ((m = Ch[lastchnum].key[v]) == n)
				continue;
			    if (m != 0) {
				putdt(Time, stdout);
				putc(m, stdout);
				putc(0, stdout);
			    }
			}
			if (n != 0) {
			    putdt(Time, stdout);
			    putc(0x90, stdout);
			    putc(n, stdout);
			    putc(0x40, stdout);
			}
		    }
		    lastchnum = chnum;
		    if (Style != 'l') {
			for (v = 0; v < MAXV && (n = Ch[chnum].key[v]); v++) {
			    putdt(Time + dur, stdout);
			    putc(n, stdout);
			    putc(0, stdout);
			}
		    }
		    Time += Step;
		    bp = peel(cp = bp);
		}
		if (Style == 'l' && lastchnum != -1) {
		    for (v = 0; v < MAXV && (n = Ch[chnum].key[v]); v++) {
			putdt(Time, stdout);
			putc(n, stdout);
			putc(0, stdout);
		    }
		}
	    }
	}
}

wrdcmp(ap, bp)
char	*ap, *bp;
{
	while (*bp && *ap == *bp) {
	    ap++;
	    bp++;
	}
	return ((*ap <= ' ' && *bp == '\0')? 0 : *ap - *bp);
}

syntax(msg)
char	*msg;
{
	fprintf(stderr, "Chord chart syntax error: %s\n", msg);
	exit(1);
}

char	*
peel(sp)
char	*sp;
{
	while (*sp > ' ')
	    sp++;
	if (*sp != '\0') {	
	    *sp++ = '\0';
	    while (*sp && *sp <= ' ')
		sp++;
	}
	return(sp);
}

putdt(time, ofp)
long	time;
FILE	*ofp;
{
	register int dt;
	static long last;

	dt = time - last;
	while (dt >= MPU_CLOCK_PERIOD) {
	    putc(RT_TCIP, ofp);
	    dt -= MPU_CLOCK_PERIOD;
	}
	putc(dt, ofp);
	last = time;
}

find(name)
char	*name;
{
	register int i;

	for (i = 0; i < Numchords; i++)
	    if (wrdcmp(name, Ch[i].name) == 0)
		return(i);
	return(-1);
}

keynum(str)
char	*str;
{
	register char *cp;
	int i, note, oct;

	cp = str;
	i = *cp++;
	if ('0' <= i && i <= '9') {		/* decimal */
	    return(atoi(--cp));
	} else if (i == 'x' || i == 'X') {	/* hex */
	    return(hex(cp[0]) << 4 + hex(cp[1]));
	} else {				/* note name */
	    if ('A' <= i && i <= 'G')
		note = Noteval[i - 'A'];
	    else if ('a' <= i && i <= 'g')
		note = Noteval[i - 'a'];
	    else
		goto oops;
	    if (*cp == '#') {
		note++;
		cp++;
	    } else if (*cp == 'b') {
		--note;
		cp++;
	    }
	    oct = atoi(cp);
	    if (oct < MINOCT || oct > MAXOCT) {
oops:
		sprintf(Ebuf, "Note format error: %s\n", str);
		syntax(Ebuf);
	    }
	    return(12 * (oct - MINOCT) + note);
	}
}

hex(c)
char	c;
{
	if ('0' <= c && c <= '9')
	    return(c - '0');
	if ('A' <= c && c <= 'F')
	    return(c - 'A' + 10);
	if ('a' <= c && c <= 'f')
	    return(c - 'a' + 10);
	fprintf(stderr, "Error in hex digit '%c'\n", c);
	return(0);
}

char	*
copy(f, t)
register char *f, *t;
{
	while (*t++ = *f++);
	return(--t);
}
