/*
** M2MPU -- Convert Music file to MPU (time-tagged MIDI)
**	psl 1/5/85
** Music file format is:
** lyric note	note  note  note  ...
** Note format is MUTRAN-like:
** e.g.	C4q	for a quarter-note of middle C
**	C#4qt	for a quarter-triplet of middle C sharp
**	Bb3e.	for a dotted eighth of Bb below middle C
*/
#include	<stdio.h>
#include	<midi.h>

#define	TIECHAR		'('

#define	MAXEVENT	4096
#define	MAXV		16
#define	MAXCHAN		16
#define	TSCALE		9		/* MPU clock multiples */

int	Chksync	= 1;			/* check voice sync at each #BAR */
int	Nn[] = { 9, 11, 0, 2, 4, 5, 7, };
int	Tv[6];
int	Nr;
long	Cc[MAXV];			/* current clock for voice */
int	Kv[MAXV];			/* key velocity for voice */
int	Chan[MAXV];			/* channel for voice */
int	Vol[]	= {			/* for #SOLO */
	0x01, 0x0e, 0x1c, 0x2a, 0x38, 0x47, 0x55, 0x63, 0x71, 0x7f,
};
double	Artic[MAXV]	= {		/* articulation, note duty cycle */
	0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8,
	0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8,
};

#define	u_char	unsigned char

struct	evstr	{
	long	when;
	u_char	mode;
	u_char	key;
	u_char	vel;
};
struct	evstr	Event[MAXEVENT];	/* key-on/off events */
struct	evstr	*Ep = Event;		/* next unused Event[] */
struct	evstr	*Lop[MAXV];		/* last key-off event pointers */

extern	double	atof();

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

/****/setbuf(stderr, 0);
	infile = (char *) 0;
	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch (argv[i][1]) {
		case 'i':		/* ignore sync problems */
		    Chksync = 0;
		    break;
		default:
		    goto syntax;
		}
	    } else if (infile == (char *) 0)
		infile = argv[i];
	    else
		goto syntax;
	}
	if (infile) {
	    if (!(ifp = fopen(infile, "r"))) {
		perror(infile);
syntax:
		fprintf(stderr, "Usage: %s [-i] [file or stdin]\n", argv[0]);
		exit(2);
	    }
	} else
	    ifp = stdin;
	tempocalc(100);		/* default tempo is 100 / minute */
	m2mpu(ifp);
	exit(0);
}

m2mpu(ifp)
FILE	*ifp;
{
	char a[7][64], buf[512];
	int nf, nv, iv, q;
	double artic;
	int comp();

	nv = 0;
	for (iv = MAXV; --iv >= 0; Lop[iv] = (struct evstr *) 0);
	for (Nr = 1; fgets(buf, sizeof buf, ifp) != NULL; Nr++) {
	    nf = sscanf(buf, "%s%s%s%s%s%s%s",
	     a[0], a[1], a[2], a[3], a[4], a[5], a[6]);
	    if (nf <= 0)
		continue;
	    if (a[0][0] == '#') {
		if (strcmp(a[0], "#ARTIC") == 0) {
		    for (iv = nv; --iv >= 0; ) {
			artic = atof(a[(iv+1) > (nf-1)? nf - 1 : iv + 1]);
			if (artic <= 0. || artic > 1.) {
			    fprintf(stderr, "Error in: %s", buf);
			    fprintf(stderr, "Artic range is 0.1 to 1.0\n");
			    artic = 0.8;
			}
			Artic[iv] = artic;
		    }
		} else if (strcmp(a[0], "#BAR") == 0) {
		    for (iv = nv; --iv > 0 && Cc[iv] == Cc[0]; );
		    if (Chksync && iv) {
			fprintf(stderr, "Voices out of sync by line %d\n", Nr);
			for (iv = nv; --iv > 0; Cc[iv] = Cc[0]);
		    }
		    Ep->when = Cc[0];
		    Ep->mode = RT_TCWME;
		    Ep++;
		} else if (strcmp(a[0], "#CHAN") == 0) {
		    if (nf - 1 != nv) {
			fprintf(stderr, "Too %s voices in line %d\n",
			 nf-1>nv? "many" : "few", Nr);
			continue;
		    }
		    for (iv = 0; iv < nv; iv++) {
			Chan[iv] = atoi(a[iv + 1]) - 1;
			if (Chan[iv] < 0 || Chan[iv] >= MAXCHAN) {
			    fprintf(stderr, "Chan %d out of range\n",
			     Chan[iv] + 1);
			}
		    }
		} else if (strcmp(a[0], "#SOLO") == 0) {
		    if (nf - 1 != nv) {
			fprintf(stderr, "Too %s voices in SOLO line %d\n",
			 nf-1>nv? "many" : "few", Nr);
			continue;
		    }
		    for (iv = 0; iv < nv; iv++) {
			q = a[iv + 1][0];
			if (q == '-')
			    Kv[iv] = 0;
			else if ('0' <= q && q <= '9')
			    Kv[iv] = Vol[q - '0'];
			else if (q == 'S')
			    Kv[iv] = 21;
			else if (q == 'M')
			    Kv[iv] = 64;
			else if (q == 'L')
			    Kv[iv] = 106;
			else {
			    fprintf(stderr, "Bad symbol '%s' in line %d\n",
			     a[iv+1], Nr);
			    continue;
			}
		    }
		} else if (strcmp(a[0], "#SYNC") == 0) {
		    q = 0;
		    for (iv = nv; --iv > 0; )
			if (Cc[iv] > Cc[0])
			    Cc[0] = Cc[iv];
		    for (iv = nv; --iv > 0; Cc[iv] = Cc[0]);
		} else if (strcmp(a[0], "#TEMPO") == 0) {
		    tempocalc(atoi(a[1]));
		} else if (strcmp(a[0], "#VOICES") == 0) {
		    mflush(nv);
		    nv = nf - 1;
		    for (iv = 0; iv < nv; iv++) {
			Cc[iv] = 0;
			Kv[iv] = 60;
		    }
		}
	    } else {
		if (nf != nv + 1) {
		    fprintf(stderr, "%s on line %d: %s",
		     nf > nv + 1? "extra garbage" : "missing note(s)", Nr, buf);
		    continue;
		}
		for (iv = 0; iv < nv; iv++)
		    addevents(iv, a[iv + 1]);
	    }
	}
	for (iv = nv; --iv > 0 && Cc[iv] == Cc[0]; );	/* EOF sync check */
	if (Chksync && iv > 0)
	    fprintf(stderr, "Voices out of sync at EOF (V%d != V0)\n", iv);
	mflush(nv);
}

mflush(nv)
{
	register int iv;

	qsort(Event, Ep - Event, sizeof *Ep, comp);
	dump(Cc[0]);
	Ep = Event;
	for (iv = nv; --iv >= 0; Cc[iv] = 0);
}

addevents(iv, code)
char	*code;
{
	register char *cp;
	int oct, note, dur, k, dotdur, adur, tied;

	cp = code;
	if (*cp == '-')
	    return;
	if (*cp == TIECHAR) {
	    tied = *cp++;
	    oct = 1;
	} else {
	    tied = 0;
	    if (*cp == 'R')
		oct = 0;
	    else if (*cp < 'A' || 'G' < *cp) {
		fprintf(stderr, "Bad note format '%s' in line %d\n", code, Nr);
		return;
	    } else {
		note = Nn[*cp++ - 'A'];
		if (*cp == 'b') {
		    --note;
		    cp++;
		} else if (*cp == '#') {
		    note++;
		    cp++;
		}
		oct = *cp - '0';
		if (oct <= 0 || 9 < oct) {
		    fprintf(stderr, "Octave out of range in '%s', line %d\n",
		     code, Nr);
		    return;
		}
	    }
	    cp++;
	}
	dur = Tv[tnum(*cp++)];
	dotdur = dur / 2;
	while (*cp == '.') {
	    dur += dotdur;
	    dotdur /= 2;
	    cp++;
	}
	while (*cp == 't') {
	    dur = (2 * dur) / 3;
	    cp++;
	}
	if (dur <= 0) {
	    fprintf(stderr, "Bad time value for '%s' in line %d\n", code, Nr);
	    return;
	}
	if (oct <= 0) {
	    Cc[iv] += dur;
	    return;
	}
	if (Kv[iv]) {
	    adur = Artic[iv] * dur + 0.5;
	    adur = adur <= 0? 1 : adur;
	    if (tied) {
		if (Lop[iv])
		    Lop[iv]->when = Cc[iv] + adur;
	    } else {
		k = 12 + 12 * oct + note;
		Ep->when = Cc[iv];
		Ep->mode = 0x90 + Chan[iv];
		Ep->key = k;
		Ep->vel = Kv[iv];
		Ep++;
		Ep->when = Cc[iv] + adur;
		Ep->mode = 0x90 + Chan[iv];
		Ep->key = k;
		Ep->vel = 0;
		Lop[iv] = Ep;
		Ep++;
	    }
	}
	Cc[iv] += dur;
}

/* Key-off comes before TCWME comes before key-on.
** Lower notes come before higher ones.
** Softer notes come before louder ones.
*/
comp(a, b)
struct	evstr	*a, *b;
{
	register int i;

	i = (int)(a->when - b->when);
	if (i == 0) {
	    if (a->mode == RT_TCWME)
		i = b->vel == 0? 1 : -1;
	    else if (b->mode == RT_TCWME)
		i = a->vel == 0? -1 : 1;
	    else if ((a->vel == 0) ^ (b->vel == 0))
		i = a->vel - b->vel;
	    else {
		i = a->key - b->key;
		if (i == 0)
		    i = a->vel - b->vel;
	    }
	}
	return(i);
}

dump(maxclock)
long	maxclock;
{
	long last;
	struct evstr *evp;
	long putdt();

	last = 0;
	for (evp = Event; evp < Ep; evp++) {
	    last = putdt(last, evp->when, stdout);
	    putc(evp->mode, stdout);
	    if (evp->mode != RT_TCWME) {
		putc(evp->key, stdout);
		putc(evp->vel, stdout);
	    }
	}
	if (last < maxclock) {
	    putdt(last, maxclock, stdout);
	    putc(MPU_NO_OP, stdout);
	}
}

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

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

tempocalc(tempo)
{
	register int cpq;

	cpq = (TSCALE * 12000) / tempo;
	Tv[tnum('w')] = 4 * cpq;
	Tv[tnum('h')] = 2 * cpq;
	Tv[tnum('q')] = cpq;
	Tv[tnum('e')] = (cpq + 1) / 2;
	Tv[tnum('s')] = (cpq + 2) / 4;
	Tv[tnum('t')] = (cpq + 4) / 8;
}

tnum(c)
char	c;
{
	switch (c) {
	case 'w':	return(0);
	case 'h':	return(1);
	case 'q':	return(2);
	case 'e':	return(3);
	case 's':	return(4);
	case 't':	return(5);
	}
	fprintf(stderr, "Bad time value '%c' in line %d\n", c, Nr);
	return(5);
}
