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