/****************************************************************************
	       seqread.c -- Phase 1 of adagio compilation...

 this module parses adagio programs and builds a linked list structure
 consisting of notes and control changes in time order.

 Copyright 1989 Carnegie Mellon University
*****************************************************************************/

/*****************************************************************************
*       Change Log
*  Date     | Change
*-----------+-----------------------------------------------------------------
* 31-Dec-85 | Created changelog
* 31-Dec-85 | Add c:\ to include directives
* 31-Dec-85 | Added standard command scanner, metronome variable, need to add 
*           | cmdline_help procedure
* 31-Dec-85 | Call intr_init
* 31-Dec-85 | Set musictrace from command line via -trace
* 31-Dec-85 | Added -poll
*  1-Jan-86 | Put error messages out to stderr
*  1-Jan-86 | Set IsAT.     Can be later overridden by -at and -xt switches,
*           | currently only used for diagnostics (may be needed for
*           | compatibles, who knows?  In which case remove the tests which
*           | confirm the type of processor)
*  1-Jan-86 | <rgd/jmn> Removed dur-adjusted message
*  1-Jan-86 | Added miditrace
* 18-Jan-86 | Shortened durations by 1/200 s to avoid roundoff problems --
*           | see buildnote for details.
*  3-Mar-86 | Allow octave and accidentals in either order after pitch name.
*           | Default octave is now one that gets nearest previous pitch,
*           |  the tritone (half an octave) interval is descending by default.
*           | Special commands handled by table search, !Rate command added
*           |  to scale all times by a percentage (50 = half speed).
*  9-Mar-86 | Use space to limit amount of storage allocation.    Otherwise
*           |    exhausting storage in phase1 caused phase2 to fail.
* 12-Mar-86 | Broke off command line parser into adagio.c, only parser remains
* 24-Mar-86 | Changed representation from note_struct to event_struct
*           | Parse M, N, O, X, and Y as control change commands
* 23-May-86 | Added , and ; syntax: "," means "N0\n", ";" means "\n"
* 16-Jul-86 | modify to only call toupper/lower with upper/lower case as
*           |  parameter to be compatible with standard C functions
*  7-Aug-86 | fixed bug with default pitches and rests
*  5-Jul-87 | F.H: Introduced new memory handling from Mac version.
*           |    Changed:    init()
*           |       ins_event()
*           |       ins_ctrl()
*           |       ins_note()
*           |    Deleted:    reverse()
*           |       nalloc()
*           |    Introduced:    event_alloc()
*           |       phase1_FreeMem()
*           |       system.h & system.c dependencies
* 10-Feb-88 | fixed parseend to accept blanks and tabs,
*           | fixed rate scaling of durations
* 11-Jun-88 | commented out gprintf of \n to ERROR after parsing finished.
* 13-Oct-88 | JCD : exclusive AMIGA version.
* 13-Apr-89 | JCD : New portable version.
* 31-Jan-90 | GWL : Cleaned up for LATTICE
* 30-Jun-90 | RBD : further changes
* 2-April-91 | JDW : further changes
* 30-Jun-91 | RBD : parse '+' and '/' in durations, * after space is comment
*****************************************************************************/

#include "switches.h"
#include "stdio.h"
#include "cext.h"
#include "cmdline.h"
#include "midifns.h" /* to get time_type */
#include "timebase.h"
#include "moxc.h"    /* to get debug declared */
#include "seq.h"
#include "seqread.h"
#include "userio.h"
/* ctype.h used to be included only by UNIX and AMIGA,
   surely everyone wants this? */
#include "ctype.h"
#ifndef toupper
/* we're already taking precautions, so inline version of toupper is ok: */
#define toupper(c) ((c)-'a'+'A')
/* CAUTION: AZTEC V5.0 defines an inline version of toupper called _toupper,
   but they got it wrong!
 */
#endif

/* cmtcmd.h references amiga message ports */
#ifdef AMIGA
#ifdef LATTICE
#include "amiga.h"
#endif
#include "exec/exec.h"
#endif
#include "cmtcmd.h"

/* public stuff */
extern long space;    /* remaining free bytes */
extern int abort_flag;

/****************************************************************************
 The following are used to simulate fixed point with the radix point
 8 bits from the right:
****************************************************************************/

#define precise(x) (((time_type) x) << 8)
#define round(x) ((((time_type) x) + 128) >> 8)
#define trunc(x) (((time_type) x) >> 8)

#define nullstring(s) (s[0] == EOS)


/****************************************************************************
* Routines local to this module:
****************************************************************************/
private void            do_a_rest();
private time_type       doabsdur();
private int             doabspitch();
private void            doclock();
private void            docomment();
private void            doctrl();
private void            dodef();
private time_type       dodur();
private void            doerror();
private int             doloud();
void            domacro();
private void            donextdur();
private int             dopitch();
private void            doprogram();
private void            dorate();
private void            doset();
private void            dospecial();
private time_type       dosymdur();
private void            dotempo();
private void            dotime();
private void            dovoice();
private void            fferror();
private void            init();
private int             issymbol();
private void            marker();
private void            parseend();
private void            parsefield();
private boolean         parsenote();
private boolean         parseparm();
private int             scan();
private int             scan1();
private long            scanint();
private void            scansymb();
private long            scansgnint();

/****************************************************************************
* data structures for parser lookup tables
****************************************************************************/

struct durt {    /* duration translation table */
    char symbol;
    time_type value;
};

#define durtable_len 7
struct durt durtable[durtable_len] = {
    'W', 4800L,
    'H', 2400L,
    'Q', 1200L,
    'I', 600L,
    'S', 300L,
    '%', 150L,
    '^', 75L
};

struct loudt {    /* loudness translation table */
    char symbol[4];
    int value;
};

struct loudt loudtable[] = {
    "PPP", 20,
    "PP\0", 26,
    "P\0\0", 34,
    "MP\0", 44,
    "MF\0", 58,
    "F\0\0", 75,
    "FF\0", 98,
    "FFF", 127
};

char too_many_error[] = "Too many parameters";

private char *ssymbols[] = {"TEMPO", "RATE", "CSEC", "MSEC", 
			    "SETI", "SETV", "CALL", "RAMP",
			    "CLOCK", "DEF", "END"};

#define sym_tempo 0
#define sym_rate 1
#define sym_csec 2
#define sym_msec 3
#define sym_seti 4
#define sym_setv 5
#define sym_call 6
#define sym_ramp 7
#define sym_clock 8
#define sym_def 9
#define sym_end 10

/* number of symbols */
#define sym_n 11

#define linesize 100
private char line[linesize];    /* the input line */
private char token[linesize];    /* a token scanned from the input line */

private boolean pitch_flag;    /* set when a pitch is indicated */
/* (if controls changes are given, only allocate a note event if
     *  a pitch was specified -- i.e. when pitch_flag is set)
     */
private boolean rest_flag;    /* set when a rest (R) is found */
/* this flag is NOT inherited by the next line */

private boolean symbolic_dur_flag;
/* true if last dur was not absolute
	 * (if this is set, then the default duration is changed
	 *  accordingly when the tempo is changed.)
	 */


#define nctrl 8

private boolean ctrlflag[nctrl];
/* true if control change was present
	 * ctrlflag[0] true if ANY control change
	 * was present
	 */
private int ctrlval[nctrl];
/* the new value of the control */
#define nmacroctrl 10
short macctrlx;                 /* index into the following: */
short macctrlnum[nmacroctrl];   /* macro ctrl number, e.g. for ~4(67), or
				 * number of parameters for a symbolic macro */
short macctrlparmx[nmacroctrl]; /* ctrl value for ctrl change, or index of
				 * parameters for symbolic macro */
short macctrlparms[nmacroctrl*nmacroparms]; /* parameters for symbolic macros */
short macctrlnextparm;
def_type macctrldef[nmacroctrl]; /* definition for symbolic macro */

private time_type time_scale; /* 1000 if centisec, 100 if millisec */
/* note: user_specified_time * (time_scale / rate) = millisec */



/****************************************************************************
*
*    variables private to this module
*
****************************************************************************/

private boolean end_flag = false;    /* set "true" when "!END" is seen */

/****************************************************************************
*               state variables
* Because each line of an Adagio score inherits properties from the previous
* line, it makes sense to implement the parser as a collection of routines
* that make small changes to some global state.     For example, pitch is a
* global variable.  When the field G4 is encountered, the dopitch routine
* assigns the pitch number for G4 to the variable pitch.  After all fields
* are processed, these variables describe the current note and contain the
* default parameters for the next note as well.
*
* Global variables that are used in this way by the parsing rountines are:
****************************************************************************/
private int
linex,    /* index of the next character to be scanned */
lineno,    /* current line number */
fieldx,    /* index of the current character within a field */
pitch,    /* pitch of note */
loud,    /* loudness of note */
voice,    /* voice (midi channel) of note */
artic;  /* articulation (a percentage of duration) */

private boolean ndurp;  /* set when a next (N) is indicated */
/* (next time defaults to the current time plus duration unless
     *  overridden by a next (N) command whose presence is signalled
     *  by ndurp.)
     */

private time_type
thetime,    /* the starting time of the note */
rate,    /* time rate -- scales time and duration, default = 100 */
ntime,    /* the starting time of the next note */
dur,    /* the duration of the note */
tempo,    /* the current tempo */
start,    /* the reference time (time of last !tempo or !rate cmd) */
ticksize; /* set by !clock command, zero for no clock */

private int pitchtable[7] = { 
    69, 71, 60, 62, 64, 65, 67 };

extern char score_na[name_length];

private seq_type the_score;  /* this is the score we are parsing */


/* def_append -- append a byte to the current definition */
/*
 * The def data structure:
 *     [code][offset][code][offset]...[0][length][data][data][data]...
 * where code is 1:nmacroparms for %n,
 *               nmacroparms+1 for %v,
 *               nmacroparms+2:nmacroparms*2+1 for ^n
 * and offset is the byte offset (from the offset byte) to the data
 *      where the parameter should be substituted
 * and length is the number of data bytes
 */
boolean def_append(def, nparms, data)
  unsigned char def[];
  int nparms;
  int data;
{
    int base = (nparms << 1) + 1;       /* this byte is the length */
    /* first parameter has to be able to reference last byte: */
    if ((def[base])++ >= (254 - (nparms << 1))) {
	fferror("Data too long");
	return false;
    }
    def[base + def[base]] = data;
    return true;
}


def_type def_lookup(symbol)
  char *symbol;
{
    def_type defn = seq_dictionary(the_score);
    while (defn) {
	if (strcmp(defn->symbol, symbol) == 0) {
	    return defn;
	}
	defn = defn->next;
    }
    return NULL;
}


void def_parm(def, nparms, code)
  unsigned char def[];
  int nparms;
  int code;
{
    int i, j;
    /* in order to insert a 2-byte parameter descriptor, the offsets from
     * previous descriptors (that precede the data) need to be increased by 2:
     */
    for (i = 1; i < (nparms << 1); i += 2) {
	def[i] += 2;
    }
    /* now i is index of length; work backwards from the last byte, moving
     * everything up by 2 bytes to make room for the new descriptor:
     */
    for (j = i + def[i] + 2; j > i; j--) {
	def[j] = def[j - 2];
    }
    /* now i is index of offset; insert the descriptor code (first byte)
     * and the offset to the parameter location in the message (second byte)
     */
    def[i - 1] = code;
    def[i] = def[i + 2] + 2;
}

/****************************************************************************
*               do_a_rest
* Effect: parses a rest (R) command
****************************************************************************/

private void do_a_rest()
{
    if (token[fieldx])
	fferror("Nothing expected after rest");
    rest_flag = true;
}

/****************************************************************************
*               doabsdur
* Effect: parses an absolute dur (U) command
****************************************************************************/

private time_type doabsdur()
{
    time_type result=1000L;
    register char c;
    if (isdigit(token[fieldx])) {
	result = precise(scanint());
	/* allow comma or paren for use in parameter lists */
	if ((c = token[fieldx]) && (c != ',') && (c != ')') && (c != '+')) {
	    fferror("U must be followed by digits only");
	}
	if (time_scale == 1000) result *= 10; /* convert to ms */
    } else fferror("No digit after U");
    return result;
}

/****************************************************************************
*               doabspitch
* Effect: parses an absolute pitch (P) command
****************************************************************************/

private int doabspitch()
{
    int result = 60;
    int startx = fieldx;
    register char c;
    int savex;
    if (isdigit (token[fieldx])) {
	result = (int) scanint();
	/* allow comma or paren for abspitch in parameter */
	if ((c = token[fieldx]) && c != ',' && c != ')')
	    fferror("P must be followed by digits only");
	else if (result < minpitch) {
	    savex = fieldx;
	    fieldx = startx;
	    fferror("Minimum pitch of 0 will be used");
	    result = minpitch;
	    fieldx = savex;
	} else if (result > maxpitch) {
	    savex = fieldx;
	    fieldx = startx;
	    fferror("Maximum pitch of 127 will be used");
	    result = maxpitch;
	    fieldx = savex;
	}
    } else fferror("No digits after P");
    return result;
}


/* doartic -- compute an articulation factor */
/*
  NOTE: artic is a percentage that scales the duration
  of notes but not the time to the next note onset. It
  is applied to the final computed duration after all
  other scaling is applied.
 */
private void doartic()
{
    if (isdigit(token[fieldx])) {
	artic = (int) scanint();
	if (token[fieldx])
	    fferror("Only digits were expected here");
    } else fferror("No digits after /");
}


/* docall -- parse a call in the form !CALL fn(p1,p2,p3) */
/**/
private void docall()
{
    boolean error_flag = true;
    ndurp = false;

    linex += scan();

    if (token[0] == 0) fferror("Function name expected");
    else {
	char symbol[100];
	struct symb_descr *desc;
	long value[SEQ_MAX_PARMS];
	int i;

	scansymb(symbol);
	if (fieldx == 1) fferror("Routine name expected");
	else if (token[fieldx] != '(') fferror("Open paren expected");
	else {
	    desc = &HASHENTRY(lookup(symbol));
	    if (!desc->symb_type) {
		fieldx = 0;
		fferror("Function not defined");
	    } else if (desc->symb_type != fn_symb_type) {
		fieldx = 0;
		gprintf(TRANS, "desc->symb_type is %d\n", desc->symb_type);
		fferror("This is not a function");
	    } else {
		error_flag = false;
		fieldx++;       /* skip over paren */
		for (i = 0; i < SEQ_MAX_PARMS; i++) value[i] = 0;
		i = 0;
		/* note that no params "()" is legal */
		while (i < SEQ_MAX_PARMS && token[fieldx] != ')' && 
		       parseparm(&value[i])) {
		    i++;
		    if (token[fieldx] == ',') {
			fieldx++;
		    } else if (token[fieldx] != ')') {
			fferror("Unexpected character");
			error_flag = true;
			break;
		    }
		}
		fieldx++;
		if (i > SEQ_MAX_PARMS) fferror("Too many parameters");
	    }
	    while (true) {
		linex += scan();
		if (nullstring(token)) {
		    break;
		} 
		switch (token[0]) {
		  case 'T':
		    fieldx = 1;
		    dotime();
		    break;
		  case 'V':
		    fieldx = 1;
		    dovoice();
		    break;
		  case 'N':
		    fieldx = 1;
		    donextdur();
		    break;
		  default:
		    fferror("Unexpected character");
		}
	    }
	    if (!error_flag)
		insert_call(the_score, round(thetime), lineno, voice, 
			    desc->ptr.routine, value, i);
	    /* advance the time only if an N field was given */
	    if (ndurp) thetime += ntime;
	}
    }
}


/* doclock -- insert a clock command */
/*
 * derivation: if there is no previous clock running, then start the
 *     clock on time.  Otherwise, start the clock half a tick early.
 *     ticksize = (beattime / 24) = ((60sec/tempo)/24) =
 *      ((60000ms/tempo)/24) = (60000/24)/tempo = 2500/tempo
 */
private void doclock()
{
    int oldticksize = ticksize;
    ticksize = (2500L << 16) / tempo;
    insert_clock(the_score, round(thetime) - (oldticksize >> 17),
		 lineno, ticksize);
}


/****************************************************************************
*               docomment
* Effect: parses a comment (*) command
****************************************************************************/

private void docomment()
{
    line[linex] = '\n'; /* force end of line to skip comment line */
    line[linex+1] = EOS;
}

/****************************************************************************
*               doctrl
* Inputs:
*    n: control number
* Effect: parses a control (K, M, O, X, or Y) command
****************************************************************************/

private void doctrl(n)
int n;
{
    ctrlval[n] = (int) scanint();
    if (token[fieldx]) {
	fferror("Only digits expected here");
    } else {
	ctrlflag[n] = true;
	ctrlflag[0] = true;    /* ctrlflag[0] set if any flag is set */
    }
}


private void dodef()
{
    /* maximum def size is 256 + 9 parms * 2 + 2 = 276 */
    unsigned char def[280];
    char symbol[100];
    int nparms = 0;
    int nibcount = 0;
    int data = 0;
    register char c;

    linex += scan();

    if (!token[0]) fferror("Symbol expected");
    else {
	strcpy(symbol, token);
	def[0] = def[1] = 0;
	while (true) {
	    linex += scan1(&line[linex]);
	    c = token[0];
	    if (!c) {
		linex--;
		if (nibcount & 1) {
		    fferror("Expected pairs of hex digits: one missing");
		    return;
		}
		break;
	    } else if (c == ' ' || c == '\t' || c == '\n') continue;
	    else if (isdigit(c)) {
		data = (data << 4) + (c - '0');
		nibcount++;
		if (!(nibcount & 1)) {
		    if (!def_append(def, nparms, data))
			return;
		    data = 0;
		}
	    } else if ('A' <= c && c <= 'F') {
		data = (data << 4) + (c - 'A') + 10;
		nibcount++; 
		if (!(nibcount & 1)) {
		    if (!def_append(def, nparms, data))
			return;
		    data = 0;
		}
	    } else if (c == 'V') {
		data = data << 4;
		nibcount++;
		/* v without a leading nibble is equivalent to 0v: */
		if (nibcount & 1) nibcount++;
		if (!def_append(def, nparms, data))
		    return;
		def_parm(def, nparms++, nmacroparms+1);
	    } else if (c == '%') {
		linex += scan1(&line[linex]);
		c = token[0];
		if (c < '1' || c > ('0' + nmacroparms)) {
		    fferror(parm_expected_error);
		    break;
		}
		if (!def_append(def, nparms, 0))
		    return;
		def_parm(def, nparms++, c - '0');               
	    } else if (c == '^') {
		linex += scan1(&line[linex]);
		c = token[0];
		if (c < '1' || c > ('0' + nmacroparms)) {
		    fferror(parm_expected_error);
		    break;
		}
		if (!def_append(def, nparms, 0))
		    return;
		def_parm(def, nparms++, (c - '0') + nmacroparms + 1);
	    } else {  /* something unexpected here -- just exit */
		linex--;
		fferror("Unexpected data");
		return;
	    }
	}
	insert_def(the_score, symbol, def,
		   (nparms << 1) + def[(nparms << 1) + 1] + 2);
    }
}

/****************************************************************************
*               dodur
* Effect: parses a duration (sum of dosymdur and/or doabsdur)
* sets symbolic_dur_flag (according to the first addend in mixed arithmetic)
*
* Returns: duration in "precise" units
****************************************************************************/
private time_type dodur()
{
    time_type result = 0L;
    symbolic_dur_flag = true;

    if (token[fieldx-1] == 'U') {
	result = doabsdur();
	symbolic_dur_flag = false;
    } else result = dosymdur();
    while (token[fieldx] == '+') {
	fieldx += 2;
	if (token[fieldx-1] == 'U') result += doabsdur();
	else result += dosymdur();
    }
    return scale(result, 100L, rate);
}

/****************************************************************************
*               doerror
* Effect: parse an unrecognized field by reporting an error
****************************************************************************/

private void doerror()
{
    fieldx = 0;
    fferror("Bad field");
}

/****************************************************************************
*               doloud
* Effect: parse a loudness (L) command
****************************************************************************/

private int doloud()
{
    int i, j;
    int result;
    int oldfieldx = fieldx;
    int newfieldx;
    char symbol[100];

    if (!token[fieldx] || token[fieldx]==')' || token[fieldx]==',') {
	fferror("L must be followed by loudness indication");
	return 100;
    }
    if (isdigit(token[fieldx])) {
	result = (int) scanint();
	newfieldx = fieldx;
	if (token[fieldx] && token[fieldx]!=')' && token[fieldx]!=',')
	    fferror("Digits expected after L");
	else if (result > 127) {
	    fieldx = oldfieldx;
	    fferror("Maximum loudness of 127 will be used");
	    fieldx = newfieldx;
	    result = 127;
	} else if (result == 0) {
	    fieldx = oldfieldx;
	    fferror("Minimum loudness of 1 will be used");
	    fieldx = newfieldx;
	    result = 1;
	}
	return result;
    }
    scansymb(symbol);
    newfieldx = fieldx;
    if ((i = strlen(symbol)) > 3 ) {    /* maximum is 3, e.g. "ppp" */
	fieldx = oldfieldx;
	fferror("Loudness field too long");
	fieldx = newfieldx;
	return 100;
    }
    symbol[i + 1] = '\0';   /* pad short symbols with 0    */
			    /* e.g. "p\0" -> "p\0\0"    */
    for (i = 0; i <= 7; i++) {    /* loop through possibilities    */
	for (j = 0; j <= 2; j++) {    /* test 3 characters    */
	    if (symbol[j] != loudtable[i].symbol[j])
		break;
	}
	if (j == 3) {
	    return loudtable[i].value;
	}
    }
    fieldx = oldfieldx;
    fferror("Bad loudness indication");
    fieldx = newfieldx;
    return 100;
}


void domacro()
{
    int control_num;
    int value;
    if (isdigit(token[1])) {
	control_num = (int) scanint();
	if (token[fieldx] == '(') {
	    fieldx++;
	    if (!isdigit(token[fieldx])) {
		fferror("Control value expected");
	    } else {
		value = (int) scanint();
		if (token[fieldx] != ')') {
		    fferror("Missing close paren");
		} else {
		    fieldx++;
		    if (token[fieldx])
			fferror("Nothing expected after paren");
		    else if (macctrlx < nmacroctrl - 1) {
			macctrlnum[macctrlx] = control_num;
			macctrlparmx[macctrlx] = value;
			macctrldef[macctrlx] = NULL;
			macctrlx++;
		    } else fferror("Too many controls");
		}
	    }
	} else fferror("Missing paren");
    } else {
	def_type def;
	char symbol[100];
	scansymb(symbol);
	if (fieldx == 1) fferror("Macro name expected");
	else if (token[fieldx] != '(') fferror("Open paren expected");
	else {
	    fieldx++;
	    def = def_lookup(symbol);
	    if (!def) {
		fieldx = 1;
		fferror("Undefined macro");
	    } else {
		long val;
		macctrlnum[macctrlx] = 0;
		macctrlparmx[macctrlx] = macctrlnextparm;
		macctrldef[macctrlx] = def;
		while (token[fieldx] != ')' && parseparm(&val)) {
		    macctrlparms[macctrlnextparm++] = val;
		    macctrlnum[macctrlx]++;
		    if (token[fieldx] == ',') {
			fieldx++;
		    } else if (token[fieldx] != ')') {
			fferror("Unexpected character");
			break;
		    }
		}
		fieldx++;
		macctrlx++;
	    }
	}
    }
}


/****************************************************************************
*               donextdur
* Effect: parse a next (N) command
* Implementation:
*    The syntax is N followed by a duration, so save dur and use dosymdur()
*    to parse the duration field.
*    The form N<digits> is parsed directly with scanint().
****************************************************************************/

private void donextdur()
{
    ndurp = true;    /* flag that N was given */
    if (isdigit(token[fieldx])) {
	ntime = precise(scanint());
	ntime = scale(ntime, (ulong)time_scale, rate);
	if (token[fieldx])
	    fferror("Only digits were expected here");
    } else {
	fieldx++;
	ntime = dodur();
    }
}

/****************************************************************************
*               dopitch
* Effect: parses a pitch command
****************************************************************************/

private int dopitch()
{
    int p, octave;
    int octflag = false;    /* set if octave is specified */
    int oldfieldx = fieldx;

    p = pitchtable[token[fieldx-1]-'A'];
    while (true) {
	if (token[fieldx] == 'S') {                /* sharp */
	    p++;
	    fieldx++;
	} 
	else if (token[fieldx] == 'N') {            /* skip */
	    fieldx++;
	} 
	else if (token[fieldx] == 'F') {            /* flat */
	    p--;
	    fieldx++;
	} 
	else if (isdigit(token[fieldx]) && !octflag) {      /* octave */
	    octave = (int) scanint();
	    octflag = true;
	} 
	else break;                /* none of the above */
    }
    if (octflag) p = (p-48) + 12 * octave;  /* adjust p to given octave */
    else {        /* adjust p to note nearest the default pitch */
	int octdiff = (p + 126 - pitch) / 12;
	p = p + 120 - (octdiff * 12);
    }
    if (p > maxpitch) {              /* pitch in range? */
	int newfield = fieldx;
	fieldx = oldfieldx;
	fferror("Pitch too high");
	fieldx = newfield;
	p = maxpitch;
    }
    /* We really should test for end-of-field, but we don't know if we're
       in a parameter list, so comma may or may not be legal */
    return p;
}

/****************************************************************************
*               doprogram
* Effect: parses a program change (Z) command
****************************************************************************/

private void doprogram()
{
    register int program = (int) scanint();
    ctrlflag[PROGRAM_CTRL] = ctrlflag[0] = true;
    if (token[fieldx]) {
	fferror("Z must be followed by digits only");
    } else if (program < minprogram) {
	fieldx = 1;
	fferror("Minimum program of 1 will be used");
	program = minprogram;
    } else if (program > maxprogram) {
	fieldx = 1;
	fferror("Maximum program of 128 will be used");
	program = maxprogram;
    }
    ctrlval[PROGRAM_CTRL] = program - 1;
}


private void doramp()
{
    int values[2];
    time_type stepsize = 100L;  /* default 10 per second */
    int index = 0;
    ndurp = false;
    values[0] = values[1] = 0;
    while (true) {
	linex += scan();
	fieldx = 1;
	if (nullstring(token)) {
	    break;
	} else if (index == 2) { /* must be stepsize in dur syntax */
	    stepsize = dodur();
	} else {
	    int ctrlx = 0;
	    static ctrl_map[] = { -BEND_CTRL, VOLUME, -TOUCH_CTRL, MODWHEEL };

	    switch (token[0]) {
	      case 'M': ctrlx++;        /* modwheel */
	      case 'O': ctrlx++;        /* aftertouch */
	      case 'X': ctrlx++;        /* volume */
	      case 'Y':                 /* pitch bend */

		if (index < 2) {
		    macctrlnum[index] = ctrl_map[ctrlx];
		    macctrlparmx[index] = (int) scanint();
		    if (token[fieldx])
			fferror("Only digits expected here");
		    macctrldef[index] = NULL;
		} else fferror("Unexpected control");
		break;
	      case '~':
		if (index < 2) {
		    domacro();
		    if (token[fieldx]) fferror("Unexpected character");
		} else fferror("Unexpected control");
		break;
	      case 'T':
		if (index < 2) fferror("Control expected");
		dotime();
		break;
	      case 'V':
		if (index < 2) fferror("Control expected");
		dovoice();
		break;
	      case 'N':
		if (index < 2) fferror("Control expected");
		donextdur();
		break;
	      default:
		if (index < 2) fferror("Control expected");
		dur = dodur();
		break;
	    }
	    if (index == 1 && (macctrlnum[0] != macctrlnum[1] ||
			       macctrldef[0] != macctrldef[1])) {
		fferror("Controls do not match");
	    }
	}
	index++;
    }
    if (index < 3) fferror("Expected 2 controls and step size");
    else {
	if (macctrldef[0]) {
	    int i, j, n;
	    n = 0;
	    i = macctrlparmx[0];
	    j = macctrlparmx[1];
	    while (n < macctrlnum[0]) {
		if (macctrlparms[i] != macctrlparms[j]) break;
		n++; i++; j++;
	    }
	    if (n >= macctrlnum[0]) n = 0;
	    /* Note: duration shortened to prevent overlap with next ramp */
	    insert_deframp(the_score, round(thetime), lineno, voice,
		round(stepsize), trunc(dur) - 1, macctrldef[0], macctrlnum[0],
		macctrlparms + macctrlparmx[0], n, macctrlparms[j]);
	} else {
	    /* Note: duration shortened to prevent overlap with next ramp */
	    insert_ctrlramp(the_score, round(thetime), lineno, voice,
		round(stepsize), trunc(dur) - 1,
		macctrlnum[0], macctrlparmx[0], macctrlparmx[1]);
	}
    }
    /* advance the time only if an N field was given */
    if (ndurp) thetime += ntime;
    else thetime += dur;
}


/****************************************************************************
*               dorate
* Effect: parses a !rate command
****************************************************************************/

private void dorate()
{
    linex += scan();
    if (!token[0])
	fferror("rate number expected");
    else {
	long oldrate = rate;
	rate = (int) scanint();
	if (token[fieldx])
	    fferror("Only digits expected here");
	if (rate == 0) {
	    fieldx = 0;
	    fferror("Rate 100 will be used here");
	    rate = 100L;
	}
	start = thetime;
	/* adjust dur in case it is inherited by next note */
	dur = (dur * oldrate);
	dur = dur / rate;
    }
}


private void doset(vec_flag)
  boolean vec_flag;
{
    ndurp = false;
    linex += scan();
    if (!token[0]) fferror("Variable name expected");
    else {
	struct symb_descr *desc = &HASHENTRY(lookup(token));
	if (!desc->symb_type) fferror("Called function not defined");
	else if (vec_flag && (desc->symb_type != vec_symb_type)) {
		fferror("This is not an array");
	} else if (!vec_flag && (desc->symb_type != var_symb_type)) {
		fferror("This is not a variable");
	} else {
	    int numargs = 1 + vec_flag;
	    int value[2];
	    int i;
	    int *address = desc->ptr.intptr;
	    value[0] = value[1] = 0;
	    i = 0;
	    while (true) {
		linex += scan();
		if (nullstring(token)) {
		    break;
		} else if (isdigit(token[0]) || token[0] == '-' ||
			   token[0] == '+') {
		    if (i < numargs) {
			value[i++] = (int) scansgnint();
			if (token[fieldx])
			    fferror("Only digits expected here");
		    } else fferror(too_many_error);
		} else {
		    switch (token[0]) {
		      case 'T':
			fieldx = 1;
			dotime();
			break;
		      case 'V':
			fieldx = 1;
			dovoice();
			break;
		      case 'N':
			fieldx = 1;
			donextdur();
			break;
		      default:
			fieldx++;
			if (i < numargs) {
			    value[i++] = round(dodur());
			} else fferror(too_many_error);
			break;
		    }
		}
	    }
	    if (vec_flag && i != 2) fferror("No index given");
	    if (vec_flag) {
		if (value[0] >= desc->size) {
		    fferror("Subscript out of bounds");
		    return;
		}
		/* reduce to the seti case: */
		address += value[0];    /* compute the vector address */
		value[0] = value[1];    /* set up value[] and i as if */
		i--;                    /* this were seti, not setv */
	    }
	    if (i != 1) fferror("No value given");
	    insert_seti(the_score, round(thetime), lineno, voice,
			address, value[0]);
	    /* advance the time only if an N field was given */
	    if (ndurp) thetime += ntime;
	}
    }
}

/****************************************************************************
*               dospecial
* Effect: parses special (those starting with "!") commands
****************************************************************************/

private void dospecial()
{
    switch (issymbol()) {
      case sym_tempo: 
	dotempo();
	break;
      case sym_rate: 
	dorate();
	break;
      case sym_csec:
	/* adjust dur for inheritance by next note */
	dur = (dur * 1000L) / time_scale;
	time_scale = 1000L;
	break;
      case sym_msec:
	dur = (dur * 100L) / time_scale;
	time_scale = 100L;
	break;
      case sym_seti:
	doset(false);
	break;
      case sym_setv:
	doset(true);
	break;
      case sym_call:
	docall();
	break;
      case sym_ramp:
	doramp();
	break;
      case sym_clock:
	doclock();
	break;
      case sym_def:
	dodef();
	break;
      case sym_end:
	end_flag = true;
	break;
      default: 
	fferror("Special command expected");
    }
    parseend(); /* flush the rest of the line */
}

/****************************************************************************
*               dosymdur
* Effect: parses a duration (^, %, S, I, Q, H, or W) command
****************************************************************************/

private time_type dosymdur()
{
    int i, dotcnt = 0;
    long dotfactor;
    time_type result;

    for (i = 0; i < durtable_len; i++) {
	if (durtable[i].symbol == token[fieldx-1]) {
	    /* the shift right is because durs are stored doubled because
	     *  otherwise a 64th note would have the value 75/2:  */
	    result = precise(durtable[i].value) >> 1;
	    break;
	}
    }
    if (i == durtable_len) {
	fieldx--;
	fferror("Duration expected: one of W, H, Q, I, S, %, or ^");
	return 0L;
    }
    while (token[fieldx]) {
	if (token[fieldx] == 'T') {     /* triplet notation */
	    result = (result * 2) / 3;  /* lose a bit but avoid scale() */
	    fieldx++;
	} 
	else if (token[fieldx] == '.') {    /* dotted notation */
	    dotcnt++;
	    fieldx++;
	} 
	else if (token[fieldx] == '/') {
	    long divisor;
	    fieldx++;
	    divisor = scanint();
	    if (divisor > 0) result = result / divisor;
	    else fferror("non-zero integer expected");
	}
	else if (isdigit(token[fieldx])) {    /* numbers are multipliers */
	    result = result * scanint();
	} 
	else break;
    }
    dotfactor = 1L;
    for (i=1; i<=dotcnt; i++)
	dotfactor = dotfactor * 2;
    result = (2 * result) - (result / dotfactor);

    return scale(result, 100L, tempo);    /* time in milliseconds */
}

/****************************************************************************
*               dotempo
* Effect: parses a !tempo command
****************************************************************************/

private void dotempo()
{
    linex += scan();
    if (!token[0])
	fferror("Tempo number expected");
    else {
	long oldtempo = tempo;
	tempo = scanint();
	if (token[fieldx])
	    fferror("Only digits expected here");
	if (tempo == 0) {
	    fieldx = 0;
	    fferror("Tempo 100 will be used here");
	    tempo = 100L;
	}
	start = thetime;
	/* adjust dur in case it is inherited by next note */
	if (symbolic_dur_flag) {
	    dur = (dur * oldtempo);
	    dur = dur / tempo;
	}
    }
}

/****************************************************************************
*               dotime
* Effect: parses a time (T) command
* Implementation: see implementation of donextdur()
****************************************************************************/

private void dotime()
{
    if (isdigit(token[fieldx])) {
	thetime = precise(scanint());
	thetime = scale(thetime, (ulong)time_scale, rate);
	if (token[fieldx] )
	    fferror("Only digits were expected here");
    } else {
	fieldx++;
	thetime = dodur(); 
    }
    thetime += start;    /* time is relative to start */
}

/****************************************************************************
*               dovoice
* Effect: parse a voice (V) command (the voice is the MIDI channel)
****************************************************************************/

private void dovoice()
{
    if (isdigit(token[fieldx])) {
	voice = (int) scanint();
	if (token[fieldx])
	    fferror("V must be followed by digits only");
	if (voice > MAX_CHANNELS) {
		char msg[40];
		sprintf(msg, "number too high, using %d instead", MAX_CHANNELS);
	    fferror(msg);
	    voice = MAX_CHANNELS;
	} 
	else if (voice < 1) {
	    fferror("number too low, using 1 instead");
	    voice = 1;
	}
    } 
    else fferror("No digit after V");
}

/****************************************************************************
*               fferror
* Inputs:
*    char *s: an error message string
* Effect:
*    prints the line with the error
*    puts a cursor (^) at the error location
*    prints the error message (s)
* Implementation:
*    this routine prints a carat under the character that
*    was copied into token[fieldx].    E.g. if fieldx = 0, the
*    carat will point to the first character in the field.
****************************************************************************/

private void fferror(s)
  char *s;
{
    gprintf(ERROR, "%3d | %s", lineno, line);
    marker(linex-strlen(token)+fieldx+1+6);
    gprintf(ERROR, "Error: %s.\n", s);
}

/****************************************************************************
*               init
* Effect:
*    initializes the state variables
****************************************************************************/

private void init()
{
    int i;

    end_flag = false;

    /* initial (default) values for all state variables */
    symbolic_dur_flag = true; /* default dur is symbolic */
    for (i = 0; i < nctrl; i++) {
	/* no initial control changes */
	ctrlflag[i] = false;
	ctrlval[i] = 0;
    }

    lineno = 0;
    pitch = seq_dflt_pitch;
    loud = seq_dflt_loud;
    voice = seq_dflt_voice;
    time_scale = 1000L;
    tempo = 100L;
    rate = 100L;
    dur = precise(600); /* default dur is quarter note */
    thetime = precise(0);
    start = thetime;
    ntime = 0L;
    ticksize = 0L;
    artic = 100;
}

/****************************************************************************
*               ins_a_note
* Returns:
*    boolean: true on success, false if not enough memory
* Effect:
*    note events (if any) corresponding to the current line are inserted
* Implementation:
*    if a note on should occur after a note off and doesn't, and the
*    two notes have the same pitch, then the note off can cancel the
*    note on.  to make it unlikely that roundoff will cause this situation,
*    dur is decreased by one half of a clock tick before rounding.
*    also, phase2 gives precedence to note-offs that are simultaneous
*    with note-ons.
****************************************************************************/

private boolean ins_a_note()
{
    long the_dur = (trunc(dur) * artic + 50) / 100;
    int the_pitch = pitch;
    event_type note;
    if (rest_flag) the_pitch = NO_PITCH;
    note = insert_note(the_score, round(thetime), lineno, voice,
		       the_pitch, the_dur, loud);
    if (!note) return false;
    return true;    /* success! */
}

/****************************************************************************
*               ins_ctrls
* Returns:
*    boolean: true on success, false if not enough memory
* Effect:
*    control events corresponding to current line are inserted in score
* Implementation:
*    ctrlflag[i] is true if control i was specified in this line, so
*    insert one control change for each ctrlflag[i] that is true
****************************************************************************/

private boolean ins_ctrls()
{
    int i;
    event_type ctrl;

    for (i = 1; i < nctrl; i++) {
	if (ctrlflag[i]) {
	    ctrl = insert_ctrl(the_score, round(thetime), lineno, i, voice,
			       ctrlval[i]);
	    if (!ctrl) return false;
	    ctrlflag[i] = false;
	    ctrlval[i] = 0;
	}
    }
    return true;    /* success! */
}

/****************************************************************************
*               issymbol
* Outputs: returns symbol number, or -1 if no match
* Assumes: token[1] has the symbol to look up (token[0] == '!')
****************************************************************************/

private int issymbol()
{
    int i, symb_num;
    char *sym;

    for (symb_num = 0; symb_num < sym_n; symb_num++) {
	sym = ssymbols[symb_num];
	i = 1;
	while (true) {
	    if (token[i] != *sym) break;
	    if (*sym == 0) return symb_num;
	    sym++; 
	    i++;
	}
    }
    return -1;
}

/****************************************************************************
*               marker
* Inputs:
*    int count: the number of characters to indent
* Effect: 
*    prints a carat (^) at the position specified on file stderr
****************************************************************************/

private void marker(count)
int count;
{
    int i;
    char s[128];
    for (i=0; i<(count-1); s[i++]=' ') /* */ ;
    s[count-1] = '^';
    s[count] = '\0';
    gprintf(ERROR,"%s\n",s);
}

/*****************************************************************
*           parseend
* Effect:
*    parse the note terminator, either ",", ";", EOS or "\n"
*
****************************************************************/

private void parseend()
{
    boolean done = false;
    while (!done) {
	linex += scan1(&line[linex]);
	switch (token[0]) {
	case ',':
	    ndurp = true;    /* switch that next time was specified */
	    ntime = 0L;
	    done = true;
	    break;
	case ';':
	case '\n':
	case EOS:
	    done = true;
	    break;
	case ' ':
	case '\t':
	    break;      /* skip over blanks and scan1 again */
	default:
	    fferror("Unexpected token");
	    linex += scan();  /* flush the token */
	    break;
	}
    }
}

/****************************************************************************
*               parsefield
* Effect: looks at first character of token and calls a parsing routine
*
****************************************************************************/

private void parsefield()
{
    fieldx = 1;
    switch (token[0]) {
    case 'T' : 
	dotime(); 
	break;
    case 'U':
    case 'W': 
    case 'H':
    case 'Q':
    case 'S':
    case 'I': 
    case '%':
    case '^':
	dur = dodur(); 
	break;
    case 'R': 
	do_a_rest(); 
	break;
    case 'A':
    case 'B':
    case 'C':
    case 'D':
    case 'E':
    case 'F':
    case 'G': 
	pitch = dopitch(); 
	pitch_flag = true;
	break;
    case 'P': 
	pitch = doabspitch(); 
	pitch_flag = true;
	break;
    case 'L': 
	loud = doloud(); 
	break;
    case 'N': 
	donextdur(); 
	break;
/*    case 'J': 
 *      doctrl(1);
 *      break;
 */
    case 'K': 
	doctrl(PSWITCH_CTRL);
	break;
    case 'M': 
	doctrl(MODWHEEL_CTRL);
	break;
    case 'O': 
	doctrl(TOUCH_CTRL);
	break;
    case 'X': 
	doctrl(VOLUME_CTRL);
	break;
    case 'Y': 
	doctrl(BEND_CTRL);
	break;
    case 'Z':
	doprogram();
	break;
    case 'V': 
	dovoice();
	break;
    case '~':
	domacro();
	break;
    case '*':
	docomment();
	break;
    case '#':
	doartic();
	break;
    default : 
	doerror();
	break;
    }
}

/****************************************************************************
*               parsenote
* Effect: 
*    parses a note line -- control events (if any) and note event (if
*    present) are inserted into score
* Assumes:
*    line contains a string to be parsed
****************************************************************************/

private boolean parsenote()
{
    boolean out_of_memory = false;
    int i;

    ndurp = false;
    rest_flag = false;

    /* this loop reads tokens for a note */
    while (token[0]) {
	parsefield();
	linex += scan();
    }

    parseend(); /* take care of note terminator */

    /*
     * insert ctrl's first so that will come before the note.
     */
    if (ctrlflag[0]) {
	out_of_memory |= !ins_ctrls();
	/* don't reset ctrlflag[0], it's used below */
    }

    /*
     * insert macro's
     */
    for (i = 0; i < macctrlx; i++) {
	event_type ctrl;
	if (macctrldef[i] == NULL) {
	    ctrl = insert_macctrl(the_score, round(thetime), lineno,
				  macctrlnum[i], voice, macctrlparmx[i]);
	} else {
	    ctrl = insert_macro(the_score, round(thetime), lineno,
			macctrldef[i], voice, macctrlnum[i],
			&(macctrlparms[macctrlparmx[i]]));
	}
	out_of_memory |= (ctrl == NULL);
    }

    /* insert a note if
	 *    (1) a pitch was specified OR
	 *    (2) no control was specified and this is not a rest 
	 *      (it's a pitch by default)
	 *
	 * NOTE: program changes during rests are advised since
	 *    synthesizers may not be able to process a program
	 *    change followed immediately by a note-on.  In fact, this
	 *    is why we insert notes whose pitch is NO_PITCH -- so that
	 *    the program change can be processed during the rest.
	 */
    if (pitch_flag ||
	(!ctrlflag[0] && !rest_flag && (macctrlx == 0))) {
	out_of_memory |= !ins_a_note();
    }

    if (ndurp) thetime += ntime;
    else thetime += dur;

    return out_of_memory;
}


private boolean parseparm(valptr)
  long *valptr;
{
    register char c = token[fieldx];
    if (isdigit(c) || c == '-') {
	*valptr = scansgnint();
	return true;
    } else {
	switch (c) {
	  case 'P':
	    fieldx++;
	    *valptr = doabspitch();
	     return true;
	  case 'A':
	  case 'B':
	  case 'C':
	  case 'D':
	  case 'E':
	  case 'F':
	  case 'G':
	    fieldx++;
	    *valptr = dopitch();
	    return true;
	  case 'U':
	  case 'W':
	  case 'H':
	  case 'Q':
	  case 'I':
	  case 'S':
	  case '%':
	  case '^':
	    fieldx++;
	    *valptr = round(dodur());
	    return true;
	  case 'L':
	    fieldx++;
	    *valptr = doloud();
	    return true;
	  case '\'':
	    fieldx++;
	    *valptr = token[fieldx];
	    fieldx++;
	    if (token[fieldx] != '\'') {
		fferror("single quote expected");
	    }
	    fieldx++;
	    return true;
	  default:
	    fferror("Parameter expected");
	    return false;
	}
    }
}



/****************************************************************************
*               scale
* Inputs:
*    time_type x
*    int (ulong?) n, d
* Outputs:
*    returns time_type: result of scaling x by n/d
****************************************************************************/

public time_type scale(x, n, d)
  ulong x;
  ulong n, d;
{
    ulong lo = (x & 0xFFFFL) * n;
    ulong hi = (x >> 16) * n;
    ulong res = hi / d;
    lo = (((hi - (res * d)) << 16) + lo + (d >> 1)) / d;
    return (time_type)( (res << 16) + lo );
}

/****************************************************************************
*               scan
* Inputs:
*    char *start: the string to scan
* Outputs:
*    returns int: the index of the next char in start to scan
* Effect: 
*    skips over leading blanks
*    copies characters from start into token, converting to upper case
*    scanning stops on delimiter: one of space, tab, newline, semicolon
****************************************************************************/

private int scan()
{
    char *start = line + linex;
    register char c;
    register int i = 0;
    register int j = 0;
    register int parens = 0;

    while (((c = start[i]) == ' ') || (c == '\t')) i++;

    while ((c = start[i]) != ' ' && c != '\n' && c != '\t' && c != EOS &&
	   (c != ',' || token[0] == '~' || parens > 0) && c != ';') {

	if (islower(start[i])) token[j] = toupper(start[i]);
	else token[j] = start[i];
	if (c == '(') parens++;
	else if (c == ')') parens--;
	j++; 
	i++;
    }
    token[j] = '\0';

    fieldx = 0;
    if (parens) fferror("Unbalanced parens");

    return i;
}

/****************************************************************************
*               scan1
* Inputs:
*    char *start: the string to scan
* Outputs:
*    returns int: the index of the next char in start to scan
* Effect: 
*    copies one char from start into token, converting to upper case
****************************************************************************/

private int scan1(start)
char *start;
{
    int i = 0;

    token[0] = *start;
    if (islower(token[0])) token[0] = toupper(token[0]);

    if (!nullstring(token)) {
	token[1] = '\0';
	i = 1;
    }
    fieldx = 0;
    return i;
}

/****************************************************************************
*               scanint
* Outputs:
*    returns long: the scanned integer
* Effect:
*    scans an unsigned long from token, starting at fieldx
*    fieldx is incremented to end of the integer
****************************************************************************/

private long scanint()
{
    long i = 0;
    char c;
    while (c = token[fieldx]) {
	if (isdigit(c)) {
	    i = (i*10) + (c - '0');
	    fieldx++;
	} else return i;
    }
    return i;
}

private long scansgnint()
{
    if (token[fieldx] == '-') {
	fieldx++;
	return -scanint();
    } else {
	if (token[fieldx] == '+') {
	    fieldx++;
	}
	return scanint();
    }
}


/* scansymb -- scan a symbol from the token */
/**/
private void scansymb(str)
  char *str;
{
    char c;
    while (c = token[fieldx]) {
	if (isdigit(c) || isalpha(c) || c == '_') {
	    *str++ = c;
	    fieldx++;
	} else break;
    }
    *str = EOS;
}

/****************************************************************************
*               seq_read
* Inputs:
*    seq_type seq: the sequence to receive the score
*    FILE *fp: input file
* Outputs:
*    none
* Effect: 
*    parses score from input file and builds score data structure
****************************************************************************/

void seq_read(seq, fp)
  seq_type seq;
  FILE *fp;
{
    boolean out_of_memory = false;    /* set when no more memory */
    /* printf("seq_read: chunklist is 0x%x\n", seq->chunklist); */
    the_score = seq;  /* current sequence is a global within this module */
    if (!seq) return;
    init();
    lineno = 0;
	
    /* make sure line is well terminated or scan might run off the end */
    line[linesize - 1] = EOS;
    line[linesize - 2] = '\n';

    /* this loop reads lines */
    while ((fgets(line, linesize - 2, fp) != NULL) && !out_of_memory &&
	   !check_aborted() && !end_flag) {
	lineno++;
	linex = 0;
	/* this loop reads notes from a line */
	while ((line[linex] != EOS) && !out_of_memory) {
	    /* loop invariant: line[linex] is first char of next note */
	    ctrlflag[0] = false;  /* other ctrlflags are reset by ins_ctrls() */
	    macctrlx = 0;
	    macctrlnextparm = 0;
	    pitch_flag = false;
	    linex += scan();
	    if (!nullstring(token)) {
		if (token[0] == '*') docomment();
		else if (token[0] == '!') dospecial();
		else out_of_memory = parsenote();
	    } 
	    else parseend();
	}
    }

    if (out_of_memory) {
	gprintf(ERROR, "Out of note memory at line %d,\n", lineno-1);
	gprintf(ERROR, "    the rest of your file will be ignored.\n");
    }

    if (check_aborted()) {
	gprintf(ERROR, "User aborted score input,\n");
	gprintf(ERROR, "    the rest of your file will be ignored.\n");
	if (abort_flag == BREAK_LEVEL) abort_flag = 0;
    }

    fclose(fp);

    gprintf(TRANS, "\nLoaded Adagio file with %ld note(s), %ld ctrl(s).\n\n",
	    seq_notecount(the_score), seq_ctrlcount(the_score));
}
