/* MOXC -- a C version of Collinge's MOXIE language	*/

/*****************************************************************************
*	Change Log
*  Date	    | Change
*-----------+-----------------------------------------------------------------
* 31-Dec-85 | Modified for use with midi
*  5-Feb-86 | Added m_rest and m_restuntil allowing rests at top level
* 28-May-86 | Added command line parsing
*  4-Jun-86 | changed keyevent to separate calls for each event type
* 10-Jul-86 | put loop in mainscore with prompt to play and replay
*****************************************************************************/

/* IMPORTS:
	asciievent(k)		user-defined action for terminal input
	bendchange(ch, val)	user-defined pitch bend handler
	ctrlchange(ch, c, val)	user-defined control change handler
	keydown(ch, p, v)	user-defined MIDI note on handler
	keyup(ch, p)		user-defined MIDI note off handler
	mainscore()		user-defined first action(s)
	musicfns		lots of time and io functions
	peddown(ch)		user-defined pedal down handler
	pedup(ch)		user-defined pedal up handler
	touchchange(ch, val)	user-defined aftertouch handler
*/
asciievent();
bendchange();
ctrlchange();
keydown();
keyup();
mainscore();
peddown();
pedup();
prgmchange();
touchchange();

/* EXPORTS:
	cause(delay, routine, p1, p2, ..., p8)
	moxcdone -- set to true to quit
	eventtime -- ideallized current time
*/

#include "cext.h"
#include "stdio.h"
#include "malloc.h"
#include "mpu.h"
#include "cmdline.h"
#include "midicode.h"
#include "moxc.h"

#define SAFEMOXC true

/* lists of switches and options */
#define nswitches 8
private char *switches[nswitches] = 
    { "-debug", "-d", "-miditrace", "-m", "-trace", "-t", "-help", "-block" };
#define noptions 1
private char *options[1] = { "-tune" };

#define n_d_sw 2
private char *d_switches[n_d_sw] = { "-d", "-debug" };


/* number of events to prepare to run on each main loop */
#define NPREPARE 5

#define NPRIORITY 10

typedef struct event {
    struct event *next; /* link to next event record */
    long time;		/* time of this event */
    int priority;	/* priority of this event (0 is highest) */
    int (*routine)();	/* who to call */
    int p1, p2, p3, p4, p5, p6, p7, p8; /* what to pass */
} *event_type;

/*
 * pending is used to sort events by priority and hold them until
 * there is time to insert them in the sorted evqueue.  Note that
 * in this implementation, only one priority is used.
 * nodes are allocated/returned from/to evfree
 */
event_type pending[NPRIORITY];
int npending;	/* tells how many events are in pending array */
event_type evqueue;		/* waiting to run event queue */
event_type evfree = NULL;	/* free list */
int moxcdone;	/* flag to halt execution */
long eventtime; /* time of current event -- used to avoid timing errors due */
		/*  to finite execution speed */
int debug = false;

/*****************************************************************************
*	Routines local to this module
*****************************************************************************/
private void		cmdline_help();
private event_type	evallocate();
private event_type	evcdr();
private event_type	evcons();
private	void		evdeallocate();
private boolean		evgtr();
private event_type	evinsert();
private	void		evrun();
private	void		evshow();
private	void		moxcinit();
private	void		moxcpoll();
private	void		schedule();

/****************************************************************************
*				cause
* Inputs:
*	int delay: time before this event should occur
*	int (*routine)(): routine that implements the event
*	int p1 through p8: parameters to pass to routine
* Effect: 
*	builds an event and puts it in pending queue for later scheduling
****************************************************************************/

void cause(delay, routine, p1, p2, p3, p4, p5, p6, p7, p8)
    int delay, (*routine)(), p1, p2, p3, p4, p5, p6, p7, p8;
{
    event_type ev;
    int priority;
/*    priorities are not currently implemented:
    if (priority >= NPRIORITY) priority = NPRIORITY-1;
    if (priority <= 0) priority = 0;
*/
    priority = 0;
#ifdef SAFEMOXC
    if (routine == 0) {
        printf("Error: cause called with NULL routine\n");
	musicterm();
	exit(1);
    }
#endif
    ev = evallocate();
    ev->time = eventtime + delay;
    ev->priority = priority;
    ev->routine = routine;
    ev->p1 = p1;
    ev->p2 = p2;
    ev->p3 = p3;
    ev->p4 = p4;
    ev->p5 = p5;
    ev->p6 = p6;
    ev->p7 = p7;
    ev->p8 = p8;

    npending++;
    pending[priority] = evcons(ev, pending[priority]);
    if (debug) {
	printf("(cause) event is pending:");
	evshow(ev);
    }
}

/****************************************************************************
*				 cmdline_help
* Effect: 
*	Prints out command line help
****************************************************************************/

private void cmdline_help()
{
    fprintf(stderr,"program_name [options]\n");
    fprintf(stderr,"	 Options are below.  Those with * are for wizards:\n");
    fprintf(stderr,"	   -block	     disable MIDI thru\n");
    fprintf(stderr,"	   -debug (-d)	     enable verbose debug mode\n");
    fprintf(stderr,"	   -help	     this message\n");
    fprintf(stderr,"	   -miditrace (-m)   turn on MIDI command trace\n");
    fprintf(stderr,"	   -tune file	     use tuning from file\n");
    fprintf(stderr,"	   -trace (-t)	     trace music\n");
}

/****************************************************************************
*			    evallocate
* Outputs:
*	returns event_type allocated from freelist or with malloc
****************************************************************************/

private event_type evallocate()
{
    if (evfree) return evcdr(&evfree);
    /* else */ return ((event_type) malloc(sizeof(struct event)));
}

/****************************************************************************
*				evcdr
* Inputs:
*	event_type *evlist: address of a list pointer
* Outputs:
*	returns the head of the list
* Effect:
*	removes the head of the list from *evlist
****************************************************************************/

private event_type evcdr(evlist)
    event_type *evlist;
{
    event_type ptr;
    ptr = *evlist;
    *evlist = (*evlist)->next;
    ptr->next = NULL;
    return ptr;
}

/****************************************************************************
*				evcons
* Inputs:
*	event_type ev: event to put at the head of the list
*	event_type evlist: the list
* Outputs:
*	returns list with ev at the head
* Effect: modifies ev's next pointer
****************************************************************************/

private event_type evcons(ev, evlist)
    event_type evlist, ev;
{
    ev->next = evlist;
    return ev;
}

/****************************************************************************
*			    evdeallocate
* Inputs:
*	event_type ev: a node to deallocate
* Effect: 
*	returns ev to free list
****************************************************************************/

private void evdeallocate(ev)
    event_type ev;
{
    evfree = evcons(ev, evfree);
}

/****************************************************************************
*			    evgtr
* Inputs:
*	event_type ev1, ev2: two events to be compared
* Outputs:
**	returns true if ev1 is earlier or has lower priority number than ev2
****************************************************************************/

private boolean evgtr(ev1, ev2)
    event_type ev1, ev2;
{
    return (ev1->time < ev2->time) ||
	   ((ev1->time == ev2->time) && (ev1->priority < ev2->priority));
}

/****************************************************************************
*			    evinsert
* Inputs:
*	event_type evlist: the list (a priority queue)
*	event_type ev: the event to insert in evlist
* Outputs:
*	returns list resulting from inserting ev into evlist
* Implementation:
*	if ev goes at the head, ev is cons onto evlist and returned
*	otherwise evlist is modified by inserting ev at the right spot
****************************************************************************/

private event_type evinsert(evlist, ev)
    event_type evlist, ev;
{
    event_type ptr;
    if (!evlist) {	/* no list, return event */
	ev->next = NULL;
	return ev;
    }
    if (evgtr(ev, evlist)) { /* make event first on list */
	return evcons(ev, evlist);
    }
    ptr = evlist;
    while ((ptr->next) && evgtr(ptr->next, ev)) {
	ptr = ptr->next;
    }
    ev->next = ptr->next;
    ptr->next = ev;
    return evlist;
}

/****************************************************************************
*				evrun
* Inputs:
*	event_type ev: the event to execute
* Effect: 
*	executes the previously scheduled event ev and deallocates it
****************************************************************************/

private void evrun()
{
    event_type ev;
    if (debug) {
	printf("(evrun) running an event: \n");
    }
    ev = evcdr(&evqueue);
    eventtime = ev->time;
    if (debug) evshow(ev);
    (*(ev->routine))
	(ev->p1, ev->p2, ev->p3, ev->p4, ev->p5, ev->p6, ev->p7, ev->p8);
    evdeallocate(ev);
}

/****************************************************************************
*				evshow
* Inputs:
*	eventtype ev: the event to show
* Effect: 
*	prints a description of ev
* Assumes:
*	ev is not null
****************************************************************************/
private void evshow(ev)
    event_type ev;
{
    printf("address:  %d\n", ev);
    printf("time:     %ld\n", ev->time);
    printf("priority: %d\n", ev->priority);
    printf("routine:  %d\n", ev->routine);
    printf("parameters: %d, %d, %d, %d, %d, %d, %d, %d\n",
	 ev->p1, ev->p2, ev->p3, ev->p4, ev->p5, ev->p6, ev->p7, ev->p8);
}

/****************************************************************************
*				    m_rest
* Inputs:
*	int time: Amount of time to rest
* Effect: 
*	Waits until the amount of time specified has lapsed
* Assumes:
*	Must not be called from a "caused" routine.  Must only be called
*	from "mainscore" or a routine called directly or indirectly from
*	"mainscore" without using "cause".
****************************************************************************/

void m_rest(time)
    int time;
{
    m_restuntil(time + gettime());	
}

/****************************************************************************
*				  m_restuntil
* Inputs:
*	int time: Event time to rest until
* Effect: 
*	Waits until the specified time has been reached (absolute time).
*	Other "caused" events will take place during the rest provided
*	this routine is called from "mainscore" (see m_rest description).
****************************************************************************/
void m_restuntil(time)
    int time;
{
    while(time > gettime()) {
	moxcpoll();
    }
}

/****************************************************************************
*				main
* Inputs:
*	int argc: number of command line arguments
*	char * argv: command line argument array
* Effect: 
*	initializes and runs moxc program
****************************************************************************/

void main(argc,argv)
    int argc;
    char * argv[];
{
    while (askbool("Type RETURN to play, or N RETURN to quit", true)) {
	moxcinit(argc, argv);	/* initialize structures */
	mainscore();		/* call user's start program */
	while (!moxcdone) {		/* test for finish */
	    if (!evqueue) moxcdone |= (npending == 0);
	    moxcpoll();		/* do work */
	}
	musicterm();
	printf("End of Moxc execution.\n");
    }
}

/****************************************************************************
*				moxcinit
* Inputs:
*	int argc: number of command line arguments
*	char * argv: command line argument array
* Effect: initializes moxc system
****************************************************************************/

private void moxcinit(argc, argv)
    int argc;
    char * argv[];
{
    int i;	/* loop variable */

    cl_init(switches, nswitches, options, noptions, argv, argc);

    if (cl_switch("-help")) {
	cmdline_help(); 
	exit(0);
    }

    debug = (cl_nswitch(d_switches, n_d_sw) != NULL);

    for (i=0; i < NPRIORITY; i++) pending[i] = NULL;
    evqueue = NULL;
    eventtime = 0;

    musicinit();    

    moxcdone = 0;
}

/****************************************************************************
*				moxcpoll
* Effect: dispatch on user inputs, cause events
****************************************************************************/

private void moxcpoll()
{
    long now;		/* current time */
    int k;		/* terminal input (this is int so users do not
			 * have to declare type of parameter */
    byte midi_data[4];	/* midi input */

    /* get the time */
	now = gettime();
    /* see if any user-caused events have happened */
	eventtime = now;
	/* poll for and decode midi keyboard input */
	    if (getbuf(false, midi_data)) {
		byte code = midi_data[0] & MIDI_CODE_MASK;
		if (code == MIDI_ON_NOTE) {
		    if (midi_data[2] == 0) {	/* velocity 0 -> note off */
			keyup((midi_data[0] & MIDI_CHN_MASK) + 1,
			      midi_data[1] - 12);
		    } else {
			keydown((midi_data[0] & MIDI_CHN_MASK) + 1,
				midi_data[1] - 12, midi_data[2]);
		    }
		} else if (code == MIDI_OFF_NOTE) {
		    keyup((midi_data[0] & MIDI_CHN_MASK) + 1,
		    	  midi_data[1] - 12);
		} else if (code == MIDI_TOUCH) {
		    touchchange((midi_data[0] & MIDI_CHN_MASK) + 1,
				midi_data[1]);
		} else if (code == MIDI_BEND) {
		    bendchange((midi_data[0] & MIDI_CHN_MASK) + 1,
			       midi_data[1] + (midi_data[2] << 7));
		} else if (code == MIDI_CTRL && midi_data[1] == SUSTAIN) {
		    if (midi_data[2] == 0)
			pedup((midi_data[0] & MIDI_CHN_MASK) + 1);
		    else peddown((midi_data[0] & MIDI_CHN_MASK) + 1);
		} else if (code == MIDI_CTRL) {
		    ctrlchange((midi_data[0] & MIDI_CHN_MASK) + 1,
			       midi_data[1], midi_data[2]);
		} else if (code == MIDI_CH_PROGRAM) {
		    prgmchange((midi_data[0] & MIDI_CHN_MASK) + 1,
		    		midi_data[1]);
		}
	    }
	/* poll ASCII keyboard */
	    if (kbhit()) {
		k = getch();
		asciievent(k);
		if (debug && evqueue)
		    printf("nextevent is scheduled for %ld\n", evqueue->time);
	    }
    /* if it is now time, run the next event */
	else if ((evqueue != NULL) && (now >= evqueue->time)) evrun();
    /* if events are waiting to be scheduled ... */
	if (npending > 0) schedule();
}

/****************************************************************************
*				quit
* Effect: tells moxc to shut down
****************************************************************************/

void quit()
{
    moxcdone = true;
}

/****************************************************************************
*				schedule
* Effect: takes events off pending queues and prepares them to run
****************************************************************************/

private void schedule()
{
    int i, n;
    event_type ev;

    /* insert up to NPREPARE events from pending queues */
    i = 0;	/* i is number of events inserted */
    n = 0;	/* n is the priority */
    while (i < NPREPARE && n < NPRIORITY) {
	if (pending[n]) {
	    npending--;
	    ev = evcdr(&pending[n]);
	    evqueue = evinsert(evqueue, ev);
	    i++;
	    if (debug) {
		printf("(main) event inserted: \n");
		evshow(ev);
	    }
	} else {
	    n++;
	}
    }		
}