/*
**	CHMAP -- Channel Map; remap Midi channels
**	psl 10/85, revised 12/85
*/
#include	<stdio.h>
#include	<libmidi.h>

#define	MAXCHAN	16
#define	MAXMAP	16

int	map[MAXCHAN][MAXMAP];
int	maplen[MAXCHAN];
int	bcnts[]	= { 4, 4, 4, 4, 3, 3, 4, 2, };

main(argc, argv)
char	*argv[];
{
	register int i, dt, b1, b2, b3, b4;
	register int bcnt, type, stat, oldchan, newchan;
	int new[MAXCHAN], old[MAXCHAN], newlen, oldlen;
	int chin[MAXCHAN], chout[MAXCHAN], vflg, oflg;

	vflg = oflg = 0;
	if (argc < 2) {
syntax:
	    fprintf(stderr, "Usage: %s [-v] NEW=OLD ... <file1 >file2\n",
	     argv[0]);
	    fprintf(stderr, "where NEW and OLD are comma separated lists");
	    fprintf(stderr, " of channel numbers,\nusing '-' for ranges.");
	    fprintf(stderr, "  Channel numbers are decimal numbers");
	    fprintf(stderr, " in the range 1-16.\n");
	    fprintf(stderr, "NEW and OLD must either contain the same number");
	    fprintf(stderr, " of channels (one to one),\n");
	    fprintf(stderr, "or NEW must be a single channel (many to one),\n");
	    fprintf(stderr, "or OLD must be a single channel (one to many).\n");
	    fprintf(stderr, "E.g. %s 1-3=1 4=2 copies channel 1 to", argv[0]);
	    fprintf(stderr, " 1, 2, and 3, and channel 2 to 4.\n");
	    fprintf(stderr, "Note: any channel not mentioned is ignored.\n");
	    fprintf(stderr, "Use -v to print statistics on channel usage.\n");
	    exit(2);
	}
	while (--argc > 0) {
	    if (argv[argc][0] == '-' && argv[argc][1] == 'v') {
		vflg++;
		continue;
	    }
	    if (chanarg(argv[argc], new, &newlen, old, &oldlen) == -1)
		goto syntax;
	    if (newlen == oldlen) {
		for (i = 0; i < newlen; i++)
		    if (addmap(new[i], old[i]) == -1)
			goto syntax;
	    } else if (newlen == 1) {
		for (i = 0; i < oldlen; i++)
		    if (addmap(new[0], old[i]) == -1)
			goto syntax;
	    } else if (oldlen == 1) {
		for (i = 0; i < newlen; i++)
		    if (addmap(new[i], old[0]) == -1)
			goto syntax;
	    } else
		goto syntax;
	    oflg++;
	}
	oldchan = -1;
	dt = 0;
	while ((b1 = getc(stdin)) != EOF) {	/* b1 is normally timing */
	    if (b1 == RT_TCIP) {
		if (oflg)
		    putc(b1, stdout);
		continue;
	    }
	    dt += b1;				/* b1 is timing */
	    if ((b2 = getc(stdin)) == EOF) {	/* b2 can be status */
		fprintf(stderr, "EOF after timing byte (0x%x)\n", b1);
		exit(1);
	    }
	    if ((b2 & M_CMD) == 0)		/* not a status byte */
		b3 = b2;
	    else {				/* a status byte */
		type = (b2 >> 4) & 0x7;
		if (bcnts[type] == 2) {		/* these have no channel */
		    if (oflg) {
			dt = putdt(dt);		/* so dump 'em & go back */
			putc(b2, stdout);
		    }
		    continue;
		}
		stat = b2 & M_CMD_MASK;
		oldchan = b2 & M_CHAN_MASK;
		bcnt = bcnts[type];
		if ((b3 = getc(stdin)) == EOF) {
		    fprintf(stderr, "EOF after %x (mode)\n", b2);
		    exit(1);
		}
	    }
	    if (bcnt == 4) {
		if ((b4 = getc(stdin)) == EOF) {
		    fprintf(stderr, "EOF before last byte of command.\n");
		    fprintf(stderr, "0x%x, 0x%x, 0x%x, ...\n", dt, b2, b3);
		    exit(1);
		}
	    }
	    if (oldchan >= 0) {
		chin[oldchan]++;	
		for (i = 0; i < maplen[oldchan]; i++) {
		    newchan = map[oldchan][i];
		    chout[newchan]++;
		    dt = putdt(dt);
		    putc(stat | newchan, stdout);
		    putc(b3, stdout);
		    if (bcnt == 4)
			putc(b4, stdout);
		}
	    }
	}
	if (vflg) {
	    for (i = 0; i < MAXCHAN; i++)
		if (chin[i])
		    fprintf(stderr, "%d event%s from chan %d\n",
		     chin[i], chin[i] == 1? "" : "s", i + 1);
	    for (i = 0; i < MAXCHAN; i++)
		if (chout[i])
		    fprintf(stderr, "%d event%s to chan %d\n",
		     chout[i], chout[i] == 1? "" : "s", i + 1);
	}
}

putdt(dt)
{
	while (dt >= 240) {
	    putc(RT_TCIP, stdout);
	    dt -= 240;
	}
	putc(dt, stdout);
	return(0);
}

/*
**	Add ochan->nchan to the map for ochan.
*/

addmap(nchan, ochan)
{
	if (maplen[ochan] >= MAXMAP) {
	    fprintf(stderr, "Too many destinations for %d\n", ochan);
	    return(-1);
	}
	map[ochan][maplen[ochan]++] = nchan;
	return(0);
}

/*
**	Interpret channel arg of the form list=list
**	where "list" is of the form understood by chanlist().
**	Fill in two ordered lists of channels, "new" and "old",
**	and put their lengths in "newlenp" and "oldlenp".
**	Return 0 for success, -1 for syntax error.
*/
chanarg(arg, new, newlenp, old, oldlenp)
char	*arg;
int	new[], *newlenp, old[], *oldlenp;
{
	register char *cp;

	for (cp = arg; *cp && *cp != '='; cp++);
	if (*cp != '=')
	    return(-1);
	*cp++ = '\0';
	if ((*newlenp = chanlist(arg, new)) == -1)
	    return(-1);
	if ((*oldlenp = chanlist(cp, old)) == -1)
	    return(-1);
	return(0);
}


/*
**	Interpret channel list of the form: #,#-#,...
**	# is a decimal number in the range 1 to 16, and "3-5" == "3,4,5".
**	Save an ordered list of channels specified in "chans" array.
**	Return the number of channels specified, -1 for error.
*/

chanlist(list, chans)
char	*list;
int	chans[MAXCHAN];
{
	register char *cp;
	register int i, j, n;

	n = 0;
	for (cp = list; *cp; ) {
	    i = j = atoi(cp) - 1;
	    while ('0' <= *cp && *cp <= '9')
		cp++;
	    if (*cp == '-') {
		j = atoi(++cp) - 1;
		while ('0' <= *cp && *cp <= '9')
		    cp++;
	    }
	    if (i < 0 || i >= MAXCHAN || j < 0 || j >= MAXCHAN)
		return(-1);
	    if (i <= j)
		for (; i <= j; chans[n++] = i++);
	    else
		for (; i >= j; chans[n++] = i--);
	    if (*cp++ != ',')
		break;
	}
	return(n);
}
