/*
 * GLIB - a Generic LIBrarian and editor for synths
 *
 * Kawai K-5 Librarian - handles SINGLE and MULTI patches.
 * Functions are annotated if they work for SINGLE, MULTI, or both kinds.
 * Full editing not implemented - there are zillions of parameters,
 * and the huge LCD on the K-5 is actually quite easy to read and use.
 *
 * Alan Bland - att!druwy!mab
 */

#define OVERLAY2

#include "glib.h"

#define K5SINGLE	0
#define K5MULTI		1
#define K5MAGIC		0x5a3c

#define NERRS		5

extern char *visnum();

/* This array contains arbitrary screen labels (SINGLE) */
struct labelinfo Lk5sin[] = {
 3,10,"Sorry, no edit capability implemented for the K-5 yet.",
15,2,"+-------------------------+--------------+",
16,2,"|Space = Play Note",16,28,"| Auto-Note    |",
17,2,"|",17,28,"|",17,43,"|",
18,2,"|h = left   q = quit      |Pitch",18,43,"|",
19,2,"|j = down   N = Set Name  |Volume",19,43,"|",
20,2,"|k = up     J = Decrement |Duration      |",
21,2,"|l = right  K = Increment |Channel       |",
22,2,"|",22,28,"|",22,43,"|",
23,2,"+-------------------------+--------------+",
-1,-1,NULL
};

/* This array defines all the editable parameters. (SINGLE) */
struct paraminfo Pk5sin[] = {
"autopitch",	NULL,	-1,-1, 18, 38, visnum, 		0, 127, 60, 0,
"autovol",	NULL,	-1,-1, 19, 38, visnum, 		0, 127, 63, 0,
"autodur",	NULL,	-1,-1, 20, 38, visnum, 		1,  20,  5, 0,
"autochan",	NULL,	-1,-1, 21, 38, visnum, 		1,  16,  1, 0,

NULL,		NULL,	-1,-1, -1, -1, visnum, 		0,   0, 0, 0
};

/* This array contains arbitrary screen labels (MULTI) */
struct labelinfo Lk5mul[] = {
 3,10,"Sorry, no edit capability implemented for the K-5 yet.",
15,2,"+-------------------------+--------------+",
16,2,"|Space = Play Note",16,28,"| Auto-Note    |",
17,2,"|",17,28,"|",17,43,"|",
18,2,"|h = left   q = quit      |Pitch",18,43,"|",
19,2,"|j = down   N = Set Name  |Volume",19,43,"|",
20,2,"|k = up     J = Decrement |Duration      |",
21,2,"|l = right  K = Increment |Channel       |",
22,2,"|",22,28,"|",22,43,"|",
23,2,"+-------------------------+--------------+",
-1,-1,NULL
};

/* This array defines all the editable parameters. (MULTI) */
struct paraminfo Pk5mul[] = {
"autopitch",	NULL,	-1,-1, 18, 38, visnum, 		0, 127, 60, 0,
"autovol",	NULL,	-1,-1, 19, 38, visnum, 		0, 127, 63, 0,
"autodur",	NULL,	-1,-1, 20, 38, visnum, 		1,  20,  5, 0,
"autochan",	NULL,	-1,-1, 21, 38, visnum, 		1,  16,  1, 0,

NULL,		NULL,	-1,-1, -1, -1, visnum, 		0,   0, 0, 0
};

/*
 * k5vnum
 *
 * Convert a voice number (0 to 47) to the string displayed in the
 * librarian (A1-D12)  (SINGLE and MULTI)
 */
char *
k5vnum(n)
register int n;
{
	static char v[4];

	if ( n < 0 || n > 47 )
		return("??");

	sprintf(v, "%c%d", n/12 + 'A', n%12 + 1);
	return(v);
}

/*
 * k5numv
 *
 * Convert an alphanumeric voice number (A1-D12) to internal
 * format (0 - 47).  (SINGLE and MULTI)
 */
k5numv(n)
register char *n;
{
	register int v,j;

	/* first better be [a-dA-D] */
	*n = toupper(*n);
	if (*n == 'A' || *n == 'B' || *n == 'C' || *n == 'D') {
		v = 12 * (*n - 'A');
	} else {
		return(-1);
	}

	/* followed by 1-12 */
	j = atoi(++n);
	if (j<1 || j>12) return(-1);
	return v + j - 1;
}

/*
 * k5sindin
 *
 * Take library bank 'data' and stuff values in the P array, by using
 * the setval function.
 */
k5sindin(data)
char *data;
{
	/* We set the 'auto-note' channel upon entry */
	setval("autochan",Channel);
}

/*
 * k5sindout
 *
 * Take (possibly changed) parameters values out of the P array and
 * put them back into the library bank 'data'.
 */
k5sindout(data)
char *data;
{
	/* If the autochan parameter has changed, update Channel */
	Channel = getval("autochan");
}

/*
 * k5muldin
 *
 * Take library bank 'data' and stuff values in the P array, by using
 * the setval function.
 */
k5muldin(data)
char *data;
{
	/* We set the 'auto-note' channel upon entry */
	setval("autochan",Channel);
}

/*
 * k5muldout
 *
 * Take (possibly changed) parameters values out of the P array and
 * put them back into the library bank 'data'.
 */
k5muldout(data)
char *data;
{
	/* If the autochan parameter has changed, update Channel */
	Channel = getval("autochan");
}

/*
 * k5sinnof
 *
 * Return a pointer to the voice name buried in library bank data. (SINGLE)
 */
char *
k5sinnof(data)
register char *data;
{
	static char currbuff[9];
	register char *p;
	register int m;

	p = currbuff;
	for ( m=0; m<16; m+=2 )
		*p++ = (data[m]<<4) | data[m+1];
	*p = '\0';
	return(currbuff);
}

/*
 * k5nameok
 *
 * Convert an ascii string to the ascii subset used for K5 names.
 */
char*
k5nameok(name)
char *name;
{
	static char okname[9];
	register int m;

	for (m=0; m<9 && name[m]; ++m) {
		okname[m] = toupper(name[m]);
		if (!isalnum(okname[m])) {
			switch (okname[m]) {
			case '-': case ':': case '/': case '*': case '?':
			case '!': case '#': case '&': case '(': case ')':
			case '"': case '+': case '.': case '=': case ' ':
				break;
			default:
				okname[m] = ' ';
				break;
			}
		}
	}
	okname[m] = '\0';
	return okname;
}

/*
 * k5sinsnof
 *
 * Set the voice name buried in data to name. (SINGLE)
 */
k5sinsnof(data,name)
char *data;
char *name;
{
	register char *p;
	register int m;

	for ( p=k5nameok(name),m=0; *p!='\0' && m<17; p++,m+=2 ) {
		data[m] = (*p & 0xf0) >> 4;
		data[m+1] = *p & 0x0f;
	}
	for ( ; m<17; m+=2 ) {
		data[m] = (' ' & 0xf0) >> 4;
		data[m+1] = ' ' & 0x0f;
	}
}
/*
 * k5mulnof
 *
 * Return a pointer to the voice name buried in library bank data. (MULTI)
 */
char *
k5mulnof(data)
char *data;
{
	static char currbuff[9];
	register char *p;
	register int m;

	p = currbuff;
	for ( m=0; m<16; m+=2 )
		*p++ = (data[m+330]<<4) | data[m+331];
	*p = '\0';
	return(currbuff);
}

/*
 * k5mulsnof
 *
 * Set the voice name buried in data to name. (MULTI)
 */
k5mulsnof(data,name)
char *data;
char *name;
{
	char *p;
	int m;

	for ( p=k5nameok(name),m=0; *p!='\0' && m<17; p++,m+=2 ) {
		data[m+330] = (*p & 0xf0) >> 4;
		data[m+331] = *p & 0x0f;
	}
	for ( ; m<17; m+=2 ) {
		data[m+330] = (' ' & 0xf0) >> 4;
		data[m+331] = ' ' & 0x0f;
	}
}

/*
 * k5sbulk
 *
 * common function to send all voices to the K-5 (SINGLE and MULTI)
 */
k5sbulk(data, which)
char *data;
int which;	/* K5SINGLE or K5MULTI */
{
	int n, err = 0;
	message("");
	for (n=0; n<Nvoices; ++n) {
		for (err=0; err<NERRS; ++err) {
			if (k5sone(n, &(VOICEBYTE(data,n,0)), which ) == 0 ) {
				windputc('+');
				windrefresh();
				break;
			}
			windputc('-');
			windrefresh();
		}
		if (err == NERRS) return(1);
	}
	return(0);
}

/*
 * k5sinsbulk
 *
 * send all voices to the K-5 (SINGLE)
 */
k5sinsbulk(data)
char *data;
{
	return k5sbulk(data, K5SINGLE);
}

/*
 * k5mulsbulk
 *
 * send all voices to the K-5 (MULTI)
 */
k5mulsbulk(data)
char *data;
{
	return k5sbulk(data, K5MULTI);
}

/*
 * k5sinsone
 *
 * send one voice to the K-5 (SINGLE)
 */
k5sinsone(iv, data)
int iv;
char *data;
{
	return k5sone(iv, data, K5SINGLE);
}

/*
 * k5mulsone
 *
 * send one voice to the K-5 (MULTI)
 */
k5mulsone(iv, data)
{
	return k5sone(iv, data, K5MULTI);
}

/*
 * k5sone
 *
 * common function to send a SINGLE or MULTI voice to the K-5.
 */
k5sone(iv, data, which)
int iv;
register char *data;
int which;	/* K5SINGLE or K5MULTI */
{
	register int i, sum;
	int length;
	int c = 0, ret = 1;
	long begin, toolong;

	flushmidi();

	length = (which == K5SINGLE) ? 984 : 352;

	/* calculate checksum */
	for (sum=0, i=0; i<length-4; ) {
		sum += data[i++] << 4;
		sum += data[i++];
		sum += data[i++] << 12;
		sum += data[i++] << 8;
	}

	sum = K5MAGIC - sum;
	data[length-4] = (sum & 0x00f0) >> 4;
	data[length-3] = (sum & 0x000f);
	data[length-2] = (sum & 0xf000) >> 12;
	data[length-1] = (sum & 0x0f00) >> 8;
	
	sendmidi(0xf0);
	sendmidi(0x40);
	sendmidi(Channel-1);
	sendmidi(0x20);
	sendmidi(0x00);
	sendmidi(0x02);
	sendmidi(which);
	sendmidi(iv);

	for (i=0; i<length; i++) sendmidi(data[i]);

	sendmidi(EOX);

	/* read the ack/nack - set up for timeout */
	begin = milliclock();
	toolong = begin + 1000 * TIMEOUT;

	/* wait for the acknowledgement */
	while ( milliclock() < toolong ) {
		if ( STATMIDI && (c=(getmidi() & 0xff)) == 0xf0 )
			break;
	}
	if ( c != 0xf0 ) {
		Reason = "Timeout waiting for K-5 response";
		goto getout;
	}
	
	/* third byte after the sysex begin is the result */
	for (i=0; i<3; ) {
		/* wait for midi byte or timeout */
		while ( ! STATMIDI ) {
			if ( milliclock() > toolong ) {
				Reason = "Timeout waiting for K-5 response";
				goto getout;
			}
		}
		/* ignore active sensing */
		if ((c = getmidi() & 0xff) != 0xfe) {
			++i;
		}
	}

	/* check the result */
	switch (c) {
	case 0x40:
		ret = 0;
		Reason = "";
		break;
	case 0x41:
		Reason = "K-5 write error";
		break;
	case 0x42:
		Reason = "K-5 write error (protected)";
		break;
	case 0x43:
		Reason = "K-5 write error (no card)";
		break;
	default:
		Reason = "Wierd response (is that really a K-5 you have?)";
		break;
	}

getout:
	return(ret);
}

k5sinsedit()
{
}

k5mulsedit()
{
}

/*
 * k5singbulk
 *
 * get all internal SINGLE voices from K-5.
 */
k5singbulk(data)
char *data;
{
	return k5gbulk(data, K5SINGLE);
}

/*
 * k5mulgbulk
 *
 * get all internal MULTI voices from K-5.
 */
k5mulgbulk(data)
char *data;
{
	return k5gbulk(data, K5MULTI);
}

/*
 * k5gbulk
 *
 * common routine - get all SINGLE or MULTI voices from K-5.
 */
k5gbulk(data, which)
register char *data;
int which;	/* K5SINGLE or K5MULTI */
{
	int c, v, sumerr = 0;
	register int n, i, sum;
	long begin, toolong;

	message("");
	flushmidi();

	for(v = 0; v < Nvoices; v++) {

retry:		
		if (which == K5MULTI) {
			/* i don't know if this is a K-5 or Amiga problem */
			/* but multi patch download seems to need this delay */
			millisleep(500);
		}
		/* request the voice */
		sendmidi(0xf0);
		sendmidi(0x40);
		sendmidi(Channel-1);
		sendmidi(0x00);
		sendmidi(0x00);
		sendmidi(0x02);
		sendmidi(which);
		sendmidi(v);
		sendmidi(EOX);
	
		/* set up for timeout */
		begin = milliclock();
		toolong = begin + 1000 * TIMEOUT;
	
		/* wait for the xf0 byte starting the dump */
		while ( milliclock() < toolong ) {
			if ( STATMIDI && (c=(getmidi() & 0xff)) == 0xf0 )
				break;
		}
		if ( c != 0xf0 ) {
			Reason = "Timeout waiting for dump from K-5";
			return 1;
		}
/*printf("%02x ", c);*/
		/* skip the next 7 bytes (remainder of sysex header) */
		for (i=0; i<7; ) {
			/* wait for midi byte or timeout */
			while ( ! STATMIDI ) {
				if ( milliclock() > toolong )
					goto timeout;
			}
			/* ignore active sensing */
			if ((c = getmidi() & 0xff) != 0xfe) {
				++i;
/*printf("%02x ", c);*/
			}
		}

		/* read voice data until EOX */
		n = 0;
		while (1) {
			/* wait for midi byte or timeout */
			while ( ! STATMIDI ) {
				if ( milliclock() > toolong )
					goto timeout;
			}
			if ((c = getmidi() & 0xff) == 0xfe) {
				/* ignore active sensing */
				continue;
			} else if (c == EOX) {
				/* finished */
				break;
			} else {
				/* got a data byte */
				VOICEBYTE(data,v,n) = c;
				++n;
			}
		}
/*printf("got block n=%d\n", n);*/
		/* verify the checksum */
		for (sum=0, i=0; i<n-4; ) {
			sum += data[i++] << 4;
			sum += data[i++];
			sum += data[i++] << 12;
			sum += data[i++] << 8;
		}

		sum = K5MAGIC - sum;
		if ((data[n-4] == (sum & 0x00f0) >> 4) &&
		    (data[n-3] == (sum & 0x000f)) &&
		    (data[n-2] == (sum & 0xf000) >> 12) &&
		    (data[n-1] == (sum & 0x0f00) >> 8)) {
			sumerr = 0;
			windputc('+');
		} else {
			/* retry a few times if checksum failed */
			windputc('-');
			if (sumerr++ >= NERRS) {
				Reason = "Too many checksum errors!";
				return(1);
			}
			goto retry;
		}
		windrefresh();

	} /* go back for another voice */
	Reason = "";
	return(0);

timeout:
	Reason = "Timeout while reading!";
	return(1);
}

