/*	MIXER -- Programmable Control panel for IOTA Midi-Fader
**	Output is "raw" MIDI (i.e. w/o time tags) and goes to stdout
**	psl 10/87
*/

#include	<suntool/sunview.h>
#include	<suntool/canvas.h>
#include	<suntool/panel.h>
#include        <pixrect/pixrect_hs.h>
#include	<midi.h>
#include	<mpuvar.h>
#include	<stdio.h>

#define	PIX_INV			PIX_NOT(PIX_DST)
#define	PIX_XOR			(PIX_SRC^PIX_DST)
#define	PIX_OR			(PIX_SRC|PIX_DST)
#define POINT2(PW,DX,DY,OP)     pw_vector(PW,DX,DY,DX,DY,OP,1)
#define HLINER(PW,X0,Y0,X1,OP)  pw_vector(PW,X0,Y0,X1,Y0,OP,1)

#define	DEFCHAN		1	/* default channel for Iota(s) */
#define	MAXSLIDE	32	/* how many sliders we can display */
#define	MAXFADE		32	/* how many faders we can control */
#define	DEFFADE		24	/* default number of faders */
#define	F2SXU(F)	(F/8)	/* convert fader # to sys.excl. unit # */
#define	FCTRL0		32	/* first fader ctrl number */
#define	MCTRL0		64	/* first mute ctrl number */

#define	INIT	1		/* menu entry values */
#define	SAVE	2
#define	RESTORE	3
#define	ALLUP	4
#define	ALLDOWN	5
#define	QUIT	6

#define	MUTE	1		/* values for Spmode[] & for Wfbu */
#define	SOLO	2

struct	fstr	{
	int	siz;		/* based on button radius */
	char	*nam;		/* font name */
} Fs[]	= {
	20,	"/usr/lib/fonts/fixedwidthfonts/cour.b.24",
	16,	"/usr/lib/fonts/fixedwidthfonts/cour.b.18",
	12,	"/usr/lib/fonts/fixedwidthfonts/cour.b.16",
	9,	"/usr/lib/fonts/fixedwidthfonts/cour.b.12",
	0,	"/usr/lib/fonts/fixedwidthfonts/sail.r.6",
};

#define	DOWN6DB		103	/* default initial setting */
int	Ticks[]	= {
	127, 47, 15, 2,		/* 24 db points */
	79, 27, 5,		/* 12 db points */
	103, 59, 35, 21, 9, 3,	/* 6 db points */
	0,
};

struct	statestr	{	/* current control state */
	int	_Slide[MAXSLIDE];	/* slider settings 0-127 */
	int	_Spsolo[MAXSLIDE];	/* slide pot solo buttons */
	int	_Spmute[MAXSLIDE];	/* slide pot mute buttons */
	int	_Fsolo[MAXSLIDE];	/* fader solo buttons */
	int	_Fmute[MAXFADE];	/* fader mute buttons */
}	Curstate;
#define	Slide	Curstate._Slide
#define	Spsolo	Curstate._Spsolo
#define	Spmute	Curstate._Spmute
#define	Fsolo	Curstate._Fsolo
#define	Fmute	Curstate._Fmute

char	*Sfilfmt = "/tmp/mixstate%d";
int	Dchange	= 0;		/* display changes */
int	Xchange	= 0;		/* transmit changes */
int	Showfm	= 1;		/* show the fader monitor */
int	Ofh	= -1;		/* where Iota commands go to */
int	Chan;			/* Iota's MIDI channel */
int	Map[MAXFADE][MAXSLIDE];	/* how sliders affect faders */
int	Nslide;			/* how many sliders we actually need */
int	Nfade;			/* how many faders we actually need */
int	Fmlx, Fmhx, Fmly, Fmhy;	/* bounds of fader monitor area */
int	Fms	= 1;		/* fader monitor includes solo buttons */
int	Fmm	= 1;		/* fader monitor includes mute buttons */
int	Fmh;			/* fader monitor height */
int	Fmy;			/* fader monitor (top) y */
int	Fmdx;			/* fader monitor x separation */
int	Fmw;			/* fader monitor width */
int	Fmx[MAXFADE];		/* fader monitor (center) x */
int	Splx, Sphx, Sply, Sphy;	/* bounds of slide pot area */
int	Spused[MAXSLIDE];	/* which sliders are actually in use */
int	Fused[MAXSLIDE];	/* which faders are actually in use */
int	Spsg[MAXSLIDE];		/* slide pot solo group */
int	Spmode[MAXSLIDE];	/* solo or mute priority? */
int	Fade[MAXFADE];		/* fader settings 0-127 */
int	Um[MAXFADE];		/* Iota fader mutes, 0=>muted */
int	Sph;			/* slide pot height */
int	Spy;			/* slide pot (top) y */
int	Spdx;			/* slide pot x separation */
int	Spx[MAXSLIDE];		/* slide pot (center) x */
int	Sy;			/* solo button y */
int	Fmsy;			/* fader monitor solo button y */
int	Sdx;			/* solo button x offset from Spx[] */
int	My;			/* mute button y */
int	Fmmy;			/* fader monitor mute button y */
int	Mdx;			/* mute button x offset from Spx[] */
int	Br;			/* button radius */
int	Fmbr;			/* fader monitor button radius */
int	Spw;			/* slide pot width */
int	Spw2;			/* slide pot half-width */
int	Mouseown	= -1;	/* slider that "claims" the mouse */
int	Wfbu		= 0;	/* "Waiting For Button Up" */
Frame	Dframe;			/* whole display's frame */
Canvas	Dcanvas;		/* whole display's canvas */
Pixwin	*Dpw	= 0;		/* whole display pixwin */
Menu	Rmenu;			/* right mouse button menu */
Pixrect	*Grey;			/* grey texture */
Pixrect	*Ltgrey;		/* light grey texture */
Pixrect	*Dkgrey;		/* dark grey texture */
struct	pixfont	*Fontp;

static	short	Grey_bits[]	= {
	0xAAAA, 0x5555, 0xAAAA, 0x5555,
	0xAAAA, 0x5555, 0xAAAA, 0x5555,
	0xAAAA, 0x5555, 0xAAAA, 0x5555,
	0xAAAA, 0x5555, 0xAAAA, 0x5555,
};

static	short	Ltgrey_bits[]	= {
	0x4444, 0x1111, 0x4444, 0x1111,
	0x4444, 0x1111, 0x4444, 0x1111,
	0x4444, 0x1111, 0x4444, 0x1111,
	0x4444, 0x1111, 0x4444, 0x1111,
};

static	short	Dkgrey_bits[]	= {
	0x7777, 0xDDDD, 0x7777, 0xDDDD,
	0x7777, 0xDDDD, 0x7777, 0xDDDD,
	0x7777, 0xDDDD, 0x7777, 0xDDDD,
	0x7777, 0xDDDD, 0x7777, 0xDDDD,
};

static	short Mixicon_bits[] = {
/* Format_version=1, Width=64, Height=64, Depth=1, Valid_bits_per_item=16
 */
	0x3FFF,0xFFFF,0xFFFF,0xFFFF,0x6000,0x0000,0x0000,0x0001,
	0xE800,0x0000,0x0000,0x0001,0xB800,0x0000,0x001F,0x83F1,
	0xA3FF,0xFFFF,0xFE3F,0xC7F9,0xA272,0xA32A,0x6230,0xC619,
	0xA2AA,0xB2AA,0xA621,0x4429,0xA2AA,0xAAAA,0xAA22,0x4449,
	0xA3FF,0xFFFF,0xFE24,0x4489,0xA000,0x0000,0x0036,0xC6D9,
	0xA000,0x0000,0x002F,0x45E9,0xA155,0x5555,0x543F,0xC7F9,
	0xA000,0x0000,0x0000,0x0001,0xA000,0x0000,0x0000,0x0001,
	0xA1DD,0xDDDD,0xDC66,0x6661,0xA155,0x5555,0x5466,0x6661,
	0xA1DD,0xDDDD,0xDC00,0x0001,0xA000,0x0000,0x0066,0x6661,
	0xA000,0x0000,0x0066,0x6661,0xA1DD,0xDDDD,0xDC00,0x0001,
	0xA155,0x5555,0x5466,0x6661,0xA1DD,0xDDDD,0xDC66,0x6661,
	0xA000,0x0000,0x0000,0x0001,0xA000,0x0000,0x0000,0x0001,
	0xA000,0x0000,0x0006,0x00C1,0xA199,0x9999,0x9882,0x1081,
	0xA088,0x8888,0x8804,0x0041,0xA088,0x8888,0x8882,0x1081,
	0xA088,0x8888,0x8884,0x1041,0xA089,0xC888,0x8802,0x0081,
	0xA089,0xC888,0x8884,0x1041,0xA09C,0x8888,0x8802,0x0081,
	0xA1DD,0x9999,0x989F,0x93F1,0xA1C8,0x89C8,0x880F,0x01E1,
	0xA088,0x89C8,0x9C84,0x1041,0xA088,0x8888,0x9C02,0x0081,
	0xA088,0x9C89,0xC804,0x0041,0xA088,0x9C89,0xC802,0x0081,
	0xA088,0x8888,0x8884,0x1041,0xA199,0x9999,0x9942,0x3881,
	0xA088,0x8888,0x8884,0x1041,0xA088,0x8888,0x8802,0x0081,
	0xA088,0x8888,0x8804,0x0041,0xA088,0x8888,0x8882,0x1081,
	0xA088,0x8888,0x89C4,0x2841,0xA088,0x8888,0x8882,0x1081,
	0xA199,0x9999,0x9804,0x0041,0xA088,0x8888,0x8802,0x0081,
	0xA088,0x8888,0x8884,0x1041,0xA088,0x8888,0x8942,0x2881,
	0xA088,0x8888,0x8884,0x1041,0xA088,0x889C,0x8802,0x0081,
	0xA199,0x999D,0x9806,0x00C1,0xA000,0x0000,0x0000,0x0001,
	0xE000,0x0000,0x0000,0x0001,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
	0xC000,0x0000,0x0000,0x0003,0x8000,0x2272,0x4E70,0x0001,
	0x9111,0x3622,0xC849,0x1111,0xAAAA,0xBE21,0x8C72,0xAAA9,
	0x8444,0x2A23,0x4850,0x4441,0x8000,0x2272,0x4E48,0x0001,
	0xC000,0x0000,0x0000,0x0003,0xFFFF,0xFFFF,0xFFFF,0xFFFF
};
DEFINE_ICON_FROM_IMAGE(Mixicon, Mixicon_bits);

main(argc, argv)
char	*argv[];
{
	char *cp, *mflg, *sflg;
	int iflg, s, f;
	extern char *getenv();

/****/setbuf(stderr, 0);
	iflg = 0;		/* no parameter initialization */
	mflg = (char *) 0;	/* no map file */
	sflg = (char *) 0;	/* no slider level file */
	if (!(cp = getenv("IOTA")) || (Chan = atoi(cp) - 1) < 0)
	    Chan = DEFCHAN;
	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'f':		/* don't display fader monitor */
		    Showfm = 0;
		    break;
		case 'i':		/* specify Iota initialization */
		    iflg++;
		    break;
		case 'm':		/* mixer map file */
		    mflg = &argv[argc][2];
		    break;
		case 's':		/* restore from saved file */
		    sflg = &argv[argc][2];
		    break;
		default:
		    goto syntax;
		}
	    } else {
syntax:
		fprintf(stderr,
		 "Usage: %s [-f] [-i] [-p[#]] [-sFILE] >file\n", argv[0]);
		fprintf(stderr, "-f suppresses the fader display\n");
		fprintf(stderr, "-i inits Midi-Fader parameters explicitly\n");
		fprintf(stderr, "-mFILE gets mixer map from 'file'\n");
		fprintf(stderr, "-sFILE gets slider levels from 'file'\n");
		exit(2);
	    }
	}
	mapinit(mflg);			/* calc Nslide & Nfade also */
	Ofh = fileno(stdout);
	if (iflg)
	    sendinit();
	miscinits();
	Dchange = Xchange = 1;
	if (sflg)
	    restore(sflg);
	else
	    for (s = Nslide; --s >= 0; )
		if (Spused[s])
		    setslider(s, DOWN6DB);
#define	DBG
#ifdef DBG
	{ int i, catcher(); for (i=16; --i>0; signal(i, catcher));}
#endif DBG
	window_main_loop(Dframe);
	exit(0);
}

#ifdef DBG
catcher(sig)
{
	fprintf(stderr, "Caught %d\n", sig);
	if (sig == 13) abort();
	signal(sig, catcher);
}
#endif DBG

mapinit(file)
char	*file;
{
	register int f, s, op, spsg;
	char buf[128], *cp;
	FILE *fp;

	for (s = MAXSLIDE; --s >= 0; ) {
	    Spsg[s] = -1;
	    Spmode[s] = MUTE;
	}
	Nslide = Nfade = spsg = 0;
	if (file && (fp = fopen(file, "r"))) {
	    while (fgets(buf, sizeof buf, fp)) {
		if (*buf == '#')			/* comment */
		    continue;
		else if (*buf == 'f') {			/* fader map */
		    f = atoi(&buf[1]) - 1;
		    if (f < 0 || MAXFADE <= f) {
			fprintf(stderr, "bad fader # (%d) in %s", f, buf);
			continue;
		    }
		    Fused[f] = 1;
		    if (f >= Nfade)
			Nfade = f + 1;
		    for (cp = buf;;) {
			while (*cp && *cp != '#'
			 && *cp != '=' && *cp != '+' && *cp != '*')
			    cp++;
			if ((op = *cp++) < ' ' || op == '#')
			    break;
			s = atoi(cp) - 1;
			if (s < 0 || MAXSLIDE <= s) {
			    fprintf(stderr, "bad slider # (%d) in %s", s, buf);
			    continue;
			}
			if (op == '*') {
			    if (Map[f][s])
				fprintf(stderr,
				 "%d=#%c%d*%d (or equiv) is an error)\n",
				 f, Map[f][s]>0? '+' : '*', s, s);
			    Map[f][s] = -1;
			} else {
			    if (Map[f][s] < 0)
				fprintf(stderr,
				 "%d=#*%d+%d (or equiv) is an error)\n",
				 f, s, s);
			    Map[f][s]++;
			}
			if (s >= Nslide)
			    Nslide = s + 1;
			Spused[s] = 1;
		    }
		} else if (*buf == 's') {		/* solo group */
		    if (spsg >= MAXSLIDE) {
			fprintf(stderr, "Too many solo groups; max is %d.\n",
			 MAXSLIDE);
			continue;
		    }
		    for (cp = buf;;) {
			while (*cp && *cp != '=' && *cp != ',' && *cp != '#')
			    cp++;
			if ((op = *cp++) < ' ' || op == '#')
			    break;
			s = atoi(cp) - 1;
			if (s < 0 || MAXSLIDE <= s) {
			    fprintf(stderr, "bad slider # (%d) in %s", s, buf);
			    continue;
			}
			Spsg[s] = spsg;
		    }
		    spsg++;
		}
	    }
	} else {				/* default case */
	    Nslide = Nfade = DEFFADE;
	    for (f = Nfade; --f >= 0; )
		Map[f][f] = Spused[f] = Fused[f] = 1;
	}
	if (spsg == 0) {			/* no solo groups specified */
	    for (s = f = 0; s < Nslide; s++) {
		if (Spused[s]) {
		    if (f++) {			/* don't mark 1st in group */
			if (f == 2)		/* until we've seen the 2nd */
			    Spsg[s - 1] = spsg;
			Spsg[s] = spsg;
		    }
		} else {
		    if (f > 1)
			spsg++;
		    f = 0;
		}
	    }
	}
}

miscinits()
{
	void resized(), input();
	Notify_value fadein();

	Dframe = window_create(NULL, FRAME,
	    FRAME_SHOW_LABEL,	FALSE,
	    FRAME_ICON,		&Mixicon,
	    WIN_WIDTH,		10 + 25 * Nslide,
	    WIN_HEIGHT,		Showfm? 328 : 248,
	    0);
	Dcanvas = window_create(Dframe, CANVAS,
	    CANVAS_FIXED_IMAGE,		FALSE,
	    CANVAS_RESIZE_PROC,		resized,
	    WIN_CONSUME_PICK_EVENT,	WIN_NO_EVENTS,
	    WIN_CONSUME_PICK_EVENT,	LOC_WINENTER,
	    WIN_CONSUME_PICK_EVENT,	LOC_WINEXIT,
	    WIN_CONSUME_PICK_EVENT,	LOC_MOVE,
	    WIN_CONSUME_PICK_EVENT,	WIN_MOUSE_BUTTONS,
	    WIN_CONSUME_PICK_EVENT,	LOC_DRAG,
	    WIN_CONSUME_KBD_EVENT,	WIN_ASCII_EVENTS,
	    WIN_EVENT_PROC,		input,
	    0);
	Dpw = canvas_pixwin(Dcanvas);
	Rmenu = menu_create(
	    MENU_BOXED,		TRUE,
	    MENU_ITEM,	MENU_STRING,	"INIT",
			MENU_VALUE,	INIT,
			0,
	    MENU_ITEM,	MENU_STRING,	"SAVE",
			MENU_VALUE,	SAVE,
			0,
	    MENU_ITEM,	MENU_STRING,	"RESTORE",
			MENU_VALUE,	RESTORE,
			0,
	    MENU_ITEM,	MENU_STRING,	"ALL UP",
			MENU_VALUE,	ALLUP,
			0,
	    MENU_ITEM,	MENU_STRING,	"ALL DOWN",
			MENU_VALUE,	ALLDOWN,
			0,
	    MENU_ITEM,	MENU_STRING,	"QUIT",
			MENU_VALUE,	QUIT,
			0,
	    0);
	Grey = mem_point(16, 16, 1, Grey_bits);
	Ltgrey = mem_point(16, 16, 1, Ltgrey_bits);
	Dkgrey = mem_point(16, 16, 1, Dkgrey_bits);
}

void
resized(canvas, width, height)
Canvas	canvas;
{
	register int i, x, y, q, chw, chh, bs;
	int fw, fh, sw, sh;
	char sb[2], mb[2];

	if (canvas != Dcanvas)
	    return;			/* oops? */
	Splx = Sply = 0;
	Sphx = width;
	Sphy = height;
	if (Showfm && Nfade > 0) {
	    Fmlx = Fmly = 0;
	    Fmhx = width;
	    Fmhy = height;
	    x = (11 * width) / Nslide;	/* ideal height for faders above */
	    y = (24 * width) / (3 * Nslide + Nfade);	/* & for side-by-side */
	    i = (x + y) / 2;		/* somewhere in between */
	    if (height >= i)		/* put faders above */
		Sply = Fmhy = height / 4;
	    else			/* put faders to the right */
		Sphx = Fmlx = (width * 3 * Nslide) / (3 * Nslide + Nfade);
	    fw = Fmhx - Fmlx;
	    fh = Fmhy - Fmly;
	    pw_replrop(Dpw, Fmlx, 0, fw, fh, PIX_SRC, Dkgrey, 0, 0);
	    Fmdx = fw / (Nfade + 1);
	    if (Fms || Fmm) {
		x = Fmdx;
		y = fh / 6;
		i = x < y? (x < 40? x : 40) : (y < 40? y : 40);
		Fmbr = (2 * i) / 5;
		if (Fmm) {
		    fh -= i;
		    Fmmy = fh + i / 2;
		}
		if (Fms) {
		    fh -= i;
		    Fmsy = fh + i / 2;
		}
	    }
	    Fmh = (4 * fh) / 5;		/* height of fader display sliders */
	    Fmw = Fmdx - 4;		/* width " */
	    Fmy = (fh - Fmh) / 2;
	    for (i = 0; i < Nfade; i++) {
		if (!Fused[i])
		    continue;
		Fmx[i] = Fmlx + (i + 1) * Fmdx;
		x = Fmx[i] - Fmw / 2;
		y = Fmy;
		pw_replrop(Dpw, x, y, Fmw, Fmh, PIX_SRC, Grey, 0, y&1);
		q = f_to_y(Fade[i]);
		pw_rop(Dpw, x, y, Fmw, q - y, PIX_SET, 0, 0, 0);
		x = Fmx[i];
		if (Fms) {
		    pw_disc(Dpw, x, Fmsy, Fmbr, Fsolo[i]? PIX_SET : PIX_CLR);
		    pw_disc(Dpw, x, Fmsy, Fmbr - 1, PIX_INV);
		}
		if (Fmm) {
		    pw_disc(Dpw, x, Fmmy, Fmbr, Fmute[i]? PIX_SET : PIX_CLR);
		    pw_disc(Dpw, x, Fmmy, Fmbr - 1, PIX_INV);
		}
	    }
	}
	sw = Sphx - Splx;
	sh = Sphy - Sply;
	Spdx = sw / Nslide;		/* slide pot separation */
	if (5 * Spdx < sh) {		/* put buttons over-under */
	    bs = sh / 5;		/* vertical space for buttons */
	    Br = bs / 5;		/* button radius */
	    if (Spdx < 3 * Br) {	/* if button would be huge */
		Br = Spdx / 3;
		bs = 5 * Br;
	    }
	    Sy = Sply + bs / 4;		/* solo button y coord */
	    My = Sply + (bs * 3) / 4;	/* mute button y coord */
	    Sdx = Mdx = 0;
	} else {			/* put buttons side-by-side */
	    bs = sh / 8;
	    Br = bs / 3;
	    Sy = Sply + (5 * bs) / 12;
	    My = Sply + (7 * bs) / 12;
	    Sdx = (2 * bs) / 5;
	    Mdx = -Sdx;
	}
	Spy = Sply + bs;			/* top of slide pot y */
	Sph = sh - bs;			/* slide pot height */
	Spw = Spdx / 2;			/* slide pot width */
	Spw2 = Spdx / 4;		/* half slide pot width */
	Sph -= 4;			/* leave room for outline */
	Spy += 2;			/* ditto */
	if (Fontp)
	    pf_close(Fontp);
	for (i = 0; Fs[i].siz > Br; i++);
	Fontp = pf_open(Fs[i].nam);
	chw = Fontp->pf_defaultsize.x / 2;
	chh = (Fontp->pf_defaultsize.y - 2) / 2;
	pw_replrop(Dpw, 0, Sply, sw, sh, PIX_SRC, Ltgrey, 0, 0);
	i = 0;
	do {
	    y = s_to_y(Ticks[i]);
	    pw_vector(Dpw, 0, y, sw, y, PIX_SET, 1);
	} while (Ticks[i++]);
	sb[0] = 'S';
	mb[0] = 'M';
	sb[1] = mb[1] = '\0';
	for (i = 0; i < Nslide; i++) {
	    if (!Spused[i])
		continue;
	    Spx[i] = i * Spdx + Spdx / 2;
	    if (Spsg[i] >= 0) {
		x = Spx[i] + Sdx;
		pw_disc(Dpw, x, Sy, Br, Spsolo[i]? PIX_SET : PIX_CLR);
		pw_disc(Dpw, x, Sy, Br - 1, PIX_INV);
		pw_ttext(Dpw, x - chw, Sy + chh, PIX_INV, Fontp, sb);
	    }
	    x = Spx[i] + Mdx;
	    pw_disc(Dpw, x, My, Br, Spmute[i]? PIX_SET : PIX_CLR);
	    pw_disc(Dpw, x, My, Br - 1, PIX_INV);
	    pw_ttext(Dpw, x - chw, My + chh, PIX_INV, Fontp, mb);
	    x = Spx[i] - Spw2;
	    y = Spy;
	    pw_rop(Dpw, x - 2, y - 2, Spw + 4, Sph + 4, PIX_SET, 0, 0, 0);
	    pw_replrop(Dpw, x, y, Spw, Sph, PIX_SRC, Grey, 0, y&1);
	    q = s_to_y(Slide[i]);
	    pw_rop(Dpw, x, y, Spw, q - y, PIX_CLR, 0, 0, 0);
	}
}

void
input(window, event, arg)
Window	window;
Event	*event;
{
	register int i = event_id(event);

	if (i == MS_LEFT || i == MS_MIDDLE || i == LOC_DRAG)
	    leftmouse(window, event, arg);
	if (i == MS_RIGHT)
	    rightmouse(window, event, arg);
}

rightmouse(window, event, arg)
Window	window;
Event	*event;
{
	char file[64];
	int s;

	switch (menu_show(Rmenu, window, event, 0)) {
	case INIT:
	    sendinit();
	    break;
	case SAVE:
	    sprintf(file, Sfilfmt, 0);
	    save(file);
	    break;
	case RESTORE:
	    sprintf(file, Sfilfmt, 0);
	    restore(file);
	    break;
	case ALLUP:
	    for (s = Nslide; --s >= 0; setslider(s, 127));
	    break;
	case ALLDOWN:
	    for (s = Nslide; --s >= 0; setslider(s, 0));
	    break;
	case QUIT:
	    exit(0);
	}
}

leftmouse(window, event, arg)
Window	window;
Event	*event;
{
	register int x, y, s, f, middown;

	if (event_is_up(event)) {
	    Mouseown = -1;
	    Wfbu = 0;
	    return;
	}
	x = event_x(event);
	y = event_y(event);
	middown = (int) window_get(Dcanvas, WIN_EVENT_STATE, MS_MIDDLE);
	if (!middown && Mouseown >= 0)	/* if a slider claims the mouse */
	    setslider(Mouseown, y_to_f(y));
	else if (Splx < x && x < Sphx && Sply < y && y < Sphy) {
	    s = (x - Splx) / Spdx;			/* the slider panel */
	    if (s < 0 || s >= Nslide || !Spused[s])
		return;				/* too far or unused */
	    if (Spy - 2 <= y && y <= Spy + Sph + 2		/* sliders */
	     && Spx[s] - Spw2 < x && x < Spx[s] + Spw2) {
		setslider(s, y_to_f(y));
		Mouseown = middown? -1 : s;
	    } else if (octd(Spx[s]+Mdx - x, My - y) < Br) {	/* MUTE */
		x = MUTE + (s << 2);
		if (Wfbu != x) {
		    invspmute(s);
		    Wfbu = x;
		}
	    } else if (octd(Spx[s]+Sdx - x, Sy - y) < Br) {	/* SOLO */
		if (Spsg[s] >= 0) {
		    x = SOLO + (s << 2);
		    if (Wfbu != x) {
			invspsolo(s);
			Wfbu = x;
		    }
		}
	    }
	} else if (Fmlx < x && x < Fmhx && Fmly < y && y < Fmhy) {
	    f = (x - Fmlx) / Fmdx;			/* the fader monitor */
	    if (f < 0 || f >= Nfade)
		return;
	    if (octd(Fmx[f] - x, Fmmy - y) < Fmbr) {		/* MUTE */
		x = MUTE + (f << 7);
		if (Wfbu != x) {
		    invfmute(f);
		    Wfbu = x;
		}
	    } else if (octd(Fmx[f] - x, Fmsy - y) < Fmbr) {	/* SOLO */
		x = SOLO + (f << 7);
		if (Wfbu != x) {
		    invfsolo(f);
		    Wfbu = x;
		}
	    }
	}
}

octd(dx, dy)				/* octagonal distance measure */
register int dx, dy;
{
	if (dx < 0)
	    dx = -dx;
	if (dy < 0)
	    dy = -dy;
	return (dx > dy? dx + (dy >> 1) : (dx >> 1) + dy);
}


f_to_y(f)
{
	register int y;

	y = Fmy + Fmh - ((f * Fmh) / 127);
	return(y < Fmy? Fmy : (y < Fmy + Fmh? y : Fmy + Fmh));
}

s_to_y(s)
{
	register int y;

	y = Spy + Sph - ((s * Sph) / 127);
	return(y < Spy? Spy : (y < Spy + Sph? y : Spy + Sph));
}

y_to_f(y)
{
	register int f;

	f = ((Spy + Sph - y) * 127) / Sph;
	return(f < 0? 0 : (f < 127? f : 127));
}

setslider(s, new)	/* set slider s to new value & tell Iota */
{
	register int old, oldy, newy;

	old = Slide[s];
	if (new != old) {
	    Slide[s] = new;
	    oldy = s_to_y(old);
	    newy = s_to_y(new);
	    if (Dchange) {
		if (newy < oldy)		/* new level is higher */
		    pw_replrop(Dpw, Spx[s] - Spw2, newy, Spw, oldy - newy,
		     PIX_SRC, Grey, 0, newy&1);
		else				/* new level is lower */
		    pw_rop(Dpw, Spx[s] - Spw2, oldy, Spw, newy - oldy,
		     PIX_CLR, 0, 0, 0);
	    }
	    if (Xchange)
		schng(s);
	}
}

schng(s)		/* a slider has changed, update faders */
{
	register int f;

	for (f = Nfade; --f >= 0; )
	    if (Map[f][s])
		setfader(f);
}

setfader(f)		/* a contributor has changed, recalculate */
{
	register int new, old, num, den, s, m, oldy, newy, mode;

	num = den = 0;
	for (s = 0; s < Nslide; s++) {
	    if (m = Map[f][s]) {
		new = Slide[s];
		if (Spsg[s] >= 0)
		    mode = Spmode[Spsg[s]];
		else
		    mode = MUTE;
		if ((mode == MUTE && Spmute[s])
		 || (mode == SOLO && !Spsolo[s]))
		    new = 0;
		if (m > 0) {
		    num += m * new;
		    den += m;
		} else if (m < 0) {
		    num *= new;
		    den *= 127;
		}
	    }
	}
	if (den > 0) {
	    old = Fade[f];
	    new = num / den;
	    if (new != old) {
		Fade[f] = new;
		if (Xchange)
		    mput(CH_CTL | Chan, FCTRL0 + f, new);
		if (Dchange && Showfm) {
		    oldy = f_to_y(old);
		    newy = f_to_y(new);
		    if (newy < oldy)		/* new level is higher */
			pw_replrop(Dpw, Fmx[f] - Fmw/2, newy,
			 Fmw, oldy - newy, PIX_SRC, Grey, 0, newy&1);
		    else				/* new level is lower */
			pw_rop(Dpw, Fmx[f] - Fmw/2, oldy,
			 Fmw, newy - oldy, PIX_SET, 0, 0, 0);
		}
	    }
	}
}

invspmute(s)			/* invert slide pot mute */
{
	Spmute[s] ^= 1;
	pw_disc(Dpw, Spx[s] + Mdx, My, Br, PIX_INV);
	if (Slide[s])
	    schng(s);
}

invfmute(f)			/* invert fader mute */
{
	Fmute[f] ^= 1;
	pw_disc(Dpw, Fmx[f], Fmmy, Fmbr, PIX_INV);
	if (Xchange)
	    domutes();
}

invspsolo(sp)			/* invert slide pot solo */
{
	register int i, s, spsg;

	Spsolo[sp] ^= 1;
	spsg = Spsg[sp];
	if (spsg < 0)
	    return;
	pw_disc(Dpw, Spx[sp] + Sdx, Sy, Br, PIX_INV);
	sp = MUTE;
	for (s = Nslide; --s >= 0; )
	    if (Spsg[s] == spsg && Spsolo[s]) {
		sp = SOLO;
		break;
	    }
	Spmode[spsg] = sp;
	for (i = Nfade; --i >= 0; setfader(i));
}

invfsolo(f)			/* invert fader solo */
{
	Fsolo[f] ^= 1;
	pw_disc(Dpw, Fmx[f], Fmsy, Fmbr, PIX_INV);
	if (Xchange)
	    domutes();
}

domutes()	/* fader mutes (not slide pot mutes) */
{
	register int i;
	int um[MAXFADE];

	if (Xchange) {
	    for (i = Nfade; --i >=0 && Fsolo[i] == 0; );
	    if (i >= 0)				/* Solo takes precedence */
		for (i = Nfade; --i >= 0; )
		    um[i] = Fsolo[i]? 1 : 0;
	    else				/* Mute takes precedence */
		for (i = Nfade; --i >= 0; )
		    um[i] = Fmute[i]? 0 : 1;
	    for (i = Nfade; --i >= 0; )
		if (Um[i] != um[i])
		    mput(CH_CTL | Chan, MCTRL0 + i, 127 * (Um[i] = um[i]));
	}
}

mput(a, b, c)
{
	u_char buf[8], *bp;

	bp = buf;
	*bp++ = a;
	*bp++ = b;
	if (c != -1)
	    *bp++ = c;
	write(Ofh, buf, bp - buf);
/****fprintf(stderr, "mput(%d, %d, %d)\n", a, b, c);
/****/
}

sysex(data, len)	/* send sys excl */
u_char	*data;
{
	u_char buf[4096], *bp;

	bp = buf;
	*bp++ = SX_CMD;
	write(Ofh, buf, bp - buf);
	write(Ofh, data, len);
	bp = buf;
	*bp++ = SX_EOB;
	write(Ofh, buf, bp - buf);
	return(0);
}

/*
** Draw a disc with radius r centered at x,y in Pixwin *pw using op.
** The boundary is a sequence of vertically, horizontally, or diagonally
** adjacent points that minimize abs(x^2+y^2-r^2).
** The disc is guaranteed to be symmetric about the horizontal, vertical,
** and diagonal axes.  */
pw_disc(pw, x, y, r, op)
Pixwin *pw;
{
	register x1, y1, eps, x0, y0;
	int dxsq, dysq, exy;

	x0 = x1 = x;
	y1 = y + r;
	y0 = y - r;
	eps = 0;	/* x^2 + y^2 - r^2 */
	dxsq = 1;		/* (x+dx)^2-x^2*/
	dysq = 1 - 2*r;
	while (y1 > y0) {
	    exy = eps + dxsq + dysq;
	    if (-exy <= eps+dxsq) {
		HLINER(pw, x0, y0, x1, op);
		HLINER(pw, x0, y1, x1, op);
		y1--;
		y0++;
		eps += dysq;
		dysq += 2;
	    }
	    if (exy <= -eps) {
		x1++;
		x0--;
		eps += dxsq;
		dxsq += 2;
	    }
	}
	HLINER(pw, x0, y0, x1, op);
}

struct	setupstr	{	/* initial setup structure */
	u_char	id[4];		/* identify Iota & Midi-Fader */
	u_char	unit;		/* system exclusive unit number */
	u_char	cmd;		/* what command (6 = setup data dump) */
	u_char	reclev;		/* receive levels over MIDI (1=yes) */
	u_char	sndlev;		/* send levels over MIDI (1=yes) */
	u_char	recpgm;		/* receive prog commands over MIDI (1=yes) */
	u_char	sndpgm;		/* send prog commands over MIDI (1=yes) */
	u_char	prgchan;	/* program change channel */
	u_char	autolev;	/* auto send levels every second (1=yes) */
	u_char	rate;		/* fader change rate (2^rate ms.) */
	u_char	sens;		/* dial sensitivity */
	struct	fsetstr	{
		u_char	fchan;	/* fader channel */
		u_char	norc;	/* fader controlled by note(1) or ctrl(0) */
		u_char	fctrl;	/* fader note or ctrl number */
		u_char	mctrl;	/* mute ctrl number */
	} fade[8];
} Isetup	= {		/* Initial setup data */
	{ ID_MISC, ID1_IOTA, ID2_IOTA, ID_IMF, },/* identify Iota Midi-Fader */
	0x00,					/* Unit number */
	IMF_SXC_DAT_SETUP,			/* command; setup data dump */
	0x01, 0x01,				/* do get/send levels */
	0x00, 0x00,				/* don't get/send prog cmds */
	0x00,					/* prog change MIDI channel */
	0x00,					/* no levels every sec */
	0x00,					/* fader change rate = 1 ms */
	0x0a,					/* dial sensitivity = 10 */
	{ 0x00, 0x00, 0x00, 0x00, },		/* fader 1 */
						/* etc. for 7 more */
};

sendinit()			/* send SysExcl initialization */
{
	register int i, f;

	for (i = 0; i < Nfade; i += 8) {
	    Isetup.unit = F2SXU(i);
	    for (f = 0; f < 8; f++) {
		Isetup.fade[f].fchan = Chan + 1;	/* N.B. +1 */
		Isetup.fade[f].norc = 0;
		Isetup.fade[f].fctrl = FCTRL0 + i + f;
		Isetup.fade[f].mctrl = MCTRL0 + i + f;
	    }
	    sysex(&Isetup, sizeof Isetup);
	}
}

save(file)				/* save current slider settings */
char	*file;
{
	int fh;

	if ((fh = creat(file, 0644)) < 0)
	    perror(file);
	else if (write(fh, &Curstate, sizeof Curstate) != sizeof Curstate)
	    perror("writing state file");
	else {
	    close(fh);
	    fprintf(stderr, "sliders saved in %s\n", file);
	}
}

restore(file)				/* restore slider settings */
char	*file;
{
	int fh, s, f;
	struct statestr	tmpstate;

	if ((fh = open(file, 0)) < 0)
	    perror(file);
	else if (read(fh, &tmpstate, sizeof tmpstate) != sizeof tmpstate)
	    perror("reading state file");
	else {
	    close(fh);
	    for (s = Nslide; --s >= 0; ) {
		setslider(s, tmpstate._Slide[s]);
		if (Spsolo[s] != tmpstate._Spsolo[s])
		    invspsolo(s);
		if (Spmute[s] != tmpstate._Spmute[s])
		    invspmute(s);
	    }
	    for (f = Nfade; --f >= 0; ) {
		if (Fsolo[f] != tmpstate._Fsolo[f])
		    invfsolo(f);
		if (Fmute[f] != tmpstate._Fmute[f])
		    invfmute(f);
	    }
	}
}
