diff options
Diffstat (limited to 'cmt/seqread.c')
-rw-r--r-- | cmt/seqread.c | 1890 |
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)); +} |