/*
**	CHART -- Display MIDI file graphically
**	psl 12/85
*/

/*#define	SUN	/* for Sun Workstation */
/*#define	JERQ	/* for Teletype DMD 5620 */

#ifdef	SUN
#include	<sun.h>
#include	<stdio.h>
#define	RECTF	rect
#endif

#ifdef	JERQ
#include	<jerq.h>
#include	<jerqio.h>
#define	Dr	Drect
#define	Display	&display
#define	o	origin
#define	c	corner
#define	RECTF	rectf
#define	getpid()	42
#define	perror(x)	fprintf(stderr, "perror(%s)\n", x)
#endif

#define	MPU_CLOCK_PERIOD	240
#define	RT_TCIP			0xF8
#define	MAXCHAN			16
#define	MAXKEY			128

#define	SBHH	16		/* scroll bar half height */

char	tempfile[32];
int	bars = 0;		/* >0 => how much to display, 0 => expand */
int	border = 0;		/* guaranteed empty space around score */
int	expand	= 1;		/* expand to fill the space (vertically) */
int	ppbar;			/* pixels per bar */
int	ppnote;			/* pixels per note (vertically) */
int	mink = 42, maxk = 78;	/* range of notes in input */
int	mint, maxt;		/* range of time in input */
FILE	*tfp;			/* slightly preprocessed data */
Rectangle	da;		/* drawing area */

int	tclefk[]	= { 77, 74, 71, 67, 64, };
int	bclefk[]	= { 43, 47, 50, 53, 57, };
int	chans[MAXCHAN]	= { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, };
int	bmark	= 9999;		/* mark every this many bars */
int	key[MAXCHAN][MAXKEY];
int	vel[MAXCHAN][MAXKEY];
long	start[MAXCHAN][MAXKEY];
int	vol[1200];		/* loudness at each pixel */
long	minwhen, maxwhen;

main(argc, argv)
char	*argv[];
{
	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'B':
		    border = atoi(&argv[argc][2]);
		    break;
		case 'b':
		    bars = atoi(&argv[argc][2]);
		    break;
		case 'c':
		    setchans(&argv[argc][2]);
		    break;
		case 'e':
		    expand = 0;
		    break;
		case 'm':
		    bmark = atoi(&argv[argc][2]);
		    break;
		default:
		    goto syntax;
		}
	    } else {
syntax:
		fprintf(stderr,
		 "Usage: %s [-B#] [-b#] [-c#[,#,...]] [-e] [-m#] <file\n",
		 argv[0]);
		fprintf(stderr, "-B20\tProvide a 20 pixel border\n");
		fprintf(stderr, "-b12\tShow the first 12 bars\n");
		fprintf(stderr, "-c1,16\tShow channels 1 & 16\n");
		fprintf(stderr, "-e\tLeave room for all possible pitches\n");
		fprintf(stderr, "-m4\tMark every 4 bars\n");
		exit(2);
	    }
	}
	sprintf(tempfile, "/tmp/ched%d", getpid());
	if ((tfp = fopen(tempfile, "w")) == (FILE *) NULL) {
	    perror(tempfile);
	    exit(1);
	}
	copyfile(stdin, tfp);
	fclose(tfp);
	if ((tfp = fopen(tempfile, "r")) == (FILE *) NULL) {
	    perror(tempfile);
	    exit(1);
	}
	if (bars > 0) {
	    minwhen = 0;
	    maxwhen = 2 * MPU_CLOCK_PERIOD * bars + 1;
	} else {
	    minwhen = mint;
	    maxwhen = maxt + 1;
	    bars = (int) (1 + (maxt - mint - 1) / (2 * MPU_CLOCK_PERIOD));
	}
	if (!expand) {
	    mink = 0;
	    maxk = MAXKEY;
	}
	setup(0);
	process(tfp);
	twiddle();
	exit(0);
}

copyfile(ifp, ofp)
FILE	*ifp, *ofp;
{
	register int i, k, v, curmode, last;
	long now;

	maxt = 0;
	mint = 99999;
	curmode = 0;
	now = 0;
	while ((i = getc(ifp)) != EOF) {
	    if (i == 0xF8) {
		now += MPU_CLOCK_PERIOD;
		continue;
	    }
	    now += i;
	    if ((k = getc(ifp)) == EOF) {
		fprintf(stderr, "EOF after 0x%x (now=%d)\n", i, now);
		exit(1);
	    }
	    if (k & 0x80) {
		if (k == 0xF9 || k == 0xFC || k == 0xFD || k == 0xFE
		 || (k & 0x80) == 0)
		    continue;
		/* there are some FF commands that should be handled here, */
		/* (I think), common. real-time, ? */
		curmode = k;
		if ((k = getc(ifp)) == EOF) {
		    fprintf(stderr, "EOF after %x (mode)\n", curmode);
		    exit(1);
		}
	    }
	    if ((curmode & 0xF0) == 0xD0) /* ignore pressure change */
		continue;
	    if (curmode == 0xFF) {	/* this is a real pain to handle */
		if (k & 0x0F) {		/* midi system message */
		    getc(ifp);
		    getc(ifp);
		} else			/* system exclusive message */
		    while (getc(ifp) != 0xF7);	/* could screw up */
		continue;
	    }
	    if ((curmode & 0xF0) == 0x90) {	/* key-on/off event */
		if ((v = getc(ifp)) == EOF) {
		    fprintf(stderr, "unexpected EOF in mode %x\n", curmode);
		    exit(1);
		}
		if (chans[curmode & 0x0F] == 0)
		    continue;
		for (i = now-last; i > MPU_CLOCK_PERIOD; i -= MPU_CLOCK_PERIOD)
		    putc(RT_TCIP, ofp);
		putc(i, ofp);
		putc(curmode, ofp);
		putc(k, ofp);
		putc(v, ofp);
		if (v != 0) {
		    if (k < mink)
			mink = k;
		    if (k > maxk)
			maxk = k;
		}
		if (now < mint)
		    mint = now;
		if (now > maxt)
		    maxt = now;
		last = now;
	    }
	}
}

process(ifp)
FILE	*ifp;
{
	register int i, k, v, curmode;
	int chan, nowx;
	long now;

	curmode = 0;
	now = -minwhen;
	while ((i = getc(ifp)) != EOF) {
	    if (i == 0xF8) {
		now += MPU_CLOCK_PERIOD;
		continue;
	    }
	    now += i;
	    if (now > maxwhen - minwhen)
		break;
	    curmode = getc(ifp);
	    k = getc(ifp);
	    v = getc(ifp);
	    if (now < 0)
		continue;
	    chan = curmode % MAXCHAN;
	    if (chans[chan] == 0)
		continue;
	    nowx = ctop(now);
	    if (v == 0) {			/* key-off */
		--key[chan][k];
		if (key[chan][k] < 0) {	/* extra note-off */
		    key[chan][k] = 0;
		    continue;
		} if (key[chan][k] == 0) {	/* end of note */
		    plotnote(start[chan][k], ktop(k), nowx);
		    addvol(start[chan][k], nowx, vel[chan][k]);
		}
	    } else {			/* key-on */
		if (key[chan][k] != 0) {
		    plotnote(start[chan][k], ktop(k), nowx);
		    addvol(start[chan][k], nowx, vel[chan][k]);
		}
		start[chan][k] = nowx;
		vel[chan][k] = v;
		key[chan][k]++;
	    }
	}
	disvol(da.o.x, da.c.x, da.c.y + SBHH + ppnote);
}

plotnote(bx, y, ex)
{
	int dy;

	dy = ppnote / 2;
	if (ex == bx)
	    ex++;
	RECTF(Display, Rect(bx, y - dy, ex, y + dy), F_STORE);
}

addvol(bx, ex, v)
register int v;
{
	register int x;

	if (ex == bx)
	    ex++;
	for (x = bx; x < ex; x++) {
	    vol[x] += v;
	    v = (36 * v + 18) / 37;
	}
}

disvol(bx, ex, y)
{
	register int x, dy, maxvol;

	maxvol = 0;
	for (x = bx; x < ex; x++)
	    if (vol[x] > maxvol)
		maxvol = vol[x];
	maxvol++;
	for (x = bx; x < ex; x++) {
	    dy = (vol[x] * SBHH) / maxvol;
	    segment(Display, Pt(x, y + dy), Pt(x, y - dy - 1), F_STORE);
	}
	RECTF(Display, Rect(bx - 1, y - SBHH, ex + 1, y + SBHH), F_XOR);
}

twiddle()
{
#ifdef	SUN
	Go();
#endif
#ifdef	JERQ
	for (;;) {
	    wait(CPU);
	    if (P->state & RESHAPED) {
		setup(1);
		P->state &= ~RESHAPED;
		fclose(tfp);
		if ((tfp = fopen(tempfile, "r")) == (FILE *) NULL) {
		    perror(tempfile);
		    exit(1);
		}
		process(tfp);
	    }
	    if (kbdchar() != -1)
		exit();
	}
#endif
}

#ifdef	SUN
Input()
{
	if (Reshaped) {
	    setup(1);
	    fseek(tfp, 0L, 0);
	    process(tfp);
	}
	if (Poll(KBD) & KBD)
	    if (kbdchar() == 'q')
	    	exit(0);
}
#endif


setchans(arg)
char	*arg;
{
	register char *cp;
	register int i, j;

	for (i = MAXCHAN; --i >= 0; chans[i] = 0);
	for (cp = arg; *cp; ) {
	    i = j = atoi(cp);
	    while ('0' <= *cp && *cp <= '9')
		cp++;
	    if (*cp == '-') {
		j = atoi(++cp);
		while ('0' <= *cp && *cp <= '9')
		    cp++;
	    } else if (*cp == ',')
		cp++;
	    if (i < 0 || i >= MAXCHAN || j < 0 || j >= MAXCHAN || i > j) {
		fprintf(stderr, "Bad channel number in %s\n", arg);
		exit(1);
	    }
	    while (i <= j)
		chans[i++] = 1;
	}
}

hex(c)
char	c;
{
	if ('0' <= c && c <= '9')
	    return(c - '0');
	if ('A' <= c && c <= 'F')
	    return(10 + c - 'A');
	if ('a' <= c && c <= 'f')
	    return(10 + c - 'a');
	return(0);
}

setup(redraw)
{
	register int i, x, y, y2;
	Point size;

#ifdef	SUN
	if (redraw == 0) {
	    InitDisplay();
	    InitDevices(KBD);
	}
#endif
#ifdef	JERQ
	if (redraw == 0) {
	    request(KBD);
	}
#endif
	RECTF(Display, Dr, F_CLR);
	da.o.x = Dr.o.x + border;
	da.o.y = Dr.o.y + border;
	da.c.x = Dr.c.x - border;
	da.c.y = Dr.c.y - border - 2 * SBHH - 6;
	size = sub(da.c, da.o);
	ppbar = (size.x - 8) / bars;
	ppnote = (size.y - 12) / (maxk - mink + 1);
	x = size.x - bars * ppbar;
	y = size.y - (maxk - mink) * ppnote;
	da.o.x += x / 2;
	da.o.y += y / 2;
	da.c.x -= x / 2;
	da.c.y -= y / 2;
	for (i = 5; --i >= 0; ) {	/* horizontal staff lines */
	    y = ktop(tclefk[i]);
	    segment(Display, Pt(da.o.x, y), Pt(da.c.x, y), F_STORE);
	    y = ktop(bclefk[i]);
	    segment(Display, Pt(da.o.x, y), Pt(da.c.x, y), F_STORE);
	}
	for (i = 0; i <= bars; i++) {	/* vertical staff lines */
	    x = da.o.x + i * ppbar - 1;
	    y = ktop(tclefk[0] + (i % bmark? 0 : 1));
	    y2 = ktop(bclefk[0] - (i % bmark? 0 : 1));
	    segment(Display, Pt(x, y), Pt(x, y2), F_STORE);
	}
}

ktop(k)		/* convert key # to vertical pixel location */
{
	return(da.c.y - ppnote * (k - mink));
}

ctop(when)	/* convert midi clocks into horizontal pixel location */
long	when;
{
	return((int) (da.o.x + (when * ppbar) / (2 * MPU_CLOCK_PERIOD)));
}
