
/*
 * lakai.c - central liblakai implementation file
 *
 * Copyright (c) 2002-2004 Frank Neumann <franky@users.sourceforge.net>
 * 
 */

/*** INCLUDES ***/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <scsi/sg.h>
#include <scsi/scsi.h>

#include "lakai.h"

/*** DEFINES ***/
#define MAX_OPEN 16
#define SCSI_CMD_LEN 6   /* all command blocks I produce are 6-byte commands */
//#define TRANSFER_LIMIT 16382    /* Known to work; try larger values for better performance */
#define TRANSFER_LIMIT_RECEIVE	65534
#define TRANSFER_LIMIT_SEND		65534
#define LAK_DIR_IN 0   /* incoming data packets */
#define LAK_DIR_OUT 1  /* outgoing data packets */

/*** GLOBAL VARIABLES ***/
static int open_cnt = -1;
static int fhandles[MAX_OPEN];

/*** PROTOTYPES ***/
static int send_command(int sg_fd, char *cmdblk, int dirflag,
		void *tosamp_ptr, int tosamp_len,
		void *fromsamp_ptr, int fromsamp_len);
static void hexprint(unsigned char *buf, int len);
static void hexprint_midi(unsigned char *buf, int len);
static int sendrecv_scsimidi(LHANDLE handle, char *cmdblk, unsigned int blksize, char *destbuf);
static int sendrecv_scsimidi2(LHANDLE handle, char *cmdblk, unsigned int blksize, char *buf);
static long get_availbytes(LHANDLE handle);
static long getbulkdata(LHANDLE handle, int numbytes, char *buf);
static long putbulkdata(LHANDLE handle, int numbytes, char *buf);


/*
 * lakai_init() - do a few generic library initializations
 * TODO: Still to be fixed when going for .so.
 */

void lakai_init()
{
	int i;
	/* currently no open files, clear all handle slots */
	if (open_cnt == -1)
	{
		for (i = 0; i < MAX_OPEN; i++)
			fhandles[i] = -1;
	}
}

/*
 * lakai_open() - open a given SCSI generic file, perform some tests.
 * Returns LHANDLE on success, -1 on failure.
 */

LHANDLE lakai_open(char *devname)
{
	int sg_fd, k, slotpos;
	struct sg_scsi_id sg_scsiid;

	/* search for a free "slot" */
	if (open_cnt == MAX_OPEN-1)
	{
		fprintf(stderr, "lakai_open: Maximum open count of liblakai reached.\n");
		return -1;
	}

	for (slotpos = 0; slotpos < MAX_OPEN; slotpos++)
	{
		if (fhandles[slotpos] == -1)
			break;
	}
	if (slotpos == MAX_OPEN)
	{
		fprintf(stderr, "lakai_open: no free slot found\n");
		return -1;
	}
	
	
	sg_fd = open(devname, O_RDWR);
	if (sg_fd < 0)
	{
		fprintf(stderr, "lakai_open: error opening file: %s\n", devname);
		return -1;
	}
    /* Check if we have a new (sg3) device driver */
    if (ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0)
	 {
		fprintf(stderr, "Unable to do ioctl() SG_GET_VERSION_NUM on sg device\n");
	 	return -1;
	}
		 
	if ((k < 30000))
	{
		fprintf(stderr, "sg device's version is too old (below version 3)\n");
		fprintf(stderr, "Please get a more recent kernel\n");
		close(sg_fd);
		return -1;
    }

	/* Check if device is a processor device */
    if (ioctl(sg_fd, SG_GET_SCSI_ID, &sg_scsiid) < 0)
	 {
		fprintf(stderr, "Unable to do ioctl() SG_GET_SCSI_ID on sg device\n");
		close(sg_fd);
		return -1;
	}

#if DEBUG
	fprintf(stderr, "SCSI_ID data:\n");
	fprintf(stderr, "Host #          : %d\n", sg_scsiid.host_no);
	fprintf(stderr, "Channel         : %d\n", sg_scsiid.channel);
	fprintf(stderr, "SCSI ID         : %d\n", sg_scsiid.scsi_id);
	fprintf(stderr, "LUN             : %d\n", sg_scsiid.lun);
	fprintf(stderr, "SCSI Type       : %d\n", sg_scsiid.scsi_type);
	fprintf(stderr, "Maxcmds per LUN : %d\n", sg_scsiid.h_cmd_per_lun);
	fprintf(stderr, "Max Queue length: %d\n", sg_scsiid.d_queue_depth);
#endif
	if (sg_scsiid.scsi_type != TYPE_PROCESSOR)
	{
		fprintf(stderr, "Incorrect device type - should be a PROCESSOR device\n");
		close(sg_fd);
		return -1;
	}

	/* open succeeded, ready to go */
	open_cnt++;
	fhandles[slotpos] = sg_fd;

	return slotpos;
}


/*
 * lakai_close() - close a previously opened lakai device
 * Returns 0 on success, -1 on failure.
 */

int lakai_close(LHANDLE handle)
{
	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_close: Bad file handle\n");
		return -1;
	}
	close(fhandles[handle]);
	fhandles[handle] = -1;
	open_cnt--;
	return 0;
}


/*
 * lakai_setmode() - sets the mode of the sampler.
 * Returns 0 on success, -1 on failure. See LAKAI_MODE_* in lakai.h
 */

int lakai_setmode(LHANDLE handle, int mode)
{
	static unsigned char cmdblk[SCSI_CMD_LEN] = {
		S2000_MODE, /* command */
		0,				/* lun/AEN */
		1, 			/* len msb */  /* gets set to correct mode before usage */
		0,				/* len */
		0,				/* len lsb */
		0 };			/* control */

	/* 
	 * Possible mode settings:
	 * MIDI:  cmdblk[2] = 0 ("MIDIMode"), cmdblk[3] = 0 (dummy)
	 * SCSI MIDI: cmdblk[2] = 1 ("SCSIMode") + cmdblk[3] = 0 ("SCSI_MIDI")
	 * SCSI BULK: cmdblk[2] = 1 ("SCSIMode") + cmdblk[3] = 1 ("SCSI_Bulk")
	 */
	if (mode == LAKAI_MODE_NORMAL)
	{
		cmdblk[2]= 0;
		cmdblk[3] = 0;
	}
	else if (mode == LAKAI_MODE_SCSI_MIDI)
	{
		cmdblk[2] = 1;
		cmdblk[3] = 0;
	}
	else if (mode == LAKAI_MODE_SCSI_BULK)
	{
		cmdblk[2] = 1;
		cmdblk[3] = 1;
	}
	else
	{
		fprintf(stderr, "Illegal mode in lakai_setmode()\n");
		return -1;
	}

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_setmode: Bad file handle\n");
		return -1;
	}

	if (send_command(fhandles[handle], cmdblk, LAK_DIR_OUT, NULL, 0, NULL, 0) != 0)
	{
		fprintf(stderr, "lakai_setmode() FAILED\n");
		return -1; /* error */
	}
	return 0; /* success */
}


/*
 * lakai_getstatus() - gets the sampler's status
 * Returns 0 on success, -1 on failure.
 */
int lakai_get_status_report(LHANDLE handle, LakaiStatus *ls)
{
	unsigned char statusbuf[100];
	int statuslen;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_RSTAT, 0x48, 0xf7};	/* SysEx: "Get Status" */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_status_report(): Bad file handle\n");
		return -1;
	}

	/* TODO Question: Can I make lakai_setmode() an internal function? */
	/* TODO: Should I reduce the # of setmode() calls during transfers? */

	statuslen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), statusbuf);
	if (statuslen > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_get_status_report() succeeded; hexdump of result follows:\n");
		hexprint(statusbuf, statuslen);
#endif
	}
	else
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
	}

	/* TODO: parse MIDI data, fill LakaiStatus struct */
	/* TODO: also in here: check if answer type is correct (STAT) */

	return 0; /* success */
}

/*
 * lakai_get_program_names() - gets the list of resident program names
 * Returns 0 on success, -1 on failure.
 */
int lakai_get_program_list(LHANDLE handle, LakaiProgramList *lp)
{
	unsigned char finalbuf[2048];
	int i, finallen, numprogs;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_RPLIST, 0x48, 0xf7};	/* SysEx: "req. list of res. prg names" */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_program_list(): Bad file handle\n");
		return -1;
	}

	finallen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), finalbuf);
	if (finallen > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_get_program_list() succeeded; hexdump of result follows:\n");
		hexprint(finalbuf, finallen);
#endif
	}
	else
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
	}
	
	numprogs = finalbuf[5] + 128 * finalbuf[6];

	if (*finalbuf == 0xf0 && *(finalbuf+3) == LC_PLIST)  /* SysEx/PLIST ok */
	{
		lp->prognames = malloc(numprogs* sizeof (char *));
		if (!lp->prognames)
		{
			fprintf(stderr, "lakai_get_program_list(): Unable to malloc prognames\n");
			return -1;
		}
		for (i = 0; i < numprogs; i++)
		{
			lp->prognames[i] = malloc(13); /* program names are 12 bytes long + '\0' */
			if (!lp->prognames[i])
			{
				fprintf(stderr, "lakai_get_program_list(): Unable to malloc progname slot\n");
				return -1;
			}
			lakai_akaitoascii(finalbuf+7+(i*12), lp->prognames[i], 12);
			*(lp->prognames[i]+12) = '\0';
		}
		lp->numprogs = numprogs;
	}
	else
		fprintf(stderr, "lakai_get_program_list(): bad answer from sampler\n");

	return numprogs; /* success */
}


/*
 * lakai_free_program_list() - release memory occupied by lakai_get_program_list()
 */
void lakai_free_program_list(LakaiProgramList *lp)
{
	int i;
	
	if (lp)
	{
		if (lp->numprogs > 0)
		{
			for (i = 0; i < lp->numprogs; i++)
			{
				if (lp->prognames[i])
				{
//					fprintf(stderr, "Freeing mem at $%lx\n", lp->prognames[i]);
					free(lp->prognames[i]);
				}
			}
//			fprintf(stderr, "Freeing pointer list at $%lx\n", lp->prognames);
			free(lp->prognames);
		}
	}
}

/*
 * lakai_get_sample_list() - gets the list of resident sample names
 * Returns # of samples on success, -1 on failure.
 */
int lakai_get_sample_list(LHANDLE handle, LakaiSampleList *ls)
{
	unsigned char finalbuf[2048];
	int i, finallen, numsamples;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_RSLIST, 0x48, 0xf7};	/* SysEx: "req. list of res. sample names" */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_sample_list(): Bad file handle\n");
		return -1;
	}

	finallen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), finalbuf);
	if (finallen > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_get_sample_list() succeeded; hexdump of result follows:\n");
		hexprint(finalbuf, finallen);
#endif
	}
	else
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
	}
	
	numsamples = finalbuf[5] + 128 * finalbuf[6];

	if (*finalbuf == 0xf0 && *(finalbuf+3) == LC_SLIST)  /* SysEx/SLIST ok */
	{
		ls->samplenames = malloc(numsamples* sizeof (char *));
		if (!ls->samplenames)
		{
			fprintf(stderr, "lakai_get_sample_list(): Unable to malloc samplenames\n");
			return -1;
		}
		for (i = 0; i < numsamples; i++)
		{
			ls->samplenames[i] = malloc(13); /* sample names are 12 bytes long + '\0' */
			if (!ls->samplenames[i])
			{
				fprintf(stderr, "lakai_get_sample_list(): Unable to malloc samplename slot\n");
				return -1;
			}
			lakai_akaitoascii(finalbuf+7+(i*12), ls->samplenames[i], 12);
			*(ls->samplenames[i]+12) = '\0';
		}
		ls->numsamples = numsamples;
	}
	else
	{
		fprintf(stderr, "lakai_get_sample_list(): bad answer from sampler\n");
		return -1;
	}
		
	return numsamples; /* success */
}


/*
 * lakai_free_sample_list() - release memory occupied by lakai_get_sample_list()
 */
void lakai_free_sample_list(LakaiSampleList *ls)
{
	int i;
	
	if (ls)
	{
		if (ls->numsamples > 0)
		{
			for (i = 0; i < ls->numsamples; i++)
			{
				if (ls->samplenames[i])
				{
//					fprintf(stderr, "Freeing mem at $%lx\n", ls->samplenames[i]);
					free(ls->samplenames[i]);
				}
			}
//			fprintf(stderr, "Freeing pointer list at $%lx\n", ls->samplenames);
			free(ls->samplenames);
		}
	}
}


/*
 * int lakai_get_program() - gets one program's common data from the sampler
 * and puts the data into the supplied LakaiProgram structure. This does not
 * transfer the keygroup(s) - these will have to be retrieved through the
 * corresponding lakai_get_keygroup(..) function.
 */
int lakai_get_program(LHANDLE handle, int prognum, unsigned char *data)
{
	unsigned char finalbuf[1024];
	int i, finallen, numvals;

	static unsigned char cmdblk1[] = {
		/* SysEx: "receive prog common data" */
		0xf0, 0x47, 0x00, LC_RPDATA, 0x48, 0x00, 0x00, 0xf7};
		/* the program number is filled in bytes 5/6 (counting from 0) later */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_program(): Bad file handle\n");
		return -1;
	}

	if (prognum < 0 || prognum > 16383)
	{
		fprintf(stderr, "lakai_get_program(): Illegal program number\n");
		return -1;
	}

	cmdblk1[6] = prognum / 128;
	cmdblk1[5] = prognum - (cmdblk1[6] *128);

	finallen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), finalbuf);
	if (finallen <= 0)
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
		return -1;
	}

#if DEBUG
	fprintf(stderr, "lakai_get_program() succeeded; hexdump of result follows:\n");
	hexprint(finalbuf, finallen);
#endif

	numvals = (finallen - 8) / 2;   /* subtract SysEx header/EOX */
	for (i = 0; i < numvals; i++)
	{
		data[i] = (finalbuf[7 + (i*2)]) | (finalbuf[7 + (i*2) + 1 ] << 4);
	}

#if DEBUG
	fprintf(stderr, "Decoded buffer dump:\n");
	hexprint(data, numvals);
#endif
	
	/* TODO (done): Fuer alle Funktionen, die über mehrere Teile hinweg Daten anfordern: 
	 * Diese Funktion ist zentralisierbar, sofern grob die max. Datenmenge 
	 * abgeschätzt werden kann. Einfach receive() bis status == 0 liefert.
	 * Obere Abschätzung für Datenmenge:
	 * - Get Status: trivial (single pass)
	 * - GetProgramNames: max. ca 500 Namen? + Header/EOX
	 * - GetSampleNames: dto.
	 * - GetProgramCommon: fix
	 * - GetSampleHeader: fix
	 * - GetKeygroup: Fix
	 * - GetPCMData/.. : Anderer Transfer-Mechanismus
	 *
	 * Also: Die aufrufende Funktion bereitet ihren cmdblk[..] vor, trägt alles
	 * nötige dort bereits ein (cmd-Länge, Program# etc) und liefert das 
	 * zusammen mit einem char * für die Zieldaten ab -> get_scsimididata(...)
	 *
	 */

	/* TODO: Hier weiter: LakaiProgram-Struktur auffüllen */
	/* Wirklich so? Oder soll die App die "rohen" Daten bekommen? */
	/* Argumente:
	 * - pro "raw": Die Daten auf Platte sollen kompatibel zu denen sein, die
	 * von cdXtract geschrieben werden. DIE sind zwar zusammengehaengt, aber
	 * eben raw. Beim spaeteren Restore-to-Sampler kann man die Daten mit
	 * minimalem Umwandlungs-Aufwand zurueckschicken.
	 * - pro "per-cooked": Die App soll spaeter die Daten dem Benutzer per GUI
	 * anbieten; dafuer muessen sie auf jeden Fall pre-cooked sein. Diese
	 * Arbeit wuerde dann nicht von der App, sondern von der Lib erledigt werden.
	 *
	 * Dritte Loesung: Die Lib bietet beides an - sowohl das Ausgeben der rohen Daten
	 * als auch (mit einer weiteren Funktion) das Umwandeln eines raw-Blocks in
	 * eine ausgefuellte Struktur. Klingt gut und sinnvoll!
	 * Dann braucht man allerdings natuerlich Konverter in beide Richtungen,
	 * xxx_tohw und xxx_fromhw.
	 * Diese Konverter sind dann noetig fuer Status (OK), MiscData (trivial),
	 * Program, SampleHeader, Keygroup, DrumSettings.
	 */

//	lpr->groups = f2buf[42];  /* number of keygroups */

	return numvals;  /* TODO */
}


/*
 * int lakai_get_keygroup() - gets one keygroup of one program from the sampler
 * and puts the data into the supplied LakaiKeygroup structure. Usually, you will first
 * get a program, determine its number of keygroups (lk->groups attribute) and then
 * retrieve/store all this data or process it further.
 */
int lakai_get_keygroup(LHANDLE handle, int prognum, int keygroupnum, unsigned char *data)
{
	unsigned char finalbuf[1024];
	int i, finallen, numvals;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_RKDATA, 0x48, 0x00, 0x00, 0x00, 0xf7};
		/* SysEx: "receive keygroup data" */
		/* the program number and keygroup numbers get filled in bytes 5/6/7 (counting
		 * from 0) later
		 */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_keygroup(): Bad file handle\n");
		return -1;
	}

	if (prognum < 0 || prognum > 16383)
	{
		fprintf(stderr, "lakai_get_keygroup(): Illegal program number\n");
		return -1;
	}

	cmdblk1[6] = prognum / 128;
	cmdblk1[5] = prognum - (cmdblk1[6] *128);

	if (keygroupnum < 0 || keygroupnum > 99)
	{
		fprintf(stderr, "lakai_get_keygroup(): Illegal keygroup number\n");
		return -1;
	}

	cmdblk1[7] = keygroupnum;

	finallen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), finalbuf);
	if (finallen <= 0)
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
		return -1;
	}

#if DEBUG
	fprintf(stderr, "lakai_get_keygroup() succeeded; hexdump of result follows:\n");
	hexprint(finalbuf, finallen);
#endif

	numvals = (finallen - 9) / 2;   /* subtract SysEx header/EOX */
	for (i = 0; i < numvals; i++)
	{
		data[i] = (finalbuf[8 + (i*2)]) | (finalbuf[8 + (i*2) + 1 ] << 4);
	}
		
#if DEBUG
	fprintf(stderr, "Decoded buffer dump:\n");
	hexprint(data, numvals);
#endif

	return numvals;
}

/*
 * int lakai_get_sample_header() - gets the sample header of one sample
 * and puts the data into the supplied buffer. Usually, you will first
 * need to find out how many samples are currently loaded into the sampler with
 * lakai_get_sample_list().
 * TODO: Please note that this function will return the raw data in the supplied buffer; no
 * interpretation of the data is performed. For that, another function (something like
 * lakai_shdr_to_struct() ) will have to be written which would then convert it all
 * into a LakaiSampleHeader struct.
 */
int lakai_get_sample_header(LHANDLE handle, int samplenum, unsigned char *data)
{
	unsigned char finalbuf[1024];
	int i, finallen, numvals;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_RSDATA, 0x48, 0x00, 0x00, 0xf7};
		/* SysEx: "receive sample header data" */
		/* the sample number gets filled in bytes 5/6 (counting
		 * from 0) later
		 */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_sample_header(): Bad file handle\n");
		return -1;
	}

	if (samplenum < 0 || samplenum > 16383)
	{
		fprintf(stderr, "lakai_get_sample_header(): Illegal sample number\n");
		return -1;
	}

	cmdblk1[6] = samplenum / 128;
	cmdblk1[5] = samplenum - (cmdblk1[6] *128);

	finallen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), finalbuf);
	if (finallen <= 0)
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
		return -1;
	}

#if DEBUG
	fprintf(stderr, "lakai_get_sample_header() succeeded; hexdump of result follows:\n");
	hexprint(finalbuf, finallen);
#endif

	numvals = (finallen - 8) / 2;   /* subtract SysEx header/EOX */
	for (i = 0; i < numvals; i++)
	{
		data[i] = (finalbuf[7 + (i*2)]) | (finalbuf[7 + (i*2) + 1 ] << 4);
	}
		
#if DEBUG
	fprintf(stderr, "Decoded buffer dump:\n");
	hexprint(f2buf, numvals);
#endif
	
	/* TODO: Finish this */
	/* slocat and slngth are multiplied by 2 to have them in bytes, not words. */
#if 0
	lsh->slocat = (f2buf[25] * 16777216 + f2buf[24] * 65536 + f2buf[23] * 256 + f2buf[22]) * 2;
	lsh->slngth = (f2buf[29] * 16777216 + f2buf[28] * 65536 + f2buf[27] * 256 + f2buf[26]) * 2;
#endif
	
	return numvals;
}


/*
 * int lakai_put_sample_header() - sends a sample header for one sample
 * to the supplied buffer. Usually, you will first
 * need to find out how many samples are currently loaded into the sampler with
 * lakai_get_sample_list().
 *
 * By sending a sample header with a large number (larger than the largest
 * sampler currently resident in the sampler) you create a new sample header
 * entry. The actual sample PCM data is then to be sent right afterwards.
 */
int lakai_put_sample_header(LHANDLE handle, int samplenum, unsigned char *data, int len)
{
	unsigned char finalbuf[1024];
	unsigned char resbuf[1024];
	int i, finallen, numvals;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_SDATA, 0x48, 0x00, 0x00};
		/* SysEx: "sample header data" */
		/* the sample number gets filled in bytes 5/6 (counting
		 * from 0) later
		 */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_put_sample_header(): Bad file handle\n");
		return -1;
	}

	if (samplenum < 0 || samplenum > 16383)
	{
		fprintf(stderr, "lakai_put_sample_header(): Illegal sample number\n");
		return -1;
	}

	cmdblk1[6] = samplenum / 128;
	cmdblk1[5] = samplenum - (cmdblk1[6] *128);

	/* assemble the whole message from header, payload and EOX */
	memcpy(finalbuf, cmdblk1, sizeof(cmdblk1));
	
	for (i = 0; i < len; i++)
	{
		finalbuf[sizeof(cmdblk1) + i*2] = data[i] & 0x0f;
		finalbuf[sizeof(cmdblk1) + i*2 + 1] = (data[i] & 0xf0) >> 4;
	}
	finalbuf[sizeof(cmdblk1)+2*len] = 0xf7; /* EOX */


	finallen = sendrecv_scsimidi(handle, finalbuf, sizeof(cmdblk1)+(len*2)+1, resbuf);

	if (finallen <= 0)
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
		return -1;
	}
	
	/* TODO: return real return code here */
	return numvals;
}

/*
 * int lakai_delete_program() - delete a program and all keygroups associated with it.
 * Will not touch any samples or sample header data.
 */
int lakai_delete_program(LHANDLE handle, int prognum)
{
	unsigned char statusbuf[100];
	int statuslen;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_DELP, 0x48, 0x00, 0x00, 0xf7};	/* SysEx: "Delete Program" */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_delete_program(): Bad file handle\n");
		return -1;
	}
	
	if (prognum < 0 || prognum > 16383)
	{
		fprintf(stderr, "lakai_delete_program(): Illegal program number\n");
		return -1;
	}

	cmdblk1[6] = prognum / 128;
	cmdblk1[5] = prognum - (cmdblk1[6] *128);

	statuslen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), statusbuf);
	/* TODO: Check return code (statusbuf[5]) for error and hand that upwards */
	if (statuslen > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_delete_program() succeeded; hexdump of result follows:\n");
		hexprint(statusbuf, statuslen);
#endif
	}
	else
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
	}

	return 0; /* success */
}


/*
 * int lakai_delete_keygroup() - delete a keygroup within a program.
 * Will not touch any samples or sample header data.
 */
int lakai_delete_keygroup(LHANDLE handle, int prognum, int kgrpnum)
{
	unsigned char statusbuf[100];
	int statuslen;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_DELK, 0x48, 0x00, 0x00, 0x00, 0xf7};	/* SysEx: "Delete Keygroup" */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_delete_keygroup(): Bad file handle\n");
		return -1;
	}
	
	if (prognum < 0 || prognum > 16383)
	{
		fprintf(stderr, "lakai_delete_keygroup(): Illegal program number\n");
		return -1;
	}

	if (kgrpnum < 0 || kgrpnum > 99)
	{
		fprintf(stderr, "lakai_delete_keygroup(): Illegal keygroup number\n");
		return -1;
	}
	cmdblk1[6] = prognum / 128;
	cmdblk1[5] = prognum - (cmdblk1[6] *128);

	cmdblk1[7] = kgrpnum;
	
	statuslen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), statusbuf);
	/* TODO: Check return code (statusbuf[5]) for error and hand that upwards */
	if (statuslen > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_delete_keygroup() succeeded; hexdump of result follows:\n");
		hexprint(statusbuf, statuslen);
#endif
	}
	else
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
	}

	return 0; /* success */
	
}


/*
 * int lakai_delete_sample() - delete a sample header and its
 * associated sample PCM data.
 * Will not touch any programs or keygroups.
 */
int lakai_delete_sample(LHANDLE handle, int samplenum)
{
	unsigned char statusbuf[100];
	int statuslen;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_DELS, 0x48, 0x00, 0x00, 0xf7};	/* SysEx: "Delete Sample" */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_delete_sample(): Bad file handle\n");
		return -1;
	}
	
	if (samplenum < 0 || samplenum > 16383)
	{
		fprintf(stderr, "lakai_delete_sample(): Illegal sample number\n");
		return -1;
	}

	cmdblk1[6] = samplenum / 128;
	cmdblk1[5] = samplenum - (cmdblk1[6] *128);

	statuslen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), statusbuf);
	/* TODO: Check return code (statusbuf[5]) for error and hand that upwards */
	if (statuslen > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_delete_sample() succeeded; hexdump of result follows:\n");
		hexprint(statusbuf, statuslen);
#endif
	}
	else
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
	}

	return 0; /* success */
}

/*
 * lakai_get_sample() - perform the actual bulk data transfer
 * from the sampler to the PC.
 * The supplied buffer should be big enough to hold "len" bytes.
 */

long lakai_get_sample(LHANDLE handle, int samplenum, char *buffer, long locat, long len)
{
	int availbytes, stilltoget, getnow, ret;
	unsigned char statusbuf[100];
	unsigned char *ptr;
	unsigned char cmdblk1[] = {
		/* SysEx: "Request Sample Packets"; bytes 0x05 and 0x06 contain sample # */
		0xf0, 0x47, 0x00, LC_RSPACK, 0x48,
		0x00,	0x00, /* sample # */
		0x00, 0x00, 0x00, 0x00, /* start address */
		0x00, 0x00, 0x00, 0x00, /* # of samples */
		0x01, 0x00, 0xf7 }; /* interval etc */
	
	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_sample: Bad file handle\n");
		return -1;
	}

	/* put in sample number */
	cmdblk1[5] = samplenum & 0x7f;
	cmdblk1[6] = (samplenum >> 7) & 0x7f;

	/* put in locat and len in 7-bit notation ( if required; should be 0 normally) */
	cmdblk1[7] = (locat & 0x7f);
	cmdblk1[8] = (locat >> 7) & 0x7f;
	cmdblk1[9] = (locat >> 14) & 0x7f;
	cmdblk1[10] = (locat >> 21) & 0x7f;
	
	cmdblk1[11] = len & 0x7f;
	cmdblk1[12] = (len >> 7) & 0x7f;
	cmdblk1[13] = (len >> 14) & 0x7f;
	cmdblk1[14] = (len >> 21) & 0x7f;
	
	lakai_setmode(handle, LAKAI_MODE_SCSI_BULK); /* switch to BULK transfer mode */

	availbytes =  sendrecv_scsimidi2(handle, cmdblk1, sizeof(cmdblk1), statusbuf);
	if (availbytes > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_get_sample() succeeded; %d bytes waiting\n", availbytes);
#endif
	}
	else
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
	}

	stilltoget = availbytes;
	ptr = buffer;
	while (stilltoget > 0)
	{
		if (stilltoget < TRANSFER_LIMIT_RECEIVE)
			getnow = stilltoget;
		else
			getnow = TRANSFER_LIMIT_RECEIVE;
		ret = getbulkdata(handle, getnow, ptr);
#if DEBUG
		fprintf(stderr, "This getbulkdata() call yielded %d bytes\n", ret);
#endif
		stilltoget -= getnow;
		ptr += getnow;
	}

	lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal */

	return 0; /* TODO */
}


/*
 * int lakai_get_miscdata() - retrieves miscellaneous data from the sampler
 */
int lakai_get_miscdata(LHANDLE handle, unsigned char *data)
{
	unsigned char finalbuf[1024];
	int i, finallen, numvals;

	static unsigned char cmdblk1[] = {
		0xf0, 0x47, 0x00, LC_RMDATA, 0x48, 0xf7}; /* SysEx: "request misc data" */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_get_get_miscata(): Bad file handle\n");
		return -1;
	}

	finallen = sendrecv_scsimidi(handle, cmdblk1, sizeof(cmdblk1), finalbuf);
	if (finallen <= 0)
	{
		fprintf(stderr, "Seems there were problems in receiving..\n");
		return -1;
	}

#if DEBUG
	fprintf(stderr, "lakai_get_miscdata() succeeded; hexdump of result follows:\n");
	hexprint(finalbuf, finallen);
#endif

	numvals = (finallen - 6) / 2;   /* subtract SysEx header/EOX */
	for (i = 0; i < numvals; i++)
	{
		data[i] = (finalbuf[5 + (i*2)]) | (finalbuf[5 + (i*2) + 1 ] << 4);
	}
		
#if DEBUG
	fprintf(stderr, "Decoded buffer dump:\n");
	hexprint(data, numvals);
#endif

	return numvals;
}



/* 
 * int lakai_put_program() - create a new program or replace an existing
 */
int lakai_put_program(LHANDLE handle, int prognum, unsigned char *data, int len)
{
	unsigned char finalbuf[1024];
	unsigned char resbuf[128];
	int i, finallen;

	static unsigned char cmdblk1[] = {
		/* SysEx: "send prog common data" */
		0xf0, 0x47, 0x00, LC_PDATA, 0x48, 0x00, 0x00 };
		/* the program number is filled in bytes 5/6 (counting from 0) later,
		 * EOX is appended after the payload has been converted to nibble format */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_put_program(): Bad file handle\n");
		return -1;
	}

	if (prognum < 0 || prognum > 16383)
	{
		fprintf(stderr, "lakai_put_program(): Illegal program number\n");
		return -1;
	}

#if DEBUG
	fprintf(stderr, "Input buffer dump:\n");
	hexprint(data, len);
#endif
	
	cmdblk1[6] = prognum / 128;
	cmdblk1[5] = prognum - (cmdblk1[6] * 128);

	/* assemble the whole message from header, payload and EOX */
	memcpy(finalbuf, cmdblk1, sizeof(cmdblk1));
	
	for (i = 0; i < len; i++)
	{
		finalbuf[sizeof(cmdblk1) + i*2] = data[i] & 0x0f;
		finalbuf[sizeof(cmdblk1) + i*2 + 1] = (data[i] & 0xf0) >> 4;
	}
	finalbuf[sizeof(cmdblk1)+2*len] = 0xf7; /* EOX */

#if DEBUG
	fprintf(stderr, "Encoded buffer dump:\n");
	hexprint(finalbuf, sizeof(cmdblk1)+1+len*2);
#endif

	finallen = sendrecv_scsimidi(handle, finalbuf, sizeof(cmdblk1)+(len*2)+1, resbuf);
	if (finallen <= 0)
	{
		fprintf(stderr, "lakai_put_program(): Seems there were problems in receiving..\n");
		return -1;
	}	
#if DEBUG
	else
	{
		fprintf(stderr, "lakai_put_program: resbuf dump:\n");
		hexprint(resbuf, finallen);
	}
#endif
	return 0;
}



/*
 * int lakai_put_keygroup() - create a new keygroup in a program or 
 * replace an existing keygroup
 * Use prognum = 255 to replace/create a keygroup in the previously created program
 */

int lakai_put_keygroup(LHANDLE handle, int prognum, int kgrpnum, unsigned char *data, int len)
{
	unsigned char finalbuf[1024];
	unsigned char resbuf[128];
	int i, finallen;

	static unsigned char cmdblk1[] = {
		/* SysEx: "send keygroup data" */
		0xf0, 0x47, 0x00, LC_KDATA, 0x48, 0x00, 0x00, 0x00 };
		/* the program number and keygroup number are filled in 
		 * bytes 5/6 resp 7 (counting from 0) later, EOX is appended 
		 * after the payload has been converted to nibble format
		 */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_put_keygroup(): Bad file handle\n");
		return -1;
	}

	if (prognum < 0 || prognum > 16383)
	{
		fprintf(stderr, "lakai_put_keygroup(): Illegal program number\n");
		return -1;
	}
	
	if (kgrpnum > 255 )
	{
		fprintf(stderr, "lakai_put_keygroup(): Illegal keygroup number\n");
		return -1;
	}

#if DEBUG
	fprintf(stderr, "Input buffer dump:\n");
	hexprint(data, len);
#endif
	
	cmdblk1[6] = prognum / 128;
	cmdblk1[5] = prognum - (cmdblk1[6] * 128);

	cmdblk1[7] = kgrpnum;
	
	/* assemble the whole message from header, payload and EOX */
	memcpy(finalbuf, cmdblk1, sizeof(cmdblk1));
	
	for (i = 0; i < len; i++)
	{
		finalbuf[sizeof(cmdblk1) + i*2] = data[i] & 0x0f;
		finalbuf[sizeof(cmdblk1) + i*2 + 1] = (data[i] & 0xf0) >> 4;
	}
	finalbuf[sizeof(cmdblk1)+2*len] = 0xf7; /* EOX */

#if DEBUG
	fprintf(stderr, "Encoded buffer dump:\n");
	hexprint(finalbuf, sizeof(cmdblk1)+1+len*2);
#endif

	finallen = sendrecv_scsimidi(handle, finalbuf, sizeof(cmdblk1)+(len*2)+1, resbuf);
	if (finallen <= 0)
	{
		fprintf(stderr, "lakai_put_keygroup(): Seems there were problems in receiving..\n");
		return -1;
	}	
	return 0;
}



/*
 * lakai_put_sample() - perform the actual bulk data transfer
 * from the PC to the sampler.
 * The supplied buffer should hold "len" bytes.
 */

long lakai_put_sample(LHANDLE handle, int samplenum, char *buffer, long locat, long len)
{
	int availbytes, stilltoput, putnow, ret;
	unsigned char statusbuf[100];
	unsigned char *ptr;
	int len_samples = len / 2;
	unsigned char cmdblk1[] = 
	{
		/* SysEx: "Accept Sample Packets"; bytes 0x05 and 0x06 contain sample # */
		0xf0, 0x47, 0x00, LC_ASPACK, 0x48,
		0x00,	0x00, /* sample # */
		0x00, 0x00, 0x00, 0x00, /* start address */
		0x00, 0x00, 0x00, 0x00, /* # of samples */
		0xf7 /* EOX */
	};
	
	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "lakai_put_sample: Bad file handle\n");
		return -1;
	}

	/* put in sample number */
	cmdblk1[5] = samplenum & 0x7f;
	cmdblk1[6] = (samplenum >> 7) & 0x7f;

	/* put in locat and len in 7-bit notation ( if required; should be 0 normally) */
	cmdblk1[7] = (locat & 0x7f);
	cmdblk1[8] = (locat >> 7) & 0x7f;
	cmdblk1[9] = (locat >> 14) & 0x7f;
	cmdblk1[10] = (locat >> 21) & 0x7f;
	
	cmdblk1[11] = len_samples & 0x7f;
	cmdblk1[12] = (len_samples >> 7) & 0x7f;
	cmdblk1[13] = (len_samples >> 14) & 0x7f;
	cmdblk1[14] = (len_samples >> 21) & 0x7f;
	
	/* TODO: TEsting here.. */
	lakai_setmode(handle, LAKAI_MODE_SCSI_BULK); /* switch to BULK transfer mode */

	availbytes = sendrecv_scsimidi2(handle, cmdblk1, sizeof(cmdblk1), statusbuf);
	if (availbytes > 0)
	{
#if DEBUG
		fprintf(stderr, "lakai_put_sample() succeeded; %d bytes\n", availbytes);
		hexprint(statusbuf, availbytes);
#endif
	}
	else
	{
//		fprintf(stderr, "lakai_put_sample(): availbytes returned with 0..\n");
	}

//fprintf(stderr, "First part of lakai_put_sample() done..\n");
/* TODO: This is unfortunately necessary, but why? */
/* TODO: IDEA: We need to check status (receive the "ok") first, right? */
//sleep(1);
usleep(100000);

	stilltoput = len;
	ptr = buffer;
	while (stilltoput > 0)
	{
		if (stilltoput < TRANSFER_LIMIT_SEND)
			putnow = stilltoput;
		else
			putnow = TRANSFER_LIMIT_SEND;
		ret = putbulkdata(handle, putnow, ptr);
//fprintf(stderr, "This putbulkdata() call sent %d bytes\n", ret);
		stilltoput -= putnow;
		ptr += putnow;
	}

	lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal */

	return 0; /* TODO */
}



/*
 * void lakai_asciitoakai() - converts a string from ascii to Akai representation
 * user is responsible for providing big enough src/dest buffers etc.
 * Non-convertible characters in src get mapped to Space (' ') char in dest.
 */
void lakai_asciitoakai(char *src, char *dst, int len)
{
	int i;
	unsigned char val_in, val_out;
	for (i = 0; i < len; i++)
	{
		val_in = *(src+i);
		if (val_in >= 48 && val_in <= 57)   /* '0'..'9' */
			val_out = val_in - 48;
		else if (val_in >= 65 && val_in <= 90)  /* 'A'..'Z' */
			val_out = val_in - 54;
		else if (val_in >= 97 && val_in <= 122) /* 'a'..'z' */
			val_out = val_in - 86;
		else if (val_in == 35)   /* '#' */
			val_out = 37;
		else if (val_in == 43)   /* '+' */
			val_out = 38;
		else if (val_in == 45)   /* '-' */
			val_out = 39;
		else if (val_in == 46)   /* '&' */
			val_out = 40;
		else
			val_out = 38;			/* non-convertible ASCII char get changed to "+" for now.. */

		*(dst+i) = val_out;
	}
}


/*
 * void lakai_akaitoascii() - converts a string from Akai to ASCII representation
 * User is responsible for providing big enough src/dest buffers etc.
 * Non-convertible characters (though there shouldn't be any :-) in src get mapped to 
 * Space (' ') char in dest.
 */
void lakai_akaitoascii(char *src, char *dst, int len)
{
	int i;
	unsigned char val_in, val_out;
	for (i = 0; i < len; i++)
	{
		val_in = *(src+i);
		if (val_in <= 9)   /* '0'..'9' */
			val_out = val_in + 48;
		else if (val_in >= 11 && val_in <= 36)  /* 'A'..'Z' */
			val_out = val_in + 54;
		else if (val_in == 37)   /* '#' */
			val_out = 35;
		else if (val_in == 38)   /* '+' */
			val_out = 43;
		else if (val_in == 39)   /* '-' */
			val_out = 45;
		else if (val_in == 40)   /* '&' */
			val_out = 46;
		else
			val_out = 32;

		*(dst+i) = val_out;
	}
}


/*****************************************************************************
 * The remaining functions in this file are internal to lakai.c and are not  *
 * to be used outside. They are utility functions, mostly used for debugging *
 * liblakai itself. Enter at your own risk. There may be dragons here.       *
 *****************************************************************************/

/*
 * send_command() - internal SCSI command processing func
 * INPUTS:
 *		int sg_fd - file descriptor to talk to
 * 	char *cmdblk - pointer to SCSI command block
 *    int dirflag - direction flag (LAK_DIR_IN or LAK_DIR_OUT)
 *		void *tosamp_ptr: buffer that holds data that is sent out (if needed - otherwise put NULL here)
 *		int tosamp_len: number of bytes we want to send
 *    void *fromsamp_ptr: buffer that will store returned data from sampler (if needed,
 *     otherwise NULL)
 *    int fromsamp_ptr: length of block expected from sampler (size of fromsamp_ptr buffer)
 * 
 * OUTPUTS:
 *    TODO: ... to be specified...
 *    
 * If a command does not expect any data sent back to it (like in lakai_setmode()),
 * it's ok to leave outbuf as NULL and outlen as 0.
 * For other commands that expect data to be sent back, allocate a buffer of the
 * required size and pass it over, together with the buffer size.
 */

int send_command(int sg_fd, char *cmdblk, int dirflag, 
		void *tosamp_ptr, int tosamp_len,
		void *fromsamp_ptr, int fromsamp_len)
{
	sg_io_hdr_t io_hdr;
	unsigned char sense_buffer[32];

	/* Prepare command */
	memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
	io_hdr.interface_id = 'S';
	io_hdr.cmd_len = SCSI_CMD_LEN; 
	/* io_hdr.iovec_count = 0; */  /* memset takes care of this */
	io_hdr.mx_sb_len = sizeof(sense_buffer);

	if (dirflag == LAK_DIR_IN)
	{
		/* incoming data */
		io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
		io_hdr.dxfer_len = fromsamp_len;
		io_hdr.dxferp = fromsamp_ptr;
	}
	else   /* LAK_DIR_OUT */
	{
		/* outgoing data */
		io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
		io_hdr.dxfer_len = tosamp_len;
		io_hdr.dxferp = tosamp_ptr;
		
#if 0
		io_hdr.dxfer_len = SCSI_CMD_LEN+len;	/* seems I cannot make this 0, though there is no data */
		/* $$$ TODO: set dxferp correctly, depending on transfer type */
/* TODO: FIX THIS		io_hdr.dxferp = cmdblk+cdbsz;	* to be transferred..hence, a dummy buffer */

#endif
	}

	io_hdr.cmdp = cmdblk;
	io_hdr.sbp = sense_buffer;
	io_hdr.timeout = 20000;     /* 20000 millisecs == 20 seconds */
	/* io_hdr.flags = 0; */     /* take defaults: indirect IO, etc */
	/* io_hdr.pack_id = 0; */
	/* io_hdr.usr_ptr = NULL; */

	if (ioctl(sg_fd, SG_IO, &io_hdr) < 0)
	{
		fprintf(stderr, "send_command(): ioctl(SG_IO, ..) failed\n");
		return -1;
	}

#if DEBUG
	if (outbuf)
	{
		fprintf(stderr, "Dump of outbuf follows:\n");
		hexprint((unsigned char *)outbuf, outlen);
	}
#endif
	
	if ((io_hdr.info & SG_INFO_OK_MASK) == SG_INFO_OK)
	{
//		fprintf(stderr, "### return code: OK\n");
	}
	else
	{
		fprintf(stderr, "### return code: NOT OK\n");
		return -1;
	}

	if (io_hdr.sb_len_wr > 0)
	{
		fprintf(stderr, "Something (%d bytes) is in the sense buffer.\n", io_hdr.sb_len_wr);
		fprintf(stderr, "Dump of sense buffer follows:\n");
		hexprint(io_hdr.sbp, io_hdr.sb_len_wr);
		return -1;
	}

	return 0;  /* success */
}

/*
 * hexprint() - print out a data buffer as a hex listing
 */

void hexprint(unsigned char *buf, int len)
{
	unsigned char hexlist[] ="0123456789ABCDEF";
	unsigned short i;
	
	for (i = 0; i < len; i++)
	{
		if ((i % 16) == 0)
		{
			/* print 4-digit address */
			putchar(hexlist[(i >> 12) & 0x0f]);
			putchar(hexlist[(i >> 8) & 0x0f]);
			putchar(hexlist[(i >> 4) & 0x0f]);
			putchar(hexlist[i & 0x0f]);
			putchar(':');
		}
		if ((i % 8) == 0)
			putchar(' ');
		
		putchar(hexlist[*(buf+i) >> 4]);
		putchar(hexlist[*(buf+i) & 0x0f]);
		putchar(' ');
		
		if ((i+1) % 16 == 0)
			putchar('\n');
	}
	putchar('\n');
}

/*
 * hexprint_midi() - print out a data buffer of MIDI bytes as a hex listing
 */
void hexprint_midi(unsigned char *buf, int len)
{
	unsigned char hexlist[] ="0123456789ABCDEF";
	unsigned short i;
	unsigned char val;
	
	for (i = 0; i < len; i++)
	{
		if ((i % 16) == 0)
		{
			/* print 4-digit address */
			putchar(hexlist[(i >> 12) & 0x0f]);
			putchar(hexlist[(i >> 8) & 0x0f]);
			putchar(hexlist[(i >> 4) & 0x0f]);
			putchar(hexlist[i & 0x0f]);
			putchar(':');
		}
		if ((i % 8) == 0)
			putchar(' ');
		
		/* MIDI data bytes are spread over 2 bytes: first is lower nibble,
		 * second is higher nibble.
		 */
		val = *(buf + i*2) + (*(buf + i*2 + 1) << 4);
		putchar(hexlist[val >> 4]);
		putchar(hexlist[val & 0x0f]);
		putchar(' ');
		
		if ((i+1) % 16 == 0)
			putchar('\n');
	}
	putchar('\n');
}

/*
 * sendrecv_scsimidi() - internal func that gets a SysEx command block, sends it out
 * to the device and collects the returned data.
 * TODO: destbuf must be given always (has to be checked here...?)
 *
 * If call fails, returns -1.
 * Otherwise, returns # of bytes transferred.
 */
int sendrecv_scsimidi(LHANDLE handle, char *sysexblk, unsigned int sysexblksize, char *destbuf)
{
	unsigned char outbuf[32];
	int templen, currentlen;

	static unsigned char cmdblk1[SCSI_CMD_LEN] = {
		S2000_SEND, /* command */
		0,				/* lun/AEN */
		0, 			/* len msb */
		0,				/* len */
		0xff,			/* len lsb */
		0};			/* control */

	static unsigned char cmdblk2[SCSI_CMD_LEN] = {
		S2000_STATUS,	/* command */
		0,					/* lun/AEN */
		0, 				/* len msb */
		0,					/* len */
		0,					/* len lsb */
		0};				/* control */

	static unsigned char cmdblk3[SCSI_CMD_LEN] = {
		S2000_RECEIVE,	/* command */
		0,					/* lun/AEN */
		0, 				/* len msb */
		0,					/* len */
		0,					/* len lsb - these 3 get filled in before the send_command call */
		0};				/* control */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "sendrecv_scsimidi: Bad file handle\n");
		return -1;
	}


	if (sysexblksize > 65534)
	{
		fprintf(stderr, "sendrecv_scsimidi: Error: SysEx msg too long\n!");
		return -1;
	}

	cmdblk1[4] = (unsigned char)sysexblksize & 0xff;
	cmdblk1[3] = (unsigned char)((sysexblksize & 0xff00) >> 8);
	cmdblk1[2] = (unsigned char)((sysexblksize & 0xff0000) >> 16);
	
	lakai_setmode(handle, LAKAI_MODE_SCSI_MIDI); /* switch to MIDI-over-SCSI mode */

	/* This is 3 tasks in one function:
	 * 1) S2000_SEND: Send off the MIDI SysEx command block
	 * 2) S2000_STATUS: Get SCSI status to determine # of bytes waiting for us at the sampler
	 * 3) S2000_RECEIVE: Send out RECEIVE command to get the actual data from sampler
	 */
	if (send_command(fhandles[handle], cmdblk1, LAK_DIR_OUT, sysexblk, sysexblksize, NULL, 0) < 0)
	{
		fprintf(stderr, "sendrecv_scsimidi: Problems in part 1\n");
		lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
		return -1;
	}

// fprintf(stderr, "Part 1 OK.\n");
	currentlen = 0;

	do
	{
		/* 2nd part: Send STATUS to get # of bytes waiting */
		if (send_command(fhandles[handle], cmdblk2, LAK_DIR_IN, NULL, 0, outbuf, 3) < 0)
		{
			fprintf(stderr, "sendrecv_scsimidi: Problems in part 2\n");
			lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
			return -1;
		}

		/* 3rd part: Send RECEIVE to get the sampler's status data */

		/* length of result is encoded in 3 bytes, MSB first */
		templen = (outbuf[0] << 16) + (outbuf[1] << 8) + outbuf[2];
//		fprintf(stderr, "Determined templen as %d bytes.\n", templen);
	
		if (templen > 0)
		{
			cmdblk3[2] = outbuf[0];
			cmdblk3[3] = outbuf[1];
			cmdblk3[4] = outbuf[2];
	
			/* Receive the first/next block of data */
			if (send_command(fhandles[handle], cmdblk3, LAK_DIR_IN, 
						NULL, 0, destbuf+currentlen, templen) < 0)
			{
				fprintf(stderr, "sendrecv_scsimidi: Problems in part 3\n");
				lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
				return -1;
			}

			currentlen += templen;
		}
	} while (templen > 0);

//	fprintf(stderr, "sendrecv_scsimidi: Transfer done; received %d bytes.\n", currentlen);
	
	lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
	return currentlen;
}

/*
 * Slightly simplified form of the previous function - this one only sends a SysEx
 * command block, determines the # of bytes waiting, and that's it. Someone else
 * will have to receive the data from the sampler.
 */
int sendrecv_scsimidi2(LHANDLE handle, char *sysexblk, unsigned int sysexblksize, char *buf)
{
	unsigned char outbuf[32];
	int templen;

	static unsigned char cmdblk1[SCSI_CMD_LEN] = {
		S2000_SEND, /* command */
		0,				/* lun/AEN */
		0, 			/* len msb */
		0,				/* len */
		0xff,			/* len lsb - gets filled in below */
		0};			/* control */

	static unsigned char cmdblk2[SCSI_CMD_LEN] = {
		S2000_STATUS,	/* command */
		0,					/* lun/AEN */
		0, 				/* len msb */
		0,					/* len */
		0,					/* len lsb */
		0x80};			/* control */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "sendrecv_scsimidi2: Bad file handle\n");
		return -1;
	}

	if (sysexblksize > 65534)
	{
		fprintf(stderr, "sendrecv_scsimidi2: Error: SysEx msg too long\n!");
		return -1;
	}

	/* assemble the data packet */
	cmdblk1[4] = (unsigned char)sysexblksize & 0xff;
	cmdblk1[3] = (unsigned char)((sysexblksize & 0xff00) >> 8);
	cmdblk1[2] = (unsigned char)((sysexblksize & 0xff0000) >> 16);

	/* This is 2 tasks in one function:
	 * 1) S2000_SEND: Send off the MIDI SysEx command block
	 * 2) S2000_STATUS: Get SCSI status to determine # of bytes waiting for us at the sampler
	 */
	if (send_command(fhandles[handle], cmdblk1, LAK_DIR_OUT, sysexblk, sysexblksize, NULL, 0) < 0)
	{
		fprintf(stderr, "sendrecv_scsimidi2: Problems in part 1\n");
		lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
		return -1;
	}
	
	/* 2nd part: Send STATUS to get # of bytes waiting */
	if (send_command(fhandles[handle], cmdblk2, LAK_DIR_IN, NULL, 0, outbuf, 3) < 0)
	{
		fprintf(stderr, "sendrecv_scsimidi2: Problems in part 2\n");
		lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
		return -1;
	}

	/* length of result is encoded in 3 bytes, MSB first */
	templen = (outbuf[0] << 16) + (outbuf[1] << 8) + outbuf[2];
//	fprintf(stderr, "srsm2: Determined templen as %d bytes.\n", templen);
	
//	lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
	return templen;
}


#if 0
/*
 * get_availbytes() - retrieves the number of bytes currently waiting to be received
 * by the host (in SCSI_BULK mode)
 *
 * The sampler must already be put into LAKAI_MODE_SCSI_BULK when this gets called.
 */

/* TODO: This is unfinished, and probably not even necessary at all.. */
static long get_availbytes(LHANDLE handle)
{
	unsigned char outbuf[32];
	
	static unsigned char cmdblk1[SCSI_CMD_LEN] = {
		S2000_STATUS,	/* command */
		0,					/* lun/AEN */
		0, 				/* len msb */
		0,					/* len */
		0,					/* len lsb */
		0x80};			/* control - Why 0x80? Laszlo says so. */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "get_availbytes: Bad file handle\n");
		return -1;
	}

	/* Send "raw" STATUS to get # of bytes waiting */
	if (send_command(fhandles[handle], cmdblk1, LAK_DIR_IN, 0, outbuf, 3) < 0)
	{
		fprintf(stderr, "get_availbytes: Problems in part 2\n");
		lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
		return -1;
	}

	/* dump the answer */
	hexprint(outbuf, 32);	

	return 0;
}
#endif


/*
 * getbulkdata() - this is the "bulk receiver". Before it can be called, data
 * should already have been requested from the sampler through e.g. the
 * sendrecv_scsimidi2() function. Also, the sampler should be in mode
 * LAKAI_SCSI_BULK when this function is called.
 * 
 * Parameters:
 * 
 * LHANDLE handle: the usual handle
 * int numbytes: number of bytes coming in
 * char *buf: where to store the incoming data
 *
 * Results:
 * long numreceived: number of bytes actually received in this call
 */

long getbulkdata(LHANDLE handle, int numbytes, char *buf)
{
	static unsigned char cmdblk[SCSI_CMD_LEN] = {
		S2000_RECEIVE,	/* command */
		0,					/* lun/AEN */
		0,					/* len msb */
		0,					/* len */
		0,					/* len lsb - gets filled in below */
		0};				/* control */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "getbulkdata: Bad file handle\n");
		return -1;
	}

	if (numbytes > 65536)
	{
		fprintf(stderr, "getbulkdata: Error: numbytes too large\n!");
		return -1;
	}

	/* assemble the data packet */
	cmdblk[3] = numbytes >> 8;
	cmdblk[4] = numbytes & 0xff;
	cmdblk[5] = 0x80;
	
	if (send_command(fhandles[handle], cmdblk, LAK_DIR_IN, NULL, 0, buf, numbytes) < 0)
	{
		fprintf(stderr, "getbulkdata: Problems in part 1\n");
		lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
		return -1;
	}
	
	return numbytes;  /* TODO: Fixme: This is a fake yet...need to get this from the sg layer */
}


/*
 * putbulkdata() - this is the "bulk sender". Before it can be called, data
 * should already have been announced to the sampler through e.g. the
 * sendrecv_scsimidi2() function. Also, the sampler should be in mode
 * LAKAI_SCSI_BULK when this function is called.
 * 
 * Parameters:
 * 
 * LHANDLE handle: the usual handle
 * int numbytes: number of bytes coming in
 * char *buf: where to store the incoming data
 *
 * Results:
 * long numreceived: number of bytes actually received in this call
 */

long putbulkdata(LHANDLE handle, int numbytes, char *buf)
{
	static unsigned char cmdblk[SCSI_CMD_LEN] = {
		S2000_SEND,		/* command */
		0,					/* lun/AEN */
		0,					/* len msb */
		0,					/* len */
		0,					/* len lsb - gets filled in below */
		0};				/* control */

	if (fhandles[handle] == -1 || handle < 0 || handle > MAX_OPEN)
	{
		fprintf(stderr, "putbulkdata: Bad file handle\n");
		return -1;
	}

	if (numbytes > 65536)
	{
		fprintf(stderr, "putbulkdata: Error: numbytes too large\n!");
		return -1;
	}

	/* assemble the data packet */
	cmdblk[3] = numbytes >> 8;
	cmdblk[4] = numbytes & 0xff;
	cmdblk[5] = 0x80; /* $$$ ??*/
	
	if (send_command(fhandles[handle], cmdblk, LAK_DIR_OUT, buf, numbytes, NULL, 0) < 0)
	{
		fprintf(stderr, "putbulkdata: Problems in part 1\n");
		lakai_setmode(handle, LAKAI_MODE_NORMAL); /* switch back to normal mode */
		return -1;
	}
//	else
//		fprintf(stderr, "getbulkdata: send_command() returned ok\n");
	
	return numbytes;  /* TODO: Fixme: This is a fake yet...need to get this from the sg layer */
}

