/* MOXC -- a C version of Collinge's MOXIE language */ /* Copyright 1989 Carnegie Mellon University */ /***************************************************************************** * 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 * 03-Jun-88 | modified for portability (AMIGA) -JCD * 07-Jul-89 | time bases -RBD * 31-Jan-90 | GWL : cleaned up for LATTICE * 30-Jun-90 | RBD : further changes * 2-Apr-91 | JDW : further changes * 4-Mar-91 | GWL : DOS allows odd inst addrs * 10-Oct-94 | nix : posicionador tridimensionale interface * 28-Apr-03 | DM : true->TRUE, false->FALSE *****************************************************************************/ #include "switches.h" #ifdef AMIGA #ifdef AZTEC #include "functions.h" #else #include "amiga.h" #endif #include "exec/exec.h" #include "cmtcmd.h" extern long event_mask; /* imported from midifns.c */ #endif extern int abort_flag; /*DMH: taken out of ifdef AMIGA for moxcrun*/ #include "stdio.h" #include "cext.h" #include "userio.h" #include "midifns.h" #include "cmdline.h" #include "midicode.h" #include "timebase.h" #include "moxc.h" #ifdef AMIGA /*DMH: only AMIGA cares about AMIGA's "proportional controllers"*/ #include "prop1.h" #endif #ifdef POSICIONADOR_3D #include "pos3d.h" #include "pos3dbuf.h" #endif /* POSICIONADOR_3D */ extern char *app_syntax; /*************************************************************************** * * 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 * app_syntax string defining extra command line options * * EXPORTS: * * cause(delay, routine, p1, p2, ..., p8) * moxcdone -- set to TRUE to quit * eventtime -- ideallized current time * *****************************************************************************/ #define SAFEMOXC TRUE #define BREAKKEY 0x03 int moxcdone; /* flag to halt execution */ time_type eventtime; /* time of current call -- used to avoid */ /* timing errors due to finite execution speed */ time_type virttime; /* virtual time of current call */ timebase_type timebase; /* time base of current call */ int mididecode = TRUE; /* whether to decode messages or just call midievent */ int debug = FALSE; int moxcdebug = FALSE; time_type next_wakeup; timebase_type default_base; #ifdef AMIGA int pub_port_signal; struct MsgPort pub_port; #endif /***************************************************************************** * Routines local to this module *****************************************************************************/ private void callrun(); private void decode(); private void moxcterm(); /**************************************************************************** * callallcancel * Inputs: * timebase_queue * Effect: * return all calls to free list * Implementation: * If timebase_queue is not empty, there's a pending call. Remove the call * (not necessarily the timebase) and repeat. ****************************************************************************/ void callallcancel() { if (moxcdebug) gprintf(GDEBUG, "cancel all calls\n"); while (timebase_queue) { timebase = timebase_queue; timebase_queue = timebase->next; while (timebase->heap_size > 0) { call_free(remove_call(timebase)); } insert_base(timebase); } } /* catchup -- bring current timebase up to date by running its calls */ /**/ void catchup() { register call_type call; /* Remember where we're going in virtual time because setting the * rate will also modify timebase->virt_base. We don't want catchup * to stop short: */ time_type target_time = timebase->virt_base; /* remember timebase here because it's possible that a call will do * a timebase_use() and change it: */ register timebase_type my_base = timebase; while (my_base->heap_size != 0 && (my_base->heap[1]->u.e.time < target_time)) { /* eventtime is the real time at which something was scheduled */ eventtime = (my_base->next_time) >> 8; call = remove_call(my_base); virttime = call->u.e.time; (*(call->u.e.routine))(CALLARGS(call)); call_free(call); } /* now that we've possibly pulled events out of the timebase, adjust * the position in the timebase queue (and possibly remove it). */ remove_base(my_base); insert_base(my_base); } /**************************************************************************** * cause * Inputs: * delay_type (long) delay: time before this call should occur * int (*routine)(): routine that implements the call * int p1 through p8: parameters to pass to routine * Effect: * builds a call and puts it in pending queue for later scheduling ****************************************************************************/ #ifndef DOTS_FOR_ARGS void cause(delay, routine, p) delay_type delay; int (*routine)(); call_args_node p; #else #include void cause(delay_type delay, ...) /* note: the routine parameter is not checked because any routine type can be passed as a parameter, but in the call struct it's an int (*)() */ #endif { register call_type call = call_alloc(); #ifdef DOTS_FOR_ARGS va_list xp; #endif if (!call) { gprintf(ERROR, "cause: out of memory\n"); EXIT(1); } #ifdef DOTS_FOR_ARGS call->u.e.time = virttime + delay; call->u.e.priority = 128; /* default priority */ va_start(xp, delay); call->u.e.routine = (int (*)()) va_arg(xp, long *); call->u.e.p = va_arg(xp, call_args_node); va_end(xp); #else call->u.e.time = virttime + delay; call->u.e.priority = 128; /* default priority */ call->u.e.routine = routine; call->u.e.p = p; #endif #ifdef SAFEMOXC if (call->u.e.routine == 0) { gprintf(ERROR,"cause called with NULL routine\n"); EXIT(1); #ifndef DOS /* IBM allows odd addresses */ #if (__APPLE__ != 1 || __i386__ != 1) /* Intel Mac allows odd addresses */ } else if (((long) call->u.e.routine) & 1) { gprintf(ERROR, "cause called with bad routine address: 0x%lx\n", call->u.e.routine); #ifndef GCC_MODEL_CPU #define GCC_MODEL_CPU "GCC_MODEL_CPU is undefined for this compilation" #endif gprintf(ERROR, GCC_MODEL_CPU); EXIT(1); #endif #endif } #endif /* put call in default queue */ callinsert(timebase, call); if (moxcdebug) { gprintf(GDEBUG,"(cause) call is pending on timebase 0x%x:\n", timebase); callshow(call); } } /**************************************************************************** * causepri * Inputs: * int delay: time before this call should occur * int pri: priority, lowest priority goes first * int (*routine)(): routine that implements the call * int p1 through p8: parameters to pass to routine * Effect: * builds a call and puts it in pending queue for later scheduling ****************************************************************************/ #ifndef DOTS_FOR_ARGS void causepri(delay, pri, routine, p) delay_type delay; int pri; int (*routine)(); call_args_node p; #else /* already included stdarg.h */ void causepri(delay_type delay, int pri, ...) #endif { register call_type call = call_alloc(); #ifdef DOTS_FOR_ARGS va_list xp; #endif if (!call) { gprintf(ERROR, "cause: out of memory\n"); EXIT(1); } #ifdef DOTS_FOR_ARGS call->u.e.time = virttime + delay; call->u.e.priority = pri; /* default priority */ va_start(xp, pri); call->u.e.routine = (int (*)()) va_arg(xp, long *); call->u.e.p = va_arg(xp, call_args_node); va_end(xp); #else call->u.e.time = virttime + delay; call->u.e.priority = pri; /* default priority */ call->u.e.routine = routine; call->u.e.p = p; #endif #ifdef SAFEMOXC if (call->u.e.routine == 0) { gprintf(ERROR,"cause called with NULL routine\n"); EXIT(1); #ifndef DOS /* IBM allows odd addresses */ #if (__APPLE__ != 1 || __i386__ != 1) /* Intel Mac allows odd addresses */ } else if (((long) call->u.e.routine) & 1) { gprintf(ERROR, "causepri called with bad routine address: 0x%lx\n", call->u.e.routine); EXIT(1); #endif #endif } #endif /* put call in default queue */ callinsert(timebase, call); if (moxcdebug) { gprintf(GDEBUG,"(cause) call is pending:"); callshow(call); } } /**************************************************************************** * callrun * Inputs: * call_type call: the call to execute * Effect: * executes the previously scheduled call call and deallocates it ****************************************************************************/ private void callrun() { call_type call; if (moxcdebug) { gprintf(GDEBUG,"(callrun) running a call: \n"); } /* remove from head of queue */ while (!timebase_queue) gprintf(TRANS, "callrun fatal error\n"); timebase = timebase_queue; timebase_queue = timebase->next; if (debug) gprintf(TRANS, "callrun time %ld\n", timebase->next_time); eventtime = (timebase->next_time) >> 8; /* real time of the call */ /* remove first call from timebase */ call = remove_call(timebase); if (debug) gprintf(TRANS, "callrun call %lx\n", (ulong)call); insert_base(timebase); virttime = call->u.e.time; /* virtual time of the call */ if (moxcdebug) callshow(call); (*(call->u.e.routine))(CALLARGS(call)); call_free(call); } /**************************************************************************** * m_restuntil * Inputs: * int time: call time to rest until * Effect: * Waits until the specified time has been reached (absolute time). * Other "caused" calls will take place during the rest provided * this routine is called from "mainscore" (see m_rest description). ****************************************************************************/ void m_restuntil(time) time_type time; { time = virt_to_real(timebase, time); while(time > gettime()) { moxcwait(time); } } /**************************************************************************** * 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) time_type time; { m_restuntil(time + real_to_virt(timebase, gettime())); } /**************************************************************************** * moxcinit * Inputs: * int argc: number of command line arguments * char * argv: command line argument array * Effect: initializes moxc system ****************************************************************************/ boolean moxcinit(argc, argv) int argc; char * argv[]; { meminit(); io_init(); #ifdef AMIGA pub_port_signal = AllocSignal(-1L); pub_port.mp_Node.ln_Type = NT_MSGPORT; pub_port.mp_SigBit = pub_port_signal; pub_port.mp_SigTask = FindTask(0L); pub_port.mp_Flags = PA_SIGNAL; pub_port.mp_Node.ln_Name = "CMTcmdport"; pub_port.mp_MsgList.lh_Head = (struct Node *)&pub_port.mp_MsgList.lh_Tail; pub_port.mp_MsgList.lh_TailPred = (struct Node *)&pub_port.mp_MsgList.lh_Head; event_mask |= (1L << pub_port_signal); AddPort(&pub_port); #endif cu_register((cu_fn_type) moxcterm, NULL); cl_syntax(midifns_syntax); cl_syntax("debugEnable verbose debugging;\ moxcEnable moxc debug mode;"); cl_syntax(app_syntax); if (!cl_init(argv, argc)) { /* make sure user gets to read the error message(s): */ gprintf(TRANS, "Type anything to exit..."); #ifdef DOS wait_ascii(); #else ggetchar(); #endif return FALSE; } debug = cl_switch("debug"); moxcdebug = cl_switch("moxc"); timebase = default_base = timebase_create(100); default_base->rate = 2560L; eventtime = 0L; next_wakeup = MAXTIME; musicinit(); #ifdef POSICIONADOR_3D ptInit(); #endif moxcdone = 0; return TRUE; } /**************************************************************************** * moxcwait * Input: * -1 => wait for next keyboard or midi event or queued event * 0 => don't wait * T => wait up to T for next keyboard or midi event or queued event * (this is used by m_restuntil) * Assume: there is work to do (npending > 0 || evqueue) ?? * Effect: dispatch on user inputs, cause calls ****************************************************************************/ void moxcwait(dateoftimeout) time_type dateoftimeout; { time_type maxtime = dateoftimeout; if (timebase_queue) { if ((timebase_queue->next_time >> 8) < maxtime) maxtime = (timebase_queue->next_time) >> 8; } eventwait(maxtime); decode(); } /**************************************************************************** * decode * Effect: dispatch on user inputs, cause calls ****************************************************************************/ private void decode() { /* It is important that midi_data is on a word boundary because we copy to it by doing a word transfer. */ byte midi_data[4]; time_type now; byte code; char k; #ifdef AMIGA struct cmd_msg *cmd; #endif now = gettime(); timebase = default_base; eventtime = now; virttime = 0L; /* gprintf(GDEBUG, "decode at time %ld\n", now); */ /********************************************** * poll for and decode midi keyboard input ***********************************************/ while (getbuf(FALSE, midi_data)) { /* only divide if necessary, divides take 100us on 8MHz 68000: */ if (virttime == 0) virttime = real_to_virt(default_base, now); /* short-circuit midi decoding */ if (!mididecode) { midievent(midi_data); continue; } code = midi_data[0] & MIDI_CODE_MASK; if (code == MIDI_ON_NOTE) { if (midi_data[2] == 0) { /* velocity 0 -> note off */ keyup(1+(midi_data[0] & MIDI_CHN_MASK), midi_data[1]); } else { keydown((midi_data[0] & MIDI_CHN_MASK)+1, midi_data[1], midi_data[2]); } } else if (code == MIDI_OFF_NOTE) { keyup((midi_data[0] & MIDI_CHN_MASK)+1, midi_data[1]); } 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] + 1); /* think C midi driver doesn't handle sysex the way the Amiga drivers do (yet) */ #ifndef MACINTOSH } else if (code == MIDI_SYSEX) { sysex(); #endif } } /********************************************** * poll for ASCII keyboard input ***********************************************/ while (get_ascii(&k)) { virttime = real_to_virt(default_base, now); asciievent(k); /* if user doesn't handle abort char in asciievent, we should exit now to avoid an infinite loop */ if (abort_flag) EXIT(1); } #ifdef POSICIONADOR_3D /********************************************** * poll for posicionador tridimensionale input **********************************************/ { pt_value pt_data; while (ptGetValue(&pt_data)) { /* only divide if necessary, divides take 100us on 8MHz 68000: */ if (virttime == 0) virttime = real_to_virt(default_base, now); ptevent(&pt_data); } } #endif /* POSICIONADOR_3D */ #ifdef AMIGA /********************************************** * poll for proportional controller port **********************************************/ if (prop_1_events) { int events; Disable(); events = prop_1_events; prop_1_events = 0; Enable(); if (events & BUTTON_1_RIGHT_CHANGE) buttonchange(3, prop_1_right_button); if (events & BUTTON_1_LEFT_CHANGE) buttonchange(2, prop_1_left_button); if (events & PROP_1_LEFT_CHANGE) propchange(2, prop_1_left_data); if (events & PROP_1_RIGHT_CHANGE) propchange(3, prop_1_right_data); } /********************************************** * poll for input from public command port ***********************************************/ while (cmd = (struct cmd_msg *) GetMsg(&pub_port)) { struct symb_descr *desc = &HASHENTRY(lookup(cmd->symbol_name)); /* gprintf(TRANS, "got %lx (%s) from pub_port\n", cmd, cmd->symbol_name); */ virttime = real_to_virt(default_base, now); if (!desc) { gprintf(TRANS, "Error, symbol %s undefined.\n", cmd->symbol_name); } else if (desc->symb_type != cmd->symb_type) { gprintf(TRANS, "Error, wrong type for symbol %s\n", cmd->symbol_name); } else if (cmd->symb_type == fn_symb_type) { /* gprintf(TRANS, "Calling routine\n"); */ (*(desc->ptr.routine))( (int) cmd->the_args[0], (int) cmd->the_args[1], (int) cmd->the_args[2], (int) cmd->the_args[3], (int) cmd->the_args[4], (int) cmd->the_args[5], (int) cmd->the_args[6], (int) cmd->the_args[7] ); } else if (cmd->symb_type == var_symb_type) { *(desc->ptr.intptr) = (int) cmd->the_args[0]; } else if (cmd->symb_type == vec_symb_type) { if (cmd->the_args[0] >= desc->size) { gprintf(TRANS, "Error: Vector %s is of size %d\n", cmd->symbol_name, desc->size); } else { (desc->ptr.intptr)[cmd->the_args[0]] = cmd->the_args[1]; /* gprintf(TRANS, "vec: setting %lx\n", &(desc->ptr.intptr)[cmd->the_args[0]]); */ } } else gprintf(TRANS, "Symbol Type Error\n"); ReplyMsg(&(cmd->msg)); } #endif /********************************************** * poll for next call in queue ***********************************************/ now = (now + 1) << 8; /* shift because next_time is also scaled, * add 256 because next_time has added priority */ if (debug) gprintf(TRANS, "now %ld next_time %ld\n", now, (timebase_queue ? timebase_queue->next_time : 1234)); /* give pending events priority, but every 100 events, loop to allow input processing (user may want to give a "quit" command) */ for (k = 0; k < 100 && timebase_queue && (now > timebase_queue->next_time); k++) { callrun(); } /******************* * flush text output ********************/ #ifdef MACINTOSH_OR_UNIX gflush(); #endif } /**************************************************************************** * quit * Effect: tells moxc to shut down ****************************************************************************/ void quit() { moxcdone = TRUE; } /* moxcrun -- schedule events until done */ /**/ void moxcrun() { moxcdone = FALSE; while (!moxcdone && !abort_flag) { /* test for finish */ if (!timebase_queue) moxcdone = TRUE; else moxcwait(MAXTIME); /* do work */ } } /* moxcterm -- clean up after moxcinit */ /**/ private void moxcterm() { #ifdef AMIGA FreeSignal((long) pub_port_signal); RemPort(&pub_port); #endif }