/* casio - download sample data from a casio fz-1 */

/* Copyright (C) 1988 Hackercorp.   All Rights Reserved */

#include <libraries/dos.h>
#include <clib/macros.h>
#include <midi/midi.h>
#include <functions.h>
#include <stdio.h>
#include <devices/timer.h>

#include "casio.h"
#include "prototypes.h"

char *program_name = "casio fz1 download program";

#define CheckMsg(m) (m->mp_MsgList.lh_Head->ln_Succ != 0)

extern struct timerequest *timer_request;

/* message ids for fz-1 system exclusive messages */
#define OPEN_MIDI_MSG 0x70
#define CLOSE_MIDI_MSG 0x71
#define OK_MIDI_MSG 0x72
#define ERR_MIDI_MSG 0x73
#define DATA_MIDI_MSG 0x74
#define EFFECT_MIDI_MSG 0x76
#define REMOTE_MIDI_MSG 0x7f

/* additional messages I define as responses from its_an_fz1_message */
#define NOT_A_FZ_MESSAGE 0
#define NOT_THE_REQUESTED_FZ_CHANNEL 1
#define DATA_ERROR 2

/* states for the i/o loop */
#define WAITING_FOR_OPEN 0
#define READING_FZ_DATA 1

#define MAX_RETRIES 10

#define CAPTURE_BUFFER_SIZE 16384
UBYTE capture_buffer[CAPTURE_BUFFER_SIZE];

void *MidiBase = NULL;
void *IntuitionBase = NULL;

struct MDest *dest = NULL;
struct MSource *source = NULL;

struct MRoute *route_a = NULL;
struct MRoute *route_b = NULL;

UBYTE *msg;		    /* buffer this in case we get shut down before freeing the current message */

int capture_position = 0;

void midi_cleanup(void)
{
    /* if (msg) 
		FreeMidiMsg(msg); */

	if (IntuitionBase)
		CloseLibrary(IntuitionBase);

    if (dest) 
		DeleteMDest(dest);
	if (source)
		DeleteMSource(source);

	if (route_a)
		DeleteMRoute(route_a);
	if (route_b)
		DeleteMRoute(route_b);

    if (MidiBase) 
		CloseLibrary(MidiBase);
}

/* if channel is -1, any channel is ok (within FZ-1 System Exclusive message) */

/* validate the incoming message, including all sorts of stuff about the header
 * and anything we know specifically within the message type as well */

UBYTE its_an_fz1_message(UBYTE *msg, int requested_channel)
{
	int channel, length, expected_length;
	struct FZ1_Message *p;

	p = (struct FZ1_Message *)msg;

	/* compare first bytes of message,
	   extract channel number and verify,
	   return function code
	 */
    length = MidiMsgLength(msg);

	if ((p->casio_id != 0x44) || (p->fz1_id != 0x200)
		|| ((p->encoded_channel & 0xf0) != 0x70))
			return(NOT_A_FZ_MESSAGE);

	channel = (p->encoded_channel & 0x0f);
	if ((requested_channel != -1) && (channel != requested_channel))
		return(NOT_THE_REQUESTED_FZ_CHANNEL);

	switch(p->command)
	{
		case OPEN_MIDI_MSG:
			expected_length = 16;
			break;

		case CLOSE_MIDI_MSG:
			expected_length = 7;
			break;

		case DATA_MIDI_MSG:
			expected_length =  136;
			if (length == expected_length && (p->body.datamsg.checksum != calc_checksum(p)))
			{
				 fprintf(stderr,"checksum error, expected %x, saw %x\n",
				 	p->body.datamsg.checksum,calc_checksum(p));
				 return(DATA_ERROR);
			}
			break;

		/* haven't seen any of these yet */
		case OK_MIDI_MSG:
		case ERR_MIDI_MSG:
		case EFFECT_MIDI_MSG:
		case REMOTE_MIDI_MSG:
			panic("got a message I don't know how to handle");

		/* it wasn't a message we understood yet it had all the makings of
		 * being one, it must be garbled */
		default:
			return(DATA_ERROR);
	}
	if (length != expected_length)
		return(DATA_ERROR);

	return(p->command);
}

void commune_with_fz1(struct MSource *source, struct MDest *dest, int channel, int outfd)
{
	long len;
	UBYTE *ip;
	int command, retry_count = 0;
	int state = WAITING_FOR_OPEN;
    ULONG waitflags;
	int packets_received = 0, error_count = 0, done = 0;
	struct FZ1_Message *p;

	/* wait for a message or control-C or "done" control var to be set */
	while ((!((waitflags = Wait(-1)) & SIGBREAKF_CTRL_C)) && !done)
	{ 	
		/* if ((waitflags & SIGBREAKF_CTRL_D) && (state = READING_FZ_DATA))
		{
			printf("saw a ^d\n");
			goto data_error;
		} */

		/* while there are messages to process, process messages */
		while (!done && (msg = GetMidiMsg(dest)))
		{		
			/* discard all but system exclusive data - should be done
			 * by the route, check someday to see if we can find our
			 * route and change it if it isn't */
			if (*msg != MS_SYSEX)
				goto message_done;

			p = (struct FZ1_Message *)msg;
			command = its_an_fz1_message((UBYTE *)p,channel);

			if (state == WAITING_FOR_OPEN)
			{
				if (command != OPEN_MIDI_MSG)
					panic("out of sync - looking for 'Open' from FZ1");

				/* we need to specify a channel ID in our messages
				 * if we're not looking for a specific one, reply
				 * with the one we got in the first message */
				if (channel == -1)
					channel = p->encoded_channel & 0x0f;
				state = READING_FZ_DATA;
				dump_open(p);
				send_fz(source,channel,OK_MIDI_MSG);
				goto message_done;
			}

			if (state == READING_FZ_DATA)
			{
				abort_timeout_timer();
				switch(command)
				{
					case DATA_MIDI_MSG:
						send_fz(source,channel,OK_MIDI_MSG);
						start_timeout_timer();
						packets_received++;
						if ((packets_received % 10) == 0)
						{
							printf("%4d ok \r",packets_received);
							fflush(stdout);
						}
						capture(p,outfd);
						retry_count = 0;
						break;

					case DATA_ERROR:
					data_error:
						send_fz(source,channel,ERR_MIDI_MSG);
						start_timeout_timer();
						printf("%4d err\r",packets_received);
						fflush(stdout);
						error_count++;
						if (++retry_count > MAX_RETRIES)
							panic("exceeded max retries for a packet - transfer failed");
						break;

					/* we get this when the fz is done */
					case CLOSE_MIDI_MSG:
						fprintf(stderr,"casio is done\n");
						done = 1;
						break;

					default:
						fprintf(stderr,"unknown command = 0x%x\n",command);
						if (state = READING_FZ_DATA)
						{
							fprintf(stderr,"will try treating it as a data error\n");
							goto data_error;
						}
						else
							panic("unknown command and not in 'read' mode");
				}
				goto message_done;
			}

			panic("state variable fried");

			message_done: FreeMidiMsg(msg);				/* free it */
		}

		if (CheckMsg(timer_request->tr_node.io_Message.mn_ReplyPort) && 
	 	(GetMsg(timer_request->tr_node.io_Message.mn_ReplyPort)) )
		{
			if (state == READING_FZ_DATA)
			{
				send_fz(source,channel,ERR_MIDI_MSG);
				printf("%4d tmo\r",packets_received);
				fflush(stdout);
				error_count++;
				if (++retry_count > MAX_RETRIES)
					panic("exceeded max retries for a packet - transfer failed");
			}
		}
    }
	if (capture_position != 0)
	{
		if (write(outfd,capture_buffer,capture_position) != capture_position)
			perror("output file");
	}
	printf("total packets received %d (%d retries)\n",packets_received,error_count);
}

void send_fz(struct MSource *source, int channel_id, int message_id)
{
	static UBYTE buf[] = {0xf0, 0x44, 0x02, 0x00, 0x70,0x00,0xf7};
	buf[4] |= channel_id;
	buf[5] = message_id;
	PutMidiMsg(source,buf);
}

int calc_checksum(struct FZ1_Message *p)
{
	UBYTE *ip, lownibble, hinibble;
	int len, running_sum;

	/* I assume the message has already been validated as a Casio FZ-1 
	 * data message
	 */

	ip = &p->body.datamsg.data[0];

	for (len = 0, running_sum = 0; len < 128; len++)
	{
		running_sum += *ip++;
	}
	return((0 - running_sum) & 0x7f);
}

void capture(struct FZ1_Message *p, int outfd)
{
	UBYTE *ip, lownibble, hinibble;
	int len;

	ip = &p->body.datamsg.data[0];

	for (len = 0; len < 64; len++)
	{
		lownibble = *ip++;
		hinibble = *ip++;
		capture_buffer[capture_position++] = (hinibble << 4) | lownibble;
		if (capture_position >= CAPTURE_BUFFER_SIZE)
		{
			if (write(outfd,capture_buffer,CAPTURE_BUFFER_SIZE) != CAPTURE_BUFFER_SIZE)
			{
				perror("output file");
			}
			capture_position = 0;
		}
	}
}

struct MRouteInfo myrouteinfo =
{
	MMF_SYSEX,0xffff,0,0,{0},{0}
};

main(int argc,char *argv[])
{

	char *sname, *dname, *filename;
	int outfd;
	int wanted_channel = -1;

	fprintf(stderr,"ready to talk to casio, the routes are done automatically from\n");
	fprintf(stderr,"midi_in if it's available, else MidiIn and to MidiOut.\n");
	fprintf(stderr,"I download and exit when done.  I automatically do retries.\n");
	fprintf(stderr,"Nonetheless, I may 'hang' when uploading.  This is soft.\n");
	fprintf(stderr,"Hit ^D to retry.  Or hit ^C, I'll exit.\n");
	fprintf(stderr,"I write to the file on the fly (every 256 packets), so watch it.\n");
    if (argc != 2)
	{
		fprintf(stderr,"usage: casio filename, routes are automatic, then\n");
		fprintf(stderr," select FZ output device as 'midi' and do a voice save\n");
		exit(1);
    }

	sname = "FZ1out";
	dname = "FZ1in";
	filename = argv[1];

	if ((outfd = creat(filename,0666)) == -1)
	{
		fprintf(stderr,"couldn't create output file\n");
		perror(filename);
		exit(2);
	}

	if (!(IntuitionBase = OpenLibrary("intuition.library",0)))
	{
		fprintf(stderr,"can't open intuition.library\n");
		goto clean;
	}

	add_cleanup(midi_cleanup);

    if (!(MidiBase = OpenLibrary(MIDINAME,MIDIVERSION))) 
	{
		fprintf(stderr,"can't open midi.library\n");
		goto clean;
    }

	/* create our public source node */
	if (!(source = CreateMSource(sname,NULL)))
	{
		fprintf(stderr,"can't create Source port %s\n",sname);
		goto clean;
	}

    /* create our dest node (public) */
    if (!(dest = CreateMDest(dname,NULL))) 
	{
		fprintf(stderr,"can't create Dest port %s\n",dname);
		goto clean;
    }

	route_a = MRoutePublic("midi_in",dname,&myrouteinfo);
	if (!route_a)
		route_a = MRoutePublic("MidiIn",dname,&myrouteinfo);

	route_b = MRoutePublic(sname,"MidiOut",&myrouteinfo);

	init_timer();

    commune_with_fz1(source,dest,wanted_channel,outfd);	   /* process until shutdown */

clean:
	close(outfd);
    cleanup();
	exit(0);
}

void _abort(void)	   /* abort routine called when CTRL-C is hit (Aztec) */
{
    /* fflush(stdout); cleanup(); exit(1); */
} /* we're polling it in the loop */

void dump_open(struct FZ1_Message *p)
{
	unsigned int nblocks;

	printf("dump of Open MIDI message received from Casio FZ-1\n");
	printf("channel %d, status %d, ",p->encoded_channel & 0x0f,p->body.openmsg.status);
	printf("banks %d, voices %d, ",p->body.openmsg.banks,p->body.openmsg.voices);

	if(p->body.openmsg.status != 1 || p->body.openmsg.banks != 0 
		|| p->body.openmsg.voices != 1)
		panic("I don't know how to do anything other than save a single voice");

	nblocks = (p->body.openmsg.nblocks_hinibble << 12) 
		| (p->body.openmsg.nblocks_next_to_hinibble << 8) 
		| (p->body.openmsg.nblocks_next_to_lonibble << 4) 
		| p->body.openmsg.nblocks_lonibble;
	printf("nblocks %d, ",nblocks);
	printf("edit bank %d, edit voice %d\n",p->body.openmsg.edit_bank,p->body.openmsg.edit_voice);
}

/* end of casio.c */

