/*	MIXER -- Control panel for IOTA Midi-Fader, psl 10/87
**	Stand-alone version; output is sent to /dev/mpu0
*/

#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	DEVMPU	"/dev/mpu0"

typedef	struct pr_pos	Point;

#define	PIX_INV			PIX_NOT(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	REINIT	1
#define	RESYNC	2
#define	QUIT	3

u_char	Abreq[]	= {		/* SysExcl request to dump active buffer */
	ID_MISC, ID1_IOTA, ID2_IOTA,	/* identify Iota */
	ID_IMF,				/* identify Midi-Fader */
	0x00,				/* Unit number */
	IMF_SXC_REQ_ABUF,		/* command; dump active buffer */
};
u_char	Isetup[]	= {	/* Initial setup data */
	ID_MISC, ID1_IOTA, ID2_IOTA,	/* identify Iota */
	ID_IMF,				/* identify 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 commands */
	0x07,				/* prog change MIDI channel */
	0x00,				/* don't send levels every second */
	0x02,				/* fader change rate = 4 ms */
	0x0a,				/* dial sensitivity = 10 */
	0x07, 0x00, 0x20, 0x30,		/* fader1, chan=8, lev=32, mute=48 */
	0x07, 0x00, 0x21, 0x31,		/* fader2, chan=8, lev=33, mute=49 */
	0x07, 0x00, 0x22, 0x32,		/* fader3, chan=8, lev=34, mute=50 */
	0x07, 0x00, 0x23, 0x33,		/* fader4, chan=8, lev=35, mute=51 */
	0x07, 0x00, 0x24, 0x34,		/* fader5, chan=8, lev=36, mute=52 */
	0x07, 0x00, 0x25, 0x35,		/* fader6, chan=8, lev=37, mute=53 */
	0x07, 0x00, 0x26, 0x36,		/* fader7, chan=8, lev=38, mute=54 */
	0x07, 0x00, 0x27, 0x37,		/* fader8, chan=8, lev=39, mute=55 */
};

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",
};

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,
};
int	Dchange	= 0;		/* display changes */
int	Xchange	= 0;		/* transmit changes */
int	Ifh	= -1;		/* where Iota data comes from */
int	Ofh	= -1;		/* where Iota commands go to */
int	Chan;			/* Iota's MIDI channel */
int	Iflg	= 0;		/* initialization flag */
int	Fade[8]	= {		/* 8 fader settings 0-127 */
	127, 127, 127, 127, 127, 127, 127, 127, 
};
int	Fh;			/* fader height */
int	Sy;			/* solo button y */
int	Br;			/* button radius */
int	Fy;			/* fader (top) y */
int	My;			/* mute button y */
int	Fw;			/* fader width */
int	Fw2;			/* fader half-width */
int	Fx[8];			/* fader (center) x */

int	Solo[8];		/* 8 solo buttons */
int	Mute[8];		/* 8 mute buttons */
int	Um[8];			/* Iota fader mutes, 0=>muted */
int	Fcntl[8]	= {	/* controller numbers for the faders */
	32, 33, 34, 35, 36, 37, 38, 39,
};
int	Mcntl[8]	= {	/* controller numbers for the mutes */
	48, 49, 50, 51, 52, 53, 54, 55,
};
int	Mouseown	= -1;	/* slider that "claims" the mouse */
int	Wfbu		= 0;	/* "Waiting For Button Up" */
Notify_client	Mynum	= (Notify_client) 4321;	/* nonsense number */
Frame	Dframe;			/* whole display's frame */
Canvas	Dcanvas;		/* whole display's canvas */
Pixwin	*Dpw	= 0;		/* whole display pixwin */
Menu	Rmenu;			/* right mouse button menu */
struct	pixfont	*Fontp;

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;
	extern char *getenv();

	if (!(cp = getenv("IOTA")) || (Chan = atoi(cp) - 1) < 0)
	    Chan = 0;
	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'i':			/* initialize Iota */
		    Iflg++;
		    break;
		case 'f':			/* use stdin/stdout */
		    Ifh = fileno(stdin);
		    Ofh = fileno(stdout);
		    break;
		default:
		    goto syntax;
		}
	    } else {
syntax:
		fprintf(stderr, "Usage: %s [-i] [-f >file]\n", argv[0]);
		fprintf(stderr, "-i initializes Midi-Fader\n");
		fprintf(stderr, "-f uses stdin/stdout (no buffering)\n");
		exit(2);
	    }
	}
	if (Ifh == -1 && Ofh == -1) {
	    if ((Ifh = Ofh = open(DEVMPU, 2)) < 0) {
		perror(DEVMPU);
		exit(1);
	    }
	} else {
	    if (Ifh == -1 && (Ifh = open(DEVMPU, 0)) < 0) {
		perror(DEVMPU);
		exit(1);
	    }
	    if (Ofh == -1 && (Ofh = open(DEVMPU, 1)) < 0) {
		perror(DEVMPU);
		exit(1);
	    }
	}
	if (Iflg)
	    sendinit();
	getidata();			/* get current setup from MIDI-Fader */
	miscinits();
	Dchange = Xchange = 1;
	window_main_loop(Dframe);
	exit(0);
}

sendinit()
{
	sysex(Isetup, sizeof Isetup, 0, 0);
}

getidata()
{
	register int i, offset;
	u_char buf[32];

	sysex(Abreq, sizeof Abreq, buf, sizeof buf);
	for (offset = 0; offset < 5 && buf[offset] != SX_CMD; offset++);
	offset += 7;
	for (i = 8; --i >= 0; setfader(i, buf[offset + i]));
	for (i = 0; i < 4; i++) {
	    setmute(i, (buf[offset + 8] & (1 << i))? 0 : 1);
	    setmute(i + 4, (buf[offset + 9] & (1 << i))? 0 : 1);
	}
	for (i = 8; --i >= 0; ) {
	    Um[i] = Mute[i]? 0 : 1;
	    setsolo(i, 0);
	}
}

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

	Dframe = window_create(NULL, FRAME,
	    FRAME_LABEL,	"-=[ MIXER ]=-",
	    FRAME_ICON,		&Mixicon,
	    WIN_WIDTH,		160,
	    WIN_HEIGHT,		188,
	    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,	"RE-INIT",
			MENU_VALUE,	REINIT,
			0,
	    MENU_ITEM,	MENU_STRING,	"RESYNC",
			MENU_VALUE,	RESYNC,
			0,
	    MENU_ITEM,	MENU_STRING,	"QUIT",
			MENU_VALUE,	QUIT,
			0,
	    0);
	notify_set_input_func(Mynum, fadein, Ifh);
}

Notify_value
fadein(clinum, fh)
Notify_client clinum;
{
	int i, j;
	static char buf[512];
	    
/****/printf("fadein(%d, %d)\n", clinum, fh);
	if (clinum != Mynum || fh != Ifh) {	/* "can't happen" */
/****/printf("Eh? clinum(%d)!=Mynum(%d) or fh(%d)!=Ifh(%d)\n",
/****/ clinum, Mynum, fh, Ifh);
	    return(NOTIFY_IGNORED);
	}
	i = read(Ifh, buf, sizeof buf);
/****/printf("read %d byte(s)\n", i);
/****/for (j = 0; j < i; j++)
/****/ printf(" %02x", buf[j]);
/****/printf("\n");
	return(NOTIFY_DONE);
}

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

	if (canvas != Dcanvas)
	    return;		/* oops */
	dx = width / 8;			/* fader separation */
	Fh = height * 0.8;		/* fader height */
	bs = (height - Fh) / 2;		/* height of button space */
	bhh = bs / 2;			/* half-height of button space */
	Sy = bhh;			/* solo button y */
	Br = bhh * 0.8;			/* button radius */
	if (dx < 3 * Br)
	    Br = dx / 3;		/* button radius */
	Fy = bs;			/* top of fader y */
	My = Fy + Fh + bhh;		/* mute button y */
	My = height - bhh;		/* mute button y */
	Fw = dx / 2;			/* fader width */
	Fw2 = dx / 4;			/* half fader width */
	Fh -= 4;			/* leave room for outline */
	Fy += 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_rop(Dpw,0,0,width,height,PIX_CLR,0,0,0);
	i = 0;
	do {
	    y = f_to_y(Ticks[i]);
	    pw_vector(Dpw, 0, y, width, y, PIX_SET, 1);
	} while (Ticks[i++]);
	sb[0] = 'S';
	mb[0] = 'M';
	sb[1] = mb[1] = '\0';
	for (i = 0; i < 8; i++) {
	    Fx[i] = i * dx + dx / 2;
	    pw_circle(Dpw, Fx[i], Sy, Br, PIX_SET);
	    pw_text(Dpw, Fx[i] - chw, Sy + chh, PIX_OR, Fontp, sb);
	    if (Solo[i])
		pw_disc(Dpw, Fx[i], Sy, Br, PIX_INV);
	    pw_circle(Dpw, Fx[i], My, Br, PIX_SET);
	    pw_text(Dpw, Fx[i] - chw, My + chh, PIX_OR, Fontp, mb);
	    if (Mute[i])
		pw_disc(Dpw, Fx[i], My, Br, PIX_INV);
	    x = Fx[i] - Fw2;
	    y = Fy;
	    pw_rop(Dpw, x - 2, y - 2, Fw + 4, Fh + 4, PIX_SET, 0, 0, 0);
	    q = f_to_y(Fade[i]);
	    pw_rop(Dpw, x, y, Fw, 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;
{
	register int i, x, y, c, mindx, dx;

	switch (menu_show(Rmenu, window, event, 0)) {
	case REINIT:
	    sendinit();
	    break;
	case RESYNC:
	    Xchange = 0;
	    getidata();
	    Xchange = 1;
	    break;
	case QUIT:
	    exit(0);
	}
}

leftmouse(window, event, arg)
Window	window;
Event	*event;
{
	register int i, x, y, c, mindx, dx;

	if (event_is_up(event)) {
	    Mouseown = -1;
	    Wfbu = 0;
	    return;
	}
	x = event_x(event);
	y = event_y(event);
	if (Mouseown >= 0)		/* if a slider claims the mouse */
	    setfader(Mouseown, y_to_f(y));
	else if (!Wfbu) {		/* who wants the mouse? */
	    if (x < Fx[0] - Fw || x > Fx[7] + Fw || y < Sy - Br || y > My + Br)
		return;				/* no one */
	    c = -1;
	    mindx = 999;
	    for (i = 8; --i >= 0; ) {
		dx = x - Fx[i];
		if (dx < 0)
		    dx = -dx;
		if (dx < mindx) {
		    mindx = dx;
		    c = i;
		}
	    }
	    if (My-Br < y && y < My+Br && mindx < Br) {		/* MUTE */
		setmute(c, Mute[c] ^ 1);
		Wfbu++;
	    } else if (Sy-Br < y && y < Sy+Br && mindx < Br) {	/* SOLO */
		setsolo(c, Solo[c] ^ 1);
		Wfbu++;
	    } else if (Fy <= y && y <= Fy+Fh && mindx < Fw2) {	/* sliders */
		Mouseown = c;
		setfader(Mouseown, y_to_f(y));
	    }
	}
}

f_to_y(f)
{
	register int y;

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

y_to_f(y)
{
	register int f;

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

setfader(n, new)
{
	register int old, oldy, newy;

	old = Fade[n];
	if (new != old) {
	    if (Xchange)
		mput(CH_CTL | Chan, Fcntl[n], new);
	    Fade[n] = new;
	    oldy = f_to_y(old);
	    newy = f_to_y(new);
	    if (Dchange) {
		if (newy < oldy) {		/* new level is higher */
		    pw_rop(Dpw, Fx[n] - Fw2, newy, Fw, oldy - newy,
		     PIX_SET, 0, 0, 0);
		} else {		/* new level is lower */
		    pw_rop(Dpw, Fx[n] - Fw2, oldy, Fw, newy - oldy,
		     PIX_CLR, 0, 0, 0);
		}
	    }
	}
}

setmute(n, value)
{
	if (Mute[n] != value) {
	    Mute[n] = value;
	    if (Dchange)
		pw_disc(Dpw, Fx[n], My, Br, PIX_INV);
	}
	if (Xchange)
	    domutes();
}

setsolo(n, value)
{
	if (Solo[n] != value) {
	    Solo[n] = value;
	    if (Dchange)
		pw_disc(Dpw, Fx[n], Sy, Br, PIX_INV);
	}
	if (Xchange)
	    domutes();
}

domutes()
{
	register int i;
	int um[8];

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

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

	buf[0] = MPU_WANT_TO_SEND_DATA + 0;	/* 0 = track */
	buf[1] = a;
	buf[2] = b;
	buf[3] = c;
	write(Ofh, buf, 4);
}

sysex(data, len, rbuf, rlen)	/* send sys excl., maybe read response */
u_char	*data, *rbuf;
{
	u_char buf[4096], *bp, *rbp, *bufend;
	int i;

	bp = buf;
	*bp++ = MPU_RESET;
	*bp++ = MPU_MIDI_THRU_OFF;
	*bp++ = MPU_SEND_SYSTEM_MESSAGE;
	*bp++ = SX_CMD;
	while (--len >= 0)
	    *bp++ = *data++;
	*bp++ = SX_EOB;
	if (!rbuf || !rlen) {			/* no reply */
	    write(Ofh, buf, bp - buf);
	    return(0);
	}
	*bp++ = MPU_EXCLUSIVE_TO_HOST_ON;	/* get reply */
	*bp++ = MPU_SEND_MEASURE_END_OFF;
	write(Ofh, buf, bp - buf);
	rbp = rbuf;
	do {
	    if ((i = iwait(Ifh, 10)) <= 0) {
		fprintf(stderr, "read(midi) timed out\n");
		break;
	    }
	    if ((i = read(Ifh, rbp, rlen)) <= 0) {
		fprintf(stderr, "read(midi) returned %d\n", i);
		break;
	    }
	    bufend = rbp + i;
	    for (bp = rbp; bp < bufend && *bp != SX_EOB; bp++);
	    rlen -= i;
	    rbp += i;
	} while (rlen > 0 && bp >= bufend);
	return(rbp - rbuf);
}

/*
** Draw a circle of radius r with center 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 circle is guaranteed to be symmetric about the horizontal, vertical,
** and diagonal axes. */
pw_circle(pw, x, y, r, op)
Pixwin *pw;
{
	register int y1, eps, dxsq, dysq, exy, y0;
	int x0, x1;

	x0 = x1 = x;
	y0 = y - r;
	y1 = y + r;
	eps = 0;	/* x^2 + y^2 - r^2 */
	dxsq = 1;		/* (x+dx)^2-x^2*/
	dysq = 1 - 2*r;
	/*x1++;		/* to offset half-open lines (jerq?) */
	if (op == PIX_INV) {	/* endpoints coincide */
	    POINT2(pw, x0, y0, op);
	    POINT2(pw, x0, y1, op);
	}
	while (y1 > y0) {
	    POINT2(pw, x0, y0, op);
	    POINT2(pw, x0, y1, op);
	    POINT2(pw, x1, y0, op);
	    POINT2(pw, x1, y1, op);
	    exy = eps + dxsq + dysq;
	    if (-exy <= eps+dxsq) {
		y1--;
		y0++;
		eps += dysq;
		dysq += 2;
	    }
	    if (exy <= -eps) {
		x1++;
		x0--;
		eps += dxsq;
		dxsq += 2;
	    }
	}
	POINT2(pw, x0, y0, op);
	POINT2(pw, x1, y0, op);
}

/*
** 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);
}
