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

