/*
** DP2MPU -- Convert drum pattern file to MPU (time-tagged MIDI)
**	psl 4/88
** See dp(5) for details.
*/
#include	<stdio.h>
#include	<midi.h>

#define	MAXNOTES	4096		/* maximum notes per file */
#define	MAXKEY		128
#define	MAXDEFS		256
#define	MAXROLLS	8
#define	SYMLEN		16
#define	TSCALE		36		/* time kept in 36ths of MPU clocks */

struct	defstr	{
	char	sym[SYMLEN];
	char	val[SYMLEN];
} Def[MAXDEFS];
int	Ndefs	= 0;

struct	rollstr	{
	char	sym;		/* char to indicate roll */
	int	vol;		/* index into Vol[] */
	int	quant;		/* beats/bar */
} Roll[MAXROLLS]	= {
	'~',	3,	64,
};
int	Nrolls	= 1;

struct	notestr	{
	long	when;			/* MPU clocks * TSCALE */
	u_char	mode;
	u_char	key;
	u_char	vel;
} N[MAXNOTES];
int	Dur	= 1;		/* 0 => make duration = 0 (else Cpn) */
int	Nnotes	= 0;
int	Quant	= 8;
int	Barlen	= TSCALE * 2 * MPU_CLOCK_PERIOD;
int	Cpn	= (TSCALE * 2 * MPU_CLOCK_PERIOD) / 8;
int	Vol[]	= {
	0x01, 0x0e, 0x1c, 0x2a, 0x38, 0x47, 0x55, 0x63, 0x71, 0x7f,
};
long	When[MIDI_MAX_CHANS][MAXKEY];

long		putdt();
struct defstr	*lookup();
struct rollstr	*rlookup();

main(argc, argv)
char	*argv[];
{
	register char *cp;
	char buf[512];
	int i, dt, chan, key;
	long now, maxwhen, nextwhen;
	struct rollstr *rp;
	int compar();

	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch (argv[i][1]) {
		case 'd':
		    if (argv[i][2] != '0' || argv[i][3] != '\0')
			goto syntax;
		    Dur = 0;
		    break;
		default:
		    goto syntax;
		}
	    } else {
syntax:
		fprintf(stderr,
		 "Usage: %s [-d0] <file.dp >file.mpu\n", argv[0]);
		exit(2);
	    }
	}
	maxwhen = 0L;
	while (fgets(buf, sizeof buf, stdin)) {
	    if (*buf == '#') {
		for (cp = buf; *cp > ' '; cp++);
		for (; *cp && *cp <= ' '; *cp++ = '\0');
		if (strcmp(buf, "#BARLEN") == 0) {
		    Barlen = TSCALE * strtol(cp, 0, 10);
		    Cpn = Barlen / Quant;
		} else if (strcmp(buf, "#DEFINE") == 0) {
		    defdef(cp);
		} else if (strcmp(buf, "#QUANT") == 0) {
		    Quant = strtol(cp, 0, 10);
		    Cpn = Barlen / Quant;
		} else if (strcmp(buf, "#ROLL") == 0) {
		    rolldef(cp);
		} else if (strcmp(buf, "#SYNC") == 0) {
		    for (chan = MIDI_MAX_CHANS; --chan >= 0; )
			for (key = MAXKEY; --key >= 0; )
			    When[chan][key] = maxwhen;
		}
		continue;
	    }
	    if (*buf == '\n')
		continue;
	    for (cp = buf; *cp > ' '; cp++);
	    for (; *cp && *cp <= ' '; *cp++ = '\0');
	    if (*cp == '\n' || *cp == '\0')
		continue;
	    if ((i = chankey(buf)) < 0)
		exit(1);
	    chan = i >> 8;
	    key = i & 0x7F;
	    for (; *cp > ' '; cp++) {
		nextwhen = When[chan][key] + Cpn;
		if ('0' <= *cp && *cp <= '9') {
		    if (Nnotes >= MAXNOTES -1) {
			fprintf(stderr, "Too many notes, limit is %d\n",
			 MAXNOTES / 2);
			exit(1);
		    }
		    hit(chan, key, *cp - '0', Cpn);
		} else if (*cp != '-') {
		    if ((rp = rlookup(*cp))) {
			dt = Barlen / rp->quant;
			while ((i = nextwhen - When[chan][key]) > 0) {
			    dt = dt > i? i : dt;
			    hit(chan, key, rp->vol, dt);
			    When[chan][key] += dt;
			}
		    } else
			continue;
		}
		When[chan][key] = nextwhen;
		if (nextwhen > maxwhen)
		    maxwhen = nextwhen;
	    }
	}
	qsort(N, Nnotes, sizeof N[0], compar);
	now = 0l;
	for (i = 0; i < Nnotes; i++) {
	    now = putdt(stdout, now, N[i].when);
	    putc(N[i].mode, stdout);
	    putc(N[i].key, stdout);
	    putc(N[i].vel, stdout);
	}
	if (now < maxwhen) {
	    putdt(stdout, now, maxwhen);
	    putc(MPU_NO_OP, stdout);
	}
}

hit(chan, key, v, d)
{
	N[Nnotes].when = When[chan][key];
	N[Nnotes].mode = CH_KEY_ON | chan;
	N[Nnotes].key = key;
	N[Nnotes].vel = Vol[v];
	Nnotes++;
	N[Nnotes].when = When[chan][key] + (Dur? d : 0);
	N[Nnotes].mode = CH_KEY_ON | chan;
	N[Nnotes].key = key;
	N[Nnotes].vel = 0;
	Nnotes++;
}

long
putdt(ofp, now, when)
FILE	*ofp;
long	now, when;
{
	register int dt;

	dt = (when - now) / TSCALE;
	when = now + dt * TSCALE;
	while (dt >= MPU_CLOCK_PERIOD) {
	    dt -= MPU_CLOCK_PERIOD;
	    putc(RT_TCIP, ofp);
	}
	putc(dt, ofp);
	return(when);
}

defdef(buf)
char	*buf;
{
	register char *cp;
	int i;
	struct defstr *dp;

	for (cp = buf; *cp > ' '; cp++);
	for (; *cp && *cp <= ' '; *cp++ = '\0');
	if (*cp == '\n' || *cp == '\0') {
	    fprintf(stderr, "Bad #DEFINE in: %s", buf);
	    exit(1);
	}
	if (strlen(buf) > SYMLEN) {
	    fprintf(stderr, "Symbol too long in #DEFINE at: %s %s", buf, cp);
	    exit(1);
	}
	if (!(dp = lookup(buf))) {
	    if (Ndefs >= MAXDEFS) {
		fprintf(stderr, "Too many #DEFINEs at: %s %s", buf, cp);
		exit(1);
	    }
	    dp = &Def[Ndefs++];
	    strcpy(dp->sym, buf);
	}
	cp[SYMLEN - 1] = '\0';
	if ((i = chankey(cp)) < 0)
	    exit(1);
	sprintf(dp->val, "%x/%02x", (i >> 8) + 1, i & 0x3F);
}

chankey(ip)
char	*ip;
{
	char *cp;
	int i, chan, key;
	struct defstr *dp;

	for (cp = ip; *cp > ' '; cp++);
	*cp = '\0';
	for (i = 0; i < 99 && ip[1] != '/'; i++) {
	    if (!(dp = lookup(ip))) {
		fprintf(stderr, "Bad chan/key in: %s\n", ip);
		exit(1);
	    }
	    ip = dp->val;
	}
	chan = strtol(ip, 0, 16) - 1;
	if (chan < 0 || chan >= MIDI_MAX_CHANS) {
	    fprintf(stderr, "Bad chan in: %s\n", ip);
	    return(-1);
	}
	key = strtol(&ip[2], 0, 16);
	if (key <= 0 || key >= MAXKEY) {
	    fprintf(stderr, "Bad key (%x) in: %s\n", key, ip);
	    return(-1);
	}
	return((chan << 8) | key);
}

struct defstr *
lookup(buf)
char	*buf;
{
	struct defstr *dp;

	for (dp = &Def[Ndefs]; --dp >= Def; )
	    if (strcmp(buf, dp->sym) == 0)
		return(dp);
	return((struct defstr *) 0);
}

rolldef(buf)
char	*buf;
{
	register char *cp;
	int sym, vol, quant;
	struct rollstr	*rp;

	cp = buf;
	sym = *cp++;
	if (sym <= ' ' || sym > '~' || *cp++ > ' ')
	    goto rdoops;
	for (; *cp <= ' '; cp++);
	vol = *cp++ - '0';
	if (vol < 0 || vol > 9 || *cp++ > ' ')
	    goto rdoops;
	for (; *cp <= ' '; cp++);
	quant = atoi(cp);
	if (quant < 1 || quant > 2 * MPU_CLOCK_PERIOD) {
rdoops:
	    fprintf(stderr, "Bad format in: #ROLL %s", buf);
	    exit(1);
	}
	if (!(rp = rlookup(sym))) {
	    if (Nrolls >= MAXROLLS) {
		fprintf(stderr, "Too many #ROLLs at: %s", buf);
		exit(1);
	    }
	    rp = &Roll[Nrolls++];
	    rp->sym = sym;
	}
	rp->vol = vol;
	rp->quant = quant;
}

struct rollstr	*
rlookup(sym)
char	sym;
{
	struct rollstr	*rp;

	for (rp = &Roll[Nrolls]; --rp >= Roll; )
	    if (rp->sym == sym)
		return(rp);
	return((struct rollstr *) 0);
}

compar(p1, p2)
struct notestr *p1, *p2;
{
	register int i;

	i = (int)(p1->when - p2->when);
	if (i == 0)	/* if Dur then off before on, else on before off */
	    i = Dur? (p1->vel - p2->vel) : (p2->vel - p1->vel);
	return(i);
}
