/*
**	FRACT -- Fractal melody interpolator
**	psl 12/86
*/

#include	<sys/types.h>
#include	<stdio.h>
#include	<midi.h>

#define	DEFRUG	4.0

u_char	Pnobuf[8];		/* pending note off command buffer */
int	Res;			/* longest fractal segment */
int	Seed;			/* "random" number seed */
int	Dflg	= 0;		/* Diatonic; force C major scale */
int	Chan	= -1;		/* channel for interpolated notes */
double	Rug	= DEFRUG;	/* ruggedness */
MCMD	Pno;			/* pending note off command */

char	*key[]	= {
    "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B",
};

main(argc, argv)
char	*argv[];
{
	u_char obuf[8], nbuf[8];
	int status;
	long onow, nnow;
	MCMD om, *nmp;
	extern double atof();

	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'c':			/* set output channel */
		    Chan = atoi(&argv[argc][2]) - 1;
		    break;
		case 'd':			/* diatonic output */
		    Dflg++;
		    break;
		case 'r':			/* set ruggedness */
		    Rug = atof(&argv[argc][2]);
		    break;
		case 's':			/* set "random" SEED */
		    Seed = atoi(&argv[argc][2]);
		    break;
		default:
		    goto syntax;
		}
	    } else if (Res == 0)
		Res = atof(argv[argc]) * 120;
	    else {
syntax:
		fprintf(stderr,
		 "Usage: %s resolution [-c#] [-diatonic] [-rRUGGED] [-sSEED]]\n",
		 argv[0]);
		fprintf(stderr, "Resolution in quarter notes (120 clocks).\n");
		fprintf(stderr, "-c# puts all added notes on channel #.\n");
		fprintf(stderr, "Diatonic forces C major scale.\n");
		fprintf(stderr, "Default ruggedness is %g.\n", DEFRUG);
		fprintf(stderr, "Default SEED is taken from the time.\n");
		exit(2);
	    }
	}
	if (Seed == 0)
	    Seed = time(0);
	onow = -1;
	nnow = 0;
	while (nmp = getmcmd(stdin, nnow)) {
	    nnow = nmp->when;
	    status = nmp->cmd[0];
	    if ((status & M_CMD_MASK) == CH_KEY_ON
	     || (status & M_CMD_MASK) == CH_KEY_OFF) {
		if (nmp->cmd[2] == 0 || (status & M_CMD_MASK) == CH_KEY_OFF) {
		    Pno.when = nnow;
		    Pno = *nmp;
		    savcmd(&Pno, Pnobuf);
		    continue;
		}
		savcmd(nmp, nbuf);
		if (onow < 0)
		    out(nmp);
		else
		    fract(&om, nmp);
		onow = nnow;
		om = *nmp;
		savcmd(&om, obuf);
	    }
	}
	Rt_tcwme.when = nnow;
	out(&Rt_tcwme);
	exit(0);
}

fract(omp, nmp)
MCMD	*omp, *nmp;
{
	u_char mbuf[8];
	int ov, nv;
	long d;
	MCMD mm;

	d = nmp->when - omp->when;
	if (d <= Res)
	    out(nmp);
	else {
	    mm = *nmp;
	    savcmd(&mm, mbuf);
	    mm.when = (omp->when + nmp->when) / 2;
	    if (Chan >= 0) {
		mm.cmd[0] &= M_CMD_MASK;
		mm.cmd[0] |= Chan;
	    }
	    mm.cmd[1] = interp(omp->cmd[1], nmp->cmd[1], mm.when, d);
	    ov = omp->cmd[2]? omp->cmd[2] : (nmp->cmd[2] / 2);
	    nv = nmp->cmd[2]? nmp->cmd[2] : (omp->cmd[2] / 2);
	    mm.cmd[2] = (ov + nv) / 2;
	    fract(omp, &mm);
	    fract(&mm, nmp);
	}
}

interp(on, nn, now, d)
u_char	on, nn;
long	now, d;
{
	double dmid;
	int imid, i;

	now += Seed;
	i = ((now * now) & 0x0FFFFFFF) % 2001;
	dmid = (on + nn) / 2. + (Rug * d * (1000 - i)) / 120000.;
	imid = dmid + 0.5;
	i = imid % 12;
	if (Dflg && key[i][1] == 'b')
	    imid = (imid > dmid)? imid - 1 : imid + 1;
	return(imid);
}

out(mp)
MCMD	*mp;
{
	static unsigned char lbuf[8];
	static MCMD l;

	if (Pno.when && Pno.when <= mp->when) {
	    putmcmd(stdout, &Pno);
	    if (l.cmd && eveq(&l, &Pno))
		l.cmd = 0;
	    if (mp != (MCMD *) 0 && eveq(mp, &Pno))
		mp->cmd = 0;
	}
	Pno.when = 0;
	if (l.cmd) {
	    l.cmd[2] = 0;
	    l.when = mp->when;
	    putmcmd(stdout, &l);
	    if (mp != (MCMD *) 0 && mp->cmd && eveq(mp, &l))
		mp->cmd = 0;
	    l.cmd = 0;
	}
	if (mp == (MCMD *) 0)
	    return;
	if (mp->cmd) {
	    putmcmd(stdout, mp);
	    if ((mp->cmd[0] & M_CMD_MASK) == CH_KEY_ON && mp->cmd[2]) {
		l = *mp;
		savcmd(&l, lbuf);
		l.cmd[2] = 0;
	    } else
		l.cmd = 0;
	} else {
	    Rt_tcwme.when = mp->when;
	    putmcmd(stdout, &Rt_tcwme);
	}
}

eveq(ap, bp)
MCMD	*ap, *bp;
{
	register int i;

	if (ap->len != bp->len)
	    return(0);
	for (i = ap->len; --i >= 0; )
	    if (ap->cmd[i] != bp->cmd[i])
		return(0);
	return(1);
}

savcmd(mp, buf)
MCMD	*mp;
unsigned char	*buf;
{
	register int i;

	for (i = mp->len; --i >= 0; )
	    buf[i] = mp->cmd[i];
	mp->cmd = buf;
}
