/*	CHED -- Sunview version of "ched", MIDI Chart Editor, psl 12/85  */

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

#define	PIX_INV	PIX_NOT(PIX_DST)

#define	ROP(OX,OY,CX,CY,OP)	pw_rop(Dpw,OX,OY,CX-(OX),CY-(OY),OP,0,0,0)
#define	ROPS(OX,OY,CX,CY,OP,S)	pw_rop(Dpw,OX,OY,CX-(OX),CY-(OY),OP,S,0,0)
#define	TREPL(X,Y,W,H,OP,T)	pw_replrop(Dpw,X,Y,W,H,OP,T,((X)&15),((Y)&15))
#define	RECTOP(R,OP)		ROP((R).o.x,(R).o.y,(R).c.x,(R).c.y,OP)
#define	RINV(OX,OY,CX,CY)	pw_rop(Dpw,OX,OY,CX-(OX),CY-(OY),PIX_INV,0,0,0)
#define	RECTINV(R)		RINV((R).o.x,(R).o.y,(R).c.x,(R).c.y)

#define	CPM			1000
	/* CPM is Clocks Per Moment (arbitrarily a thousand clocks) */
#define	MAXCHAN			16
#define	MINKEY			0
#define	MAXKEY			128
#define	KRANGE		(MAXKEY - MINKEY)
#define	MAXVEL			128
#define	MAXBL			1024	/* maximum number of TCWMEs */
#define	VOLRES			1200	/* maximum display width, pixels */

#define	SBHH		16	/* scroll bar half height */
#define	MAXCMINK	42	/* always show staves initially */
#define	MINCMAXK	78
#define	NOTEHH		(Ppk >> 1)	/* note half height */

#define	FONTPATH	"/usr/lib/fonts/fixedwidthfonts/screen.r.14"

/* values for Mymouse */
#define	VUMOUSE	1
#define	HSMOUSE	2
#define	VSMOUSE	3
#define	CAMOUSE	4

/* modes for selpart() */
#define	SEL		0	/* just split into selected & unselected */
#define	OPEN		1	/* open selected time */
#define	CLOSE		2	/* close selected time */
/* dummy file arg for selpart() */
#define	NO_FILE		((char *) 0)

/* chart area macros -- these depend on Ppm, Ppk, Cmint, & Cmink */
#define	CAX_T(X)	(1 + Cmint + (((X) - Ca.o.x) * CPM - 1) / Ppm)
#define	CAT_X(T)	(Ca.o.x + (((T) - Cmint) * Ppm) / CPM)
/****#define	CAX_T(X)	(Cmint + (((X) - Ca.o.x) * CPM + Ppm / 2) / Ppm)
/****#define	CAX_T(X)	(Cmint + (((X) - Ca.o.x) * CPM) / Ppm)
/****#define	CAT_X(T)	(Ca.o.x + (((T) - Cmint) * Ppm + CPM / 2) / CPM)
/****/
#define	CAK_Y(K)	(Ca.c.y - ((K) - Cmink) * Ppk)
#define	CAK_YA(K)	(CAK_Y(K) - Ppk + 1)
#define	CAK_YB(K)	(CAK_Y(K) + Ppk - 1)
#define	CAY_K(Y)	(Cmink + (Ca.c.y - (Y) + NOTEHH) / Ppk)
#define	CAY_KA(Y)	(CAY_K(Y) + 1)
#define	CAY_KB(Y)	(CAY_K(Y) - 1)

/* horizontal scroll area macros */
#define	HSAX_T(X)	(1 + Mint + (((X) - Hsa.o.x) * Ranget - 1) / Hspix)
#define	HSAT_X(T)	(Hsa.o.x + (((T) - Mint) * Hspix) / Ranget)
#define	HSAK_Y(K)	(Hsa.o.y + ((MAXKEY - (K)) * 2 * SBHH - 1) / MAXKEY)
#define	VOLT_I(T)	(((T) * Hspix) / Ranget)

/* vertical scroll area macros */
#define	VSAY_K(Y)	(1 + (((Vsa.c.y - (Y)) * KRANGE - 1) / Vspix))
#define	VSAK_Y(K)	(Vsa.c.y - ((K) * Vspix) / KRANGE)

typedef	struct	point	{
	int	x, y;
} Point;
typedef	struct	recta	{
	Point	o, c;
} Recta;

char	Oldfile[128];		/* where the data comes from for "read" */
char	Newfile[128];		/* where the data goes for "write" */
char	Workfile[128];		/* where it lives while we work */
char	Flbuf[128];		/* frame label buffer */
char	*Selfile = "/tmp/chedsel";	/* where the selected piece goes */
char	*Playfile = "/tmp/chedplay";	/* where stuff to be played goes */
char	*Playcmd = "/usr/psl/MIDI/bin/play";
char	*Mergecmd = "/usr/psl/MIDI/bin/merge";
char	*Tshiftcmd = "/usr/psl/MIDI/bin/tshift";
int	Debug	= 0;		/* for extra debugging output */
int	Dirty	= 0;		/* changed but unwritten */
int	Tgrid	= 0;		/* grid ticks per bar */
int	Mymouse	= 0;		/* who "owns" the mouse at the moment */
int	Veldisp = 0;		/* 1=> show velocities, 0=> don't */
int	Garbsel = 2;		/* 0=>none, 1=>sel, 2=>unsel, 3=>both */
int	Barlen	= 480;		/* MIDI clocks per bar, 0=> use TCWMEs */
int	Baroff	= -2;		/* set to first TCWME (% Barlen) by process() */
int	Tempo	= 100;		/* basis for play -t argument */
int	Margin	= 10;		/* guaranteed empty space around score */
int	Ppm;			/* pixels per moment (see CPM def) */
int	Ppk;			/* pixels per note (vertically) */
int	Vuwidth;		/* spacing for VU meters */
int	Playpid	= 0;		/* >0 ==> something is playing */
int	Mink, Maxk;		/* min and max+1 key values in input */
int	Rangek;			/* range of key values in input */
int	Cmink, Cmaxk;		/* min and max+1 key values to display */
int	Crangek;		/* range of key values to display */
int	Smink, Smaxk;		/* min and max+1 key values in selection */
int	Skmin, Skmax;		/* min and max+1 possible keys in Sel */
int	Hspix;			/* width of Hsa, pixels */
int	Vspix;			/* height of Vsa, pixels */
long	Mint, Maxt;		/* min and max+1 times in input */
long	Ranget;			/* range of time in input */
long	Cmint, Cmaxt;		/* min and max+1 times to display */
long	Cranget;		/* range of time to display */
long	Smint, Smaxt;		/* min and max+1 times in selection */
long	Stmin, Stmax;		/* min and max+1 possible times in Sel */
FILE	*Tfp;			/* slightly preprocessed data */
Recta	Da;			/* entire drawing area (minus Margin) */
Recta	Va;			/* VU area */
Recta	Ca;			/* chart area */
Recta	Hsa;			/* horizontal scroll area */
Recta	Vsa;			/* vertical scroll area */
Recta	Sel;			/* selected area */
Point	Csize;			/* max width & height of Ca, pixels */
Pixrect	*Ltgrey;		/* light grey texture */
Pixrect	*Grey;			/* grey texture */
Pixrect	*Dkgrey;		/* dark grey texture */
Frame	Dframe;			/* whole display's frame */
Canvas	Dcanvas;		/* whole display's canvas */
Pixwin	*Dpw	= 0;		/* whole display pixwin */
Pixfont	*Fontp;			/* the font we use (almost everywhere) */
int	Fontwidth;		/* the width of chars in *Fontp */
Panel	Controls;		/* control panel */
int	Cntlshow = FALSE;	/* whether to show control panel */
Panel_item Bslide;		/* id for clocks/bar slider */
Panel_item Oslide;		/* id for offset slider */
Panel_item Gslide;		/* id for ticks/bar slider */
Panel_item Tslide;		/* id for tempo slider */
Panel_item Mchoic;		/* id for vmode choice */
Panel_item Gchoic;		/* id for gmode choice */

short	Ltgrey_bits[]	= {
	0x0000, 0x5555, 0x0000, 0xAAAA,
	0x0000, 0x5555, 0x0000, 0xAAAA,
	0x0000, 0x5555, 0x0000, 0xAAAA,
	0x0000, 0x5555, 0x0000, 0xAAAA,
};

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

short	Dkgrey_bits[]	= {
	0xFFFF, 0x5555, 0xFFFF, 0xAAAA,
	0xFFFF, 0x5555, 0xFFFF, 0xAAAA,
	0xFFFF, 0x5555, 0xFFFF, 0xAAAA,
	0xFFFF, 0x5555, 0xFFFF, 0xAAAA,
};

#define	C_OFF	0x0000		/* channel is off */
#define	C_ON	0x0001		/* channel is on (play & display) */
#define	C_GREY	0x0002		/* channel gets highlight (display in grey) */

int	Tclefk[]	= { 77, 74, 71, 67, 64, };
int	Bclefk[]	= { 43, 47, 50, 53, 57, };
int	Chans[MAXCHAN];
int	Key[MAXCHAN][MAXKEY];
int	Vel[MAXCHAN][MAXKEY];
int	Vol[VOLRES];		/* key velocities (volumes) */
int	Kiu[MAXKEY];		/* keys in use (in current data) */
int	Barline[MAXBL];		/* where the TCWMEs are */
int	Nbl	= 0;		/* index into MAXBL */

/* Button 2 menu - operations on the selected area */
Menu	B2mp;
#define	PLAY2	1
#define	BBOX2	2
#define	CUT2	3
#define	PASTE2	4
#define	GLOM2	5
#define	FILTER2	6
#define	IPROC2	7
#define	INFO2	8
#define	ZOOMI2	9
#define	ZOOMO2	10
#define	WRITE2	11
#define	OPEN2	12
#define	CLOSE2	13
#define	B2MINIT() B2mp = menu_create( \
MENU_INITIAL_SELECTION, MENU_SELECTED, \
MENU_INITIAL_SELECTION_SELECTED, TRUE, \
MENU_STRINGS, \
	"PLAY", \
	"BBOX", \
	"CUT", \
	"PASTE", \
	"GLOM", \
	"FILTER", \
	"IPROC", \
	"INFO", \
	"ZOOM IN", \
	"ZOOM OUT", \
	"WRITE", \
	"OPEN", \
	"CLOSE", \
0, 0)

/* Button 2 menu - used when the selected area is empty */
Menu	B2emp;
#define	B2EMINIT() B2emp = menu_create(MENU_STRINGS, \
	"No area selected.", \
	"Sweep an area with button 1", \
0, 0)

/* Button 3 menu - operations on the entire file */
Menu	B3mp;
#define	PLAY3	1
#define	CNTRLS3	2
#define	FILTER3	3
#define	IPROC3	4
#define	READ3	5
#define	WRITE3	6
#define	QUIT3	7
#define	B3MINIT() B3mp = menu_create( \
MENU_INITIAL_SELECTION, MENU_SELECTED, \
MENU_INITIAL_SELECTION_SELECTED, TRUE, \
MENU_STRINGS, \
	"PLAY", \
	"CONTROLS", \
	"FILTER", \
	"IPROC", \
	"READ", \
	"WRITE", \
	"QUIT", \
0, 0)

/* Button 3 menu for scroll areas */
Menu	B3emp;
#define	B3EMINIT() B3emp = menu_create(MENU_STRINGS, \
	"Use button 1 to sweep a range.", \
	"Use button 2 to move the range.", \
0, 0)



static	short Chedicon_bits[] = {
/* Format_version=1, Width=64, Height=64, Depth=1, Valid_bits_per_item=16
 */
	0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x8000,0x0000,0x0000,0x0001,
	0x8000,0x0000,0x0000,0x0001,0x87FF,0xFFFF,0xFFFF,0xFFC1,
	0x8400,0x0000,0x0000,0x0041,0x84EE,0xEEEE,0xEEEE,0xEE41,
	0x84EE,0xEEEE,0xEEEE,0xEE41,0x84EE,0xEEEE,0xEEEE,0xEE41,
	0x8400,0x0000,0x0000,0x0041,0x87FF,0xFFFF,0xFFFF,0xFFC1,
	0x8000,0x0000,0x0000,0x0001,0x8000,0x0000,0x0000,0x0001,
	0x8000,0x0000,0x0000,0x0001,0x8000,0x0000,0x0000,0x0001,
	0x8000,0x0000,0x0040,0x0001,0x8000,0x0000,0x0040,0x0001,
	0x9FFF,0xFFFF,0xFFFF,0xFFF9,0x9401,0x0000,0x2040,0x0011,
	0x9401,0x0000,0x2040,0x0011,0x9401,0x0002,0x2040,0x0011,
	0x9401,0x0002,0x2040,0x0011,0x9401,0x0002,0x2040,0x0011,
	0x9FFF,0xFFFF,0xFFFF,0xFFF9,0x9401,0x0002,0x23C0,0x0011,
	0x9401,0x0002,0x26C0,0x0011,0x9401,0x0202,0x2440,0x0011,
	0x940F,0x0202,0x26C1,0xF811,0x941F,0x0202,0x23C1,0xF811,
	0x9FFF,0xFFFF,0xFFFF,0xFFF9,0x941F,0x021E,0x2040,0x0011,
	0x940E,0x023E,0x2040,0x0011,0x9400,0x023E,0x2040,0x0011,
	0x9400,0x023E,0x23C0,0x0011,0x9400,0x021C,0x26C0,0x0011,
	0x9FFF,0xFFFF,0xFFFF,0xFFF9,0x9400,0x1E00,0x26C0,0x0011,
	0x9400,0x3600,0x23C0,0x0011,0x9400,0x2200,0x2040,0x0011,
	0x9400,0x3600,0x23C0,0x0011,0x9400,0x1C00,0x26C0,0x0011,
	0x9FFF,0xFFFF,0xFFFF,0xFFF9,0x8000,0x0000,0x06C0,0x0001,
	0x8000,0x0000,0x0380,0x0001,0x8000,0x0000,0x0000,0x0001,
	0xAAAA,0xAAAA,0xAAAA,0xAAA9,0x9555,0x5555,0x5555,0x5555,
	0xAAAA,0xAAAA,0xAAAA,0xAAA9,0x9555,0x5555,0x5555,0x5555,
	0xAAAA,0x8288,0x8082,0xAAA9,0x9555,0x1111,0x1511,0x5555,
	0xAAAA,0x8888,0x8A88,0xAAA9,0x9555,0x1511,0x1511,0x5555,
	0xAAAA,0x8A80,0x8288,0xAAA9,0x9555,0x1511,0x1511,0x5555,
	0xAAAA,0x8888,0x8A88,0xAAA9,0x9555,0x1111,0x1511,0x5555,
	0xAAAA,0x8288,0x8082,0xAAA9,0x9555,0x5555,0x5555,0x5555,
	0xAAAA,0xAAAA,0xAAAA,0xAAA9,0x9555,0x5555,0x5555,0x5555,
	0xAAAA,0xAAAA,0xAAAA,0xAAA9,0x9555,0x5555,0x5555,0x5555,
	0x8000,0x0000,0x0000,0x0001,0xFFFF,0xFFFF,0xFFFF,0xFFFF
};
DEFINE_ICON_FROM_IMAGE(Chedicon, Chedicon_bits);

static	short Vuoff_bits[] = {
	0xffff, 0xffff, 0xd557, 0xebeb, 0xdd77, 0xfaab, 0xfd5f, 0xeeab, 
	0xd757, 0xebab, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 
};
static	short Vuon_bits[] = {
	0xffff, 0xffff, 0xc003, 0xc3c3, 0xcc33, 0xd00b, 0xe017, 0xc023, 
	0xc043, 0xc183, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 
};
static	short Vugoff_bits[] = {
	0xffff, 0xffff, 0xd557, 0xebeb, 0xdd77, 0xfaab, 0xfd5f, 0xeeab, 
	0xd757, 0xffff, 0xd113, 0xc447, 0xd113, 0xc447, 0xd113, 0xffff, 
};
static	short Vugon_bits[] = {
	0xffff, 0xffff, 0xc003, 0xc3c3, 0xcc33, 0xd00b, 0xe017, 0xc023, 
	0xc043, 0xffff, 0xd113, 0xc447, 0xd113, 0xc447, 0xd113, 0xffff, 
};

mpr_static(Vuoff_pr, 16, 16, 1, Vuoff_bits);
mpr_static(Vuon_pr, 16, 16, 1, Vuon_bits);
mpr_static(Vugoff_pr, 16, 16, 1, Vugoff_bits);
mpr_static(Vugon_pr, 16, 16, 1, Vugon_bits);
Pixrect	*Vupr[]	= { &Vuoff_pr, &Vuon_pr, &Vugoff_pr, &Vugon_pr, };

main(argc, argv)
char	*argv[];
{
	int barspec;
	extern FILE *sopen();

/****/setbuf(stderr, 0);
	barspec = 0;
	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'B':
		    Barlen = atoi(&argv[argc][2]);
		    break;
		case 'b':
		    barspec = atoi(&argv[argc][2]);
		    break;
		case 'd':
		    Debug++;
		    break;
		case 'm':
		    Margin = atoi(&argv[argc][2]);
		    break;
		case 't':
		    Tgrid = atoi(&argv[argc][2]);
		    break;
		default:
		    goto syntax;
		}
	    } else if (*Oldfile == 0)
		sprintf(Oldfile, "%s", argv[argc]);
	    else {
syntax:
		fprintf(stderr, "Usage: %s [options] [file]\n", argv[0]);
		fprintf(stderr, "-B120\tSet bar length to 120 MPU clocks\n");
		fprintf(stderr, "-b12\tShow the first 12 bars\n");
		fprintf(stderr, "-d\tTurn Debug on\n");
		fprintf(stderr, "-m20\tProvide a 20 pixel margin\n");
		fprintf(stderr, "-t4\tSet ticks/bar to 4\n");
		exit(2);
	    }
	}
	if (initfile(Oldfile) == 0) {	/* no file read */
	    Mint = 0;
	    if (Barlen)
		Maxt = 2 * Barlen;
	    else
		Maxt = 4 * MPU_CLOCK_PERIOD;
	    Mink = MAXCMINK;
	    Maxk = MINCMAXK;
	    Tfp = 0;
	}
	Cmink = min(Mink - 2, MAXCMINK);
	Cmaxk = max(Maxk + 9, MINCMAXK);
	Cmint = Mint;
	Cmaxt = Maxt;
	if (barspec > 0) {
	    if (Barlen)
		Cmaxt = Cmint + barspec * Barlen + 1;
	    else if (Nbl >= barspec)
		Cmaxt = Barline[barspec] + 1;
	}
	if (!(Fontp = pf_open(FONTPATH)))
	    Fontp = pf_default();
	Fontwidth = Fontp->pf_defaultsize.x;
	miscinits();
	window_main_loop(Dframe);
	unlink(Workfile);
	exit(0);
}

miscinits()	/* one-time SunView inits */
{
	Rect tmp;
	void resized(), input();

	B2MINIT();
	B2EMINIT();
	B3MINIT();
	B3EMINIT();
	tmp.r_left = 8;
	tmp.r_top = 64;
	tmp.r_width = 1136;	/* max width - 16 */
	tmp.r_height = 660;	/* max height - 240 */
	sprintf(Flbuf, "-=[ CHED ]=-   src:%s   dst:%s", Oldfile, Newfile);
	Dframe = window_create(NULL, FRAME,
	    FRAME_LABEL,	Flbuf,
	    FRAME_ICON,		&Chedicon,
	    WIN_FONT,		Fontp,
	    FRAME_OPEN_RECT,	&tmp,
	    0);
	Dcanvas = window_create(Dframe, CANVAS,
	    CANVAS_FIXED_IMAGE,	FALSE,
	    CANVAS_RESIZE_PROC,	resized,
	    0);
	window_set(Dcanvas, WIN_CONSUME_PICK_EVENTS,
	    WIN_NO_EVENTS,
	    LOC_WINENTER, LOC_WINEXIT, LOC_MOVE, WIN_MOUSE_BUTTONS, 0,
	    0);			/* set the canvas defaults */
	/* set non-defaults that we need */
	window_set(Dcanvas, WIN_CONSUME_KBD_EVENTS, WIN_ASCII_EVENTS, 0, 0);
	window_set(Dcanvas, WIN_CONSUME_PICK_EVENTS, LOC_DRAG, 0, 0);
	window_set(Dcanvas, WIN_EVENT_PROC, input, 0);
	Dpw = canvas_pixwin(Dcanvas);
	Ltgrey = mem_point(16, 16, 1, Ltgrey_bits);
	Grey = mem_point(16, 16, 1, Grey_bits);
	Dkgrey = mem_point(16, 16, 1, Dkgrey_bits);
	gencntl();
}

gencntl()	/* generate control panel; called also to re-generate */
{
	int swidth, i;
	void cntlchng();

	if (Controls) {
	    window_destroy(Controls);
	    Controls = 0;
	}
	i = (int) window_get(Dframe, WIN_WIDTH);
	if (i > 400)
	    swidth = (i - 375) | 1;
	else
	    swidth = i / 10;
	Controls = window_create(Dframe, PANEL,
	    WIN_X,		0,
	    WIN_Y,		0,
	    WIN_WIDTH,		WIN_EXTEND_TO_EDGE,
	    WIN_SHOW,		Cntlshow,
	    WIN_FONT,		Fontp,
	    PANEL_SHOW_MENU,	FALSE,
	    PANEL_LABEL_BOLD,	TRUE,
	    PANEL_ITEM_X_GAP,	25,
	    0);
	i = max(0, Barlen - swidth / 2);
	Bslide = panel_create_item(Controls, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"CLOX/BAR",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Barlen,
	    PANEL_MIN_VALUE,	i,
	    PANEL_MAX_VALUE,	i + swidth - 1,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	if (Barlen > 0) {
	    Oslide = panel_create_item(Controls, PANEL_SLIDER,
		PANEL_LABEL_STRING,	"  OFFSET",
		PANEL_SLIDER_WIDTH,	swidth,
		PANEL_VALUE,		Baroff,
		PANEL_MIN_VALUE,	-1,
		PANEL_MAX_VALUE,	Barlen - 1,
		PANEL_NOTIFY_PROC,	cntlchng,
		0);
	    Gslide = panel_create_item(Controls, PANEL_SLIDER,
		PANEL_LABEL_STRING,	"TICKS/BAR ",
		PANEL_SLIDER_WIDTH,	swidth,
		PANEL_VALUE,		Tgrid,
		PANEL_MIN_VALUE,	0,
		PANEL_MAX_VALUE,	32,
		PANEL_NOTIFY_PROC,	cntlchng,
		0);
	}
	Tslide = panel_create_item(Controls, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"   TEMPO",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Tempo,
	    PANEL_MIN_VALUE,	10,
	    PANEL_MAX_VALUE,	250,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	Gchoic = panel_create_item(Controls, PANEL_CHOICE,
	    PANEL_LABEL_STRING,	"GMODE",
	    PANEL_LAYOUT,	PANEL_VERTICAL,
	    PANEL_ITEM_Y,	4,
	    PANEL_CHOICE_YS,	24, 40, 56, 72, 0,
	    PANEL_MARK_YS,	20, 36, 52, 68, 0,
	    PANEL_CHOICE_STRINGS,	"NONE", "SEL", "UNSEL", "BOTH", 0,
	    PANEL_VALUE,	Garbsel,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	Mchoic = panel_create_item(Controls, PANEL_CHOICE,
	    PANEL_LABEL_STRING,	"VMODE",
	    PANEL_LAYOUT,	PANEL_VERTICAL,
	    PANEL_ITEM_Y,	4,
	    PANEL_CHOICE_YS,	24, 44, 0,
	    PANEL_MARK_YS,	20, 40, 0,
	    PANEL_CHOICE_STRINGS,	"OFF", "ON", 0,
	    PANEL_VALUE,	Veldisp,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	window_fit_height(Controls);
	if (Cntlshow) {		/* I don't know why this should be necessary */
	    window_set(Controls, WIN_SHOW, 0, 0);
	    window_set(Controls, WIN_SHOW, Cntlshow, 0);
	}
}

void
cntlchng(item, value, event)
Panel_item item;
Event	*event;
{
	register int change;

	change = 0;
	if (item == Bslide) {
	    if (Barlen != value) {
		Barlen = value;
		gencntl();
		change++;
	    }
	} else if (item == Oslide) {
	    if (Baroff != value) {
		Baroff = value;
		change++;
	    }
	} else if (item == Gslide) {
	    if (Tgrid != value) {
		Tgrid = value;
		change++;
	    }
	} else if (item == Tslide) {
	    Tempo = value;
	} else if (item == Mchoic) {
	    if (Veldisp != value) {
		Veldisp = value;
		change++;
	    }
	} else if (item == Gchoic)
	    Garbsel = value;
	if (change)
	    redraw();
}

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

	i = event_id(event);
	if (i == MS_LEFT || i == MS_MIDDLE || i == MS_RIGHT
	 || i == LOC_DRAG)
	    mousing(window, event);
}

initfile(oldfile)
char	oldfile[];
{
	int i;
	FILE *ifp;

	if (*oldfile) {
	    if ((ifp = sopen(oldfile, "r")) == NULL) {
		perror(oldfile);
		return(0);
	    }
	    if (*Newfile == '\0')
		sprintf(Newfile, "%s.new", oldfile);
	} else {
	    ifp = stdin;
	    sprintf(Oldfile, "stdin");
	    if (*Newfile == '\0')
		sprintf(Newfile, "ched.new");
	}
	sprintf(Flbuf, "-=[ CHED ]=-   src:%s   dst:%s", Oldfile, Newfile);
	window_set(Dframe, FRAME_LABEL, Flbuf, 0);
	sprintf(Workfile, "/tmp/ched%d", getpid());
	if ((Tfp = fopen(Workfile, "w")) == (FILE *) NULL) {
	    perror(Workfile);
	    exit(1);
	}
	for (i = MAXCHAN; --i >= 0; Chans[i] = C_OFF);
	copyfile(ifp, Tfp);
	if (ifp != stdin)
	    fclose(ifp);
	fclose(Tfp);
	if ((Tfp = fopen(Workfile, "r")) == (FILE *) NULL) {
	    perror(Workfile);
	    exit(1);
	}
	Sel.o.x = Sel.o.y = Sel.c.x = Sel.c.y = 0;
	Stmin = Stmax = 0;
	Skmin = Skmax = 0;
	Baroff = -2;
	return(1);
}

copyfile(ifp, ofp)
FILE	*ifp, *ofp;
{
	register int status, mode, chan;
	long now;
	MCMD *mp;

	Maxk = 0;
	Mink = 99999;
	Mint = 0;
	putmcmd((FILE *) 0, (MCMD *) 0);
	for (now = 0L; mp = getmcmd(ifp, now); now = putmcmd(ofp, mp)) {
	    status = *mp->cmd;
	    mode = (status & M_CMD_MASK);
	    chan = (status & M_CHAN_MASK);
	    if (mode == CH_KEY_ON || mode == CH_KEY_OFF) {
		if (mode == CH_KEY_OFF) {	/* turn key_off into key_on */
		    mp->cmd[0] = CH_KEY_ON | chan;
		    mp->cmd[2] = 0;
		} else if (mp->cmd[2] != 0) {
		    Chans[chan] = C_ON;
		    if (mp->cmd[1] < Mink)
			Mink = mp->cmd[1];
		    if (mp->cmd[1] >= Maxk)
			Maxk = mp->cmd[1] + 1;
		}
	    }
	}
	Maxt = now + 1;
	Ranget = Maxt - Mint;
	Rangek = Maxk - Mink;
}

process(ifp)	/* set Baroff to -2 to look for first TCWME */
FILE	*ifp;
{
	register int k, v, status, mode;
	int i, chan, chanmode;
	long now, start[MAXCHAN][MAXKEY];
	MCMD *mp;

	if (Dpw)
	    pw_batch_on(Dpw);
	for (i = sizeof Vol / sizeof Vol[0]; --i >= 0; Vol[i] = 0);
	for (i = sizeof Kiu / sizeof Kiu[0]; --i >= 0; Kiu[i] = 0);
	for (chan = 0; chan < MAXCHAN; chan++)
	    if (Chans[chan] & C_ON)
		for (k = Mink; k < Maxk; k++)
		    Key[chan][k] = 0;
	ROP(Hsa.o.x-1, Hsa.o.y-1, Hsa.c.x+1, Hsa.c.y+1, PIX_SET);
	ROP(Hsa.o.x+1, Hsa.o.y+1, Hsa.c.x-1, Hsa.c.y-1, PIX_CLR);
	Nbl = 0;
	for (now = 0L; mp = getmcmd(ifp, now); ) {
	    now = mp->when;
	    status = mp->cmd[0];
	    if (status == RT_TCWME) {
		Barline[Nbl++] = now;
		if (Baroff == -2)
		    Baroff = now;
	    }
	    mode = (status & M_CMD_MASK);
	    if (mode != CH_KEY_ON)
		continue;
	    chan = status & M_CHAN_MASK;
	    if (((chanmode = Chans[chan]) & C_ON) == 0)
		continue;
	    k = mp->cmd[1];
	    v = mp->cmd[2];
	    if (v == 0) {			/* key-off */
		--Key[chan][k];
		if (Key[chan][k] < 0) {		/* extra note-off */
		    Key[chan][k] = 0;
		    continue;
		}
		if (Key[chan][k] == 0) {	/* end of note */
		    plotnote(start[chan][k], k, now, Vel[chan][k], chanmode);
		    addvol(start[chan][k], now, Vel[chan][k]);
		}
	    } else {				/* key-on */
		if (Key[chan][k] != 0) {
		    plotnote(start[chan][k], k, now, Vel[chan][k], chanmode);
		    addvol(start[chan][k], now, Vel[chan][k]);
		}
		start[chan][k] = now;
		Vel[chan][k] = v;
		Key[chan][k]++;
	    }
	}
	for (chan = 0; chan < MAXCHAN; chan++) {   /* check notes left on */
	    if (Chans[chan] & C_ON) {
		for (k = Cmink; k < Cmaxk; k++) {
		    if (Key[chan][k]) {
			plotnote(start[chan][k], k, now, Vel[chan][k], chanmode);
			addvol(start[chan][k], now, Vel[chan][k]);
		    }
		}
	    }
	}
	if (Dpw)
	    pw_batch_off(Dpw);			/* for deferred update */
	if (Baroff == -2)			/* no TCWME found */
	    Baroff = 0;
	else if (Barlen > 0)
	    Baroff %= Barlen;
}

plotnote(bt, k, et, v, chanmode)
long	bt, et;
{
	int bx, ex, y, dy;

	if (!Veldisp && (chanmode & C_ON)) {	/* draw Hor scrollbar data */
	    bx = HSAT_X(bt);
	    ex = HSAT_X(et);
	    if (ex == bx)
		ex++;
	    y = HSAK_Y(k);
	    pw_vector(Dpw, bx, y, ex, y, PIX_SET, 1);
	}
	Kiu[k]++;
	if (et < Mint || Maxt <= bt || k < Mink || Maxk <= k)
	    return;
	bx = CAT_X(bt);
	if (bx < Ca.c.x) {
	    bx = max(bx, Ca.o.x);
	    ex = CAT_X(et);
	    if (ex >= Ca.o.x) {
		if (ex == bx)
		    ex++;
		y = CAK_Y(k);
		dy = Veldisp? (v * NOTEHH) / MAXVEL : NOTEHH;
		if (dy <= 0)
		    dy = 1;
		if (Ca.o.y < (y + dy) && (y + dy) < Ca.c.y) {
		    y -= dy;
		    if (chanmode & C_GREY)
			TREPL(bx, y, ex - bx, 2 * dy, PIX_SRC, Grey);
		    else
			ROP(bx, y, ex, y + 2 * dy, PIX_SET);
		}
	    }
	}
}

addvol(bt, et, v)
long bt, et;
register int v;
{
	register int i, ei;

	i = VOLT_I(bt);
	ei = VOLT_I(et);
	if (ei == i)
	    ei++;
	for (; i < ei; i++) {
	    Vol[i] += v;
	    v = (36 * v + 18) / 37;
	}
}

drawhs()	/* display horiz. scroll area */
{
	register int i, x, y, dy, maxvol;
	long t;

	if (Veldisp) {		/* if !Veldisp, data drawn in plotnote() */
	    maxvol = 0;
	    for (i = 0; i < Hspix; i++)
		if (Vol[i] > maxvol)
		    maxvol = Vol[i];
	    maxvol++;
	    y = (Hsa.o.y + Hsa.c.y) >> 1;
	    for (i = 0; i < Hspix; i++) {
		x = Hsa.o.x + i;
		dy = (Vol[i] * SBHH) / maxvol;
		pw_vector(Dpw, x, y + dy, x, y - dy, PIX_INV, 1);
	    }
	}
	dy = SBHH / 3;
	if (Barlen) {			/* bar line ticks */
	    for (t = Baroff + Barlen; t < Maxt; t += Barlen) {
		x = HSAT_X(t);
		pw_vector(Dpw, x, Hsa.o.y, x, Hsa.o.y + dy, PIX_INV, 1);
		pw_vector(Dpw, x, Hsa.c.y, x, Hsa.c.y - dy, PIX_INV, 1);
	    }
	} else {
	    for (i = 0; i < Nbl; i++) {
		x = HSAT_X(Barline[i]);
		pw_vector(Dpw, x, Hsa.o.y, x, Hsa.o.y + dy, PIX_INV, 1);
		pw_vector(Dpw, x, Hsa.c.y, x, Hsa.c.y - dy, PIX_INV, 1);
	    }
	}
	fliphs();
}

Recta
hsrect()
{
	Recta q;

	q.o.x = HSAT_X(Cmint);
	q.o.y = Hsa.o.y;
	q.c.x = HSAT_X(Cmaxt);
	q.c.y = Hsa.c.y;
	return(q);
}

fliphs()
{
	Recta q;

	q = hsrect();
	RECTINV(q);
}

drawvs()	/* display vert. scroll area */
{
	register int i, x, y, dx, dy;

	ROP(Vsa.o.x-1, Vsa.o.y-1, Vsa.c.x+1, Vsa.c.y+1, PIX_SET);
	ROP(Vsa.o.x+1, Vsa.o.y+1, Vsa.c.x-1, Vsa.c.y-1, PIX_CLR);
	for (i = 5; --i >= 0; ) {	/* horizontal staff lines */
	    y = VSAK_Y(Tclefk[i]);
	    pw_vector(Dpw, Vsa.o.x, y, Vsa.c.x, y, PIX_INV, 1);
	    y = VSAK_Y(Bclefk[i]);
	    pw_vector(Dpw, Vsa.o.x, y, Vsa.c.x, y, PIX_INV, 1);
	}
	x = (Vsa.o.x + Vsa.c.x) / 2;
	dx = (Vsa.c.x - Vsa.o.x) / 6;
	dy = (VSAK_Y(0) - VSAK_Y(1)) / 2;
	for (i = sizeof Kiu / sizeof Kiu[0]; --i >= 0; ) {
	    if (Kiu[i]) {
		y = VSAK_Y(i);
		ROP(x - dx, y - dy, x + dx, y + dy, PIX_SET);
	    }
	}
	flipvs();
}

Recta
vsrect()	/* generate vertical scroll rectangle */
{
	Recta q;

	q.o.x = Vsa.o.x;
	q.o.y = VSAK_Y(Cmaxk-1)+1;
	q.c.x = Vsa.c.x;
	q.c.y = VSAK_Y(Cmink);
	return(q);
}

flipvs()
{
	Recta q;

	q = vsrect();
	RECTINV(q);
}

mousing(window, event)
Window	window;
Event	*event;
{
	register int change;

	if (Mymouse == VUMOUSE		/* cursor in VU meter area */
	 || (Mymouse == 0 && einr(event, Va))) {
	    change = vamouse(window, event);
	} else if (Mymouse == HSMOUSE	/* cursor in horizontal scroll area */
	 || (Mymouse == 0 && einr(event, Hsa))) {
	    Mymouse = HSMOUSE;
	    change = hsamouse(window, event);
	} else if (Mymouse == VSMOUSE	/* cursor in vertical scroll area */
	 || (Mymouse == 0 && einr(event, Vsa))) {
	    Mymouse = VSMOUSE;
	    change = vsamouse(window, event);
	} else if (Mymouse == CAMOUSE	/* cursor in chart area */
	 || Mymouse == 0) {
	    Mymouse = CAMOUSE;
	    change = camouse(window, event);
	}
	if (change)
	    redraw();
}

einr(e, r)	/* true if event e is in rectangle r */
Event	*e;
Recta	r;
{
	register int x, y;

	x = e->ie_locx;
	y = e->ie_locy;
	return (r.o.x <= x && x <= r.c.x && r.o.y <= y && y <= r.c.y);
}

vamouse(window, event)
Window	window;
Event	*event;
{
	register int i, code;

	if (Mymouse) {
	    if (event_is_up(event))		/* we've already done it */
		Mymouse = 0;			/* give up the mouse */
	} else {	/* state changes when button is first pushed */
	    Mymouse = VUMOUSE;
	    code = event_id(event);
	    if (code != MS_LEFT && code != MS_MIDDLE)
		return(0);
	    i = (event->ie_locx - Va.o.x) / Vuwidth;
	    if (0 <= i && i < MAXCHAN) {
		Chans[i] ^= (code == MS_LEFT? C_ON : C_GREY);
		drawvu();
		return(1);
	    }
	}
	return(0);
}

hsamouse(window, event)		/* mouse event in horizontal scroll area */
Window	window;
Event	*event;
{
	int id, x, y;
	long t, q;
	Recta hsrect(), oldR;
	static int state, onend, dt;
	static long lastt, ocmaxt, ocmint;

	id = event_id(event);
	oldR = hsrect();
	if (state == 0) {		/* waiting for button down */
	    ocmaxt = Cmaxt;
	    ocmint = Cmint;
	    lastt = -1;
	    t = HSAX_T(event_x(event));
	    if (id == MS_LEFT) {
		onend = HSAX_T(event_x(event));
		Cmaxt = Maxt;
		Cmint = Mint;
		uprect(oldR, hsrect());
		state = id;
	    } else if (id == MS_MIDDLE) {
		dt = Cmaxt - Cmint;
		if (Cmint <= t && t <= Cmaxt) {
		    x = (oldR.o.x + oldR.c.x) >> 1;
		    y = (oldR.o.y + oldR.c.y) >> 1;
		    window_set(window, WIN_MOUSE_XY, x, y, 0);
		}
		state = id;
	    } else if (id == MS_RIGHT)
		menu_show(B3emp, window, event, 0);
	} else if (id == LOC_DRAG) {
	    if (state == MS_LEFT) {
		t = HSAX_T(event_x(event));
		t = Maxt < t? Maxt : t;
		t = Mint > t? Mint : t;
		if (t != lastt) {
		    Cmaxt = onend > t? onend : t;
		    Cmint = onend < t? onend : t;
		    if (Cmaxt == Cmint) {
			Cmaxt = Maxt;
			Cmint = Mint;
		    }
		    uprect(oldR, hsrect());
		    lastt = t;
		}
	    } else if (state == MS_MIDDLE) {
		t = HSAX_T(event_x(event));
		t = (q = Maxt - dt / 2) < t? q : t;
		t = (q = Mint + dt / 2) > t? q : t;
		if (t != lastt) {
		    Cmint = t - dt / 2;
		    Cmaxt = Cmint + dt;
		    uprect(oldR, hsrect());
		    lastt = t;
		}
	    }
	} else if (event_is_up(event)) {			/* button up */
	    state = 0;
	    Mymouse = 0;			/* give up the mouse */
	    return(Cmaxt != ocmaxt || Cmint != ocmint);
	}
	return(0);
}

vsamouse(window, event)		/* mouse event in vertical scroll area */
Window	window;
Event	*event;
{
	register int k, id, x, y;
	Recta vsrect(), oldR;
	static int state, onend, lastk, dk, ocmaxk, ocmink;

	id = event_id(event);
	oldR = vsrect();
	if (state == 0) {		/* waiting for button down */
	    ocmaxk = Cmaxk;
	    ocmink = Cmink;
	    lastk = -1;
	    k = VSAY_K(event_y(event));
	    if (id == MS_LEFT) {
		onend = k + 1;
		Cmink = MINKEY;
		Cmaxk = MAXKEY;
		uprect(oldR, vsrect());
		state = id;
	    } else if (id == MS_MIDDLE) {
		dk = Crangek;
		if (Cmink <= k && k <= Cmaxk) {
		    x = (oldR.o.x + oldR.c.x) >> 1;
		    y = (oldR.o.y + oldR.c.y) >> 1;
		    window_set(window, WIN_MOUSE_XY, x, y, 0);
		}
		state = id;
	    } else if (id == MS_RIGHT)
		menu_show(B3emp, window, event, 0);
	} else if (id == LOC_DRAG) {
	    if (state == MS_LEFT) {
		k = VSAY_K(event_y(event));
		k = max(MINKEY, min(MAXKEY, k));
		if (k != lastk) {
		    Cmink = min(onend, k);
		    Cmaxk = max(onend, k);
		    if (Cmaxk == Cmink) {
			Cmink = MINKEY;
			Cmaxk = MAXKEY;
		    }
		    uprect(oldR, vsrect());
		    lastk = k;
		}
	    } else if (state == MS_MIDDLE) {
		k = VSAY_K(event_y(event));
		k = max(MINKEY + dk / 2, min(MAXKEY - dk / 2, k));
		if (k != lastk) {
		    Cmink = k - dk / 2;
		    Cmaxk = Cmink + dk;
		    uprect(oldR, vsrect());
		    lastk = k;
		}
	    }
	} else if (event_is_up(event)) {		/* button up */
	    state = 0;
	    Mymouse = 0;			/* give up the mouse */
	    return(Cmink != ocmink || Cmaxk != ocmaxk);
	}
	return(0);
}

camouse(window, event)		/* mouse event in chart area */
Window	window;
Event	*event;
{
	register int i, change;

	change = 0;
	i = event_id(event);
	if ((i == LOC_DRAG && window_get(Dcanvas, WIN_EVENT_STATE, MS_LEFT))
	 || i == MS_LEFT)
	    selectca(window, event);
	else if (i == MS_MIDDLE)
	    change = menu2ca(window, event);
	else if (i == MS_RIGHT)
	    change = menu3ca(window, event);
	return(change);
}

selectca(window, event)		/* define selected area in chart area */
Window	window;
Event	*event;
{
	register int x, y;
	static int firstx, firsty, lastx, lasty;
	Recta old;

	x = event_x(event);
	y = event_y(event);
	if (event_id(event) == MS_LEFT) {
	    if (event_is_down(event)) {			/* button down */
		RECTINV(Sel);
		Sel.o.x = Sel.c.x = firstx = lastx = x;
		Sel.o.y = Sel.c.y = firsty = lasty = y;
	    } else {					/* button up */
		Stmin = CAX_T(Sel.o.x);
		Stmax = CAX_T(Sel.c.x) + 1;
		Skmin = CAY_KA(Sel.c.y);
		Skmax = CAY_KB(Sel.o.y) + 1;
		firstx = firsty = lastx = lasty = 0;
		Mymouse = 0;			/* give up the mouse */
	    }
	    return;
	} else if (event_id(event) == LOC_DRAG && (x != lastx || y != lasty)) {
	    old = Sel;
	    Sel.o.x = min(firstx, lastx = x);
	    Sel.o.y = min(firsty, lasty = y);
	    Sel.c.x = max(firstx, x);
	    Sel.c.y = max(firsty, y);
	    uprect(old, Sel);
	}
}

menu2ca(window, event)
Window	window;
Event	*event;
{
	register int i, change;
	char *file;

	if (Skmin == Skmax || Stmin == Stmax) {
	    menu_show(B2emp, window, event, 0);
	    return(0);
	}
	change = 0;
	i = (int) menu_show(B2mp, window, event, 0);
	Mymouse = 0;			/* give up the mouse */
	RECTINV(Ca);
	switch(i) {
	case BBOX2:
	    selpart(NO_FILE, NO_FILE, SEL);	/* set Smink, Smint, etc. */
	    RECTINV(Sel);
	    if (Smint < Smaxt && Smink < Smaxk) {
		Sel.o.x = CAT_X(Stmin = Smint) - 1;
		Sel.c.x = CAT_X((Stmax = Smaxt) - 1) + 1;
		Sel.c.y = CAK_YB(Skmin = Smink);
		Sel.o.y = CAK_YA((Skmax = Smaxk) - 1);
		RECTINV(Sel);
	    } else {
		Sel.o.x = Sel.c.x = Sel.o.y = Sel.c.y = 0;
		Stmin = Stmax = 0;
		Skmax = Skmin = 0;
	    }
	    break;
	case CLOSE2:
	    if (selpart(Selfile, file = "/tmp/chedtmp", CLOSE)) {
		unlink(Workfile);
		link(file, Workfile);
		unlink(file);
		Dirty++;
		change = 1;
	    }
	    break;
	case CUT2:
	    if (selpart(Selfile, file = "/tmp/chedtmp", SEL)) {
		unlink(Workfile);
		link(file, Workfile);
		unlink(file);
		change = 1;
		Dirty++;
	    }
	    break;
	case IPROC2:
	    change = iproc(0);
	    break;
	case FILTER2:
	    if (change = filter(0))
		Dirty++;
	    break;
	case GLOM2:
	    selpart(Selfile, NO_FILE, SEL);
	    break;
	case INFO2:
	    RECTINV(Ca);
	    infomenu(window, event);
	    RECTINV(Ca);
	    break;
	case OPEN2:
	    if (selpart(NO_FILE, file = "/tmp/chedtmp", OPEN)) {
		unlink(Workfile);
		link(file, Workfile);
		unlink(file);
		change = 1;
		Dirty++;
	    }
	    break;
	case PASTE2:
	    if (change = paste(Selfile, Workfile))
		Dirty++;
	    break;
	case PLAY2:
	    change = play(0);
	    break;
	case WRITE2:
	    change = writefile(0);
	    break;
	case ZOOMI2:
	    change = zoomin();
	    break;
	case ZOOMO2:
	    change = zoomout();
	    break;
	}
	if (change == 0)
	    RECTINV(Ca);
	return(change);
}

infomenu(window, event)
Window	window;
Event	*event;
{
	char bgc[64], bgq[64], bgb[64];
	char edc[64], edq[64], edb[64];
	char top[64], bot[64];
	int i;
	double q;
	Menu m;
	char *key2name();

	m = menu_create(MENU_STRINGS, "SELECTED AREA", 0, 0);
	sprintf(bgc, "Beg clk: %d", Stmin);
	menu_set(m, MENU_STRING_ITEM, bgc, 0, 0);
	sprintf(edc, "End clk: %d", Stmax - 1);
	menu_set(m, MENU_STRING_ITEM, edc, 0, 0);
	sprintf(bgq, "Beg beat: %g", Stmin / 120.);
	menu_set(m, MENU_STRING_ITEM, bgq, 0, 0);
	sprintf(edq, "End beat: %g", (Stmax - 1) / 120.);
	menu_set(m, MENU_STRING_ITEM, edq, 0, 0);
	if (Barlen) {
	    sprintf(bgb, "Beg bar: %g", Stmin / (float) Barlen);
	    menu_set(m, MENU_STRING_ITEM, bgb, 0, 0);
	    sprintf(edb, "End bar: %g", (Stmax - 1) / (float) Barlen);
	    menu_set(m, MENU_STRING_ITEM, edb, 0, 0);
	} else {		/* using TCWME */
	    for (i = Nbl; --i >= 0 && Barline[i] > Stmin; );
	    if (i < 0)
		sprintf(bgb, "Beg bar: <0");
	    else {
		q = Stmin - Barline[i];			/* residue */
		if (i == Nbl - 1)
		    sprintf(bgb, "Beg bar: %d + %g clocks", i, q);
		else
		    sprintf(bgb, "Beg bar: %g",
		     i + q / (Barline[i + 1] - Barline[i]));
	    }
	    menu_set(m, MENU_STRING_ITEM, bgb, 0, 0);
	    for (i = Nbl; --i >= 0 && Barline[i] > Stmax - 1; );
	    if (i < 0)
		sprintf(edb, "End bar: <0");
	    else {
		q = Stmax - 1 - Barline[i];		/* residue */
		if (i == Nbl - 1)
		    sprintf(edb, "End bar: %d + %g clocks", i, q);
		else
		    sprintf(edb, "End bar: %g",
		     i + q / (Barline[i + 1] - Barline[i]));
	    }
	    menu_set(m, MENU_STRING_ITEM, edb, 0, 0);
	}
	i = Skmax - 1;
	sprintf(top, "Top key: %d (X%x) %s", i, i, key2name(i));
	menu_set(m, MENU_STRING_ITEM, top, 0, 0);
	sprintf(bot, "Bot key: %d (X%x) %s", Skmin, Skmin, key2name(Skmin));
	menu_set(m, MENU_STRING_ITEM, bot, 0, 0);
	menu_show(m, window, event, 0);
	menu_destroy(m);
}

menu3ca(window, event)
Window	window;
Event	*event;
{
	register int i, change;

	change = 0;
	i = (int) menu_show(B3mp, window, event, 0);
	Mymouse = 0;			/* give up the mouse */
	RECTINV(Ca);
	switch(i) {
	case CNTRLS3:
	    RECTINV(Ca);
	    Cntlshow = (TRUE + FALSE) - Cntlshow;
	    window_set(Controls, WIN_SHOW, Cntlshow, 0);
	    RECTINV(Ca);
	    break;
	case FILTER3:
	    if (change = filter(1))
		Dirty++;
	    break;
	case IPROC3:
	    change = iproc(1);
	    break;
	case PLAY3:
	    change = play(1);
	    break;
	case QUIT3:
	    change = quit();
	    break;
	case READ3:
	    change = readfile();
	    break;
	case WRITE3:
	    change = writefile(1);
	    break;
	}
	if (change == 0)
	    RECTINV(Ca);
	return(change);
}

play(entire)
{
	register int i;
	char *file, buf[16];

	while (Playpid > 0)
	    if ((i = wait(0)) == -1 || i == Playpid)
		Playpid = 0;
	if (entire)
	    file = Workfile;
	else
	    selpart(file = Playfile, NO_FILE, SEL);
	sprintf(buf, "-t%d", Tempo);
	Playpid = forkexec(-1, -1, Playcmd, buf, file, 0, 0, 0);
	return(0);
}

quit()
{
	register int i;
	char buf[32];

	if (Dirty) {
	    sprintf(buf, "y");
	    i = gstring("Unwritten changes!  Okay? ",
	     buf, sizeof buf, 10, 10, 300, 25);
	    if (i == 0 || *buf != 'y')
		return(0);
	}
	while (Playpid > 0)
	    if ((i = wait(0)) == -1 || i == Playpid)
		Playpid = 0;
	if (Workfile && *Workfile)
	    unlink(Workfile);
	if (Playfile && *Playfile)
	    unlink(Playfile);
	if (Selfile && *Selfile)
	    unlink(Selfile);
	exit(0);
	/*NOTREACHED*/
}

gstring(msg, retbuf, len, x, y, w, h)
char	*msg, *retbuf;
{
	register char *cp;
	char buf[512];

	printf("%s <%s> ", msg, retbuf);
	if (fgets(buf, len, stdin)) {
	    for (cp = buf; *cp && *cp != '\n' && *cp != '\r'; cp++);
	    if (cp != buf) {
		*cp = '\0';
		strcpy(retbuf, buf);
	    }
	    return(1);
	}
	return(0);
}

readfile()
{
	char buf[32];
	int i, oldbo;

	if (Dirty) {
	    sprintf(buf, "y");
	    i = gstring("UNWRITTEN CHANGES!  Okay? ",
	     buf, sizeof buf, 10, 10, 300, 25);
	    if (i == 0 || *buf != 'y')
		return(0);
	}
	i = gstring("File: ", Oldfile, sizeof Oldfile, 10, 10, 400, 25);
	if (i == 0 || *Oldfile == '\0')
	    return(0);
	oldbo = Baroff;
	Baroff = -2;
	if (initfile(Oldfile) == 0)
	    return(0);
	if (Baroff != oldbo)
	    gencntl();
	if (Cmint >= Maxt)
	    Cmint = Mint;
	if (Maxt < Cmaxt)
	    Cmaxt = Maxt;
	return(1);
}

/* Merge "part" (offset by Stmin) and "whole" into "Workfile" */
/* return 1 for success, 0 for failure */
paste(part, whole)
char	*part, *whole;
{
	char buf[512], *tmp1, *tmp2;
	int change;
	FILE *ifp, *tfp;

	change = 1;		/* assume we'll change something */
	tmp1 = "/tmp/chedpaste1";
	tmp2 = "/tmp/chedpaste2";
	ifp = tfp = (FILE *) NULL;
	sprintf(buf, "%s -c%d <%s >%s; %s %s %s >%s",
	 Tshiftcmd, Stmin, part, tmp1,
	 Mergecmd, whole, tmp1, tmp2);
	if (system(buf)) {
	    perror(buf);
	    change = 0;
	}
	if (change && (ifp = fopen(tmp2, "r")) == (FILE *) NULL) {
	    perror(tmp2);
	    change = 0;
	}
	if (change && (tfp = fopen(Workfile, "w")) == (FILE *) NULL) {
	    perror(Workfile);
	    change = 0;
	}
/****/if (change) fprintf(stderr, "about to copy %s to %s\n", tmp2, Workfile);
	if (change)
	    copyfile(ifp, tfp);
	if (ifp)
	    fclose(ifp);
	if (tfp)
	    fclose(tfp);
	unlink(tmp1);
	unlink(tmp2);
	if (!change)
	    fprintf(stderr, "Aborting\n");
	return(change);
}

/* copy selected part of Workfile to selfile, the rest to unselfile */
/* if op is CLOSE, then close up selected time */
/* if op is OPEN, then open up selected time */
/* Return 1 for success, 0 for failure */
selpart(selfile, unselfile, op)
char	*selfile, *unselfile;
{
	register int k, v, status, mode;
	unsigned char mbuf[16];
	int chan, kmin, kmax, syet, sg, ug;
	long now, tmin, tmax;
	FILE *ifp, *sfp, *ufp;
	MCMD m, *mp;

	m.cmd = mbuf;
	if ((ifp = fopen(Workfile, "r")) == NULL) {
	    perror(Workfile);
	    return(0);
	}
	if (selfile == NO_FILE)
	    sfp = 0;
	else if ((sfp = fopen(selfile, "w")) == NULL) {
	    perror(selfile);
	    fclose(ifp);
	    return(0);
	}
	if (unselfile == NO_FILE)
	    ufp = 0;
	else if ((ufp = fopen(unselfile, "w")) == NULL) {
	    perror(unselfile);
	    fclose(ifp);
	    if (sfp)
		fclose(sfp);
	    return(0);
	}
	tmin = Stmin;
	tmax = Stmax;
	if (op == CLOSE || op == OPEN) {
	    kmin = MINKEY;
	    kmax = MAXKEY;
	} else {
	    kmin = Skmin;
	    kmax = Skmax;
	}
	Smink = 999;
	Smaxk = 0;
	Smint = tmax;
	Smaxt = tmin;
	for (chan = 0; chan < MAXCHAN; chan++)	/* only check selected notes */
	    if (Chans[chan] & C_ON)
		for (k = kmin; k < kmax; k++)
		    Key[chan][k] = 0;
	syet = 0;
	if (ufp)
	    iputmcmds(0, ufp, 0L);
	if (sfp)
	    iputmcmds(1, sfp, tmin);
	for (now = 0L; mp = getmcmd(ifp, now); ) {
	    now = mp->when;
	    if (now >= tmin && syet == 0) {	/* just entered selected time */
		if (op != OPEN || sfp) {	/* stop/start notes in play */
		    m.when = tmin;
		    m.len = 3;
		    for (chan = 0; chan < MAXCHAN; chan++) {
			if (Chans[chan] & C_ON) {
			    m.cmd[0] = CH_KEY_ON | chan;
			    for (k = kmin; k < kmax; k++) {
				if (Key[chan][k]) {	/* already playing */
				    m.cmd[1] = k;
				    if (ufp) {
					m.cmd[2] = 0;
					putmcmds(0, &m);
				    }
				    if (sfp) {
					m.cmd[2] = Vel[chan][k];
					putmcmds(1, &m);
				    }
				}
			    }
			}
		    }
		}
		if (op == OPEN)
		    now += tmax - tmin;	/* open requested space */
		if (op == CLOSE)
		    Sonow[0] += tmax - tmin;	/* close requested space */
		syet = 1;
	    }
	    if (now >= tmax && syet == 1) {	/* just left selected time */
		if (op != OPEN || sfp) {	/* stop/start notes in play */
		    m.when = tmax;
		    m.len = 3;
		    for (chan = 0; chan < MAXCHAN; chan++) {
			if (Chans[chan] & C_ON) {
			    m.cmd[0] = CH_KEY_ON | chan;
			    for (k = kmin; k < kmax; k++) {
				if (Key[chan][k]) {
				    m.cmd[1] = k;
				    if (sfp) {
					m.cmd[2] = 0;
					putmcmds(1, &m);
				    }
				    if (ufp) {
					m.cmd[2] = Vel[chan][k];
					putmcmds(0, &m);
				    }
				}
			    }
			}
		    }
		    if (sfp) {
			m.when = tmax;
			m.len = 1;
			m.cmd[0] = MPU_NO_OP;
			putmcmds(1, &m);
			fclose(sfp);
			sfp = 0;
		    }
		}
		if (!ufp)
		    break;
		syet = 2;
	    }
	    status = mp->cmd[0];
	    mode = status & M_CMD_MASK;
	    chan = status & M_CHAN_MASK;
	    /* decide what to do with non-note "garbage" */
	    sg = (Garbsel & 1) && sfp && (syet == 1);
	    ug = (Garbsel & 2) && ufp;
	    if (mode != CH_KEY_ON) {
		if (sg)
		    putmcmds(1, mp);
		if (ug)
		    putmcmds(0, mp);
		continue;
	    }
	    k = mp->cmd[1];
	    v = mp->cmd[2];
	    if (v == 0) {			/* key-off */
		--Key[chan][k];
		if (Key[chan][k] < 0)		/* extra note-off */
		    Key[chan][k] = 0;
	    } else {				/* key-on */
		Vel[chan][k] = v;
		Key[chan][k]++;
	    }
	    if (tmin <= now && now < tmax
	     && kmin <= k && k < kmax
	     && (Chans[chan] & C_ON)) {
		putmcmds(1, mp);
		if (v == 0) {
		    if (now >= Smaxt)
			Smaxt = now + 1;
		} else {
		    if (now < Smint)
			Smint = now;
		}
		if (k < Smink)
		    Smink = k;
		if (k >= Smaxk)
		    Smaxk = k + 1;
	    } else if (now >= Sonow[0])	/* avoid unplayed Chans during CLOSE */
		putmcmds(0, mp);
	}
	fclose(ifp);
	if (sfp) {		/* we're still in the selected area */
	    m.when = now;
	    for (chan = 0; chan < MAXCHAN; chan++) {
		if (Chans[chan] & C_ON) {
		    m.cmd[0] = CH_KEY_ON | chan;
		    for (k = kmin; k < kmax; k++) {
			if (Key[chan][k]) {
			    m.cmd[1] = k;
			    putmcmds(1, &m);
			}
		    }
		}
	    }
	    fclose(sfp);
	}
	if (ufp)
	    fclose(ufp);
	return(1);
}

/*VARARGS3*/
forkexec(in, out, p, a1, a2, a3, a4)
char	*p;
{
	register int pid;

	switch (pid = fork()) {
	case -1:
	    perror("fork()");
	    break;
	case 0:
	    close(0);
	    if (in >= 0)
		dup2(0, in);
	    close(1);
	    if (out >= 0)
		dup2(1, out);
	    execl(p, p, a1, a2, a3, a4);
	    perror(p);
	    exit(1);
	}
	return(pid);
}

writefile(entire)
{
	char *file, buf[512];
	int i, ifh, ofh;

	if (entire) {
	    i = gstring("Write entire file to: ",
	     Newfile, sizeof Newfile, 10, 10, 500, 25);
	    if (i == 0 || *Newfile == '\0')
		return(0);
	    file = Workfile;
	    Dirty = 0;
	} else {
	    i = gstring("Write selected area to: ",
	     Newfile, sizeof Newfile, 10, 10, 500, 25);
	    if (i == 0 || *Newfile == '\0')
		return(0);
	    selpart(file = "/tmp/chedtmp", NO_FILE, SEL);
	}
	sprintf(Flbuf, " CHED    src:%s   dst:%s", Oldfile, Newfile);
	window_set(Dframe, FRAME_LABEL, Flbuf, 0);
	if ((ifh = open(file, 0)) < 0)
	    perror(file);
	else if ((ofh = creat(Newfile, 0644)) < 0)
	    perror(Newfile);
	else {
	    while ((i = read(ifh, buf, sizeof buf)) > 0)
		write(ofh, buf, i);
	    close(ofh);
	}
	close(ifh);
	printf("%s written\n", Newfile);
	return(0);
}

zoomin()
{
	if (Stmin >= Stmax || Skmin >= Skmax)
	    return(0);
	Cmint = Stmin - 1;
	Cmaxt = Stmax + 1;
	Cmink = Skmin - 1;
	Cmaxk = Skmax + 1;
	return(1);
}

zoomout()
{
	int lx, hx, dx, ly, hy, dy, dy2, omink, omaxk, nmink, nmaxk;
	long omint, omaxt, nmint, nmaxt;

	omint = Cmint;
	omaxt = Cmaxt;
	omink = Cmink;
	omaxk = Cmaxk;
	dx = Sel.c.x - Sel.o.x;
	if (dx == 0) {
	    Cmint = Mint;
	    Cmaxt = Maxt;
	} else {
	    lx = Sel.o.x - Vsa.c.x;
	    hx = Sel.c.x - Vsa.c.x;
	    nmint = (omint * hx - omaxt * lx) / dx;
	    nmaxt = nmint + (Csize.x * (omaxt - omint)) / dx;
	    Cmint = nmint < Mint? Mint : nmint;
	    Cmaxt = nmaxt > Maxt? Maxt : nmaxt;
	}
	dy = Sel.c.y - Sel.o.y;
	if (dy == 0) {
	    Cmink = MINKEY;
	    Cmaxk = MAXKEY;
	} else {
	    ly = Hsa.o.y - Sel.o.y;
	    hy = Hsa.o.y - Sel.c.y;
	    dy2 = dy >> 1;
	    nmink = (omink * ly - omaxk * hy + dy2) / dy;
	    nmaxk = nmink + (Csize.y * (omaxk - omink) + dy2) / dy;
	    Cmink = nmink < MINKEY? MINKEY : nmink;
	    Cmaxk = nmaxk > MAXKEY? MAXKEY : nmaxk;
	}
	Stmin = omint;
	Stmax = omaxt;
	Skmin = omink;
	Skmax = omaxk;
	return(1);
}

iproc(entire)
{
	char buf[256];
	int i;
	static char cmd[128] = "da";

	i = gstring("Command: ", cmd, sizeof cmd, 10, 10, 500, 25);
	if (i == 0 || *cmd == '\0')
	    return(0);
	if (entire) {
	    sprintf(buf, "cat %s | %s", Workfile, cmd);
	    if (system(buf))
		perror(buf);
	} else {
	    selpart(Selfile, NO_FILE, SEL);
	    sprintf(buf, "cat %s | %s", Selfile, cmd);
	    if (system(buf))
		perror(buf);
	}
	return(0);
}

filter(entire)
{
	char *therest, buf[256];
	int i;
	static char cmd[128];

	i = gstring("Command: ", cmd, sizeof cmd, 10, 10, 500, 25);
	if (i == 0 || *cmd == '\0')
	    return(0);
	if (entire) {
	    unlink(Selfile);
	    link(Workfile, Selfile);
	    unlink(Workfile);
	    sprintf(buf, "%s <%s >%s", cmd, Selfile, Workfile);
	    if (system(buf)) {
		perror(buf);
		unlink(Workfile);
		link(Selfile, Workfile);
		unlink(Selfile);
	    }
	} else {
	    if (!selpart(Selfile, therest = "/tmp/chedtmp", SEL))
		return(0);
	    sprintf(buf, "%s <%s >%s", cmd, Selfile, "/tmp/ched2");
	    if (system(buf)) {
		perror(buf);
		return(0);
	    }
	    i = paste("/tmp/ched2", therest);
	    unlink(therest);
	    if (!i)
		return(0);
	}
	return(1);
}

redraw()	/* called whenever resized or cmd changes view */
{
	if (Tfp)
	    fclose(Tfp);
	if ((Tfp = fopen(Workfile, "r")) == (FILE *) NULL) {
	    perror(Workfile);
	    exit(1);
	}
	cascale();
	if (Skmax != Skmin) {
	    Sel.o.x = CAT_X(Stmin);
	    Sel.c.x = CAT_X(Stmax);
	    Sel.o.y = CAK_YA(Skmax);
	    Sel.c.y = CAK_YB(Skmin);
	}
	pw_writebackground(Dpw, 0, 0, Da.c.x+Margin, Da.c.y+Margin, PIX_CLR);
	process(Tfp);
	drawstaves();
	drawhs();
	drawvs();
	drawvu();
	RECTINV(Sel);
}

void
resized(canvas, width, height)
Canvas	canvas;
{
	if (canvas != Dcanvas)
	    return;		/* oops */
	Da.o.x = Da.o.y = Margin;
	Da.c.x = width - Margin;
	Da.c.y = height - Margin;	/* entire display area (-borders) */
	Vuwidth =  min((Da.c.x - Da.o.x) / MAXCHAN, 20);
	Va.o.x = (Da.o.x + Da.c.x) / 2 - 8 * Vuwidth;	/* VU area */
	Va.c.x = Va.o.x + MAXCHAN * Vuwidth;
	Va.o.y = Da.o.y;
	Va.c.y = Da.o.y + 20;
	Hsa = Da;				/* horizontal scroll area */
	Hsa.o.x += 16;
	Hsa.o.y = Hsa.c.y - 2 * SBHH - 4;
	Hspix = Hsa.c.x - Hsa.o.x;
	Vsa = Da;				/* vertical scroll area */
	Vsa.c.x = Vsa.o.x + 16;
	Vsa.c.y = Hsa.o.y;
	Vspix = Vsa.c.y - Vsa.o.y;
	gencntl();
	redraw();
}

cascale()	/* calc Csize, Ppm, caoff, etc. when scaling occurs */
{		/* after this, CAT_X(), CAY_K(), CAX_T(), etc. are usable */
	register int x, y;

	Ca.o.x = Vsa.c.x + 1;			/* maximum chart area rect */
	Ca.o.y = Va.c.y + 1;
	Ca.c.x = Hsa.c.x;
	Ca.c.y = Hsa.o.y - 1;
	Csize.x = Ca.c.x - Ca.o.x;		/* maximum chart size */
	Csize.y = Ca.c.y - Ca.o.y;
	Cranget = Cmaxt - Cmint;
	Crangek = Cmaxk - Cmink;
	Ppm = (Csize.x * CPM) / Cranget;
	Ppk = Csize.y / Crangek;
	x = Csize.x - (Cranget * Ppm) / CPM;	/* adjust to make both */
	y = Csize.y - Crangek * Ppk;		/* Ppm and Ppk exact */
	Ca.o.x += x / 2;			/* center in maximum area */
	Ca.o.y += y / 2;
	Ca.c.x -= x / 2;
	Ca.c.y -= y / 2;
	Csize.x = Ca.c.x - Ca.o.x;		/* real chart area in use */
	Csize.y = Ca.c.y - Ca.o.y;
}

drawstaves()
{
	register int i, lx, hx, ly, hy;
	long t, dt;

	if (Dpw)
	    pw_batch_on(Dpw);
	lx = CAT_X(Cmint);			/* horizontal staff lines */
	lx = max(lx, Ca.o.x);
	hx = CAT_X(Cmaxt);
	for (i = 5; --i >= 0; ) {
	    ly = CAK_Y(Tclefk[i]);
	    if (Ca.o.y < ly && ly < Ca.c.y)
		pw_vector(Dpw, lx, ly, hx, ly, PIX_SET, 1);
	    ly = CAK_Y(Bclefk[i]);
	    if (Ca.o.y < ly && ly < Ca.c.y)
		pw_vector(Dpw, lx, ly, hx, ly, PIX_SET, 1);
	}
	ly = Ca.o.y;				/* various vertical lines */
	hy = Ca.c.y;
	if (Tgrid && Barlen) {			/* grid ticks */
	    dt = Barlen / Tgrid;
	    for (t = Baroff + dt * ((Mint + dt - 1) / dt); t < Cmaxt; t += dt) {
		lx = CAT_X(t);
		if (lx < Ca.o.x)
		    continue;
		if (lx > Ca.c.x)
		    break;
		TREPL(lx, ly, 1, hy - ly, PIX_SRC, Ltgrey);
	    }
	}
	ly = CAK_Y(Tclefk[0]);
	hy = CAK_Y(Bclefk[0]);
	hy = min(hy, Ca.c.y);
	if (Barlen) {				/* vertical staff lines */
	    dt = Barlen;
	    for (t = Baroff + dt * ((Mint + dt - 1) / dt); t < Cmaxt; t += dt) {
		lx = CAT_X(t);
		if (lx > Ca.c.x)
		    break;
		if (lx >= Ca.o.x)
		    pw_vector(Dpw, lx, ly, lx, hy, PIX_SET, 1);
	    }
	} else {
	    for (i = 0; i < Nbl; i++) {
		t = Barline[i];
		lx = CAT_X(t) - 1;
		if (lx > Ca.c.x)
		    break;
		if (lx >= Ca.o.x)
		    pw_vector(Dpw, lx, ly, lx, hy, PIX_SET, 1);
	    }
	}
	if (Dpw)
	    pw_batch_off(Dpw);			/* for deferred update */
}

drawvu()
{
	register int i, x, y, cxo, cyo;

	if (Dpw)
	    pw_batch_on(Dpw);
	x = Va.o.x;
	y = Va.o.y;
	cxo = 8 - Fontwidth / 2;
	cyo = 30;
	for (i = 0; i < MAXCHAN; i++) {
	    ROPS(x, y, x + 16, y + 16, PIX_SRC, Vupr[Chans[i]]);
	    pw_char(Dpw, x + cxo, y + cyo, PIX_SRC, Fontp,
	     "0123456789ABCDEF"[i]);
	    x += Vuwidth;
	}
	if (Dpw)
	    pw_batch_off(Dpw);			/* for deferred update */
}

uprect(old, new)
Recta	old, new;
{
	if (new.o.x < old.o.x) {
	    RINV(new.o.x, new.o.y, old.o.x, new.c.y);
	    new.o.x = old.o.x;
	} else if (old.o.x < new.o.x) {
	    RINV(old.o.x, old.o.y, new.o.x, old.c.y);
	    old.o.x = new.o.x;
	}
	if (new.o.y < old.o.y) {
	    RINV(new.o.x, new.o.y, new.c.x, old.o.y);
	    new.o.y = old.o.y;
	} else if (old.o.y < new.o.y) {
	    RINV(old.o.x, old.o.y, old.c.x, new.o.y);
	    old.o.y = new.o.y;
	}
	if (new.c.x > old.c.x) {
	    RINV(old.c.x, new.o.y, new.c.x, new.c.y);
	    new.c.x = old.c.x;
	} else if (old.c.x > new.c.x) {
	    RINV(new.c.x, old.o.y, old.c.x, old.c.y);
	    old.c.x = new.c.x;
	}
	if (new.c.y > old.c.y) {
	    RINV(new.o.x, old.c.y, new.c.x, new.c.y);
	    new.c.y = old.c.y;
	} else if (old.c.y > new.c.y) {
	    RINV(old.o.x, new.c.y, old.c.x, old.c.y);
	    old.c.y = new.c.y;
	}
}
