#include <stdio.h>
#include <midi.h>
/*
**	SCAT -- Scat (on a Dectalk) a MIDI melody
**	psl 1/86
*/

char	*syl[]	= {
	"doo", "bee",
};

#define	NSYL	(sizeof syl / sizeof syl[0])
/*
**	NOTEDUR -- Adjust note durations
**	Two pass, first copy all but key-offs and calc key-off times.
**	On second pass insert key-offs.
**	newdur = max(fact * olddur + konst, mindur)
**	psl 1/86
*/
#include	<stdio.h>
#include	<midi.h>

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

#define	MAXQ	4096
struct	qstr	{		/* key-off events */
	long	when;
	char	mode;
	char	key;
} queue[MAXQ];
struct	qstr	*qip = queue;
struct	qstr	*qop = queue;


char	tempfile[64];
int	konst	= 0;		/* constant note duration, MIDI clocks */
int	fact	= UNITY;	/* proportion of old duration */
int	mindur	= 10;		/* minimum note duration, MIDI clocks */
int	join	= 0;		/* if set, join overlapped notes */

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

	if (argc < 2) {
syntax:
	    fprintf(stderr, "Usage: %s [options] <old >new\n", argv[0]);
	    fprintf(stderr, "-f#.# sets the new/old duration ratio.\n");
	    fprintf(stderr, "-k# sets a duration constant in MIDI clocks.\n");
	    fprintf(stderr, "-m# sets the minimum duration in MIDI clocks.\n");
	    fprintf(stderr, "   new_dur = max(f * old_dur + k, m)\n");
	    fprintf(stderr, "-legato is a synonym for: -f1.5.\n");
	    fprintf(stderr, "-staccato is a synonym for: -f0.25 -k10.\n");
	    fprintf(stderr, "-articulated is a synonym for: -f0.98.\n");
	    fprintf(stderr, "-join causes overlapped notes to become one.\n");
	    fprintf(stderr, "defaults are: -f1.0 -k0 -m10\n");
	    exit(2);
	}
	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'a':
		    fact = UNITY * 0.98;
		    break;
		case 'f':
		    fact = UNITY * atof(&argv[argc][2]);
		    break;
		case 'j':
		    join = 1;
		    break;
		case 'k':
		    konst = atoi(&argv[argc][2]);
		    break;
		case 'l':
		    fact = UNITY * 1.5;
		    break;
		case 'm':
		    mindur = atoi(&argv[argc][2]);
		    break;
		case 's':
		    fact = UNITY * 0.25;
		    konst = 10;
		    break;
		default:
		    goto syntax;
		}
	    }
	}
	if (mindur < 0)
	    goto syntax;
	sprintf(tempfile, "/tmp/notedur%d", getpid());
	if ((tfp = fopen(tempfile, "w")) == (FILE *) NULL) {
	    perror(tempfile);
	    exit(1);
	}
	copyfile(stdin, tfp);
	fclose(tfp);
	qsort(queue, qip - qop, sizeof queue[0], comp);
	if ((tfp = fopen(tempfile, "r")) == (FILE *) NULL) {
	    perror(tempfile);
	    exit(1);
	}
	merge(tfp, stdout);
	unlink(tempfile);
}

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

	for (chan = MAXCHAN; --chan >= 0; )
	    for (k = MAXKEY; --k >= 0; )
		key[chan][k] = 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);
		}
		if (i == 0x80)		/* hack to turn 0x80 into 0x90 */
		    v = 0;
		if (v != 0) {			/* key-on */
		    if (key[chan][k] == 0 || join == 0) {
			put4(ofp, now - last, curmode, k, v);
			last = now;
			start[chan][k] = now;
		    }
		    key[chan][k]++;
		} else {			/* key-off */
		    if (--key[chan][k] < 0) {	/* extra key-off */
			key[chan][k] = 0;
			continue;
		    }
		    if (key[chan][k] == 0 || join == 0) {
			dur = (fact * (now - start[chan][k])) / UNITY + konst;
			dur = dur < mindur? mindur : dur;
			qip->when = start[chan][k] + dur;
			qip->mode = 0x90 | chan;
			qip->key = k;
			if (++qip == &queue[MAXQ])
			    qip = queue;
			if (qip == qop) {
			    fprintf(stderr, "Key-off queue overflow!\n");
			    exit(1);
			}
		    }
		}
	    }
	}
}

comp(q1, q2)
struct	qstr	*q1, *q2;
{
	return((int) (q1->when - q2->when));
}

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

	curmode = 0;
	now = last = 0;
	while ((i = getc(ifp)) != EOF) {
	    if (i == RT_TCIP) {
		now += MPU_CLOCK_PERIOD;
		continue;
	    }
	    now += i;
	    while (qop != qip && qop->when <= now) {
		put4(ofp, qop->when - last, qop->mode, qop->key, 0);
		last = qop->when;
		if (++qop == &queue[MAXQ])
		    qop = queue;
	    }
	    curmode = getc(ifp);
	    stat = curmode & 0xF0;
            if (curmode > 0xF3 || curmode == 0xF1) {
		put2(ofp, now - last, curmode);
		last = now;
		continue;
	    }
	    if (curmode == 0xF0) {	/* sys excl */
		put2(ofp, now - last, curmode);
		last = now;
		do {
		    k = getc(ifp);
		    putc(k, ofp);
		} while (k != 0xF7);
		continue;
	    }
            k = getc(ifp);
            if (stat == 0xC0 || stat == 0xD0 || curmode == 0xF3) {
		put3(ofp, now - last, curmode, k);
		last = now;
                continue;
	    }
            v = getc(ifp);
	    put4(ofp, now - last, curmode, k, v);
	    last = now;
	}
	while (qop != qip) {
	    put4(ofp, qop->when - last, curmode = qop->mode, qop->key, 0);
	    last = qop->when;
	    if (++qop == &queue[MAXQ])
		qop = queue;
	}
	if (curmode != 0xF9)		/* a final command */
	    put2(ofp, now > last? now - last : 0, 0xF9);
}

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);
}
