/****************************************************************************
               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-Apr-91 | JDW : further changes
* 30-Jun-91 | RBD : parse '+' and '/' in durations, * after space is comment
* 28-Apr-03 |  DM : changes for portability
*****************************************************************************/

#include "switches.h"

#include <stdio.h>
#include <string.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 seqround(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=0;

        scansymb(symbol);
        if (fieldx == 1) fferror("Routine name expected");
        else if (token[fieldx] != '(') fferror("Open paren expected");
        else {
            desc = &HASHENTRY(hash_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, seqround(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, seqround(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++] = (short) 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=0;
    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 int 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, seqround(thetime), lineno, voice,
                seqround(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, seqround(thetime), lineno, voice,
                seqround(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(hash_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++] = seqround(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, seqround(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 = 0;

    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, seqround(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, seqround(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, seqround(thetime), lineno,
                                  macctrlnum[i], voice, macctrlparmx[i]);
        } else {
            ctrl = insert_macro(the_score, seqround(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 = seqround(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); -- don't close the file; if you do, Nyquist's garbage
       collector will close Nyquist's copy, and closing the file twice
       in Linux will crash Nyquist */

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