/*
** BARS -- Either count the number of bars (measures) in the input,
** or copy the specified number of bars of MIDI data from
** (the head or tail of) the input to the standard output.
** A bar is defined as 480 MPU clocks by default.
** A bar is defined as the data between TCWME codes if the "-L" option is used.
** A bar is defined as 119 MPU clocks if the "-L119" option is used.
** Examples:
**	'bars foo >fum' puts a measure count (based on 480 clock bars) in fum.
**	'bars -L foo >fum' puts a measure count (based on TCWME) in fum.
**	'bars -q foo >fum' puts a measure count in fum without file name.
**	'bars -h4 foo >fum' copies the first 4 bars of foo into fum.
**	'bars -t4 foo >fum' copies the last 4 bars of foo into fum.
**	'bars -f4 -l8 foo >fum' copies bars 4 through 8 of foo into fum.
** Algorithm is multi-pass:
** On first pass determine all bar times in bartime[] and copy into temp file.
** If no args, just output bar counts and exit.
** If Head specified make a pass to output Head bars;
** If Frst & Last specified make a pass to output Frst through Last bars;
** If Tail specified make a pass to output Tail bars.
** psl 10/85
*/
#include	<stdio.h>
#include	<midi.h>

syntax(prog)
char	*prog;
{
	fprintf(stderr, "Usage: %s [options] [files or stdin]\n", prog);
	fprintf(stderr, "-B\tuse TCWME codes to define bars\n");
	fprintf(stderr, "-B#\tset fixed bar length to # MIDI clocks\n");
	fprintf(stderr, "-c\tdon't clean up hanging notes\n");
	fprintf(stderr, "-f#\tfirst; start output at bar #\n");
	fprintf(stderr, "-h#\thead; output the first # bars\n");
	fprintf(stderr, "-l#\tlast; stop output at bar #\n");
	fprintf(stderr, "-q\tquiet; don't print file names\n");
	fprintf(stderr, "-t#\ttail; output the last # bars\n");
	exit(2);
}

#define	CPB	(2 * MPU_CLOCK_PERIOD)		/* default clocks per bar */
#define	MAXBARS	1024
#define	UNSPEC	-1.
int	Clean	= 1;				/* terminate hanging notes */
int	Quiet	= 0;				/* more terse */
int	Barlen	= CPB;				/* fixed bar length if > 0 */
int	Data	= 0;				/* output requested */
long	Numticks;				/* file length in MIDI ticks */
int	Numbars	= 0;				/* index into bartime[] */
long	Bartime[MAXBARS];			/* when bars start */
double	Frst = UNSPEC, Last = UNSPEC;
double	Head, Tail;

long	pass1(), b2t();

extern	float	atof();

main(argc,argv)
char	*argv[];
{
	int i, filearg;
	FILE *f;

	filearg = 0;
	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch (argv[i][1]) {
		case 'B': Barlen = atoi(&argv[i][2]); break;
		case 'c': Clean = 0; break;
		case 'f': Data++; Frst = atof(&argv[i][2]); break;
		case 'h': Data++; Head = atof(&argv[i][2]); break;
		case 'l': Data++; Last = atof(&argv[i][2]); break;
		case 'q': Quiet++; break;
		case 't': Data++; Tail = atof(&argv[i][2]); break;
		default: syntax(argv[0]);
		}
	    } else
		filearg = 1;
	}
	if (filearg == 0)
	    bars(stdin, "stdin");
	else {
	    for (i = 1; i < argc; i++) {
		if (argv[i][0] != '-') {
		    if (f = sopen(argv[i],"r")) {
			bars(f, argv[i]);
			sclose(f);
		    } else
			perror(argv[i]);
		}
	    }
	}
	exit(0);
}

bars(fp, file)
FILE	*fp;
char	*file;
{
	char tempfile[64];
	double fract, maxbars, f, l;
	FILE *tfp;

	if (Data) {
	    sprintf(tempfile, "/tmp/bars%d", getpid());
	    if ((tfp = fopen(tempfile, "w")) == NULL) {
		perror(tempfile);
		exit(1);
	    }
	} else
	    tfp = 0;
	Numticks = pass1(fp, tfp);
	if (tfp)
	    fclose(tfp);
	if (Barlen)
	    maxbars = Numticks * 1. / Barlen;
	else if (Numbars > 1) {
	    fract = Numticks - Bartime[Numbars - 1];
	    fract = fract / (Bartime[Numbars - 1] - Bartime[Numbars - 2]);
	    maxbars = Numbars - 1. + fract;
	} else
	    maxbars = 0.;		/* this could be wrong... */
	if (!Data) {
	    if (!Quiet)
		printf("%s:\t", file);
	    printf("%g\n", maxbars);
	    return;
	}
	if ((tfp = fopen(tempfile, "r")) == NULL) {
	    perror(tempfile);
	    exit(1);
	}
	if (Head > 0.)
	    pass2(tfp, stdout, 0., Head);
	if (Frst != UNSPEC || Last != UNSPEC) {
	    f = (Frst == UNSPEC)? 0. : Frst;
	    l = (Last == UNSPEC)? maxbars : Last;
	    pass2(tfp, stdout, f, l);
	}
	if (Tail > 0.)
	    pass2(tfp, stdout, maxbars - Tail, maxbars);
	fclose(tfp);
	unlink(tempfile);
}

long
pass1(fp, tp)
FILE	*fp, *tp;
{
	long now, when;
	MCMD *mp;

	now = 0;
	putmcmd(0, 0);
	Numbars = 0;
	Bartime[Numbars++] = now;	/* start with a barline */
	while (mp = getmcmd(fp, now)) {
	    now = mp->when;
	    if (tp)
		putmcmd(tp, mp);
	    if (!Barlen
	     && mp->cmd[0] == RT_TCWME
	     && Bartime[Numbars - 1] != now) {
		if (Numbars >= MAXBARS)
		    fprintf(stderr, "Too many TCWME\n");
		else
		    Bartime[Numbars++] = now;
	    }
	}
	if (Barlen)
	    for (when = Barlen; when <= now; when += Barlen)
		Bartime[Numbars++] = when;
	return(now);
}

pass2(tfp, ofp, bstrt, bstop)
FILE	*tfp, *ofp;
double	bstrt, bstop;
{
	u_char buf[4];
	int c, k, on[MIDI_MAX_CHANS][MIDI_NUM_KEYS];
	long now, strt, stop;
	MCMD m, *mp;

	putmcmd(0, 0);
	if (Barlen) {
	    strt = bstrt * Barlen;
	    if (strt < 0)
		strt = 0;
	    stop = bstop * Barlen;
	} else {
	    strt = b2t(bstrt);
	    stop = b2t(bstop);
	}
	if (strt >= stop)
	    return;
	fseek(tfp, 0L, 0);
	now = 0L;
	if (Clean)
	    for (c = MIDI_MAX_CHANS; --c >= 0; )
		for (k = MIDI_NUM_KEYS; --k >= 0; on[c][k] = 0);
	while (mp = getmcmd(tfp, now)) {
	    now = mp->when;
	    if (now >= stop)
		break;
	    if (now >= strt) {
		mp->when -= strt;
		putmcmd(ofp, mp);
		if (Clean) {
		    if ((mp->cmd[0] & M_CMD_MASK) == CH_KEY_ON
		     && mp->cmd[2] == 0)
			mp->cmd[0] = CH_KEY_OFF | (mp->cmd[0] & M_CHAN_MASK);
		    if ((mp->cmd[0] & M_CMD_MASK) == CH_KEY_ON)
			on[mp->cmd[0] & M_CHAN_MASK][mp->cmd[1]]++;
		    else if ((mp->cmd[0] & M_CMD_MASK) == CH_KEY_OFF)
			--on[mp->cmd[0] & M_CHAN_MASK][mp->cmd[1]];
		}
	    }
	}
	if (Clean) {
	    m.when = stop - strt;
	    m.len = 3;
	    m.cmd = buf;
	    m.cmd[2] = 0;
	    for (c = MIDI_MAX_CHANS; --c >= 0; ) {
		for (k = MIDI_NUM_KEYS; --k >= 0; ) {
		    while (--on[c][k] >= 0) {
			m.cmd[0] = CH_KEY_ON | c;
			m.cmd[1] = k;
			putmcmd(ofp, &m);
		    }
		}
	    }
	}
	Mpu_nop.when = stop - strt;
	putmcmd(ofp, &Mpu_nop);
}

long
b2t(bar)
double	bar;
{
	register int i;
	long tick;
	double fract;

	i = bar;
	if (i >= Numbars)
	    tick = Numticks;
	else if (i >= 0) {
	    tick = Bartime[i];
	    fract = bar - i;
	    if (fract > 0. && i + 1 < Numbars)
		tick += (long) ((Bartime[i + 1] - Bartime[i]) * fract);
	} else
	    tick = 0;
	return(tick);
}
