/*
**	MIDI2M -- Turn MIDI files into m-format files
**	psl 3/86
**	Added "percussion"; ignore note-offs & use the next note-on
**		to define time value.
**		Also use "X" for lyric instead of "-"
**	Added triplets.
**	Added forced barlines
*/

#include <stdio.h>
#include <midi.h>
#include <math.h>

#define	MAXCHAN	16
#define	MAXV	8
#define	MAXKEY	128
#define	SCALE	8
#define	BAR	(SCALE * 2 * MPU_CLOCK_PERIOD)

char	*Title	= 0;			/* title of piece */
int	Meter1	= 4, Meter2 = 4;	/* time signature of piece */
int	Bflg	= 0;			/* force barlines */
int	Cpb	= BAR;			/* clocks per bar (scaled) */
int	Cpw	= BAR;			/* clocks per whole note (scaled) */
int	Perc	= 0;			/* input is percussion */
long	Start[MAXCHAN];			/* starts of notes */
int	Curkey[MAXCHAN];		/* keys of notes */
int	Qpb	= 192;			/* quanta per bar */
int	Quant;				/* clocks per quantum */
int	Numv	= 1;			/* number of voices to display */
int	Voices[MAXCHAN]	= { 1, };	/* associate voices & channels */
int	Chans[MAXV + 1]	= { 0, };	/* associate channels & voices */

long	quantize();

syntax(prog)
char	*prog;
{
	fprintf(stderr, "Usage: %s [options] files...\n", prog);
	fprintf(stderr, "-b\tForce barlines every bar (instead of TCWME)\n");
	fprintf(stderr, "-L#\tSet # MIDI clocks per bar\n");
	fprintf(stderr, "-m#/#\tSet meter to #/#\n");
	fprintf(stderr, "-p\tTreat the inmput as percussion (weird)\n");
	fprintf(stderr, "-q#\tSet smallest quantum to #\n");
	fprintf(stderr, "-tTITLE\tSet title to TITLE\n");
	fprintf(stderr, "-v#=#\tTake voice # (1-8) from channel # (1-16)\n");
	exit(2);
}

main(argc, argv)
char *argv[];
{
	int i, n, voice, chan;
	FILE *f;

	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch (argv[i][1]) {
		case 'b':
		    Bflg++;
		    break;
		case 'L':
		    Cpb = SCALE * atoi(&argv[i][2]);
		    break;
		case 'm':
		    Meter1 = atoi(&argv[i][2]);
		    if (argv[i][3] == '/')
			Meter2 = atoi(&argv[i][4]);
		    else if (argv[i][4] == '/')
			Meter2 = atoi(&argv[i][5]);
		    else
			syntax(argv[0]);
		    if (Meter2 <= 1 || Meter2 <= 0)
			syntax(argv[0]);
		    break;
		case 'p':
		    Perc = 1;
		    break;
		case 'q':
		    Qpb = atoi(&argv[i][2]);
		    break;
		case 't':
		    Title = &argv[i][2];
		    break;
		case 'v':
		    voice = atoi(&argv[i][2]);
		    chan = atoi(&argv[i][4]) - 1;
		    if (argv[i][3] != '='
		     || voice < 1 || voice > MAXV
		     || chan < 0 || chan >= MAXCHAN)
			syntax(argv[0]);
		    if (voice > Numv)
			Numv = voice;
		    Chans[voice] = chan;
		    Voices[chan] = voice;
		    break;
		default:
		    syntax(argv[0]);
		}
	    }
	}
	Quant = Cpb / Qpb;
	if (Qpb * Quant != Cpb)
	    printf("Warning: %d does not divide %d evenly enough.\n",
	     Qpb, Cpb / SCALE);
	Cpw = (Meter2 * Cpb) / Meter1;
	n = 0;
	for (i = 1; i < argc; i++) {
	    if (argv[i][0] != '-') {
		if ((f = sopen(argv[i], "r")) != NULL) {
		    cpn(f, stdout);
		    sclose(f);
		    n++;
		} else
		    perror(argv[i]);
	    }
	}
	if (n == 0)
	    cpn(stdin, stdout);
}

cpn(ifp, ofp)
FILE	*ifp, *ofp;
{
	int status, type, voice, chan, key, vel;
	long now, snow, nbar;
	MCMD *mp;
	long flsh();

	if (Title)
	    fprintf(ofp, "#TITLE\t%s\n", Title);
	fprintf(ofp, "#METER\t%d\t%d\n", Meter1, Meter2);
	fprintf(ofp, "#VOICES");
	for (voice = 1; voice <= Numv; fprintf(ofp, "\t%d", voice++));
	fprintf(ofp, "\n");
	fprintf(ofp, "#CHAN");
	for (voice = 1; voice <= Numv; fprintf(ofp, "\t%d", Chans[voice++]+1));
	fprintf(ofp, "\n");
	fprintf(ofp, "#BAR\n");
	nbar = Bflg? Cpb / SCALE : 999999;
	now = 0L;
	while (mp = getmcmd(ifp, now)) {
	    now = mp->when;
	    snow = SCALE * now;
	    status = mp->cmd[0];
	    type = (status & M_CMD_MASK);
	    if (type == CH_KEY_OFF) {
		type = CH_KEY_ON;
		mp->cmd[2] = 0;
	    }
	    if (type == CH_KEY_ON) {
		if (nbar <= mp->when)
		    nbar = flsh(ofp, nbar, mp->when, snow);
		chan = (status & M_CHAN_MASK);
		if (Voices[chan] == 0)
		    continue;
		key = mp->cmd[1];
		vel = mp->cmd[2];
		if (vel > 0) {
		    putcpn(ofp, chan, snow);
		    Curkey[chan] = key;
		} else if (key == Curkey[chan] && !Perc) {
		    putcpn(ofp, chan, snow);
		    Curkey[chan] = 0;
		}
	    } else if (status == RT_TCWME && nbar == 999999)
		flsh(ofp, nbar, 0L, snow);
	}
	flsh(ofp, nbar, now, snow);
	if (nbar < now + Cpb / SCALE)
	    fprintf(ofp, "#BAR\n");
}

long
flsh(ofp, nbar, when, snow)
FILE	*ofp;
long	nbar, when, snow;
{
	register int voice;

	for (voice = 1; voice <= Numv; voice++) {
	    putcpn(ofp, Chans[voice], snow);
	    Start[Chans[voice]] = snow;	/* shouldn't be needed, but */
	}
	while (nbar <= when) {
	    fprintf(ofp, "#BAR\n");
	    nbar += Cpb / SCALE;
	}
	if (nbar == 999999)
	    fprintf(ofp, "#BAR\n");
	return(nbar);
}

putcpn(fp, chan, now)	/* current note or rest ends now; output it */
FILE	*fp;
long	now;
{
	int key, dur;
	long tbeg;

	key = Curkey[chan];
	tbeg = Start[chan];
	dur = quantize(now - tbeg);
	if (dur)
	    Start[chan] = tbeg + putnote(fp, key, dur, chan);
}

char	*nn[]	= {
	"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
};

putnote(fp, n, t, chan)		/* put out note and return actual duration */
FILE	*fp;
{
	register int i, voice, vd, vt, dur;
	static char dbuf[8], tbuf[8];

	voice = Voices[chan];
	fprintf(fp, "%c\t", Perc? 'X' : '/');
	for (i = 1; i < voice; i++)
	    fprintf(fp, "-\t");
	if (n == 0)
	    fprintf(fp, "R");
	else
	    fprintf(fp, "%s%d", nn[n%12], n / 12 - 1);
	if ((vd = tvd(t, dbuf))  == t
	 || vd >= (vt = tvt(t, tbuf))) {
	    fputs(dbuf, fp);
	    dur = vd;
	} else {
	    fputs(tbuf, fp);
	    dur = vt;
	}
	for (i = voice; i < Numv; i++)
	    fprintf(fp, "\t-");
	putc('\n', fp);
	if (t - dur > Cpw / 64)
	    dur += putnote(fp, n, t - dur, chan);	/* divine [sic] */
	return(dur);
}

tvd(t, cp)	/* return closest dotted approx. (always <= t) */
register char	*cp;
{
	register int i, v, vd;

	v = Cpw;
	for (i = 0; v > t && i < 6; v >>= 1, i++);
	*cp++ = "whqestf"[i];
	vd = v;
	for (i = 0; vd + (v >>= 1) <= t && i < 4 && v >= Quant; i++) {
	    *cp++ = '.';
	    vd += v;
	}
	*cp = '\0';
	return(vd);
}

tvt(t, cp)	/* return closest triplet approx. (always <= t) */
register char	*cp;
{
	register int i, v;

	v = (2 * Cpw) / 3;
	for (i = 0; v > t && i < 6; v >>= 1, i++);
	*cp++ = "whqestf"[i];
	*cp++ = 't';
	*cp = '\0';
	return(v);
}

long
quantize(t)
long	t;
{
	return(Quant * ((t + Quant / 2) / Quant));
}
