/*
 * GLIB - a Generic LIBrarian and editor for synths
 */

#include "glib.h"
#include <ctype.h>

char *Reason = "";

int Currrow = 0;	/* at top of screen, for messages */
int Libbank = 0;	/* from 0 to LIBBANKS-1, is the current library bank*/
int Nsynths = 0;

int Lastvalue = 0;

char *Currdata;
char *Yankdata;		/* current 'yank' buffer (middle of screen) */

struct peredinfo *PE;	/* array of per-editor miscellany */

char Buff[BUFSIZ];
int Redraw = 0;		/* if non-0, edit screen is completely redrawn. */
			/* parameter functions can make use of this. */
int Changed = 0;
int DataID = 0;		/* Data Id for file write and read operations */

/* All the global values below are set as appropriate for the */
/* synthesizer currently being dealt with. */

int Nvoices = 0;
int Voicesize =0;
int Namesize = 0;
int Libindex;		/* from 0 to Nvoices-1 */
int Synindex;		/* from 0 to Nvoices-1 */
int Channel;
int Editrow;		/* from 0 to NUMONSCREEN-1 */
int Editcol;		/* 0==synth, 1==library */
char *Libdata;		/* current library data (includes all LIBBANKS) */
			/* ie. the stuff on the right side of the screen */
char *Syndata;		/* current synth data (1 bank), ie. the left side */
struct paraminfo *P;	/* list of parameter info */
struct labelinfo *L;	/* arbitrary screen labels for edit screen */
char *Synthname;

int (*Sendedit)();	/* function to send parameters to synth's edit buffer*/
int (*Datain)();	/* convert data from file-storage format to the */
			/* format stored in the P[] parameter array (p_val) */
int (*Dataout)();	/* reverse of Datain */
int (*Sendone)();	/* function to send one (permanent) voice to synth */
int (*Sendbulk)();	/* function to send bulk dump to synth */
int (*Getbulk)();	/* reverse of Sendbulk */
char *(*Nameof)();	/* pulls voice name out of file-storage data */
int (*Setnameof)();	/* reverse of Nameof */
char *(*Numof)();	/* convert voice number to on-screen text */
int (*Cvtnum)();	/* convert visible voice number to std. format */
int (*Cvtanum)();	/* convert alphanumeric voice number to std. format */
			/* should never define both Cvtnum and Cvtanum */
main()
{
	int n;

	hello();
	windinit();
	initstuff();

	if ( Nsynths == 0 )
		windstr("Hey, the E array is empty?");
	else if ( Nsynths == 1 ) {
		/* If there's only 1 synth, don't bother asking */
		setedit(0);
		libinteract();
	}
	else {
		while ( (n=choosesynth()) >= 0 ) {
			setedit(n);
			libinteract();
			unsetedit(n);
		}
	}
	bye();
}

/* choose a synth, returning its position in the E array */
choosesynth()
{
	int n, pick;

    retry:
	flushconsole();
	windclear();
	windgoto(2,23);
#ifdef KAWAIK1
	windstr("Kawai K1 Librarian/Editor");
#else
#ifdef ROLANDD10
	windstr("Roland D-10 Librarian/Editor");
#else
	windstr("GLIB - A Generic Librarian/Editor");
#endif
#endif

	for ( n=1; n<=Nsynths; n++ )
		libchoice(n);
	windgoto(8+Nsynths,27);
	windstr("q  -  Quit");
	windgoto(11+Nsynths,27);
	windstr("Make selection --> ");
	windrefresh();
	pick = mouseorkey();
	if ( pick == 'q' || pick == EOF )
		return(-1);
	if ( pick != MOUSE )
		pick = pick - '0';
	else {
		int row, col;
		getmouse(&row,&col);
		/* wait until mouse goes down */
		while ( statmouse() > 0 )
			;
		pick = row - 6; 
	}
	if ( pick < 1 || pick > Nsynths )
		goto retry;
	return(pick-1);
}

libchoice(n)
{
	windgoto(6+n,27);
	sprintf(Buff,"%d  -  %s",n,E[n-1].ed_name);
	windstr(Buff);
}

#ifndef SINGLEDATA

initstuff()
{
	int n, banksize, maxvsize;
	
	maxvsize = 0;
	for ( n=0; E[n].ed_name != NULL; n++ ) {
		if ( maxvsize < E[n].ed_vsize )
			maxvsize = E[n].ed_vsize;
	}
	Nsynths = n;
	Currdata = alloc( maxvsize );

	/* allocate an array of peredinfo structs */
	PE =(struct peredinfo *)alloc((int)(Nsynths*sizeof(struct peredinfo)));
	for ( n=0; n<Nsynths; n++ ) {
		banksize =  E[n].ed_nvoices * E[n].ed_vsize;

		PE[n].ed_libdata = alloc(LIBBANKS * banksize);
		clrdata(PE[n].ed_libdata, LIBBANKS * banksize);

		PE[n].ed_syndata = alloc(banksize);
		clrdata(PE[n].ed_syndata, banksize);

		PE[n].ed_yankdata = alloc(E[n].ed_vsize);
		clrdata(PE[n].ed_yankdata, E[n].ed_vsize);

		PE[n].ed_libindex = 0;
		PE[n].ed_synindex = 0;
		PE[n].ed_channel = 1;
		PE[n].ed_erow = 0;
		PE[n].ed_ecol = 0;
	}
}

#else

initstuff()
{
	int n, banksize, maxvsize;
	char *lib, *syn, *yank;
	
	maxvsize = 0;
	for ( n=0; E[n].ed_name != NULL; n++ ) {
		if ( maxvsize < E[n].ed_vsize )
			maxvsize = E[n].ed_vsize;
	}
	Nsynths = n;
	Currdata = alloc( maxvsize );

	PE =(struct peredinfo *)alloc((int)(Nsynths*sizeof(struct peredinfo)));

	/* allocate common data areas */
	banksize =  E[0].ed_nvoices * E[0].ed_vsize;

	lib = alloc(LIBBANKS * banksize);
	clrdata(lib, LIBBANKS * banksize);

	syn = alloc(banksize);
	clrdata(syn, banksize);

	yank = alloc(E[0].ed_vsize);
	clrdata(yank, E[0].ed_vsize);

	for ( n=0; n<Nsynths; n++ ) {
		PE[n].ed_libdata = lib;
		PE[n].ed_syndata = syn;
		PE[n].ed_yankdata = yank;
		PE[n].ed_libindex = 0;
		PE[n].ed_synindex = 0;
		PE[n].ed_channel = 1;
		PE[n].ed_erow = 0;
		PE[n].ed_ecol = 0;
	}
}

#endif

clrdata(data,size)
char *data;
{
	register char *p, *endp;

	p = data;
	endp = data+size;

	while ( p<endp )
		*p++ = 0;
}

#ifndef SINGLEDATA

setedit(n)
{
	Synthname = E[n].ed_name;
	Datain = E[n].ed_din;
	Dataout = E[n].ed_dout;
	Nvoices = E[n].ed_nvoices;
	Sendedit = E[n].ed_sedit;
	Sendone = E[n].ed_sone;
	Sendbulk = E[n].ed_sbulk;
	Getbulk = E[n].ed_gbulk;
	Nameof = E[n].ed_nof;
	Numof = E[n].ed_numof;
	Cvtnum = E[n].ed_cvtnum;
	Cvtanum = E[n].ed_cvtanum;
	Setnameof = E[n].ed_snof;
	Voicesize = E[n].ed_vsize;
	Namesize = E[n].ed_nsize;
	DataID = E[n].ed_dataid;
	Libdata = PE[n].ed_libdata;
	Syndata = PE[n].ed_syndata;
	Yankdata = PE[n].ed_yankdata;
	Libindex = PE[n].ed_libindex;
	Synindex = PE[n].ed_synindex;
	Channel = PE[n].ed_channel;
	Editrow = PE[n].ed_erow;
	Editcol = PE[n].ed_ecol;
	clrdata(Currdata,Voicesize);
	P = E[n].ed_params;
	L = E[n].ed_labels;
}

unsetedit(n)
{
	int k;

	DataID = 0;
	PE[n].ed_libindex = Libindex;
	PE[n].ed_synindex = Synindex;
	PE[n].ed_channel = Channel;
	PE[n].ed_erow = Editrow;
	PE[n].ed_ecol = Editcol;
	for ( k=0; k<Voicesize; k++ )
		PE[n].ed_yankdata[k] = Yankdata[k];
}

#else

setedit(n)
{
	Synthname = E[n].ed_name;
	Datain = E[n].ed_din;
	Dataout = E[n].ed_dout;
	Nvoices = E[n].ed_nvoices;
	Sendedit = E[n].ed_sedit;
	Sendone = E[n].ed_sone;
	Sendbulk = E[n].ed_sbulk;
	Getbulk = E[n].ed_gbulk;
	Nameof = E[n].ed_nof;
	Numof = E[n].ed_numof;
	Cvtnum = E[n].ed_cvtnum;
	Cvtanum = E[n].ed_cvtanum;
	Setnameof = E[n].ed_snof;
	Voicesize = E[n].ed_vsize;
	Namesize = E[n].ed_nsize;
	DataID = E[n].ed_dataid;
	Libdata = PE[n].ed_libdata;
	Syndata = PE[n].ed_syndata;
	Yankdata = PE[n].ed_yankdata;
	Libindex = PE[n].ed_libindex;
	Synindex = PE[n].ed_synindex;
	Channel = PE[n].ed_channel;
	Editrow = PE[n].ed_erow;
	Editcol = PE[n].ed_ecol;
	clrdata(Currdata,Voicesize);
	P = E[n].ed_params;
	L = E[n].ed_labels;
}

unsetedit(n)
{
	int j, k;

	DataID = 0;
	for(j=0; j < Nsynths; j++) {
		PE[j].ed_libindex = Libindex;
		PE[j].ed_synindex = Synindex;
		PE[j].ed_channel = Channel;
		PE[j].ed_erow = Editrow;
		PE[j].ed_ecol = Editcol;
		for ( k=0; k<Voicesize; k++ ) {
			PE[j].ed_yankdata[k] = Yankdata[k];
		}
	}
}

#endif

/* template - show the boxes and such on the main library screen */
template()
{
	int n, k, r;

	r = FIRSTROW-1;
	sprintf(Buff,"%s",Synthname);
	n = 13 - strlen(Buff)/2;	/* center it */
	windgoto(r,n<0?0:n);
	windstr(Buff);
	r++;
	windgoto(r,0);
	for ( n=0; n<25; n++ )
		windputc('=');
	windgoto(r,53);
	for ( n=0; n<25; n++ )
		windputc('=');
	for ( n=r+1; n<(r+13); n++ ) {
		windgoto(n,0);
		windputc('|');
		windgoto(n,5);
		windputc('|');
		windgoto(n,24);
		windputc('|');

		k=53;
		windgoto(n,k);
		windputc('|');
		windgoto(n,k+5);
		windputc('|');
		windgoto(n,k+24);
		windputc('|');
	}
	windgoto(r+13,0);
	for ( n=0; n<25; n++ )
		windputc('=');
	windgoto(r+13,53);
	for ( n=0; n<25; n++ )
		windputc('=');

	windgoto(YANKROW-2,YANKCOL);
	windstr("  Yank Buffer");
	windgoto(YANKROW-1,YANKCOL);
	windstr(" ------------- ");
	windgoto(YANKROW,YANKCOL);
	windstr("               ");
	windgoto(YANKROW+1,YANKCOL);
	windstr(" ------------- ");
	windrefresh();
}

/* clear the message area */
clearmess()
{
	int n;
	for(n=1;n<(FIRSTROW-1);n++)
		winderaserow(n);
	Currrow = 0;
}

/* set the current voice (ie. the synth's edit buffer) to the indicated */
/* voice.  c==0 is the synth (left) side, c==1 is the library (right) side. */
editto(r,c)
{
	int voicenum;

	/* Clear the existing '*' */
	editchar(' ',Editrow,Editcol);
	editchar('*',Editrow=r,Editcol=c);
	if ( Editcol==0 ) {
		/* we're on the synth side */
		voicenum = Editrow+Synindex;
		tocurrent(Syndata,voicenum);
	}
	else {
		/* we're on the lib side */
		voicenum = Editrow+Libindex;
		tocurrent(bankvoice(0),voicenum);
	}
}

editchar(ec,r,c)
{
	r = r + FIRSTROW + 1;
	if ( c == 0 )
		c = LEFTSIDE-1;
	else
		c = RIGHTSIDE-1;
	windgoto(r,c);
	windputc(ec);
	windrefresh();
}

/* control interaction on the main library bank screen */
libinteract()
{
	int c, n, swap, voicenum, maxindex, q;
	char *p, *data;

	flushmidi();
	drawall();
	for ( ;; ) {
		Currrow = 0;
		winderaserow(Currrow);
		windgoto(Currrow,0);
		windstr("Command --> ");
		windrefresh();
		
		c = mouseorkey();
		if ( c == MOUSE ) {
			libmouse();
			continue;
		}

		if ( isprint(c) )
			windputc(c);
		clearmess();
		switch ( c ) {
		case ' ':
			playnote(1);
			break;
		case '\n':
#ifndef macintosh
		case '\r':
#endif
			/* ignore */
			break;
		case EOF:
		case 'q':
			return;
		case CH_REDRAW:
			drawall();
			break;
		case 's':
		case 'p':
			swap = (c=='s')?1:0;
			if ( Editcol==0 )
				tosyn(Synindex+Editrow,Yankdata,swap);
			else
				tolib(Libindex+Editrow,Yankdata,swap);
			updatedisplay();
			pryankname();
			break;
		case 'y':
			for(n=0;n<Voicesize;n++)
				Yankdata[n] = Currdata[n];
			pryankname();
			break;
		case '?':
			helpmessage();
			break;
		case SCR_DOWN:
			if(Nvoices < NUMONSCREEN)
				q = Nvoices;
			else
				q = NUMONSCREEN;

			maxindex = Nvoices - q;
			if ( Editcol==0 ) {
				/* we're on the synth side */
				if ( (Synindex+=q/2) > maxindex )
					Synindex = maxindex;
			}
			else {
				/* we're on the lib side */
				if ( (Libindex+=q/2) > maxindex )
					Libindex = maxindex;
			}
			updatedisplay();
			break;
		case SCR_UP:
			if ( Editcol==0 ) {
				/* we're on the synth side */
				if ( (Synindex-=NUMONSCREEN/2) < 0 )
					Synindex = 0;
			}
			else {
				/* we're on the lib side */
				if ( (Libindex-=NUMONSCREEN/2) < 0 )
					Libindex = 0;
			}
			updatedisplay();
			break;
		case '\033':
		case '`':
			allnotesoff();
			break;
		case 't':
			transcmd();
			editto(Editrow,Editcol);
			break;
		case 'd':	/* download FROM synth */
			clrdata(Syndata,Nvoices*Voicesize);
			if ( readsynth(Syndata) == 0 ) {
				syntodisplay(Synindex=0);
				if(Editcol == 0)
					editto(Editrow,Editcol);
			}
			break;
		case 'u':	/* upload TO synth */
			if ( Editcol==0 ) {
				voicenum = Editrow+Synindex;
				data = &(VOICEBYTE(Syndata,voicenum,0));
			}
			else
				data = bankvoice(Editrow+Libindex);
			upload(data);
			break;
		case 'r':
			readall();
			if(Editcol == 1)
				editto(Editrow,Editcol);
			break;
		case 'R':
			readprintable();
			if(Editcol == 1)
				editto(Editrow,Editcol);
			break;
		case 'w':
			writeall();
			break;
		case 'W':
			writeprintable();
			break;
		case 'c':
			setchan();
			break;
		case 'b':
			/* cycle through banks, from 0 to LIBBANKS-1 */
			if ( ++Libbank >= LIBBANKS )
				Libbank = 0;
			libtodisplay(Libindex);
			updatedisplay();
			break;
		case 'e':
			if ( Namesize == 0)
				p = NULL;
			else
				p = (*Nameof)(Currdata);
			if ( Editcol==0 ) {
				voicenum = Editrow+Synindex;
				data = &(VOICEBYTE(Syndata,voicenum,0));
				editdata(p,data);
				windclear();
				/* Update Currdata */
				for ( n=0; n<Voicesize; n++ )
				    Currdata[n] = VOICEBYTE(Syndata,voicenum,n);
			}
			else {
				data = bankvoice(Editrow+Libindex);
				editdata(p,data);
			}
			drawall();
			break;
		case 'g': /* goto a specific voice */
			do_goto();
			break;
		case CH_LEFT:
		case ALTCH_LEFT:
			if ( Editcol==1 )
				editto(Editrow,0);
			break;
		case CH_DOWN:
		case ALTCH_DOWN:
			if(Nvoices < NUMONSCREEN)
				q = Nvoices;
			else
				q = NUMONSCREEN;
			if ( Editrow < (q-1) )
				editto(Editrow+1,Editcol);
			else {
				/* we're at the bottom, so try to scroll */
				if ( Editcol==0 ) {
					/* we're on the synth side */
					if (Synindex<(Nvoices-q))
						Synindex++;
				}
				else {
					/* we're on the lib side */
					if (Libindex<(Nvoices-q))
						Libindex++;
				}
				updatedisplay();
			}
			break;
		case CH_UP:
		case ALTCH_UP:
			if ( Editrow>0 )
				editto(Editrow-1,Editcol);
			else {
				/* we're at the top, so try to scroll */
				if ( Editcol==0 ) {
					/* we're on the synth side */
					if (Synindex>0)
						Synindex--;
				}
				else {
					/* we're on the lib side */
					if (Libindex>0)
						Libindex--;
				}
				updatedisplay();
			}	
			break;
		case CH_RIGHT:
		case ALTCH_RIGHT:
			if ( Editcol==0 )
				editto(Editrow,1);
			break;
		case 'f':
			filelist();
			break;
		default:
			message("Unrecognized command!  Press '?' for help.");
			break;
		}
	}
}

filelist()
{
	char *p, *q, buff[BUFSIZ];
	int n, ninline = 0, nprinted = 0;

	clearmess();
	Currrow = -1;	/* To start message on top line */
	openls();
	message("Files in current directory:");
	strcpy(buff,"  ");
	while ( (p=nextls()) != NULL ) {
		/* add the next file to the line being constructed */
		q = &buff[strlen(buff)];
		strcpy(q,p);
		q += (n=strlen(p));
		while ( n++ < 15 )
			*q++ = ' ';
		*q = '\0';
		if ( ninline++ > 3 ) {
			message(buff);
			if ( nprinted++ > 4 ) {
				message("Press any key to continue ...");
				getconsole();
				clearmess();
				nprinted = 0;
			}
			strcpy(buff,"  ");
			ninline = 0;
		}
	}
	if ( ninline > 0 )
		message(buff);
	closels();
}

libmouse()
{
	int row, col, q;

	getmouse(&row,&col);
	if(Nvoices < NUMONSCREEN)
		q = Nvoices;
	else
		q = NUMONSCREEN;
	if ( row <= FIRSTROW || row > FIRSTROW+q+1 )
		goto getout;
	if ( col < Cols/2 )
		col = 0;
	else
		col = 1;
	row = row - FIRSTROW - 1;
	editto(row,col);
getout:
	/* wait until mouse button is released */
	while ( statmouse() > 0 )
		;
}

do_goto()
{
	int n, r, maxindex, new_ecol,q;
	char sbuf[100], *sp;
	
	message("");
	message("Where to? ");
	windgets(sbuf);
	
	sp = sbuf;
	switch(*sp++) {
	  case 's': /* synth side */
		new_ecol = 0;
		break;
	  case 'l': /* library side */
		new_ecol = 1;
		break;
	  default: /* no change in side */
	  	new_ecol = Editcol;
		sp--;	/* but don't trash the first character */
		break;
	}
	
	clearmess();
	r = sscanf(sp, "%d", &n); /* this may fail - we handle it later */
	if(Cvtnum != NULL) { /* convert to internal format if needed */
		n = (*Cvtnum)(n) + 1; /* we are 1-based for user input */
	}
	if (Cvtanum != NULL) { /* convert using alphanumeric voice number */
		n = (*Cvtanum)(sp) + 1;
		if (n) r = 1;
	}
	if(r != 1) {
		message("type one of: sn, ln, or n");
		message("  s = synth side (literal character 's')");
		message("  l = library side (literal character 'l')");
#ifdef KAWAIK1
		message("  n = voice number to select (letter + number)");
#else
#ifdef KAWAIK5
		message("  n = voice number to select (letter + number)");
#else
		message("  n = voice number to select (an integer)");
#endif
#endif
		return;
	}
	if(n <= 0 || n > Nvoices) { /* 1-based */
		message("Bad voice number!");
		return;
	}
	
	/* it can be done.  nuke the old '*' and change columes (if needed) */
	editchar(' ', Editrow, Editcol);
	Editcol = new_ecol;
	
	/* try to center it */
	if(Nvoices < NUMONSCREEN)
		q = Nvoices;
	else
		q = NUMONSCREEN;
	maxindex = Nvoices - q;
	if(Editcol == 0) {
		Synindex = (n - 1) - q/2; /* 0-based */
		if(Synindex < 0) { /* impossible to center */
			Synindex = 0; /* so do your best */
		} else if(Synindex > maxindex) {
			Synindex = maxindex;
		}
		Editrow = (n - 1) - Synindex; /* and put a '*' on it */
	} else {
		Libindex = (n - 1) - q/2; /* 0-based */
		if(Libindex < 0) {
			Libindex = 0;
		} else if(Libindex > maxindex) {
			Libindex = maxindex;
		}
		Editrow = (n - 1) - Libindex;
	}
	
	updatedisplay(); /* do the real work */
	return;
}

upload(data)
char *data;
{
	int c, n;
	char num[16];

	message("");
	message("Upload TO synth:");
	message("                 c - current voice");
	message("                 a - ALL voices");
	message("Choose --> ");
	c = getconsole();
	if ( c == 'c' ) {
		clearmess();
		if ( Sendone == NULL ) {
			message("Single voices can't be sent to that synth!");
			return;
		}
		message("What voice number to you want to send it TO? --> ");
		windgets(num);
		clearmess();
		n = atoi(num);
		if(Cvtnum != NULL) { /* convert to internal format if needed */
			n = (*Cvtnum)(n) + 1; /* we are 1-based for user input */
		}
		if (Cvtanum != NULL) { /* howzabout alphanumeric format? */
			n = (*Cvtanum)(num) + 1;
		}
		if ( n > 0 && n <= Nvoices ) {
			if ( (*Sendone)(n-1,data) != 0 ) { /* 0-based on calls -SAF */
				message("Unable to write data to synth!");
				sprintf(Buff,"Reason: %s",Reason);
				message(Buff);
				return;
			}
		} else {
			message("Bad voice number!");
		}
	}
	else if ( c == 'a' ) {
		clearmess();
		if ( Sendbulk != NULL ) {
			message("Uploading to synth using bulk routine.");
			if ( (*Sendbulk)(Syndata) != 0 ) {
				message("Upload failure!");
				sprintf(Buff,"Reason: %s\n",Reason);
				message(Buff);
				return;
			}
		} else {
			message("Uploading to synth using single routine.");
			for ( n=0; n<Nvoices; n++ ) {
				if ( (*Sendone)(n, &(VOICEBYTE(Syndata,n,0)) ) != 0 ) {
					message("Unable to write data to synth!");
					sprintf(Buff,"Reason: %s",Reason);
					message(Buff);
					return;
				}
			}
		}
		/* clearmess(); */
	}
	else {
		clearmess();
		message("Bad choice!");
	}
}

helpmessage()
{
clearmess();
sprintf(Buff,"%s,%s,%s,%s - move around              e - edit current voice",
	STR_LEFT,STR_DOWN,STR_UP,STR_RIGHT);
message(Buff);
message("r - read voices from a file        y - yank into buffer");
message("w - write voices to a file         p - put from buffer");
message("b - cycle through library banks    s - swap current voice with yank buffer");
message("t - transfer all voices            f - list files in current directory");
message("d - download voices from synth     c - set MIDI channel");
message("u - upload voices to synth         g - 'goto' form of moving around");
message("q - quit                           <space> - play a note");
}

updatedisplay()
{
	if ( Editcol==0 ) {
		/* we're on the synth side */
		syntodisplay(Synindex);
		editto(Editrow,Editcol);
	}
	else {
		/* we're on the lib side */
		libtodisplay(Libindex);
		editto(Editrow,Editcol);
	}
}

pryankname()
{
	char ybuff[33];
	char *p;

	if ( Namesize == 0 )
		return;

	windgoto(YANKROW,YANKCOL-4);
	windstr("                    ");
	strcpy(ybuff,(*Nameof)(Yankdata));
	/* take off trailing blanks */
	p = ybuff + strlen(ybuff) - 1;
	while ( p>ybuff && *p == ' ' )
		*p-- = '\0';
	windgoto(YANKROW,YANKCOL+7-strlen(ybuff)/2);
	windstr(ybuff);
	windrefresh();
}

transcmd()
{
	int fromc;

	message("");
	message("Transfer ALL voices:");
	message("                       1:   <<-----   from library bank to synth bank");
	message("                       2:   ----->>   from synth bank to library bank");
	message("1 or 2 --> ");
	fromc = getconsole();
	windputc(fromc);
	if ( fromc!='1' && fromc!='2' ) {
		clearmess();
		return;
	}
	switch ( fromc ) {
	case '1':
		copyall(bankvoice(0),Syndata);
		syntodisplay(Synindex);
		clearmess();
		message("Use the 'u'pload command to actually send the synth bank voices to the synth.");
		break;
	case '2':
		copyall(Syndata,bankvoice(0));
		libtodisplay(Libindex);
		clearmess();
		break;
	}
}

copyall(fromdata,todata)
char *fromdata;
char *todata;
{
	int n, v;

	for ( v=0; v<Nvoices; v++ )
		for ( n=0; n<Voicesize; n++ )
			VOICEBYTE(todata,v,n) = VOICEBYTE(fromdata,v,n);
}

message(s)
char *s;
{
	windgoto(++Currrow,0);
	windstr(s);
	windrefresh();
}

setchan()
{
	int c;

	message("New MIDI channel --> ");
	windgets(Buff);
	if ( (c=atoi(Buff)) <= 0 || c > 16 ) {
		clearmess();
		message("Invalid channel!");
	}
	else {
		clearmess();
		Channel = c;
		showchan();
	}
}

showchan()
{
	windgoto(20,31);
	windstr("MIDI Channel: ");
	sprintf(Buff,"%d ",Channel);
	windstr(Buff);
	windrefresh();
}

/* read data from a file, filling the current library bank. */
readall()
{
	char fname[100];
	FILE *f;
	int v, n, r;
	char *p;

	message("File name --> ");
	windgets(fname);
#ifdef BSD
	OPENBINFILE(f,fname,"r");
#else
	OPENBINFILE(f,fname,"rb");
#endif
	if (f == NULL ) {
		sprintf(Buff,"Can't open '%s'!",fname);
		message(Buff);
		return;
	}
	/* If the first byte matches DataID, then the format is OK. */
	if (DataID) n = (getc(f) & 0xff);
	else n = 0;

#ifndef ROLANDD10
#ifndef KAWAIIK1
	if (n != DataID && n <= 31) {
		ungetc(n,f);
		n = DataID;
	}
#endif
#endif

	if ( n == DataID ) {
		p = bankvoice(0);
		for ( v=0; v<Nvoices; v++ ) {
			for ( n=0; n<Voicesize; n++ )
				*p++ = (getc(f) & 0x7f);
		}
		r = 0;
	} else {
		sprintf(Buff, "'%s' is invalid for this function!\n", fname);
		message(Buff);
		r = 1;
	}
	fclose(f);
	if ( r==0 ) {
		libtodisplay(Libindex=0);
		clearmess();
	}
}

/* read printable data from a textfile, filling the current library bank. */
readprintable()
{
	char fname[100];
	FILE *pfin;
	int v, n, r, val;
	char *p;
	char vname[20];

	message("File name --> ");
	windgets(fname);
#ifdef BSD
	OPENBINFILE(pfin,fname,"r");
#else
	OPENBINFILE(pfin,fname,"rb");
#endif
	if (pfin == NULL ) {
		sprintf(Buff,"Can't open '%s'!",fname);
		message(Buff);
		return;
	}
	/* If the first byte matches DataID, then the format is OK. */
	r = fscanf(pfin, "BANK TYPE %d", &n);

	v = -1;
	if ( r == 1 && n == DataID ) while (r) {
	    r = fscanf(pfin, " %s ", vname);

	    if (r == 1) {
		if (strcmp(vname, "voice") == 0) {
			if (v > 0) (*Dataout)(p);
			r = fscanf(pfin,"%d = %[ !-~]", &v, vname);
			if (r == 2 && v > 0) {
			    p = bankvoice(v-1);
			    (*Setnameof)(p, vname);
			}
		} else {
			r = fscanf(pfin, "= %d", &val);
			if (r == 1) setval(vname, val);
		}
	    }
	    if (r == EOF) r = 0;
	} else {
		sprintf(Buff, "'%s' is invalid for this function!\n", fname);
		message(Buff);
		r = 1;
	}
	if (v > 0) (*Dataout)(p);
	fclose(pfin);
	if ( r==0 ) {
		libtodisplay(Libindex=0);
		clearmess();
	}
}

/* write current library bank to a file */
writeall()
{
	char fname[100];
	FILE *f;
	int v, n;
	char *p;

	message("File name --> ");
	windgets(fname);
#ifdef BSD
	OPENBINFILE(f,fname,"w");
#else
	OPENBINFILE(f,fname,"wb");
#endif
	if ( f == NULL ) {
		sprintf(Buff,"Can't open '%s'!",fname);
		message(Buff);
		return;
	}
	if (DataID) putc(DataID,f);		/* write data identifier */

	p = bankvoice(0);
	for ( v=0; v<Nvoices; v++ ) {
		for ( n=0; n<Voicesize; n++ )
			putc(*p++,f);
	}
	fclose(f);
	clearmess();
}


FILE *pfout;
int pfoutflag = 0;

/* write current library bank to a file in printable form */
writeprintable()
{
	char fname[100];
	int v;
	char *p, *q;

	message("File name --> ");
	windgets(fname);
#ifdef BSD
	OPENBINFILE(pfout,fname,"w");
#else
	OPENBINFILE(pfout,fname,"wb");
#endif
	if ( pfout == NULL ) {
		sprintf(Buff,"Can't open '%s'!",fname);
		message(Buff);
		return;
	}
	fprintf(pfout, "BANK TYPE %d\n", DataID);

	for (v=0; v<Nvoices; v++) {
		p = bankvoice(v);
		q = (*Nameof)(p);
		if (q[0] > ' ') {
			(*Datain)(p);
			fprintf(pfout, "voice %d = %s\n", v+1, q);
			pfoutflag = 1;
			(*Dataout)(p);
			pfoutflag = 0;
		}
	}
	fclose(pfout);
	clearmess();
}

/* draw main library/synth voice bank screen */
drawall()
{
	windclear();
	template();
	libtodisplay(Libindex);
	syntodisplay(Synindex);
	editto(Editrow,Editcol);
	pryankname();
	showchan();
}

/*
 * tosyn
 *
 * Store the given 'data' in in voice 'voicenum' (both in Syndata
 * AND on the synth itself).  If swap is non-zero, the voice is swapped
 * with the current voice in Syndata.
 */

tosyn(voicenum,data,swap)
char *data;
{
	int n, t;

	for ( n=0; n<Voicesize; n++ ) {
		if ( swap ) {
			t = VOICEBYTE(Syndata,voicenum,n);
			VOICEBYTE(Syndata,voicenum,n) = data[n];
			data[n] = t;
		}
		else
			VOICEBYTE(Syndata,voicenum,n) = data[n];
	}
}

tolib(voicenum,data,swap)
char *data;
{
	int n, t;
	char *p = bankvoice(voicenum);

	for ( n=0; n<Voicesize; n++ ) {
		if ( swap ) {
			t = *p;
			*p = data[n];
			data[n] = t;
		}
		else
			*p = data[n];
		p++;
	}
}

tocurrent(data,voicenum)
char *data;
int voicenum;
{
	int n, f = 0;

	for ( n=0; n<Voicesize; n++ )
		if ( (Currdata[n] = VOICEBYTE(data,voicenum,n)) != 0) f = 1;
	if( f == 1 ) (*Sendedit)(Currdata);
}

/*
 * readsynth
 *
 * Read a bulk dump from the synth, with some tolerance for errors.
 */
readsynth(data)
char *data;
{
	if ( Getbulk == NULL ) {
		message("That synth is unable to dump anything!!");
		return(1);
	}
	message("Downloading from synth");
	if ( (*Getbulk)(data) == 0 )
		return(0);

	message("Unable to read data from synth!");
	sprintf(Buff,"Reason: %s",Reason);
	message(Buff);
	message("Perhaps connections are amiss?");
	return(1);
}

char *
vnumtext(n)
{
	static char vnbuff[6];

	if ( Numof == NULL ) {
		sprintf(vnbuff,"%2d",n);
		return(vnbuff);
	}
	else
		return((*Numof)(n - 1)); /* keep this 0-based */
}

/*
 * syntodisplay(n)
 *
 * Tranfer Syndata names to dialog boxes (Dxvoices) starting at n.
 */
syntodisplay(n)
{
	int k, r, q;

	if(Nvoices < NUMONSCREEN)
		q = Nvoices;
	else
		q = NUMONSCREEN;
	for ( k=0; k<q; k++ ) {
		r = FIRSTROW+1+k;
		windgoto(r,LEFTSIDE);
		sprintf(Buff,"%s |                 ",vnumtext(n+k+1));
		windstr(Buff);
		/* pull the name out of the Syndata */
		if ( Namesize != 0 ) {
			windgoto(r,LEFTSIDE+5);
			windstr((*Nameof)( & (VOICEBYTE(Syndata,n+k,0) )) );
		}
	}
	windrefresh();
#ifdef KAWAIK1
	synnames();
#endif
}

#ifdef KAWAIK1
/*
 * synnames()
 *
 * Tranfer Syndata names to array.
 */
synnames()
{	int i, k;
	char *p, *q;

	if (Nvoices != 64) return;

	for ( k=0; k < 64; k++ ) {
		p = (*Nameof)( & (VOICEBYTE(Syndata,k,0) ));
		q = sngnames[k];
		for (i=0; i < 10; i++) *q++ = *p++;
		*q = 0;
	}
}
#endif

/*
 * libtodisplay
 *
 * Tranfer Libdata names to the screen, starting at voice 'firstv'.
 */

libtodisplay(firstv)
{
	int k, r, q;

	windgoto(FIRSTROW-1,RIGHTSIDE+2);
	sprintf(Buff,"Library (Bank %d)",Libbank+1);
	windstr(Buff);

	if(Nvoices < NUMONSCREEN)
		q = Nvoices;
	else
		q = NUMONSCREEN;
	for ( k=0; k<q; k++ ) {
		r = FIRSTROW+1+k;
		windgoto(r,RIGHTSIDE);
		sprintf(Buff,"%s |                 ",vnumtext(firstv+k+1));
		windstr(Buff);
		/* pull the name out of the Libdata */
		if ( Namesize != 0 ) {
			windgoto(r,RIGHTSIDE+5);
			windstr((*Nameof)(bankvoice(firstv+k)));
		}
	}
	windrefresh();
}

allnotesoff()
{
	int n;

	/* Go through all channels. */
	for ( n=0; n<15; n++ ) {
		sendmidi(n | 0xb0);
		sendmidi(0x7b);
		sendmidi(0x00);
	}
}

/*
 * Below are generic edit routines, which manage a screen display
 * showing parameter values, and let you roam around and make changes.
 * The display is managed by the contents of the P array, which
 * contains the name, screen location, min/max values, etc. of
 * each parameter.
 */

int Prow = 0;
int Pcol = 0;
int Parm = 0;

/* redraw the parameter screen */
showallparms(name)
char *name;
{
	int n;
	char *s;

	windclear();

	if( Namesize != 0 ) {
		showname(name);
		windgoto(1,0);
		for(n=strlen(name)+6;n>0;n--)
			windputc('=');
	}

	/* The L array contains arbitrary screen labels */
	for ( n=0; L[n].l_text != NULL; n++ ) {
		windgoto(L[n].l_row,L[n].l_col);
		windstr(L[n].l_text);
	}
	/* Display each parameter value, and a label if there is one. */
	for ( n=0; P[n].p_name != NULL; n++ ) {
		if ( P[n].p_flags != 0 )
			continue;
		if ( (s=P[n].p_label) != NULL )
			showstr(s,P[n].p_lrow,P[n].p_lcol,0);
		showparam(n,0);
	}
	windrefresh();
}

showname(name)
char *name;
{
	windgoto(0,0);
	windstr("Name:                 ");
	windgoto(0,6);
	windstr(name);
}

showparam(n,eras)
{
	char *p;

	/* The p_tovis element of the P array is a function which, given */
	/* the parameter value as an argument, returns a string which is */
	/* what should be displayed on the screen. */
	p = (*(P[n].p_tovis))(P[n].p_val);
	showstr(p,P[n].p_vrow,P[n].p_vcol,eras);
}

showstr(p,row,col,eras)
register char *p;
register int col;
{
	register int c;

	windgoto(row,col);
	while ( (c=(*p++)) != '\0' ) {
		switch(c){
		case '~':
			switch( (c=(*p++)) ) {
			case 'u': row--; goto wgoto;
			case 'd': row++; goto wgoto;
			case 'l': col--; goto wgoto;
			case 'r': col++;
			    wgoto:
				windgoto(row,col);
				break;
			default:
				windputc(eras?' ':c);
				col++;
				break;
			}
			break;
		default:
			windputc(eras?' ':c);
			col++;
			break;
		}
	}
}

/* Allow roaming around and changing of parameter values. */
editdata(name,data)
char *name;
char *data;	/* vmem format */
{
	int c, n;

	windclear();
	windrefresh();
	/* enable all the parameters */
	for ( n=0; P[n].p_name != NULL; n++ )
		enableparm(n);

	/* Take the voice data and put it into P */
	(*Datain)(data);

	Prow = Pcol = 0;
	Changed = 0;
	Redraw = 1;
	Lastvalue = 0;
	gotoparm(CH_RIGHT);	/* Get to the first parameter */
	for ( ;; ) {
		if ( Redraw ) {
			showallparms(name);
			Redraw = 0;
		}
		windgoto(Prow,Pcol);
		windrefresh();
		c = mouseorkey();
		if ( c == MOUSE ) {
			editmouse();
			continue;
		}
		switch(c){
		case CH_RIGHT:
		case ALTCH_RIGHT:
		case CH_UP:
		case ALTCH_UP:
		case CH_DOWN:
		case ALTCH_DOWN:
		case CH_LEFT:
		case ALTCH_LEFT:
			gotoparm(c);
			break;
		case CH_SAME:
			sameparm();
			break;
		case CH_REDRAW:
			showallparms(name);
			break;
		case 'N':
			if ( Namesize != 0 ) {
			/* Allow changing of voice name */
				windgoto(0,5);
				windstr("                ");
				windgoto(0,6);
				windrefresh();
				windgets(Buff);
				if ( Buff[0]!='\0' && Buff[0]!='\n' )
					(*Setnameof)(data,Buff);
				showname(name=(*Nameof)(data));
				Changed = 1;
			}
			break;

		case CH_INC:
			adjuparm(1);
			break;
		case CH_INC2:
			adjuparm(4);
			break;
		case CH_INC3:
			adjuparm(P[Parm].p_max - P[Parm].p_min);
			break;
		case CH_DEC:
			adjuparm(-1);
			break;
		case CH_DEC2:
			adjuparm(-4);
			break;
		case CH_DEC3:
			adjuparm(P[Parm].p_min - P[Parm].p_max);
			break;
#ifdef OLDSTUFF
		case 'a':
			sendaced(data);
			playnote(0);
			break;
#endif
		case ' ':
		case '\n':
#ifndef macintosh
		case '\r':
#endif
			if ( Changed ) {
				(*Dataout)(data);
				(*Sendedit)(data);
				Changed = 0;
			}
			playnote(0);
			break;
		case '\033':
		case '`':
			allnotesoff();
			break;
		case 'q':
		case EOF:
			if ( Changed ) {
				(*Dataout)(data);
				(*Sendedit)(data);
			}
			return;
		default:
			break;
		}
	}
}

adjuparm(incdec)
{
	int v, n;

	v = P[Parm].p_val + incdec;
	if ( v < (n=P[Parm].p_min) )
		v = n;
	if ( v > (n=P[Parm].p_max) )
		v = n;
	Lastvalue = v;
	Changed = 1;
	showparam(Parm,1);	/* erase the old val */
	P[Parm].p_val = v;
	showparam(Parm,0);	/* show the new val */
}

sameparm()
{
	int v, n;

	v = Lastvalue;
	if ( v < (n=P[Parm].p_min) )
		v = n;
	if ( v > (n=P[Parm].p_max) )
		v = n;
	Changed = 1;
	showparam(Parm,1);	/* erase the old val */
	P[Parm].p_val = v;
	showparam(Parm,0);	/* show the new val */
}

editmouse()
{
	int row, col, thisparm;

	getmouse(&row,&col);
	thisparm = closeparm(row,col);
	if ( thisparm == Parm ) {
		if ( statmouse() > 1 )
			adjuparm(-1);	/* right button */
		else if ( statmouse() == 1 ) /* added by mab - bug fix */
			adjuparm(1);	/* left button */
	}
	else {
		Parm = thisparm;
		Prow = P[Parm].p_vrow;
		Pcol = P[Parm].p_vcol;
	}
}

/* closeparm - Find the closest parameter */
closeparm(row,col)
{
	register struct paraminfo *pp;
	register int n;
	int dist, mindist, minparm, dr, dc;

	minparm = 0;
	mindist = Rows + Cols;
	for ( n=0,pp=P; pp->p_name != NULL; n++,pp++ ) {
		if ( pp->p_flags != 0 )
			continue;
		if ( (dr=row-(pp->p_vrow)) < 0 )
			dr = -dr;
		if ( (dc=col-(pp->p_vcol)) < 0 )
			dc = -dc;
		if ( (dist=dr*dr+dc*dc) < mindist ) {
			minparm = n;
			mindist = dist;
		}
	}
	return(minparm);
}

/* playnote - play the 'auto' note */
playnote(i)
{
	int pitch, vol, dur, chan;
	long endtime;

	pitch = getval("autopitch");
	if(i == 0) { /* called from inside edit-mode */
		chan = getval("autochan");
	} else { /* called from the top level */
		chan = Channel;
	}
	vol = getval("autovol");
	dur = getval("autodur");
	endtime = milliclock() + dur * 100;
	midinote(1,chan,pitch,vol);
	while ( milliclock() < endtime )
		;
	midinote(0,chan,pitch,vol);
}

/* gotoparm - search for the next parameter in the specified direction */
gotoparm(dir)
{
	int n, k, inc, pm, orig, r = Prow, c = Pcol;

	switch(dir) {
	case ALTCH_UP:		dir = CH_UP;	break;
	case ALTCH_DOWN:	dir = CH_DOWN;	break;
	case ALTCH_LEFT:	dir = CH_LEFT;	break;
	case ALTCH_RIGHT:	dir = CH_RIGHT;	break;
	}

	if ( dir==CH_LEFT || dir==CH_RIGHT ) {
		if ( dir==CH_LEFT )
			c--;
		else
			c++;
		orig = c;
		inc = 0;
		pm = -1;
		/* look up and down, alternately */
		for ( n=2*Rows; n>0; n-- ) {
			r += (pm * inc++);
			pm = -pm;
			if ( r < 0 || r >= Rows )
				continue;
			if ( dir == CH_LEFT ) {
				for ( c=orig; c>=0; c-- ) {
					if ( parmat(r,c) )
						return;
				}
			}
			else {
				for ( c=orig; c<Cols; c++ ) {
					if ( parmat(r,c) )
						return;
				}
			}
		}
		return;
	}
	if ( dir==CH_DOWN || dir==CH_UP ) {
		if ( dir==CH_DOWN )
			r++;
		else
			r--;
		orig = c;
		while ( r >= 0 && r < Rows ) {
			/* look toward both sides at the same time */
			inc = 0;
			pm = -1;
			for ( k=2*Cols; k>0; k-- ) {
				c += (pm * inc++);
				pm = -pm;
				if ( c < 0 || c >= Cols )
					continue;
				if ( parmat(r,c) )
					return;
			}
			if ( dir==CH_DOWN )
				r++;
			else
				r--;
			c = orig;
		}
		return;
	}
}

/* paramat - return non-zero if a parameter value is at position r,c */
parmat(r,c)
register int r, c;
{
	register int n;
	register struct paraminfo *pp;

	for ( n=0,pp=P; pp->p_name != NULL; n++,pp++ ) {
		if ( pp->p_flags != 0 )
			continue;
		if ( pp->p_vrow==r && pp->p_vcol==c ) {
			Prow = r;
			Pcol = c;
			Parm = n;
			return(1);
		}
	}
	return(0);
}

/* parmindex - return index (in P) of a given parameter name. */
parmindex(name)
char *name;
{
	int n;
	char *s;

	for ( n=0; (s=P[n].p_name) != NULL; n++ ) {
		if ( strcmp(s,name) == 0 )
			return(n);
	}
	sprintf(Buff,"HEY, PARMINDEX(%s) NOT FOUND!\n",name);
	windstr(Buff);
	windrefresh();
	return(-1);
}

setval(name,v)
char *name;
{
	int n;

	if ( (n=parmindex(name)) < 0 )
		return;
	P[n].p_val = v;
}

getval(name)
char *name;
{
	int n;

	if ( (n=parmindex(name)) < 0 )
		return(0);

	if (pfoutflag)
		fprintf(pfout, "%s = %d\n", name, P[n].p_val);

	return(P[n].p_val);
}


enableparm(n)
{
	if ( P[n].p_flags != 0 )
		P[n].p_flags = 0;
}

disableparm(n)
{
	if ( P[n].p_flags == 0 )
		P[n].p_flags = 1;
}

midinote(onoff,chan,pitch,vol)
{
	sendmidi( ((onoff==1)?(0x90):(0x80)) | ((chan-1)&0xf) );
	sendmidi( pitch & 0x7f );
	sendmidi( vol & 0x7f );
}

static char Nbuff[16];

char *visnum(v)
{
	sprintf(Nbuff,"%d",v);
	return(Nbuff);
}
char *visonoff(v)
{
	if ( v==0 )
		return("OFF");
	else
		return("ON ");
}

char *
bankvoice(voice)
{
	int offset = Libbank * Nvoices * Voicesize + voice * Voicesize;
	return(Libdata + offset);
}
