/*	MFM -- Mirage FM synthesis a la DX7 psl 3/88 */

#include	<suntool/sunview.h>
#include	<suntool/canvas.h>
#include	<suntool/panel.h>
#include	<suntool/scrollbar.h>
#include	<midi.h>
#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/stat.h>

#define	PSLSIN			/* approximate, but FAST */

#define	DATAWIDTH	1024
#define	DATAHEIGHT	256
#define	MARGIN		4		/* space around data in canvas */
#define	VIEWWIDTH	(DATAWIDTH+2*MARGIN)
#define	VIEWHEIGHT	(DATAHEIGHT+2*MARGIN)
#define	SCROLLHEIGHT	20
#define	PANELHEIGHT	78
#define	BORDER		16
#define	MISC		16

#define	BPP		256
#define	ZERO		0x80		/* Mirage data zero */

#define	DSCMAGIC	05504

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

#define	OUTFILE	"mfm.data"
#define	TMPFILE	"/tmp/mfm.tmp"
#define	WDRFILE	"/tmp/mfm.wdr"
#define	SEQFILE	"/tmp/mfm.seq"

#define	PIX_INV	PIX_NOT(PIX_DST)
#define	PIX_XOR	(PIX_DST^PIX_SRC)

#define	ROP(OX,OY,CX,CY,OP)	pw_rop(Dpw,OX,OY,CX-(OX),CY-(OY),OP,0,0,0);
#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)

/* pixel macros for left border area */
#define	D_TO_BP(D)	(MARGIN+0xFF-(D))
#define	BP_TO_D(P)	(MARGIN+0xFF-(P))

/* pixel macros for viewing area */
#define	B_TO_PIX(B)	(1+(MARGIN*BPP+(B)*Ppp-1)/BPP)
#define	D_TO_PIX(D)	(BORDER+D_TO_BP(D))
#define	E_TO_PIX(S)	(BORDER+MARGIN+0x7F-(((S)+2)>>2))
#define	PIX_TO_B(P)	((((P)-MARGIN)*BPP)/Ppp)
#define	PIX_TO_D(P)	(BORDER+BP_TO_D(P))
#define	PIX_TO_E(P)	((BORDER+MARGIN+0x7F-(P))<<2)

#define	D_TO_NUM(D)	((D)-ZERO)
#define	NUM_TO_D(N)	((N)+ZERO)

typedef	unsigned char	uchar;

#define	NUMSCALE	256

struct	estr	{		/* envelope structure */
	int	num;			/* number of points in use */
	int	disp;			/* 0 => not being displayed */
	int	bpos[NUMSCALE];		/* byte position */
	short	scale[NUMSCALE];	/* scale * 256 */
} Scale	= {
	1,	1,	{ 0, },	{ 256, },
};

struct	opstr	{	/* operator settings for FM */
	int	f;		/* freq for op (* 100) */
	int	g;		/* gain for op (* 100) */
	struct	estr	e;	/* envelope for op */
};
struct	fmpstr	{	/* entire FM setup */
	int	pcnt;		/* length to generate (256 byte pages) */
	int	cycs;		/* cycles to generate */
	int	fb;		/* feedback (op1) (* 100) */
	struct	opstr	op[2];	/* freqs, gains, & envelopes */
} Fmp	= {
	16,	16,	0,
	{	100,	100,
		{ 1,	0,	{ 0, }, { 256, }, },
	},
	{	100,	100,
		{ 1,	0,	{ 0, }, { 256, }, },
	},
};

char	Oldfile[128];		/* where the data comes from for "read" */
char	Flbuf[128];		/* frame label buffer */
uchar	*Data;			/* pointer to wavesample (malloc) */
int	Chan	= 2;		/* Where the Mirage listens */
int	Pcnt	= 0;		/* data page count */
int	Ppp	= 16;		/* pixels/page (magnification) */
int	Tticks	= BPP;		/* bytes per tickmark (time ticks) */
int	Aticks	= 64;		/* values per tickmark (amplitude ticks) */
int	Ramt	= 1;		/* rotation amount */
int	Cbusy	= 0;		/* semaphore, to hide cursor while busy */
Pixrect	*Grey;			/* grey texture */
Frame	Dframe;			/* whole display's frame */
Canvas	Dlborder;		/* left border */
Canvas	Dcanvas;		/* waveform area */
Scrollbar Dcsb;			/* waveform area's scrollbar */
int	Dcwidth, Dcheight;	/* width & height of Dcanvas */
Pixwin	*Bpw	= 0;		/* display pixwin for border area (Dborder) */
Pixwin	*Dpw	= 0;		/* display pixwin for waveform area (Dcanvas) */
Panel	Controls;		/* control panel */
Panel_item Mslide;		/* id for magnification slider */
Panel_item Tslide;		/* id for time ticks slider */
Panel_item Aslide;		/* id for amplitude ticks slider */
Panel_item Rslide;		/* id for rotation amount slider */
Panel	Fmparms;		/* FM control panel */
Panel_item Fpgslide;		/* id for FM page count slider */
Panel_item Fcyslide;		/* id for FM cycle count slider */
Panel_item Ffbslide;		/* id for FM feedback slider */
Panel_item Ff1slide;		/* id for FM freq 1 slider */
Panel_item Fg1slide;		/* id for FM gain 1 slider */
Panel_item Ff2slide;		/* id for FM freq 2 slider */
Panel_item Fg2slide;		/* id for FM gain 2 slider */

/* Button 3 menu */
Menu	b3mp;
#define	PLAY		1
#define	DUMP		2
#define	LOAD		3
#define	CONTROLS	4
#define	FM		5
#define	ROTL		6
#define	ROTR		7
#define	TRIM		8
#define	SCALE		9
#define	UNSCALE		10
#define	NORMALIZE	11
#define	READ		12
#define	WRITE		13
#define	READDSC		14
#define	WRITEDSC	15
#define	QUIT		16
#define	B3MINIT() b3mp = menu_create( \
MENU_INITIAL_SELECTION, MENU_SELECTED, \
MENU_INITIAL_SELECTION_SELECTED, FALSE, \
MENU_STRINGS, \
	"PLAY SEQ", \
	"MIRAGE->MFM", \
	"MFM->MIRAGE", \
	"CONTROLS", \
	"FM SYNTH", \
	"ROT LEFT", \
	"ROT RIGHT", \
	"TRIM", \
	"SCALE", \
	"UNSCALE", \
	"NORMALIZE", \
	"READ", \
	"WRITE", \
	"READ DSC", \
	"WRITE DSC", \
	"QUIT", \
0, 0)

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

extern	double	sin(), cos(), atof();

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

/****/setbuf(stderr, 0);	/* THOSE ASSHOLES! */
	while (--argc > 0) {
	    if (argv[argc][0] == '-') {
		switch (argv[argc][1]) {
		case 'm':		/* magnify */
		    Ppp = atoi(&argv[argc][2]);
		    if (Ppp < 1 || Ppp > BPP)
			goto syntax;
		    break;
		default:
		    goto syntax;
		}
	    } else if (*Oldfile == 0)
		sprintf(Oldfile, "%s", argv[argc]);
	    else {
syntax:
		fprintf(stderr, "Usage: %s [-m#] [file]\n", argv[0]);
		exit(2);
	    }
	}
	if (readdata(Oldfile) == 0)	/* no file read */
	    exit(1);
	Fmp.pcnt = Pcnt;
	miscinits();
	redraw();
	window_main_loop(Dframe);
	exit(0);
}

redraw()
{
	busy_cursor();
	drawdata();
	drawticks();
	if (Scale.disp)
	    flipenv(&Scale, 0);
	if (Fmp.op[0].e.disp)
	    flipenv(&Fmp.op[0].e, 0);
	if (Fmp.op[1].e.disp)
	    flipenv(&Fmp.op[1].e, 0);
	ready_cursor();
}

drawdata()
{
	register int i, x, y, ox, oy, nbytes;

	i = Dcwidth > VIEWWIDTH? Dcwidth : VIEWWIDTH;
	pw_writebackground(Dpw, 0, BORDER, i, VIEWHEIGHT, PIX_CLR);
	if (Dpw)
	    pw_batch_on(Dpw);
	nbytes = BPP * Pcnt;
	ox = oy = -1;
	for (i = 0; i < nbytes; i++) {
	    x = B_TO_PIX(i);
	    y = D_TO_PIX(Data[i] & 0xFF);
	    if (ox >= 0)
		pw_vector(Dpw, ox, oy, x, y, PIX_SET, 1);
	    ox = x;
	    oy = y;
	}
	if (Dpw)
	    pw_batch_off(Dpw);
}

drawticks()
{
	register int i, x, nbytes, h2;

	if (Dpw)
	    pw_batch_on(Dpw);
	nbytes = BPP * Pcnt;
	i = Dcwidth > VIEWWIDTH? Dcwidth : VIEWWIDTH;
	pw_writebackground(Dpw, 0, 0, i, BORDER, PIX_CLR);
	if (Tticks) {
	    for (i = 0; i < nbytes; i += Tticks) {
		x = B_TO_PIX(i);
		pw_vector(Dpw, x, 0, x, BORDER - 2, PIX_SRC, 1);
	    }
	}
	pw_writebackground(Bpw, 0, 0, BORDER, DATAHEIGHT, PIX_CLR);
	h2 = D_TO_BP(ZERO);
	pw_vector(Bpw, 0, h2, BORDER - 2, h2, PIX_INV, 1);
	if (Aticks) {
	    for (i = Aticks; i < h2; i += Aticks) {
		pw_vector(Bpw, 0, h2 + i, BORDER - 2, h2 + i, PIX_INV, 1);
		pw_vector(Bpw, 0, h2 - i, BORDER - 2, h2 - i, PIX_INV, 1);
	    }
	}
	if (Dpw)
	    pw_batch_off(Dpw);
	return(1);
}

flipenv(ep, flg)		/* XOR an envelope line in */
struct	estr *ep;
{
	register int i, x, y, ox, oy, nbytes;

	ep->disp ^= flg;
	if (ep->num < 1)
	    return;
	x = B_TO_PIX(ep->bpos[0]);
	y = E_TO_PIX(ep->scale[0]);
	nbytes = BPP * Pcnt;
	pw_vector(Dpw, x - 3, y, x, y - 3, PIX_INV, 1);
	pw_vector(Dpw, x, y - 3, x + 3, y, PIX_INV, 1);
	pw_vector(Dpw, x + 3, y, x, y + 3, PIX_INV, 1);
	pw_vector(Dpw, x, y + 3, x - 3, y, PIX_INV, 1);
	if (ep->num == 1)
	    pw_vector(Dpw, B_TO_PIX(0), y, B_TO_PIX(nbytes), y, PIX_INV, 1);
	else {
	    if (ep->bpos[0] > 0)
		pw_vector(Dpw, B_TO_PIX(0), y, x, y, PIX_INV, 1);
	    for (i = 1; i < ep->num; i++) {
		ox = x;
		oy = y;
		x = B_TO_PIX(ep->bpos[i]);
		y = E_TO_PIX(ep->scale[i]);
		pw_vector(Dpw, ox, oy, x, y, PIX_INV, 1);
		pw_vector(Dpw, x - 3, y, x, y - 3, PIX_INV, 1);
		pw_vector(Dpw, x, y - 3, x + 3, y, PIX_INV, 1);
		pw_vector(Dpw, x + 3, y, x, y + 3, PIX_INV, 1);
		pw_vector(Dpw, x, y + 3, x - 3, y, PIX_INV, 1);
	    }
	    if (ep->bpos[ep->num - 1] < nbytes)
		pw_vector(Dpw, x, y, B_TO_PIX(nbytes), y, PIX_INV, 1);
	}
}

upmisc()		/* update misc - canvas size, frame label, etc. */
{
	sprintf(Flbuf, "      MFM       %s, %d page%s",
	 Oldfile, Pcnt, Pcnt==1? "" : "s");
	window_set(Dframe, FRAME_LABEL,	Flbuf, 0);
	Dcwidth = Ppp * Pcnt;
	window_set(Dcanvas, CANVAS_WIDTH, Dcwidth, 0);
	scrollbar_scroll_to(Dcsb, 0);
	panel_set(Mslide, PANEL_VALUE, Ppp, 0);
}

miscinits()		/* do one-time inits */
{
	Rect tmp;
	Cursor cursor;
	void input();
	Pixfont *pfont;

	if (!(pfont = pf_open(PANELFONT)))
	    pfont = pf_default();
	tmp.r_left = 8;
	tmp.r_top = 8;
	tmp.r_width = BORDER + VIEWWIDTH;
	tmp.r_height = BORDER + VIEWHEIGHT
	 + SCROLLHEIGHT + PANELHEIGHT + MISC;
	Dframe = window_create(NULL, FRAME,
	    FRAME_OPEN_RECT,		&tmp,
	    0);
	Dlborder = window_create(Dframe, CANVAS,
	    CANVAS_FIXED_IMAGE,		TRUE,
	    WIN_X,			0,
	    WIN_Y,			BORDER,
	    WIN_WIDTH,			BORDER,
	    WIN_HEIGHT,			VIEWHEIGHT,
	    0);
	Dcsb = scrollbar_create(
	    SCROLL_DIRECTION,		SCROLL_HORIZONTAL,
	    SCROLL_PLACEMENT,		SCROLL_SOUTH,
	    SCROLL_THICKNESS,		SCROLLHEIGHT,
	    0);
	Dcwidth = Ppp * Pcnt;
	Dcanvas = window_create(Dframe, CANVAS,
	    CANVAS_FIXED_IMAGE,		TRUE,
	    CANVAS_AUTO_SHRINK,		FALSE,
	    WIN_X,			BORDER,
	    WIN_Y,			0,
	    WIN_WIDTH,			VIEWWIDTH,
	    CANVAS_WIDTH,		Dcwidth,
	    WIN_HEIGHT,			BORDER + VIEWHEIGHT + SCROLLHEIGHT,
	    CANVAS_HEIGHT,		BORDER + VIEWHEIGHT,
	    WIN_HORIZONTAL_SCROLLBAR,	Dcsb,
	    WIN_CURSOR,			cursor_create(
					    CURSOR_SHOW_CROSSHAIRS, TRUE,
					    0),
	    WIN_CONSUME_PICK_EVENTS,	WIN_NO_EVENTS,
					LOC_WINENTER,
					LOC_WINEXIT,
					LOC_MOVE,
					WIN_MOUSE_BUTTONS,
					0,
	    WIN_EVENT_PROC,		input,
	    0);
	Bpw = canvas_pixwin(Dlborder);
	Dpw = canvas_pixwin(Dcanvas);
	Grey = mem_point(16, 16, 1, Grey_bits);
	B3MINIT();
	genfmp(pfont);
	window_set(Fmparms, WIN_SHOW, FALSE, 0);
	gencntl(pfont);
	window_set(Controls, WIN_SHOW, TRUE, 0);
	upmisc();
}

gencntl(font)
Pixfont	*font;
{
	int swidth;
	void cntlchng(), pinput();

	swidth = 258;
	Controls = window_create(Dframe, PANEL,
	    WIN_X,		0,
	    WIN_Y,		BORDER + VIEWHEIGHT + SCROLLHEIGHT + 5,
	    WIN_WIDTH,		WIN_EXTEND_TO_EDGE,
	    WIN_HEIGHT,		PANELHEIGHT,
	    PANEL_SHOW_MENU,	FALSE,
	    PANEL_LABEL_BOLD,	TRUE,
	    PANEL_ITEM_X_GAP,	25,
	    PANEL_ITEM_Y_GAP,	12,
	    WIN_EVENT_PROC,	pinput,
	    WIN_FONT,		font,
	    0);
	Mslide = panel_create_item(Controls, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"PIXELS/PAGE",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Ppp,
	    PANEL_MIN_VALUE,	4,
	    PANEL_MAX_VALUE,	BPP,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	Tslide = panel_create_item(Controls, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	" BYTES/TICK",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Tticks,
	    PANEL_MIN_VALUE,	2,
	    PANEL_MAX_VALUE,	BPP,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	Aslide = panel_create_item(Controls, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"VALUES/TICK",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Aticks,
	    PANEL_MIN_VALUE,	2,
	    PANEL_MAX_VALUE,	128,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	Rslide = panel_create_item(Controls, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	" ROTATE AMT",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Ramt,
	    PANEL_MIN_VALUE,	1,
	    PANEL_MAX_VALUE,	BPP,
	    PANEL_NOTIFY_PROC,	cntlchng,
	    0);
	window_fit_height(Controls);
}

genfmp(font)
Pixfont	*font;
{
	int swidth;
	void fmpchng(), pinput();

	Fmparms = window_create(Dframe, PANEL,
	    WIN_X,		0,
	    WIN_Y,		BORDER + VIEWHEIGHT + SCROLLHEIGHT + 5,
	    WIN_WIDTH,		WIN_EXTEND_TO_EDGE,
	    WIN_HEIGHT,		PANELHEIGHT,
	    PANEL_SHOW_MENU,	FALSE,
	    PANEL_LABEL_BOLD,	TRUE,
	    PANEL_ITEM_X_GAP,	24,
	    PANEL_ITEM_Y_GAP,	2,
	    WIN_EVENT_PROC,	pinput,
	    WIN_FONT,		font,
	    0);
	swidth = 170;
	Fpgslide = panel_create_item(Fmparms, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"PAGES",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Fmp.pcnt,
	    PANEL_MIN_VALUE,	1,
	    PANEL_MAX_VALUE,	64,
	    PANEL_NOTIFY_PROC,	fmpchng,
	    0);
	Fcyslide = panel_create_item(Fmparms, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"CYCLES",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Fmp.cycs,
	    PANEL_MIN_VALUE,	1,
	    PANEL_MAX_VALUE,	64,
	    PANEL_NOTIFY_PROC,	fmpchng,
	    0);
	Ffbslide = panel_create_item(Fmparms, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"FB",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Fmp.fb,
	    PANEL_MIN_VALUE,	0,
	    PANEL_MAX_VALUE,	100,
	    PANEL_NOTIFY_PROC,	fmpchng,
	    0);
	swidth = 300;
	Ff1slide = panel_create_item(Fmparms, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"FREQ1",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Fmp.op[0].f,
	    PANEL_MIN_VALUE,	0,
	    PANEL_MAX_VALUE,	1200,
	    PANEL_NOTIFY_PROC,	fmpchng,
	    0);
	Fg1slide = panel_create_item(Fmparms, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	" GAIN1",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Fmp.op[0].g,
	    PANEL_MIN_VALUE,	0,
	    PANEL_MAX_VALUE,	100,
	    PANEL_NOTIFY_PROC,	fmpchng,
	    0);
	Ff2slide = panel_create_item(Fmparms, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	"FREQ2",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Fmp.op[1].f,
	    PANEL_MIN_VALUE,	0,
	    PANEL_MAX_VALUE,	1200,
	    PANEL_NOTIFY_PROC,	fmpchng,
	    0);
	Fg2slide = panel_create_item(Fmparms, PANEL_SLIDER,
	    PANEL_LABEL_STRING,	" GAIN2",
	    PANEL_SLIDER_WIDTH,	swidth,
	    PANEL_VALUE,	Fmp.op[1].g,
	    PANEL_MIN_VALUE,	0,
	    PANEL_MAX_VALUE,	100,
	    PANEL_NOTIFY_PROC,	fmpchng,
	    0);
	window_fit_height(Fmparms);
}

void
cntlchng(item, value, event)
Panel_item item;
Event	*event;
{
	if (item == Mslide) {
	    if (Ppp != value) {
/****
		pw_writebackground(Dpw, 0,BORDER, Dcwidth,VIEWHEIGHT, PIX_CLR);
****/
		Ppp = value;
		upmisc();
		redraw();
	    }
	} else if (item == Tslide) {
	    if (Tticks != value) {
		Tticks = value;
		drawticks();
	    }
	} else if (item == Aslide) {
	    if (Aticks != value) {
		Aticks = value;
		drawticks();
	    }
	} else if (item == Rslide)
	    Ramt = value;
}

void
fmpchng(item, value, event)
Panel_item item;
Event	*event;
{
	if (item == Fpgslide) {
	    if (Fmp.pcnt != value)
		Fmp.pcnt = value;
	} else if (item == Fcyslide) {
	    if (Fmp.cycs != value)
		Fmp.cycs = value;
	} else if (item == Ffbslide) {
	    if (Fmp.fb != value)
		Fmp.fb = value;
	} else if (item == Ff1slide) {
	    if (Fmp.op[0].f != value)
		Fmp.op[0].f = value;
	} else if (item == Ff2slide) {
	    if (Fmp.op[1].f != value)
		Fmp.op[1].f = value;
	} else if (item == Fg1slide) {
	    if (Fmp.op[0].g != value)
		Fmp.op[0].g = value;
	} else if (item == Fg2slide) {
	    if (Fmp.op[1].g != value)
		Fmp.op[1].g = value;
	}
}

busy_cursor()
{
	Cursor cursor;

	if (Cbusy++ == 0) {
/****/fprintf(stderr, "busy... ");
#ifdef	NOTHING_WORKS
	    cursor = window_get(Dframe, WIN_CURSOR);
	    cursor_set(cursor, CURSOR_SHOW_CROSSHAIRS, FALSE, 0);
	    window_set(Dframe, WIN_CURSOR, cursor, 0);
/****/window_release_event_lock(Dframe);
#endif
	}
	return(Cbusy);
}

ready_cursor()
{
	Cursor cursor;

	if (--Cbusy <= 0) {
/****/fprintf(stderr, "\n");
#ifdef	NOTHING_WORKS
	    cursor = window_get(Dframe, WIN_CURSOR);
	    cursor_set(cursor, CURSOR_SHOW_CROSSHAIRS, TRUE, 0);
	    window_set(Dframe, WIN_CURSOR, cursor, 0);
#endif
	    Cbusy = 0;
	}
	return(Cbusy);
}

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

	i = event_id(event);
	if (i != MS_RIGHT) {
	    window_default_event_proc(window, event, arg);
	    return;
	}
	busy_cursor();
	change = mouseright(window, event);
	if (change)
	    redraw();
	ready_cursor();
}

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

	i = event_id(event);
	if (i != MS_LEFT && i != MS_MIDDLE && i != MS_RIGHT)
	    return;
	busy_cursor();
	change = 0;
	if (i == MS_LEFT)
	    change = mouseleft(window, event);
	else if (i == MS_MIDDLE)
	    change = mousemiddle(window, event);
	else if (i == MS_RIGHT)
	    change = mouseright(window, event);
	if (change)
	    redraw();
	ready_cursor();
}

mouseleft(window, event)
Window	window;
Event	*event;
{
	char tbuf[64], vbuf[64], abuf[64];
	int x, y, byte, val, p, b;
	Menu m;

	if (!event_is_down(event))
	    return(0);
	x = event_x(event);
	y = event_y(event);
	byte = PIX_TO_B(x);
	val = PIX_TO_D(y);
	p = byte / BPP;
	b = byte % BPP;
	sprintf(tbuf, "%d page%s, %d byte%s",
	 p, p==1? "" : "s", b, b==1? "" : "s");
	sprintf(vbuf, "Crosshair: %d", val);
	sprintf(abuf, "Amplitude: %d", Data[byte]);
	m = menu_create(MENU_STRINGS, tbuf, vbuf, abuf, 0, 0);
	menu_show(m, window, event, 0);
	menu_destroy(m);
	return(0);
}

mousemiddle(window, event)
Window	window;
Event	*event;
{
	char tbuf[64], vbuf[64];
	char tsbuf[64], asbuf[64], dsbuf[64];
	char t1buf[64], a1buf[64], d1buf[64];
	char t2buf[64], a2buf[64], d2buf[64];
	int i, byte, p, b, val, dsi, d1i, d2i;
	Menu m;

	if (!event_is_down(event))
	    return(0);
	byte = PIX_TO_B(event_x(event));
	val = PIX_TO_E(event_y(event));
	p = byte / BPP;
	b = byte % BPP;
	sprintf(tbuf, "%d page%s, %d byte%s",
	 p, p==1? "" : "s", b, b==1? "" : "s");
	sprintf(vbuf, "Data:%d, Scale:%g", Data[byte], val / 256.);
	m = menu_create(MENU_STRINGS, "ENVELOPE", tbuf, vbuf, 0, 0);
	dsi = envmenu(m, "SCALING", &Scale, val, byte, tsbuf, asbuf, dsbuf);
	d1i = envmenu(m, "OP 1", &Fmp.op[0].e, val, byte, t1buf, a1buf, d1buf);
	d2i = envmenu(m, "OP 2", &Fmp.op[1].e, val, byte, t2buf, a2buf, d2buf);
	i = (int) menu_show(m, window, event, 0);
	menu_destroy(m);
	if (i == (int) tsbuf)
	    flipenv(&Scale, 1);
	else if (i == (int) asbuf)
	    envadd(&Scale, val, byte);
	else if (i == (int) dsbuf)
	    envdel(&Scale, dsi);
	else if (i == (int) t1buf)
	    flipenv(&Fmp.op[0].e, 1);
	else if (i == (int) a1buf)
	    envadd(&Fmp.op[0].e, val, byte);
	else if (i == (int) d1buf)
	    envdel(&Fmp.op[0].e, d1i);
	else if (i == (int) t2buf)
	    flipenv(&Fmp.op[1].e, 1);
	else if (i == (int) a2buf)
	    envadd(&Fmp.op[1].e, val, byte);
	else if (i == (int) d2buf)
	    envdel(&Fmp.op[1].e, d2i);
	return(0);
}

envmenu(m, name, ep, val, byte, tbuf, abuf, dbuf)
Menu	m;
char	*name, *tbuf, *abuf, *dbuf;
struct	estr *ep;
{
	int i = -1;

	sprintf(tbuf, "%s: %s envelope?", name, ep->disp? "hide" : "display");
	menu_set(m, MENU_STRING_ITEM, tbuf, tbuf, 0);
	if (ep->disp) {
	    sprintf(abuf, "  Add %g @ %d", val / 256., byte);
	    menu_set(m, MENU_STRING_ITEM, abuf, abuf, 0);
	    if (ep->num > 0) {
		for (i = 0; i < ep->num-1 && byte > ep->bpos[i]; i++);
		if (i > 0 && ep->bpos[i] - byte > byte - ep->bpos[i-1])
		    --i;
		sprintf(dbuf,
		 "  delete %g @ %d", ep->scale[i] / 256., ep->bpos[i]);
		menu_set(m, MENU_STRING_ITEM, dbuf, dbuf, 0);
	    }
	}
	return(i);
}

envadd(ep, val, byte)				/* add a new point */
struct	estr *ep;
{
	int i, j;

	flipenv(ep, 1);
	for (i = 0; i < ep->num && byte > ep->bpos[i]; i++);
	if (i < ep->num && byte == ep->bpos[i]) {
	    if (val != ep->scale[i])
		ep->scale[i] = val;
	} else if (ep->num >= NUMSCALE) {
	    printf("Too many scale points.\n");
	} else {
	    if (i < ep->num) {
		for (j = ep->num; --j >= i; ) {
		    ep->bpos[j+1] = ep->bpos[j];
		    ep->scale[j+1] = ep->scale[j];
		}
	    }
	    ep->num++;
	    ep->bpos[i] = byte;
	    ep->scale[i] = val;
	}
	flipenv(ep, 1);
}

envdel(ep, i)				/* delete old point */
struct	estr *ep;
{
	flipenv(ep, 1);
	for (--ep->num; i < ep->num; i++) {
	    ep->bpos[i] = ep->bpos[i+1];
	    ep->scale[i] = ep->scale[i+1];
	}
	flipenv(ep, 1);
}

mouseright(window, event)
Window	window;
Event	*event;
{
	register int i, nbytes, change, fh, beg, end;
	char *fp, *bp, fbuf[128];
	uchar buf[BPP];

	change = 0;
	i = (int) menu_show(b3mp, window, event, 0);
	switch(i) {
	case PLAY:
	    if (close(open(SEQFILE, 0)) < 0) {
		if ((fh = creat(SEQFILE, 0644)) < 0) {
		    perror(SEQFILE);
		    break;
		} else {
		    buf[0] = 0;
		    buf[1] = 0x90 | Chan;
		    buf[2] = i;
		    buf[3] = 0x40;
		    buf[4] = 24;
		    buf[5] = i;
		    buf[6] = 0;
		    for (i = 36; i < 97; i++) {
			buf[2] = buf[5] = i;
			write(fh, buf, 7);
		    }
		    close(fh);
		}
	    }
	    sprintf(buf, "play %s", SEQFILE);
	    system(buf);
	    break;
	case DUMP:
	    if ((i = creat(WDRFILE, 0644)) < 0) {
		perror(WDRFILE);
		break;
	    }
	    buf[0] = SX_CMD;
	    buf[1] = ID_ENSONIQ;
	    buf[2] = ID_MIRAGE;
	    buf[3] = MIR_SXF_REQ_WAVE;
	    buf[4] = SX_EOB;
	    write(i, buf, 5);
	    close(i);
	    strcpy(Oldfile, "/tmp/mfm.dmp");
	    sprintf(buf, "sxmpu <%s -r15 >%s", WDRFILE, Oldfile);
	    system(buf);
	    readdata(Oldfile);
	    upmisc();
	    change++;
	    break;
	case LOAD:
	    if ((i = creat(TMPFILE, 0644)) < 0) {
		perror(TMPFILE);
		break;
	    }
	    sprintf(buf, "mirset 26=1 27=1 60=0 61=%d 72=61", Pcnt - 1);
	    system(buf);
	    writedata(i);
	    close(i);
	    sprintf(buf, "sxmpu <%s", TMPFILE);
	    system(buf);
	    fprintf(stderr, "\"%s\" written & sent\n", TMPFILE);
	    break;
	case FM:
	    change = fm();
	    break;
	case ROTL:
	    nbytes = BPP * Pcnt;
	    for (i = 0; i < Ramt; i++)
		buf[i] = Data[i];
	    for (i = Ramt; i < nbytes; i++)
		Data[i - Ramt] = Data[i];
	    for (i = 0; i < Ramt; i++)
		Data[nbytes - Ramt + i] = buf[i];
	    change++;
	    break;
	case ROTR:
	    nbytes = BPP * Pcnt;
	    for (i = 0; i < Ramt; i++)
		buf[i] = Data[nbytes - Ramt + i];
	    for (i = nbytes; --i >= Ramt; )
		Data[i] = Data[i - Ramt];
	    for (i = 0; i < Ramt; i++)
		Data[i] = buf[i];
	    change++;
	    break;
	case TRIM:
	    nbytes = BPP * Pcnt;
	    for (beg = 0; beg < nbytes && Data[beg] == ZERO; beg++);
	    for (end = nbytes; --end > beg && Data[end] == ZERO; );
	    if (beg > 0)
		for (i = beg; i < nbytes; i++)
		    Data[i - beg] = Data[i];
	    Pcnt = (end - beg + BPP - 1) / BPP;
	    upmisc();
	    change++;
	    break;
	case SCALE:
	    scale(0);
	    change++;
	    break;
	case UNSCALE:
	    scale(1);
	    change++;
	    break;
	case NORMALIZE:
	    nbytes = BPP * Pcnt;
	    {		int minv, maxv, den, q;
		minv = maxv = ZERO;
		for (i = nbytes; --i >= 0; ) {
		    if (Data[i]) {
			if (Data[i] < minv)
			    minv = Data[i];
			else if (Data[i] > maxv)
			    maxv = Data[i];
		    }
		}
		minv = ZERO - minv;
		maxv = maxv - ZERO;
		den = maxv > minv? maxv : minv;
/****/fprintf(stderr, "scaling=%g\n", (float) 0x7F / den);
		if (den < 0x7F) {
		    for (i = nbytes; --i >= 0; ) {
			if (Data[i] != 0) {
			    q = D_TO_NUM(Data[i]);
			    Data[i] = NUM_TO_D((q * 0x7F) / den);
			}
		    }
		    change++;
		}
	    }
	    break;
	case READ:
	    printf("Filename? [%s] ", Oldfile);
	    if (fgets(fbuf, sizeof fbuf, stdin)) {
		if (*fbuf && *fbuf != '\n') {
		    for (fp = Oldfile, bp = fbuf; (*fp = *bp++) > ' '; fp++);
		    *fp = '\0';
		}
		readdata(Oldfile);
		upmisc();
		change++;
	    }
	    break;
	case WRITE:
	    if ((i = creat(OUTFILE, 0644)) < 0)
		perror(OUTFILE);
	    else {
		writedata(i);
		close(i);
		fprintf(stderr, "\"%s\" written\n", OUTFILE);
	    }
	    break;
	case READDSC:
	    printf("Filename? [%s] ", Oldfile);
	    if (fgets(fbuf, sizeof fbuf, stdin)) {
		if (*fbuf && *fbuf != '\n') {
		    for (fp = Oldfile, bp = fbuf; (*fp = *bp++) > ' '; fp++);
		    *fp = '\0';
		}
		readdsc(Oldfile);
		upmisc();
		change++;
	    }
	    break;
	case WRITEDSC:
	    if ((i = creat(OUTFILE, 0644)) < 0)
		perror(OUTFILE);
	    else {
		writedsc(i);
		close(i);
		fprintf(stderr, "\"%s\" written in dsc format\n", OUTFILE);
	    }
	    break;
	case CONTROLS:
	    i = (int) window_get(Controls, WIN_SHOW);
	    window_set(Controls, WIN_SHOW, (TRUE + FALSE) - i, 0);
	    window_set(Fmparms, WIN_SHOW, i, 0);
	    break;
	case QUIT:
	    quit();
	    break;
	}
	return(change);
}

scale(unflg)
{
	register int i, d, s, b;
	int nbytes, lb, hb, db, ls, hs, ds, topb;

	nbytes = BPP * Pcnt;
	if (Scale.num < 1)
	    return;
	if (Scale.num == 1) {
	    s = Scale.scale[0];
	    s = s? s : 1;
	    for (i = nbytes; --i >= 0; ) {
		if ((d = Data[i]) == 0)		/* don't mess with 0 */
		    continue;
		d = D_TO_NUM(d);
		if (unflg)
		    d = ((d << 8) + (s >> 1)) / s;
		else
		    d = (d * s + 127) >> 8;
		d = NUM_TO_D(d);
		Data[i] = d <= 0? 1 : (d > 0xFF? 0xFF : d);
	    }
	} else {
	    for (i = 0; i <= Scale.num; i++) {
		if (i <= 0) {
		    ls = Scale.scale[0];
		    lb = 0;
		} else {
		    ls = Scale.scale[i-1];
		    lb = Scale.bpos[i-1];
		    if (lb >= nbytes)
			return;
		}
		if (i >= Scale.num) {
		    hs = Scale.scale[Scale.num-1];
		    hb = 65536;
		} else {
		    hs = Scale.scale[i];
		    hb = Scale.bpos[i];
		}
		topb = hb >= nbytes? nbytes : hb;
		ds = hs - ls;
		db = hb - lb;
		if (ls != 256 || ds != 0) {
		    for (b = lb; b < topb; b++) {
			if ((d = Data[b]) == 0)	/* don't mess with 0 */
			    continue;
			d = D_TO_NUM(d);
			s = ls + (ds * (b - lb)) / db;
			if (s == 0)
			    s = 1;
			if (unflg)
			    d = ((d << 8) + (s >> 1)) / s;
			else
			    d = (d * s + 127) >> 8;
			d = NUM_TO_D(d);
			Data[b] = d < 0? 1 : (d > 0xFF? 0xFF : d);
		    }
		}
	    }
	}
}

readdata(file)
char	*file;
{
	register int i, j, nbytes;
	FILE *ifp;

	if (file && *file) {
	    if ((ifp = sopen(file, "r")) == NULL) {
		perror(file);
		return(0);
	    }
	} else {
	    ifp = 0;
	    sprintf(Oldfile, "BLANK");
	}
	while (ifp) {				/* find wavedata */
	    while ((i = fgetc(ifp)) != EOF && i != SX_CMD);
	    if (i != SX_CMD) {
		fprintf(stderr, "no wavedata found in %s\n", file);
		exit(1);
	    }
	    if (fgetc(ifp) == ID_ENSONIQ
	     && fgetc(ifp) == ID_MIRAGE
	     && fgetc(ifp) == MIR_SXF_DAT_WAVE)
		break;
	}
	if (ifp) {
	    Pcnt = fgetc(ifp);
	    Pcnt += fgetc(ifp) << 4;
	} else
	    Pcnt = 16;
	if (Pcnt <= 0 || Pcnt > 256) {
	    fprintf(stderr, "Weird page count; %d\n", Pcnt);
	    exit(1);
	}
	nbytes = BPP * Pcnt;
	if (Data)
	    free(Data);
	if (!(Data = (uchar *) malloc(nbytes))) {
	    fprintf(stderr, "malloc(%d) failed\n", nbytes);
	    exit(3);
	}
	for (i = 0; i < nbytes; i++) {
	    if (ifp) {
		j = fgetc(ifp);
		j += fgetc(ifp) << 4;
	    } else
		j = ZERO;
	    Data[i] = j;
	}
	if (ifp)
	    fclose(ifp);
	Ppp = VIEWWIDTH / Pcnt;
	return(1);
}

writedata(fh)
{
	register uchar *dp, *bp;
	register int csum;
	uchar buf[2 * BPP];
	int p, i;

	csum = 0;
	bp = buf;
	*bp++ = RT_RESET;
	*bp++ = SX_CMD;
	*bp++ = ID_ENSONIQ;
	*bp++ = ID_MIRAGE;
	*bp++ = MIR_SXF_DAT_WAVE;
	*bp++ = (Pcnt & 0x0F);
	*bp++ = (Pcnt >> 4);
	csum = bp[-2] + bp[-1];
	write(fh, buf, bp - buf);
	dp = Data;
	for (p = 0; p < Pcnt; p++) {
	    bp = buf;
	    for (i = 0; i < BPP; i++) {
		*bp = (*dp & 0x0F);
		csum += *bp++;
		*bp = (*dp++ & 0xF0) >> 4;
		csum += *bp++;
	    }
	    write(fh, buf, 512);
	    csum &= 0x7F;
	}
	buf[0] = (csum & 0x7F);
	buf[1] = 0xF7;
	write(fh, buf, 2);
}

readdsc(file)
char	*file;
{
	register int i, j, k, nbytes;
	char c;
	long rnbytes;
	struct stat statb;
	FILE *ifp;

	if (file && *file) {
	    if ((ifp = sopen(file, "r")) == NULL) {
		perror(file);
		return(0);
	    }
	} else
	    return(readdata((char *) 0));
	j = getc(ifp);
	k = getc(ifp);
	if (((k << 8) | j) != DSCMAGIC) {
	    fprintf(stderr, "%s is not a DSC file\n", file);
	    return(0);
	}
	if (fstat(fileno(ifp), &statb) < 0) {
	    perror(file);
	    return(0);
	}
	rnbytes = (statb.st_size - 2) / 2;	/* ignore half of them 16->8 */
	Pcnt = (rnbytes + BPP - 1) / BPP;
	if (Pcnt <= 0 || Pcnt > 256) {
	    fprintf(stderr, "weird size; %d (pages)\n", Pcnt);
	    exit(1);
	}
	nbytes = BPP * Pcnt;
	if (Data)
	    free(Data);
	if (!(Data = (uchar *) malloc(nbytes))) {
	    fprintf(stderr, "malloc(%d) failed\n", nbytes);
	    exit(3);
	}
	for (i = 0; i < nbytes; i++) {
	    if (ifp) {
		j = fgetc(ifp);				/* low byte */
		k = fgetc(ifp);				/* high byte */
		if (j == EOF || k == EOF) {
		    fclose(ifp);
		    ifp = 0;
		    k = ZERO;
		} else {
		    c = k;				/* extend sign */
		    j = c + ((j & 0x80)? 1 : 0);	/* half adjust */
		    k = j + ZERO;			/* move 0 to ZERO */
		}
	    } else
		k = ZERO;
	    Data[i] = (k > 0xFF? 0xFF : (k < 1? 1 : k));
	}
	if (ifp)
	    fclose(ifp);
	Ppp = VIEWWIDTH / Pcnt;
	return(1);
}

writedsc(fh)
{
	register uchar *dp, *bp;
	uchar buf[2 * BPP];
	int p, i;

	buf[0] = (DSCMAGIC & 0xFF);
	buf[1] = (DSCMAGIC >> 8);
	write(fh, buf, 2);
	dp = Data;
	for (p = 0; p < Pcnt; p++) {
	    bp = buf;
	    for (i = 0; i < BPP; i++) {
		*bp++ = 0;
		*bp++ = *dp++ - ZERO;		/* move ZERO to 0 */
	    }
	    write(fh, buf, bp - buf);
	}
}

quit()
{
	while (ready_cursor());
	exit(0);
	/*NOTREACHED*/
}

/*
**	FM - Simulate FM synthesis
**	Simple, 2 operator model	psl 3/88
**    +----fb-----+
**    |  +------+ |      +------+
**    +->| op1  |-+-g1-->| op2  |-g2->
**   f1->| env1 |    f2->| env2 |
**       +------+        +------+
**	args are: op1 freq, op1 gain, op2 freq, op2 gain, feedback
*/

#define	PI		0x8000

fm()
{
	register int t, nbytes, v, bpc;
	double w1, w2, ppr, fbg, og1, og2, o1, o2;
	struct estr *e1, *e2;
	double env();
	extern double ipslsin();

	Pcnt = Fmp.pcnt;
	nbytes = BPP * Pcnt;
	bpc = nbytes / Fmp.cycs;
	if (Data)
	    free(Data);
	if (!(Data = (uchar *) malloc(nbytes))) {
	    fprintf(stderr, "malloc(%d) failed\n", nbytes);
	    return(0);
	}
	w1 = (Fmp.op[0].f * 0.02 * PI) / bpc;
	w2 = (Fmp.op[1].f * 0.02 * PI) / bpc;
	ppr = PI / 3.1415926535;	/* PSLs per radian */
	fbg = ppr * Fmp.fb / 100.;
	og1 = ppr * Fmp.op[0].g / 100.;
	og2 = 127 * Fmp.op[1].g / 100.;
	e1 = &Fmp.op[0].e;
	e2 = &Fmp.op[1].e;
	o1 = 0.;
	for (t = 0; t < nbytes; t++) {
	    o2 = ipslsin((int) (w2 * t + og1 * env(e1, t) * o1 + 0.5));
	    o1 = ipslsin((int) (w1 * t + fbg * o1 + 0.5));
	    v = ZERO + og2 * env(e2, t) * o2;
	    Data[t] = (v <= 0? 1 : (v > 255? 255 : v));
	}
	Ppp = VIEWWIDTH / Pcnt;
	strcpy(Oldfile, "FM");
	upmisc();
	return(1);
}

double
env(ep, t)	/* return envelope amplitude at time t */
struct	estr *ep;
{
	register int i, ls, lb, ds, db, s;

	if ((i = ep->num) < 1)
	    return(1.);
	if (i == 1)
	    return(ep->scale[0] / 256.);
	for (i = ep->num; --i >= 0 && t < ep->bpos[i]; );
	if (i < 0)
	    return(ep->scale[0] / 256.);
	if (i == ep->num - 1 || t == ep->bpos[i])
	    return(ep->scale[i] / 256.);
	ls = ep->scale[i];
	ds = ep->scale[i + 1] - ls;
	lb = ep->bpos[i];
	db = ep->bpos[i + 1] - lb;
	s = ls + (ds * (t - lb) + (db >> 1)) / db;
	return(s / 256.);
}

