/*
**	ADJUST -- Adjust note placements
**	Timing marks are taken from specified channel.
**	Two pass; on first observe timing marks & calculate timing changes.
**	On second pass adjust all key-ons and key-offs.
**	psl 1/86
*/
#include	<stdio.h>
#include	<midi.h>

#define	MAXCHAN	16
#define	MAXKEY	128
#define	UNITY	1024

#define	MAXQ	4096
long	mark[MAXQ];		/* when the timing marks appeared */
int	nmark	= 0;

char	tempfile[64];
int	cpm	= MPU_CLOCK_PERIOD / 2;		/* clocks per mark */
int	tchan	= -1;				/* timing mark channel */

main(argc, argv)
char	*argv[];
{
	FILE *tfp;
	int comp();

	if (argc < 2) {
syntax:
	    fprintf(stderr, "Usage: %s -c# [-q#] <old >new\n", argv[0]);
	    fprintf(stderr, "-c# sets the timing mark channel (1..16)\n");
	    fprintf(stderr, "-q# sets the number of timing marks per bar\n");
	    fprintf(stderr, "default is: -q4\n");
	    exit(2);
	}
	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'c':
		    tchan = atoi(&argv[argc][2]) - 1;
		    break;
		case 'q':
		    cpm = (2 * MPU_CLOCK_PERIOD) / atoi(&argv[argc][2]);
		    break;
		default:
		    goto syntax;
		}
	    }
	}
	if (tchan < 0)
	    goto syntax;
	sprintf(tempfile, "/tmp/adjust%d", getpid());
	if ((tfp = fopen(tempfile, "w")) == (FILE *) NULL) {
	    perror(tempfile);
	    exit(1);
	}
	pass1(stdin, tfp);
	fclose(tfp);
	if ((tfp = fopen(tempfile, "r")) == (FILE *) NULL) {
	    perror(tempfile);
	    exit(1);
	}
	process(tfp, stdout);
	unlink(tempfile);
}

pass1(ifp, ofp)
FILE	*ifp, *ofp;
{
	register int i, k, v, curmode, dur, chan;
	long now, last, start[MAXCHAN][MAXKEY];

	mark[nmark++] = 0;		/* first mark is at 0 */
	curmode = 0;
	now = last = 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 & 0xF0) == 0xF0) {
		    if (k < 0xF8) {		/* sys excl or sys common */
			if (k == 0xF0) {	/* sys excl */
			    put2(ofp, now - last, k);
			    do {
				k = getc(ifp);
				putc(k, ofp);
			    } while (k != 0xF7);
			} else if (k == 0xF2) {	/* song position */
			    k = getc(ifp);
			    put4(ofp, now - last, 0xF2, k, getc(ifp));
			} else if (k == 0xF3) {	/* song select */
			    put3(ofp, now - last, k, getc(ifp));
			} else {
			    put2(ofp, now - last, k);
			}
			curmode = 0;
		    } else			/* sys real time */
			put2(ofp, now - last, k);
		    last = now;
		    continue;
		}
		curmode = k;
		if ((k = getc(ifp)) == EOF) {
		    fprintf(stderr, "EOF after %x (mode)\n", curmode);
		    exit(1);
		}
	    }
	    i = curmode & 0xF0;
	    chan = curmode % MAXCHAN;
	    if (i == 0xA0				/* poly pressure */
	     || i == 0xB0				/* mod wheel */
	     || i == 0xE0) {				/* pitch bend */
		put4(ofp, now - last, curmode, k, getc(ifp));
		last = now;
		continue;
	    } else if (i == 0xC0			/* prog change */
	     || i == 0xD0) {				/* chan pressure */
		put3(ofp, now - last, curmode, k);
		last = now;
		continue;
	    } else if (i == 0x90 || i == 0x80) {	/* key-on/off event */
		if ((v = getc(ifp)) == EOF) {
		    fprintf(stderr, "unexpected EOF in mode %x\n", curmode);
		    exit(1);
		}
		put4(ofp, now - last, curmode, k, v);
		last = now;
		if (i == 0x90 && v != 0 && chan == tchan && now != 0)
		    mark[nmark++] = now;
	    }
	}
	mark[nmark] = 2 * mark[nmark - 1] - mark[nmark - 2];	/* in case */
}

process(ifp, ofp)
FILE	*ifp, *ofp;
{
	register int i, k, v, curmode, stat;
	long onow, nnow, last;
	long adjust();

	curmode = 0;
	onow = last = 0;
	while ((i = getc(ifp)) != EOF) {
	    if (i == RT_TCIP) {
		onow += MPU_CLOCK_PERIOD;
		continue;
	    }
	    onow += i;
	    nnow = adjust(onow);
	    curmode = getc(ifp);
	    stat = curmode & 0xF0;
            if (curmode > 0xF3 || curmode == 0xF1) {
		put2(ofp, nnow - last, curmode);
		last = nnow;
		continue;
	    }
	    if (curmode == 0xF0) {	/* sys excl */
		put2(ofp, nnow - last, curmode);
		last = nnow;
		do {
		    k = getc(ifp);
		    putc(k, ofp);
		} while (k != 0xF7);
		continue;
	    }
            k = getc(ifp);
            if (stat == 0xC0 || stat == 0xD0 || curmode == 0xF3) {
		put3(ofp, nnow - last, curmode, k);
		last = nnow;
                continue;
	    }
            v = getc(ifp);
	    put4(ofp, nnow - last, curmode, k, v);
	    last = nnow;
	}
	if (curmode != RT_TCWME)		/* a final command */
	    put2(ofp, nnow > last? nnow - last : 0, RT_TCWME);
}

long
adjust(t)
long	t;
{
	register int h, l, dt;

	for (h = 1; h < nmark && mark[h] < t; h++);
	l = h - 1;
	dt = (mark[h] - mark[l]);
	if (dt < 1)
	    dt = 1;
	return(l * cpm + ((t - mark[l]) * cpm) / dt);
}

put2(ofp, dt, mode)
FILE	*ofp;
long	dt;
{
	if (ofp == (FILE *) 0)
	    return;
	if (dt < 0)
	    dt = 0;
	putdt(ofp, dt);
	putc(mode, ofp);
}

put3(ofp, dt, mode, n)
FILE	*ofp;
long	dt;
{
	if (ofp == (FILE *) 0)
	    return;
	if (dt < 0)
	    dt = 0;
	putdt(ofp, dt);
	putc(mode, ofp);
	putc(n, ofp);
}

put4(ofp, dt, mode, k, v)
FILE	*ofp;
long	dt;
{
	if (ofp == (FILE *) 0)
	    return;
	if (dt < 0)
	    dt = 0;
	putdt(ofp, dt);
	putc(mode, ofp);
	putc(k, ofp);
	putc(v, ofp);
}

putdt(ofp, dt)
FILE	*ofp;
long	dt;
{
	while (dt >= MPU_CLOCK_PERIOD) {
	    putc(RT_TCIP, ofp);
	    dt -= MPU_CLOCK_PERIOD;
	}
	putc((char) dt, ofp);
}
