/*
**	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;		/* when it should happen */
	char	eflg;		/* set when key-off for 0-length note */
	char	mode;		/* status & channel */
	char	key;		/* key number */
} 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	Mono	= 0;		/* if set, clip overlapped notes */
int	Join	= 0;		/* if set, join overlapped notes */
int	Legato	= 0;		/* if set, connect 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 MPU clocks.\n");
	    fprintf(stderr, "-legato connects notes (per channel)\n");
	    fprintf(stderr, "-m# sets the minimum duration in MPU clocks.\n");
	    fprintf(stderr, "-mono clips overlapping notes (per channel).\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, "In general, new_dur = max(f * old_dur + k, m)\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':
		    Legato++;
		    break;
		case 'm':
		    if (argv[argc][2] == 'o')
			Mono++;
		    else
			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)		/* first pass, copy all but note offs */
FILE	*ifp, *ofp;
{
	register int chan, k, v;
	int key[MAXCHAN][MAXKEY], lkey[MAXCHAN];
	long now, start[MAXCHAN][MAXKEY];
	MCMD *mp;

	for (chan = MAXCHAN; --chan >= 0; lkey[chan] = 0)
	    for (k = MAXKEY; --k >= 0; key[chan][k] = 0);
	for (now = 0L; mp = getmcmd(ifp, now); ) {
	    now = mp->when;
	    if ((mp->cmd[0] & M_CMD_MASK) != CH_KEY_OFF
	     && (mp->cmd[0] & M_CMD_MASK) != CH_KEY_ON) {
		putmcmd(ofp, mp);
		continue;
	    }
	    if ((mp->cmd[0] & M_CMD_MASK) == CH_KEY_OFF)
		mp->cmd[2] = 0;
	    chan = mp->cmd[0] & M_CHAN_MASK;
	    k = mp->cmd[1];
	    v = mp->cmd[2];
	    if (v != 0) {			/* key-on */
		if (key[chan][k] == 0 || Join == 0) {
		    putmcmd(ofp, mp);
		    start[chan][k] = now;
		}
		key[chan][k]++;
		if (!Legato && !Mono)
		   continue;
		if (lkey[chan] && lkey[chan] != k)
		    key_off(now, chan, lkey[chan], start, key, lkey);
		lkey[chan] = k;
	    } else				/* key-off */
		if (!Legato)
		    key_off(now, chan, k, start, key, lkey);
	}
	for (chan = MAXCHAN; --chan >= 0; )
	    for (k = MAXKEY; --k >= 0; )
		if (key[chan][k])
		    key_off(now, chan, k, start, key, lkey);
}

key_off(when, chan, k, start, key, lkey)
long	when;
long	start[MAXCHAN][MAXKEY];
int	key[MAXCHAN][MAXKEY], lkey[MAXCHAN];
{
	int dur;

	if (k == lkey[chan])		/* key-off */
	    lkey[chan] = 0;
	if (--key[chan][k] < 0) {		/* extra key-off */
	    key[chan][k] = 0;
	    return;
	}
	if (key[chan][k] == 0 || Join == 0) {
	    dur = (Fact * (when - start[chan][k])) / UNITY + Konst;
	    dur = dur < Mindur? Mindur : dur;
	    Qip->when = start[chan][k] + dur;
	    Qip->eflg = (dur == 0);
	    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;
{
	register int i;

	i = (int) (q1->when - q2->when);
	return(i? i : (q1->eflg - q2->eflg));
}

merge(ifp, ofp)
FILE	*ifp, *ofp;
{
	register int lastmode;
	unsigned char mbuf[8];
	long now;
	MCMD *mp, m;

	lastmode = 0;
	m.len = 3;
	m.cmd = mbuf;
	mbuf[2] = 0;
	putmcmd((FILE *) 0, (MCMD *) 0);		/* reset output clock */
	for (now = 0L; mp = getmcmd(ifp, now); lastmode = mp->cmd[0]) {
	    now = mp->when;
	    while (Qop != Qip
	     && (Qop->when < now || (Qop->when == now && Qop->eflg == 0))) {
		m.when = Qop->when;
		mbuf[0] = Qop->mode;
		mbuf[1] = Qop->key;
		putmcmd(ofp, &m);
		if (++Qop == &Queue[MAXQ])
		    Qop = Queue;
	    }
	    putmcmd(ofp, mp);
	}
	while (Qop != Qip) {
	    m.when = Qop->when;
	    mbuf[0] = lastmode = Qop->mode;
	    mbuf[1] = Qop->key;
	    putmcmd(ofp, &m);
	    if (++Qop == &Queue[MAXQ])
		Qop = Queue;
	}
	if (lastmode != RT_TCWME) {		/* a final command */
	    Rt_tcwme.when = now;
	    putmcmd (ofp, &Rt_tcwme);
	}
}
