/* seq.c -- implement adagio scores as abstract data type */

/*****************************************************************************
*       Change Log
*  Date | Change
*-----------+-----------------------------------------------------------------
*  2-Apr-91 | JDW : further changes
* 16-Feb-92 | GWL : use reg_timebase in seq_play()
*****************************************************************************/

#include "stdio.h"
#include "cext.h"
#include "userio.h"
#include "midicode.h"
#include "midifns.h"
#include "timebase.h"
#include "moxc.h"
#include "seq.h"

extern int moxcdebug;
extern timebase_type default_base;

boolean seq_print = false;      /* debugging print switch */

seq_type sequence;      /* this is a global to be accessed by routines called
			 * from the sequence */

/* clock state: */
time_type clock_ticksize;       /* millisec per tick shifted 16 bits */
boolean clock_running = false;  /* true if clock is running */
boolean external_midi_clock = false;
boolean suppress_midi_clock = false;

private void insert_event();
private void process_event();

private char *chunk_alloc();
private void clock_tick();
private void ramp_event(seq_type seq, event_type event, unsigned int value,
    unsigned int to_value, int increment, time_type step, int n);
/*private*/ void send_macro();

/* chunk_alloc -- allocate data for a sequence */
/*
 * NOTE: This assumes one chunk is already allocated.
 * The first chunk holds shared sequence information in 
 * the info struct, and by convention this is always in
 * the first chunk.
 */
private char *chunk_alloc(seq_type seq, int size)
{
    chunk_type chunk = seq->chunklist->u.info.last_chunk;
    /* gprintf(TRANS, "chunk_alloc: seq %lx size %d\n", seq, size); */
    if (size & 1) size++;	/* make it even */
    if (chunk->free + size >= CHUNK_SIZE) {
	chunk_type new_chunk = chunk_create(false);
	if (!chunk) {
	    gprintf(FATAL, "Out of memory while reading seq\n");
	    return NULL;
	}
	/* add new_chunk to chunk chain */
	seq->chunklist->u.info.last_chunk = new_chunk;
	chunk->next = new_chunk;
	chunk = new_chunk;
    }
    chunk->free += size;
    return &(chunk->u.data[chunk->free - size]);
}


/* chunk_create -- create a new chunk for seq data */
/*
 * If this is the first chunk, set first_flag to reserve
 * space for the info structure.
 */
chunk_type chunk_create(boolean first_flag)
{
    chunk_type result = (chunk_type) memget(sizeof(chunk_node));
    if (result) {
	result->next = NULL;
	result->u.info.refcount = 1;  /* pre-initialize for caller */
	result->free = 0;
	if (first_flag) {
	    result->free = sizeof(struct info_struct);
	    result->u.info.last_chunk = result;
	    result->u.info.dictionary = NULL;
	    result->u.info.eventlist = NULL; 
	    result->u.info.ctrlcount = 0;
	    result->u.info.notecount = 0;
	    result->u.info.duration = 0;
	    result->u.info.used_mask = 0;
	}
    }
    /* gprintf(TRANS, "chunk_create: got %lx (size %d)\n", */
		   /* result, sizeof(chunk_node)); */
    return result;
}

/* clock_tick -- advance the clock and send a tick */
/**/
private void clock_tick(seq, fraction)
  seq_type seq;
  time_type fraction;
{
    int delay;
    fraction += clock_ticksize;
    delay = fraction >> 16;
    fraction &= 0xFFFF;
    if (seq->runflag && clock_ticksize && seq->note_enable) {
	midi_clock();
	cause((delay_type)delay, clock_tick, seq, fraction);
    } else {
	clock_running = false;
	midi_stop();
	midi_clock(); /* stop takes effect on next clock, so provide one */
    }
}

private void cycle(seq)
  seq_type seq;
{
    seq_reset(seq);
    seq_play(seq);
}



/****************************************************************************
*               event_create
* Inputs:
*    seq_type seq: the seq to hold the event
*    int size: the size of the event in bytes
*    time_type etime: the time of the event
*    int eline: the line number of the event
* Returns:
*    event_type: a new event structure or
*               NULL if there is not enough memory left
* Effect:
*    allocates memory from the chunk, then heap as needed
* Implementation:
*    to reduce the per block storage overhead, we allocate memory in
*    large chunks and do our own allocation.  Allocate from first
*    chunk first.  If full, allocate a new chunk.
* WARNING: this implementation assumes that individual events are never freed!!
****************************************************************************/

private event_type event_create(seq, size, etime, eline)
  seq_type seq;
  int size;
  time_type etime;
  int eline;
{
    event_type result = (event_type) chunk_alloc(seq, size);
    if (result) {
	result->ntime = etime;
	result->nline = eline;
	/* since we know the time, we can insert now: */
	insert_event(seq, result);
	seq_duration(seq) = max(seq_duration(seq), etime);
    }
    return result;
}


/* insert_call -- add a call event to the seq */
/**/
event_type insert_call(seq, ctime, cline, voice, addr, value, n)
  seq_type seq;
  time_type ctime;
  int cline;
  int voice;
  int (*addr)();
  long value[SEQ_MAX_PARMS];
  int n;
{
    int i;
    register event_type event = event_create(seq, callsize, ctime, cline);
    if (seq_print) {
	gprintf(TRANS, 
	    "call(%lx): time %ld, line %d, voice %d, fn %lx,\n\tvalues:",
	    event, ctime, cline, voice, addr);
	for (i = 0; i < n; i++) gprintf(TRANS, " %ld", value[i]);
	gprintf(TRANS, "\n");
    }
    if (event) {
	seq_used_mask(seq) |= 1 << (voice - 1);
	event->nvoice = ctrl_voice(ESC_CTRL, voice);
	event->value = CALL_VALUE;
	event->u.call.routine = addr;
	/* save the arguments */
	for (i = 0; i < n; i++) event->u.call.args.a[i] = value[i];
	seq_ctrlcount(seq)++;
    }
    return event;       
}

  
/* insert_clock -- add a clock cmd to the seq */
/**/
event_type insert_clock(seq, ctime, cline, ticksize)
  seq_type seq;
  time_type ctime;
  int cline;
  time_type ticksize;
{
    register event_type event = event_create(seq, clocksize, ctime, cline);

    if (seq_print) {
	gprintf(TRANS, "clock(%lx): time %ld, line %d\n", event, ctime, cline);
    }
    if (event) {
	event->nvoice = ctrl_voice(ESC_CTRL, 1);
	event->value = CLOCK_VALUE;
	event->u.clock.ticksize = ticksize;
	seq_ctrlcount(seq)++;
    }
    return event;
}


/* insert_ctrl -- add a control to the seq */
/**/
event_type insert_ctrl(seq, ctime, cline, ctrl, voice, value)
  seq_type seq;
  time_type ctime;
  int cline;
  int ctrl;
  int voice;
  int value;
{
    register event_type event = event_create(seq, ctrlsize, ctime, cline);
    if (seq_print) {
	gprintf(TRANS,
	    "ctrl(%lx): time %ld, line %d, ctrl %d, voice %d, value %d\n",
	    event, ctime, cline, ctrl, voice, value);
    }
    if (event) {
	seq_used_mask(seq) |= 1 << (voice - 1);
	event->nvoice = ctrl_voice(ctrl, voice);
	event->value = value;
	seq_ctrlcount(seq)++;
    }
    return event;
}


/* insert_ctrlramp -- add a control ramp event to the seq */
/**/
event_type insert_ctrlramp(seq, rtime, rline, voice, step, dur, ctrl, v1, v2)
  seq_type seq;
  time_type rtime;
  int rline;
  int voice;
  time_type step;
  time_type dur;
  int ctrl;
  int v1, v2;
{
    register event_type event = event_create(seq, ctrlrampsize, rtime, rline);
    if (seq_print) {
	gprintf(TRANS, 
	    "ctrlramp(%lx): time %ld, line %d, step %ld, dur %ld, ctrl %d, voice %d\n",
	    event, rtime, rline, step, dur, ctrl, voice);
	gprintf(TRANS, "\tfrom %d to %d\n", v1, v2);
    }

    if (event) {
	seq_used_mask(seq) |= 1 << (voice - 1);
	event->nvoice = ctrl_voice(ESC_CTRL, voice);
	event->value = CTRLRAMP_VALUE;
	if (dur <= 0) dur = 1L; /* don't allow zero duration */
	event->u.ramp.dur = dur;
	event->u.ramp.ctrl = ctrl;
	if (step <= 0) step = 1; /* don't allow zero step size */
	event->u.ramp.step = step;
	event->u.ramp.u.ctrl.from_value = v1;
	event->u.ramp.u.ctrl.to_value = v2;
	seq_ctrlcount(seq)++;
	seq_duration(seq) = max(seq_duration(seq), rtime + dur);
    }
    return event;
}


/* insert_def -- add a definition to the dictionary */
/**/
def_type insert_def(seq, symbol, definition, deflen)
  seq_type seq;
  char *symbol;
  unsigned char *definition;
  int deflen;
{
    int i;
    def_type defn = (def_type) chunk_alloc(seq, sizeof(def_node));
    defn->symbol = chunk_alloc(seq, strlen(symbol) + 1);
    defn->definition = (unsigned char *) chunk_alloc(seq, deflen);
    strcpy(defn->symbol, symbol);
    for (i = 0; i < deflen; i++) {
	defn->definition[i] = definition[i];
    }
    defn->next = seq_dictionary(seq);
    seq_dictionary(seq) = defn;
    if (seq_print) {
	gprintf(TRANS, "def(%ld): symbol %s defn \n", defn, symbol);
	for (i = 0; i < deflen; i++) gprintf(TRANS, "%x", definition[i]);
	gprintf(TRANS, "\n");
    }
    return defn;
}


/* insert_deframp -- add a def ramp event to the seq */
/**/
event_type insert_deframp(seq, rtime, rline, voice, step, dur,
			  def, nparms, parms, parm_num, to_value)
  seq_type seq;
  time_type rtime;
  int rline;
  int voice;
  time_type step;
  time_type dur;
  def_type def;
  int nparms;           /* number of parameters for macro */
  short parms[];        /* actual parameter vector */
  int parm_num;         /* which of the actual parameters to ramp */
  int to_value;         /* final destination of ramp */
{
    register event_type event = event_create(seq, deframpsize, rtime, rline);
    if (seq_print) {
	int i;
	gprintf(TRANS, 
	    "deframp(%ld): time %ld, line %d, voice %d, step %ld, dur %ld\n",
	    event, rtime, rline, voice, step, dur);
	gprintf(TRANS, "def %ld, parms");
	for (i = 0; i < nparms; i++) gprintf(TRANS, " %d", parms[i]);
	gprintf(TRANS, "parm_num %d to %d\n", parm_num, to_value);
    }
    if (event) {
	int i;
	seq_used_mask(seq) |= 1 << (voice - 1);
	event->nvoice = ctrl_voice(ESC_CTRL, voice);
	event->value = DEFRAMP_VALUE;
	if (dur <= 0) dur = 1L; /* don't allow zero duration */
	event->u.ramp.dur = dur;
	event->u.ramp.ctrl = 0;
	if (step <= 0) step = 1; /* don't allow zero step size */
	event->u.ramp.step = step;
	event->u.ramp.u.def.definition = def->definition;
	 for (i = 0; i < nmacroparms; i++) {
	    event->u.ramp.u.def.parameter[i] = (i < nparms ? parms[i] : 0);
	}
	event->u.ramp.u.def.parm_num = parm_num;
	event->u.ramp.u.def.to_value = to_value;
	seq_ctrlcount(seq)++;
	seq_duration(seq) = max(seq_duration(seq), rtime + dur);
    }
    return event;
}


/****************************************************************************
*               insert_event
* Inputs:
*    seq_type seq: where to put the event
*    event_type event: the event to insert
* Effect:
*    inserts event into the event list
*    NOTE: it is inserted *after* previously inserted events with the same time
* Implementation:
*    adagio files often contain many independent voices.  Although each voice
*    consists of events in sequence, the voices need not be inter-twined in
*    the input file.  Rather, all the events of voice 1 appear followed by all
*    the events of voice 2, and so forth.  As phase one merges these event
*    sequences, it must make many passes over an increasingly long list of
*    events: expensive if we always start from the beginning of the list!
*    we can exploit the fact that each voice is sequential by starting the
*    search for the proper point of insertion at the last event inserted.
*    the variable "last_event" is used to remember this hint.  We could
*    also snapshot "last_event" in "ref_event" when a !tempo or !rate
*    command occurs as another hint, but we don't.
****************************************************************************/

private void insert_event(seq, event)
  seq_type seq;
  register event_type event;
{
    event_type *evlptr = &(seq_eventlist(seq));
    if ((*evlptr == NULL) ||
	(event->ntime < (*evlptr)->ntime)) {
	/* insert at the head of the list */
	event->next = *evlptr;
	*evlptr = event;
	seq->current = event;
    } else {
	/* insert somewhere after the head of the list
	 * assume: current is not NULL.  why?  because it is only null
	 * before the first insertion, which is taken
	 * care of in the previous arm of this conditional.
	 */
	register event_type previous;
	register event_type insert_before;

	if (event->ntime >= seq->current->ntime) {
	    /* insertion point is after current */
	    previous = seq->current;
	    insert_before = previous->next;
	} else {
	    /* insertion point is before current; start at beginning */
	    /* assume: not inserting at very head of list; that would
	     * have been taken care of above */
	    previous = seq_events(seq);
	    insert_before = previous->next;
	}

	while ((insert_before != NULL) &&
	    (event->ntime >= insert_before->ntime)) {
	    previous = insert_before;
	    insert_before = insert_before->next;
	}
	previous->next = event;
	event->next = insert_before;
	seq->current = event;
    }
}


/* insert_macctrl -- add a control to the seq */
/**/
event_type insert_macctrl(seq, ctime, cline, ctrl, voice, value)
  seq_type seq;
  time_type ctime;
  int cline;
  int ctrl;
  int voice;
  int value;
{
    register event_type event = event_create(seq, macctrlsize, ctime, cline);
    if (seq_print) {
	gprintf(TRANS, 
	    "macctrl(%lx): time %ld, line %d, ctrl %d, voice %d, value %d\n",
	    event, ctime, cline, ctrl, voice, value);
    }
    if (event) {
	seq_used_mask(seq) |= 1 << (voice - 1);
	event->nvoice = ctrl_voice(ESC_CTRL, voice);
	event->value = MACCTRL_VALUE;
	event->u.macctrl.ctrl_number = ctrl;
	event->u.macctrl.value = value;
	seq_ctrlcount(seq)++;
    }
    return event;
}


/* insert_macro -- insert a macro call seq */
/**/
event_type insert_macro(seq, ctime, cline, def, voice, nparms, parms)
  seq_type seq;
  time_type ctime;
  int cline;
  def_type def;
  int voice;
  int nparms;
  short *parms;
{
    register event_type event = event_create(seq, macrosize, ctime, cline);
    if (seq_print) {
	int i;
	gprintf(TRANS, 
	    "macro(%lx): time %ld, line %d, def %ld, voice %d, parms",
	    event, ctime, cline, def, voice);
	for (i = 0; i < nparms; i++) gprintf(TRANS, " %d", parms[i]);
	gprintf(TRANS, "\n");
    }
    if (event) {
	seq_used_mask(seq) |= 1 << (voice - 1);
	event->nvoice = ctrl_voice(ESC_CTRL, voice);
	event->value = MACRO_VALUE;
	event->u.macro.definition = def->definition;
	while (nparms-- > 0) {
	    event->u.macro.parameter[nparms] = parms[nparms];
	}
	seq_ctrlcount(seq)++;
    }
    return event;
}


/* insert_note -- add a note to the seq */
/**/
event_type insert_note(seq, ntime, nline, voice, pitch, dur, loud)
  seq_type seq;
  time_type ntime;
  int nline;
  int voice;
  int pitch;
  time_type dur;
  int loud;
{
    register event_type event = event_create(seq, notesize, ntime, nline);

    if (seq_print) {
	gprintf(TRANS,
  "note(%lx): time %ld, line %d, dur %ld, pitch %d, voice %d, loudness %d\n",
		event, ntime, nline, dur, pitch, voice, loud);
    }
    
    if (event) {
	seq_used_mask(seq) |= 1 << (voice - 1);
	event->nvoice = voice - 1;
	event->value = pitch;
	event->u.note.ndur = (dur << 8) + loud;
	seq_notecount(seq)++;
	seq_duration(seq) = max(seq_duration(seq), ntime + dur);
    }
    return event;
}


/* insert_seti -- add a seti event to the seq */
/**/
event_type insert_seti(seq, stime, sline, voice, addr, value)
  seq_type seq;
  time_type stime;
  int sline;
  int voice;
  int *addr;
  int value;
{
    register event_type event = event_create(seq, setisize, stime, sline);
    if (seq_print) {
	gprintf(TRANS, 
	    "seti(%ld): time %ld, line %d, voice %d, addr %ld, value %d\n",
	    event, stime, sline, voice, addr, value);
    }
    if (event) {
	event->nvoice = ctrl_voice(ESC_CTRL, voice);
	event->value = SETI_VALUE;
	event->u.seti.int_to_set = addr;
	event->u.seti.value = value;
	seq_ctrlcount(seq)++;
    }
    return event;       
}


/* noop -- just returns, the default stopfunc for sequences */
/**/
void noop(seq_type seq) {}


private void process_event(seq)
  seq_type seq;
{
    register event_type event;
    if (!seq->runflag) return;
    while ((event = seq->current) && (event->ntime <= virttime)) {
	int voice;
	/* process all current (and earlier) events */
	if (is_note(event)) {   /*** play a note or rest ***/
	    /* if this note is not a rest, play it and schedule an off event */
	    if (event->value != NO_PITCH &&
		  (seq_channel_mask(seq) &
		   (1 << ((voice = vc_voice(event->nvoice)) - 1)))) {
		seq_noteon(seq, voice, event->value,
			 (int) event->u.note.ndur & 0xFF);
		if (debug) {
		    gprintf(TRANS, "play pitch %d at %ld\n",
				event->value, event->ntime);
		}
		seq_cause_noteoff(seq, (event->u.note.ndur) >> 8,
		    voice, event->value);

	    }
	} else {                    /*** send a control command ***/
	    int n;
	    time_type step;
	    int delta;
	    long increment;
	    int voice = vc_voice(event->nvoice);
	    ulong enabled = seq_channel_mask(seq) & (1 << (voice - 1));

	    switch (vc_ctrl(event->nvoice)) {
	      case PSWITCH_CTRL:
		if (!enabled) break;
		seq_midi_ctrl(seq, voice, PORTASWITCH, event->value);
		break;
	      case MODWHEEL_CTRL:
		if (!enabled) break;
		seq_midi_ctrl(seq, voice, MODWHEEL, event->value);
		break;
	      case TOUCH_CTRL:
		if (!enabled) break;
		seq_midi_touch(seq, voice, event->value);
		break;
	      case VOLUME_CTRL:
		if (!enabled) break;
		seq_midi_ctrl(seq, voice, VOLUME, event->value);
		break;
	      case BEND_CTRL:
		if (!enabled) break;
		seq_midi_bend(seq, voice, (event->value << 6));
		break;
	      case PROGRAM_CTRL:
		if (!enabled) break;
		seq_midi_program(seq, voice, event->value + 1);
		break;
	      case ESC_CTRL:
		switch (event->value) {
		  case CALL_VALUE:
		    sequence = seq;
		    (*(event->u.call.routine))(event->u.call.args);
		    break;
		  case CLOCK_VALUE:
		    clock_ticksize = event->u.clock.ticksize;
		    if (!clock_running && !suppress_midi_clock && 
			!external_midi_clock) {
			clock_running = true;
			midi_start();
			clock_tick(seq, 0L);
		    }
		    break;
		  case MACCTRL_VALUE:
		    if (!enabled) break;
		    seq_midi_ctrl(seq, voice, event->u.macctrl.ctrl_number,
					      event->u.macctrl.value);
		    break;
		  case MACRO_VALUE: {
		    if (!enabled) break;
		    send_macro(event->u.macro.definition, voice,
			       event->u.macro.parameter, -1, 0,
			       event->nline);
		    break;
		  }
		  case CTRLRAMP_VALUE:
		  case DEFRAMP_VALUE: {
		    int from, to;
		    if (!enabled) break;

		    step = event->u.ramp.step;
		    if (event->value == CTRLRAMP_VALUE) {
			from = event->u.ramp.u.ctrl.from_value;
			to = event->u.ramp.u.ctrl.to_value;
		    } else {
			from = event->u.ramp.u.def.parameter[
				    event->u.ramp.u.def.parm_num];
			to = event->u.ramp.u.def.to_value;
		    }
		    delta = to - from;
		    increment = delta;
		    if (delta < 0) delta = -delta;
		    /* Note: Step is always non-zero */
		    n = event->u.ramp.dur / step;
		    increment = (increment << 8) / n;
		    ramp_event(seq, event, from << 8, to << 8, 
			       (int) increment, step, n);
		    seq->noteoff_count++;
		    break;
		  }
		  case SETI_VALUE:
		    *(event->u.seti.int_to_set) = event->u.seti.value;
		    break;
		  default:
		    gprintf(TRANS, "unexpected ESC_CTRL value\n");
		    break;
		}
		break;
	      default:
		gprintf(TRANS, "unexpected seq data\n");
		break;
	    }
	}
	seq->current = event->next;
    }
    if (seq->current) {
	cause((delay_type)(event->ntime - virttime), process_event, seq);
    } else if (seq->noteoff_count == 0 && seq->note_enable) {
	/* if we're just advancing to a start point, note_enable will be
	 * false and this won't get called:
	 */
	if (seq->stopfunc) {
	    (*(seq->stopfunc))(seq);
	}
    }
}


/* ramp_event -- generate a ramp */
/**/
private void ramp_event(seq, event, value, to_value, increment, step, n)
  seq_type seq;
  register event_type event;
  unsigned int value;
  unsigned int to_value;
  int increment;
  time_type step;
  int n;
{
    if (seq->runflag) {
	int voice = vc_voice(event->nvoice);
/*      printf("ramp_event: value %d to_value %d increment %d step %d n %d time %d\n",
	       value, to_value, increment, step, n, virttime); */
	if (n == 0) value = to_value;
	else {
	    causepri((delay_type)step, 5, ramp_event, seq, event, value + increment,
		   to_value, increment, step, n - 1);
	}
	if (event->value == CTRLRAMP_VALUE) {
	    int ctrl = event->u.ramp.ctrl;
	    if (ctrl == -TOUCH_CTRL) midi_touch(voice, value >> 8);
	    else if (ctrl == -BEND_CTRL) midi_bend(voice, value >> 2);
	    else midi_ctrl(voice, ctrl, value >> 8);
	} else { /* must be DEFRAMP_VALUE */
	    send_macro(event->u.ramp.u.def.definition,
		       vc_voice(event->nvoice),
		       event->u.ramp.u.def.parameter,
		       event->u.ramp.u.def.parm_num, value >> 8,
		       event->nline);
	}
	if (n == 0) seq_end_event(seq);
    }
}


/* report_enabled_channels -- print out concise listing of channels */
/*
 * to fit on one line, write out ranges, e.g. 1-5 9-11 
 */
void report_enabled_channels(seq)
  seq_type seq;
{
    ulong mask = seq_channel_mask(seq);
    int i, range_open_at = 0;

    for (i = 1; i <= MAX_CHANNELS; i++) {
	if (!range_open_at && (mask & 1)) {
	    gprintf(TRANS, " %d", i);
	    range_open_at = i;
	} else if (range_open_at && !(mask & 1)) {
	    if (i > (range_open_at + 1)) {
		gprintf(TRANS, "-%d", i - 1);
	    }
	    range_open_at = 0; /* false */
	}
	mask = mask >> 1;
    }
    if (range_open_at) gprintf(TRANS, "-%d", MAX_CHANNELS);
}


/* send_macro -- instantiate macro and send it */
/*
 * note: to support ramping, "value" is used in place of
 *       parameter["parm_num"]
 */
/*private*/
void send_macro(ptr, voice, parameter, parm_num, value, nline)
  register unsigned char *ptr;
  int voice;
  short parameter[];
  int parm_num;
  int value;    
  int nline;
{
    register unsigned char code, *loc;
    while (code = *ptr++) {
	loc = ptr + *ptr;
	ptr++;
	if (code <= nmacroparms) {
	    code--;
	    *loc = (code == parm_num ? value : parameter[code]) & 0x7f;
	} else if (code == nmacroparms + 1) {
	    /* take old high order bits and OR in 4 voice bits */
	    *loc = (*loc & 0xF0) | ((voice - 1) & 0xF);
	} else {
	    code -= (nmacroparms + 2);
	    *loc = ((code == parm_num ? value : parameter[code]) >> 7) & 0x7F;
	}
    }
    if (ptr[1] == MIDI_SYSEX) {
	midi_exclusive(ptr + 1);
    } else {
	/* make sure user didn't try to send more than 3 bytes.  This test
	 * could be done at sequence read time, but it's tricky because the
	 * first byte could be a parameter, so in general you need to 
	 * plug the actual parameters into the message and then do the test.
	 * Currently, this is the only place parameters are plugged in.
	 */
	if (*ptr > 3) {
	    gprintf(ERROR, 
		    "Non-sysex macro longer than 3 bytes ignored, line %d.\n",
		     nline);
	} else {
	    midi_write((int) *ptr, MIDI_PORT(voice), ptr[1], ptr[2], ptr[3]);
	}
    }
}


/* seq_alloc -- a utility function to allocate a seq struct */
/**/
seq_type seq_alloc()
{
    seq_type seq;
    seq = (seq_type) memget(sizeof(seq_node));
    return seq;
}    


/* seq_at_end -- set the function to be called at sequence end */
/**/
void seq_at_end(seq, fn)
  seq_type seq;
  void (*fn)(seq_type);
{
    if (!fn) fn = noop;
    seq->stopfunc = fn;
}


/* seq_cause_noteoff_meth -- turn off a note in the future */
/**/
void seq_cause_noteoff_meth(seq, delay, voice, pitch)
  seq_type seq;
  time_type delay;
  int voice;
  int pitch;
{
    if (seq->note_enable) {
	pitch += seq->transpose;
	while (pitch < 0) pitch += 12;
	while (pitch > 127) pitch -= 12;
	seq->noteoff_count++;
	causepri((delay_type) delay, 10, seq->noteoff_fn,
		 seq, voice, pitch);
    }
}

/* seq_copy -- copy a sequence, share the eventlist */
/**/
seq_type seq_copy(from_seq)
  seq_type from_seq;
{
    register seq_type seq = seq_init(seq_alloc(), false);
    if (!seq) return NULL;
    seq->chunklist = from_seq->chunklist;
    seq->current = seq_events(seq);
    seq->chunklist->u.info.refcount++;
    seq->transpose = from_seq->transpose;
    seq->loudness = from_seq->loudness;
    seq->rate = from_seq->rate;
    seq->paused = from_seq->paused;
    seq->noteoff_count = 0;
    return seq;
}


/* seq_create -- create a seq structure and an initial event chunk */
/**/
seq_type seq_create()
{
    return seq_init(seq_alloc(), true);
}


/* seq_cycle -- set parameters for cycling a sequence */
/**/
void seq_cycle(seq_type seq, boolean flag, time_type dur)
{
    seq->cycleflag = flag;
    seq->cycledur = dur;
}


/* seq_end_event -- call this when an score-generated event ends */
/*
 * Assumes that noteoff_count was incremented when event started.
 */
void seq_end_event(seq)
  seq_type seq;
{
    /*gprintf(TRANS, "nd");*/
    seq->noteoff_count--;
    if (seq->current == NULL /* finished seq */ &&
	seq->noteoff_count == 0 /* finished noteoff's */ &&
	seq->runflag /* we've not been stopped */) {
	if (seq->cycleflag) {
	    cause((delay_type) (seq->cycledur - virttime), cycle, seq);
	} else if (seq->stopfunc) {
	    (*(seq->stopfunc))(seq);
	}
    }
}



/****************************************************************************
*               seq_free_meth
* Input: a seq_type
* Effect:
*    frees storage occupied by a seq
****************************************************************************/

private void seq_free_meth(seq)
  seq_type seq;
{
    seq_free_chunks(seq);
    if (seq->timebase) timebase_free(seq->timebase);
    memfree((void *) seq, sizeof(seq_node));
}


/* seq_free_chunks -- free storage for note list */
/*
 * NOTE: in its original form, this routine was perhaps more readable,
 * but would not compile under Microsoft C V7.00 due to a compiler bug.
 * I rewrote the code until the bug disappeared, hopefully without
 * changing the semantics!  If you change this code, make sure it still
 * compiles under Microsoft C.
 *
 * This module frees chunks from a seq_type in preparation for freeing
 * the seq_type itself.  Reference counts are checked and chunks are
 * only freed when the last reference is removed.
 */
public void seq_free_chunks(seq)
  seq_type seq;
{
    chunk_type tail;
    chunk_type head;

    head = seq->chunklist;
    if (((head->u.info.refcount)--) != 0) return;

    while (head != NULL) {
	tail = head->next;
	memfree((void *) head, sizeof(chunk_node));
	head = tail;
	seq->chunklist = head;
    }
}


seq_type seq_init(seq, create_chunk)
    seq_type seq;
    int create_chunk;
{
    if (!seq || !(seq->timebase = timebase_create(50))) {
	return NULL;
    }
    seq->chunklist = NULL;
    if (create_chunk) {
	seq->chunklist = chunk_create(true);
	if (!seq->chunklist) {
	    seq_free(seq);
	    return NULL;
	}
    }
    seq->cause_noteoff_fn = seq_cause_noteoff_meth;
    seq->midi_bend_fn     = seq_midi_bend_meth;
    seq->midi_ctrl_fn     = seq_midi_ctrl_meth;
    seq->midi_program_fn  = seq_midi_program_meth;
    seq->midi_touch_fn    = seq_midi_touch_meth;
    seq->noteoff_fn       = seq_noteoff_meth;
    seq->noteon_fn        = seq_noteon_meth;
    seq->free_fn          = seq_free_meth;
    seq->reset_fn         = seq_reset_meth;

    seq->current = NULL;
    seq->transpose = 0;
    seq->loudness = 0;
    seq->cycleflag = false;
    seq->cycledur = 0L;
    seq->rate = 256L;
    seq->paused = false;
    seq->stopfunc = noop;
    seq->channel_mask = 0xFFFFFFFFL;
    seq->runflag = seq->note_enable = false;
    return seq;
}


/* seq_midi_bend_meth -- send a midi bend */
/**/
void seq_midi_bend_meth(seq_type seq, int voice, int value)
{
    midi_bend(voice, value);
}


/* seq_midi_ctrl_meth -- send a midi ctrl change */
/**/
void seq_midi_ctrl_meth(seq_type seq, int voice, int ctrl, int value)
{
    midi_ctrl(voice, ctrl, value);
}


/* seq_midi_program_meth -- send a midi program change */
/**/
void seq_midi_program_meth(seq_type seq, int voice, int prog)
{
    midi_bend(voice, prog);
}


/* seq_midi_touch_meth -- send a midi touch */
/**/
void seq_midi_touch_meth(seq_type seq, int voice, int value)
{
    midi_touch(voice, value);
}


/* seq_noteoff_meth -- turn a seq note off */
/**/
void seq_noteoff_meth(seq, voice, pitch)
  seq_type seq;
  int voice;
  int pitch;
{
    midi_note(voice, pitch, 0);
	/*gprintf(TRANS, "_e");*/
    seq_end_event(seq);
}


/* seq_noteon_meth -- play a note with transformations */
/**/
void seq_noteon_meth(seq, chan, pitch, vel)
  seq_type seq;
  int chan, pitch, vel;
{
    if (seq->note_enable) {
	pitch += seq->transpose;
	while (pitch < 0) pitch += 12;
	while (pitch > 127) pitch -= 12;

	vel += seq->loudness;
	if (vel <= 0) vel = 1;
	else if (vel > 127) vel = 127;

	midi_note(chan, pitch, vel);
    }
}


/* seq_pause -- stop playing momentarily or resume playing */
/**/
time_type seq_pause(seq_type seq, boolean flag)
{
    if (!seq->paused && flag) {
	seq->paused = true;
	seq->rate = seq->timebase->rate;
	set_rate(seq->timebase, STOPRATE);
    } else if (seq->paused && !flag) {
	seq_play(seq);
    }
    return (time_type) seq->timebase->virt_base;
}


/* seq_play -- play a sequence from the current event forward */
/**/
void seq_play(seq)
  seq_type seq;
{
    timebase_type prev_timebase = timebase;
    register timebase_type reg_timebase = seq->timebase;

    if (!seq->runflag) {
	seq_reset(seq);
    }
    if (!seq->paused) return;
    eventtime = gettime();

    /* assume that virt_base is correct virtual time as the result
	of seq_start_time or seq_reset
     */
    timebase = reg_timebase;
    virttime = reg_timebase->virt_base;
    /* note that set_rate will set reg_timebase->real_base to eventtime */
    set_rate(reg_timebase, seq->rate);
    seq->paused = false; /* in case the score had been paused; note that
			    seq_pause() has no effect if paused is true */
    seq->runflag = true;
    seq->note_enable = true;

    /* restore previous timebase */
    timebase_use(prev_timebase);
}


/* seq_reset_meth -- reset a sequence to start back at the first event */
/**/
void seq_reset_meth(seq)
  seq_type seq;
{
    timebase_type old_timebase = timebase;

    if (seq->runflag) {
	/* maybe this seq is already reset, and process_event is
	 * already scheduled.  If so, don't schedule another one.
	 */
	if ((seq->timebase->virt_base == 0) &&
	    (seq->timebase->rate == STOPRATE)) {
	    /* in case the reader just iterated through the list without
	     * cause'ing events, reset the event list
	     */
	    seq->current = seq_events(seq);
	    return;
	}
	/* Otherwise, the seq is running, so stop it. */
	seq_stop(seq);
    }
    
    timebase_use(seq->timebase);
    set_rate(seq->timebase, STOPRATE);
    set_virttime(seq->timebase, 0L);
    seq->current = seq_events(seq);
    seq->noteoff_count = 0L;
    seq->runflag = true;
    seq->paused = true;
    if (seq->current)
	cause((delay_type)(seq->current->ntime - virttime), process_event, seq);
    timebase_use(old_timebase);
}


/* seq_set_loudness -- set the loudness offset of a sequence */
/**/
void seq_set_loudness(seq, loud)
  seq_type seq;
  int loud;
{
    seq->loudness = loud;
}

/* seq_set_rate -- set the rate of a sequence */
/**/
void seq_set_rate(seq, rate)
  seq_type seq;
  time_type rate;
{
    seq->rate = rate;
    if (!seq->paused) set_rate(seq->timebase, rate);
}


/* seq_set_transpose -- set the sequence transposition */
/**/
void seq_set_transpose(seq, trans)
  seq_type seq;
  int trans;
{
    seq->transpose = trans;
}


/* seq_start_time -- set the current pointer so the sequence starts here */
/**/
void seq_start_time(seq, start_time)
  seq_type seq;
  time_type start_time;
{
    timebase_type prev_timebase = timebase;
    if (!seq->runflag) {
	seq_reset(seq);
    }
    if (real_to_virt(seq->timebase, eventtime) > start_time) {
	seq_reset(seq);
    }
    timebase_use(seq->timebase);
    seq->note_enable = false;
    /* prime the pump */
    set_rate(timebase, STOPRATE);
    set_virttime(timebase, start_time);
    catchup();
    seq->note_enable = true;
    seq->paused = true;
    /* restore previous timebase */
    timebase_use(prev_timebase);
}


/* seq_stop -- stop a sequence, clear out all pending events */
/**/
void seq_stop(seq)
  seq_type seq;
{
    timebase_type prev_timebase = timebase;

    if (seq->runflag) {
	if (moxcdebug)
	    gprintf(TRANS, "seq_reset swap from timebase 0x%x to 0x%x\n",
		    timebase, seq->timebase);
	timebase = seq->timebase;
	seq->runflag = false;
	set_rate(timebase, STOPRATE);
	set_virttime(timebase, MAXTIME);
	catchup();
    }
    timebase_use(prev_timebase);
}
