summaryrefslogtreecommitdiff
path: root/cmt/seqread.c
diff options
context:
space:
mode:
Diffstat (limited to 'cmt/seqread.c')
-rw-r--r--cmt/seqread.c1890
1 files changed, 1890 insertions, 0 deletions
diff --git a/cmt/seqread.c b/cmt/seqread.c
new file mode 100644
index 0000000..62d759d
--- /dev/null
+++ b/cmt/seqread.c
@@ -0,0 +1,1890 @@
+/****************************************************************************
+ 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(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(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));
+}