chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / parstuff.c
diff --git a/mup/mup/parstuff.c b/mup/mup/parstuff.c
new file mode 100644 (file)
index 0000000..dff001d
--- /dev/null
@@ -0,0 +1,1501 @@
+
+/* Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2002, 2003, 2004 by Arkkra Enterprises */
+/* All rights reserved */
+
+/* parser functions related to STUFF */
+
+
+#include <string.h>
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+/* if user specifies a "til" clause on stuff with a number of measures > 0,
+ * we need to save away info about where the til clause will end, to make sure
+ * that it doesn't fall off the end of the measure or the piece. This is the
+ * struct we use to save this info. */
+static struct TIL_INFO {
+       char    *inputfile;     /* where STUFF was defined */
+       int     inputlineno;    /* where STUFF was defined */
+       int     measnum;        /* number of measure in which til clause ends */
+       float   count;          /* count in measure where til clause ends */
+       struct TIL_INFO *next;  /* for linked list */
+} *Til_info_list_p;
+
+/* info about the STUFF currently being collected from input */
+static int Curr_stuff_type;    /* ST_* */
+static int Stuff_size;         /* point size of stuff text string */
+static int Modifier;           /* TM_* for text, L_* for phrase */
+static int Measnum = 1;                /* to check til clauses. Can't use Meas_num
+                                * global because it doesn't count invisible
+                                * bars but til clauses do */
+static int Multi_adjust;       /* adjustment to Measnum to account
+                                * for multirests */
+
+/* head and tail of list of STUFF currently being collected from input */
+static struct STUFF *Head_stufflist_p;
+static struct STUFF *Tail_stufflist_p;
+
+/* current pedal state for each staff. YES if in the middle of doing pedal,
+ * NO if not. */
+static short Pedal_state[MAXSTAFFS + 1];
+static char *Ped_begin_str;    /* will point to "\(begped)" */
+static char *Ped_up_down_str;  /* will point to "\(pedal)" */
+
+/* static functions */
+static struct STUFF *clone_stufflist P((struct STUFF *stufflist_p,
+                       int staffno, int all));
+static void do_attach P((int staffno, int all, struct RANGELIST *vno_range_p));
+static void midi_attach P((int staffno, struct STAFF *staff_p,
+                       struct RANGELIST *vno_range_p, int all));
+static void free_stufflist P((struct STUFF *stuff_P));
+static void free_tils P((struct TIL_INFO *til_p));
+static void fix_pedal P((int staffno, struct STUFF *stuff_p));
+static void ped_order_chk P((void));
+\f
+
+/* save current stuff type value. Also check that we are in data (music)
+ * context */
+
+void
+set_stuff_type(stuff_type)
+
+int stuff_type;
+
+{
+       Curr_stuff_type = stuff_type;
+
+       (void) contextcheck(C_MUSIC, "statement");
+}
+\f
+
+/* return current stuff type */
+
+int
+get_stuff_type()
+
+{
+       return(Curr_stuff_type);
+}
+\f
+
+/* check all the things in an input line of stuff, up to the colon,
+ * for consistency, and save interesting info away for later use. */
+
+void
+chk_stuff_header(size, modifier, place, dist_usage)
+
+int size;              /* point size, or -1 if to use default */
+int modifier;          /* TM_* for text, L_* for phrase */
+int place;             /* PL_* */
+int dist_usage;                /* SD_* */
+
+{
+
+       debug(4, "chk_stuff_header");
+
+       switch (Curr_stuff_type) {
+       case ST_ROM:
+       case ST_BOLD:
+       case ST_ITAL:
+       case ST_BOLDITAL:
+       case ST_MUSSYM:
+               break;
+       case ST_PEDAL:
+               if (place != PL_BELOW && place != PL_UNKNOWN) {
+                       yyerror("pedal must be below");
+               }
+               /*FALLTHRU*/
+       default:
+               if (size != -1) {
+                       yyerror("can't specify size except with a font or mussym");
+               }
+               if (modifier != TM_NONE && Curr_stuff_type != ST_PHRASE) {
+                       l_yyerror(Curr_filename, yylineno,
+                                       "can't specify %s except with a font",
+                                       stuff_modifier(modifier));
+               }
+               if (Curr_stuff_type == ST_PHRASE && modifier != L_NORMAL &&
+                               modifier != L_DOTTED && modifier != L_DASHED) {
+                       l_yyerror(Curr_filename, yylineno,
+                                       "only dotted or dashed line type can be specified for phrase");
+               }
+               break;
+       }
+
+       if (Curr_stuff_type == ST_OCTAVE) {
+               if (is_tab_range() == YES) {
+                       yyerror("octave not allowed on tablature staff");
+               }
+               else if(place == PL_BETWEEN) {
+                       yyerror("octave must be above or below");
+                       place = PL_ABOVE;
+               }
+       }
+
+       if (Curr_stuff_type == ST_PHRASE && place == PL_BETWEEN) {
+               yyerror("phrase must be above, below, or omitted");
+               place = PL_ABOVE;
+       }
+
+       if (dist_usage != SD_NONE) {
+               if (Curr_stuff_type == ST_PEDAL) {
+                       yyerror("dist not allowed on pedal");
+               }
+               else if (Curr_stuff_type == ST_PHRASE) {
+                       yyerror("dist not allowed on phrase");
+               }
+               else if (Curr_stuff_type == ST_MIDI) {
+                       yyerror("dist not allowed on midi");
+               }
+
+               if (place == PL_BETWEEN) {
+                       yyerror("dist not allowed with 'between'");
+               }
+       }
+
+       /* Save the modifier value.
+        * Have to set this before calling dflt_place() */
+       Modifier = modifier;
+
+       /* fill in default values if user didn't specify */
+       if (place == PL_UNKNOWN) {
+               place = dflt_place();
+       }
+
+       Stuff_size = size;
+       Place = (short) place;
+
+       /* make sure current list of stuff is empty */
+       Head_stufflist_p = Tail_stufflist_p = (struct STUFF *) 0;
+}
+\f
+
+/* return default value for place depending on value of Curr_stuff_type */
+
+int
+dflt_place()
+
+{
+       switch (Curr_stuff_type) {
+
+       case ST_PEDAL:
+               return(PL_BELOW);
+
+       case ST_OCTAVE:
+               yyerror("must specify above or below with octave");
+               /* arbitrarily return above. If we leave it as unknown,
+                * we can get double error messages in some cases */
+               return(PL_ABOVE);
+
+       case ST_PHRASE:
+               /* stays unknown at this point */
+               return(PL_UNKNOWN);
+
+       default:
+               if (Modifier == TM_ANALYSIS || Modifier == TM_FIGBASS) {
+                       return(PL_BELOW);
+               }
+               /* default for everything else is above */
+               return(PL_ABOVE);
+       }
+}
+\f
+
+/* Add a space padding to a string (except if is it boxed).
+ * If padding was added, free the passed-in string and return the padded string,
+ * else return the string as is. The incoming string
+ * is expected to already be converted to font/size/string
+ * internal format by this time, although still in input ASCII form.
+ */
+
+char *
+pad_string(string, modifier)
+
+char *string;
+int modifier;  /* TM_* */
+
+{
+       char *padded_string;            /* string with 1-space padding at end */
+       char *str_p;                    /* walk through padded_string */
+       int len;                        /* length of string */
+       int last_was_backslash;         /* YES/NO */
+       int count_backslashed;          /* YES/NO if to count backslashed or
+                                        * unbackslashed colons */
+       int colons;                     /* how many colons found */
+       int extra;                      /* how many extra bytes to malloc */
+
+       /* Boxed and circled strings don't get any extra padding,
+        * so we can use what we have */
+       if (string[2] == '\\' && (string[3] == '[' || string[3] == '{')) {
+               return(string);
+       }
+
+       /* Make a new copy with a space at the end.
+        * But if the string ends in the middle of a pile,
+        * we need to implicitly end the pile before adding the space.
+        * Since the string is still in ASCII form,
+        * we have to count up the number of colons
+        * to see if we are mid-pile. In chord/analysis/figbass
+        * we need to count unbackslashed colon,
+        * otherwise backslashed.*/
+       count_backslashed = (IS_CHORDLIKE(modifier) ? NO : YES);
+       /* figbass implicitly begins with a pile */
+       colons = (modifier == TM_FIGBASS ? 1 : 0);
+       last_was_backslash = NO;
+       for (str_p = string + 2; *str_p != '\0'; str_p++) {
+               if (last_was_backslash == YES) {
+                       if (*str_p == ':' && count_backslashed == YES) {
+                               colons++;
+                       }
+                       last_was_backslash = NO;
+               }
+               else {
+                       if (*str_p ==  ':' && count_backslashed == NO) {
+                               colons++;
+                       }
+                       last_was_backslash = (*str_p == '\\' ? YES : NO);
+               }
+       }
+
+       /* If odd number of colons, we are mid-pile.  Will need
+        * add extra byte to hold the colon to implicitly end the
+        * pile, and if it needs to be a backslashed colon,
+        * another extra byte for that. */
+       if (colons & 1) {
+               extra = (count_backslashed == YES ? 2 : 1);
+       }
+       else {
+               extra = 0;
+       }
+
+       len = strlen(string);
+
+       /* +2 is for space/null at end */
+       MALLOCA(char, padded_string, len + 2 + extra);
+       (void) memcpy(padded_string, string, len);
+       str_p = padded_string + len;
+
+       /* add implicit end-pile if needed */
+       if (extra == 2) {
+               *str_p++ = '\\';
+       }
+       if (extra > 0) {
+               *str_p++ = ':';
+       }
+
+       /* now add space padding */
+       *str_p++ = ' ';
+       *str_p = '\0';
+       FREE(string);
+       return(padded_string);
+}
+\f
+
+/* check a "stuff" item  and add to list */
+
+void
+add_stuff_item(start_count, start_steps, gracebackup, string, bars, count,
+               dist, dist_usage)
+
+double start_count;            /* where in measure to start this stuff */
+double start_steps;            /* offset by this many stepsizes */
+int gracebackup;       /* how many grace notes to back up from start */
+char *string;          /* what to print */
+int bars;              /* how many bar lines to cross with this stuff */
+double count;          /* how many beats into last measure */
+int dist;              /* dist for this specific STUFF, to override param */
+int dist_usage;                /* meaning of dist, SD_*  */
+
+{
+       struct STUFF *new_p;            /* where to store STUFF */
+       struct TIL_INFO *til_info_p;    /* to save info about til clause */
+       int len;                        /* length of stuff text string */
+       char *padded_string;            /* string with 1-space padding at end */
+       char lch;                       /* last character of string */
+
+
+       if (bars != 0 || count != 0.0) {
+               /* has a "til" clause. Check if that is valid */
+               if (Curr_stuff_type == ST_MUSSYM) {
+                       if (string == (char *) 0) {
+                               yyerror("missing string");
+                               return;
+                       }
+
+                       /* not yet changed to internal form, need to compare
+                        * in ASCII form */
+                       if ((strcmp(string + 2, "tr") != 0) &&
+                                       (strcmp(string + 2, "\\(tr)") != 0)) {
+                               yyerror("til not allowed on mussym except on trills");
+                       }
+               }
+
+               else if (Curr_stuff_type == ST_PEDAL) {
+                       yyerror("til not allowed on pedal");
+               }
+
+               else if (Curr_stuff_type == ST_MIDI) {
+                       yyerror("til not allowed on midi");
+               }
+
+               if (Curr_stuff_type != ST_PHRASE &&
+                       (Modifier == TM_CHORD || Modifier == TM_ANALYSIS) ) {
+                       l_yyerror(Curr_filename, yylineno,
+                                       "til not allowed with %s",
+                                       stuff_modifier(Modifier));
+               }
+
+               if (bars == 0) {
+                       if (count > Score.timenum + 1) {
+                               yyerror("'til' value must be <= numerator of time signature + 1");
+                       }
+
+                       if (count < start_count) {
+                               yyerror("til value must be >= start value");
+                       }
+               }
+
+       }
+       else {
+               /* doesn't have a "til" clause. Check if one is required */
+               if (Curr_stuff_type == ST_CRESC ||
+                                               Curr_stuff_type == ST_DECRESC) {
+                       yyerror("til required on cresc/decresc");
+               }
+       }
+       
+       if (start_count > Score.timenum + 1) {
+               yyerror("beat offset must be <= numerator of time signature + 1");
+       }
+
+       if (Curr_stuff_type == ST_CRESC || Curr_stuff_type == ST_DECRESC) {
+               if (string != (char *) 0) {
+                       yyerror("string not allowed with cresc/decresc");
+               }
+               Modifier = TM_DYN;
+       }
+
+       else if (Curr_stuff_type == ST_PHRASE) {
+               if (string != (char *) 0) {
+                       yyerror("string not allowed with phrase");
+               }
+       }
+
+       else if (Curr_stuff_type == ST_PEDAL) {
+               if ( (string != (char *) 0)
+                               && (strcmp(string + 2, "\\(endped)") != 0) ) {
+                       yyerror("pedal string must be either blank or *");
+               }
+       }
+
+       else {
+               if (string == (char *) 0) {
+                       yyerror("string is required");
+                       return;
+               }
+       }
+
+       if (gracebackup != 0 && Place == PL_BETWEEN) {
+               yyerror("grace backup not allowed with 'between'");
+       }
+
+       /* we can't deal with step offset on phrase marks very well,
+        * so warn and ignore if we get one */
+       if (start_steps != 0.0 && Curr_stuff_type == ST_PHRASE) {
+               l_warning(Curr_filename, yylineno, "step offset ignored on phrase mark");
+               start_steps = 0.0;
+       }
+
+       switch (Curr_stuff_type) {
+       case ST_ROM:
+       case ST_BOLD:
+       case ST_ITAL:
+       case ST_BOLDITAL:
+               /* the text-type stuffs are supposed to have a 1-space padding
+                * at the end of them */
+               if (bars != 0 || count != 0.0) {
+                       /* don't add padding if has wavy or solid line
+                        * til clause */
+                       lch = last_char(string);
+                       if (lch == '~' || lch == '_') {
+                               break;
+                       }
+               }
+               string = pad_string(string, Modifier);
+               break;
+
+       case ST_MUSSYM:
+               /* in mussym, user can specify things without the usual
+                * \(---) convention. Change to include them */
+               if (string[2] == '\\' && string[3] == '(') {
+                       /* if user unnecessarily put in the \(--), leave it */
+                       break;
+               }
+
+               len = strlen(string + 2);
+               MALLOCA(char, padded_string, len + 6);
+               (void) sprintf(padded_string, "%c%c\\(%s)", FONT_TR, DFLT_SIZE,
+                                               string + 2);
+               FREE(string);
+               string = padded_string;
+               break;
+
+       default:
+               break;
+       }
+
+       /* fill in a new STUFF struct with appropriate info */
+       new_p = newSTUFF(string, dist, dist_usage, start_count, start_steps,
+               gracebackup, bars, count,
+               Curr_stuff_type, Modifier, Place, Curr_filename, yylineno);
+
+       /* if bars > 0, need to save away til info for later error
+        * checking */
+       if (bars > 0) {
+               CALLOC(TIL_INFO, til_info_p, 1);
+               til_info_p->measnum = Measnum + bars;
+               til_info_p->count = count;
+               til_info_p->inputfile = new_p->inputfile;
+               til_info_p->inputlineno = new_p->inputlineno;
+               til_info_p->next = Til_info_list_p;
+               Til_info_list_p = til_info_p;
+       }
+
+       /* above/between go on the head of the list, below goes on the
+        * tail of the list, so that things come out in the right order.
+        * Midi always goes at the end */
+       if (Place == PL_BELOW || Curr_stuff_type == ST_MIDI) {
+               /* link onto list tail */
+               if ( Tail_stufflist_p == (struct STUFF *) 0) {
+                       Head_stufflist_p = new_p;
+               }
+               else {
+                       Tail_stufflist_p->next = new_p;
+               }
+               Tail_stufflist_p = new_p;
+       }
+       else {
+               /* link onto head of list */
+               new_p->next = Head_stufflist_p;
+               Head_stufflist_p = new_p;
+               if (Tail_stufflist_p == (struct STUFF *) 0) {
+                       Tail_stufflist_p = new_p;
+               }
+       }
+}
+\f
+
+/* return YES if given string consists entirely of the specific music symbol */
+/* the string should be in the internal format of font/size/string */
+
+int
+string_is_sym(string, sym, symfont)
+
+char *string;  /* which string to check */
+int sym;       /* check for this music symbol */
+int symfont;   /* FONT_MUSIC*  */
+
+{
+       int font, size;
+
+
+       if (string == (char *) 0) {
+               return(NO);
+       }
+
+       font = *string++;
+       size = *string++;
+       if (next_str_char(&string, &font, &size) != sym) {
+               return(NO);
+       }
+       if (font != symfont) {
+               return(NO);
+       }
+       if (next_str_char(&string, &font, &size)  == '\0') {
+               return(YES);
+       }
+       return (NO);
+}
+\f
+
+/* connect a list of STUFF to a STAFF. If there is already something on
+ * that STAFF's STUFF list, attach at the end or beginning as appropriate
+ * depending on place. */
+
+void
+attach_stuff()
+
+{
+       struct SVRANGELIST *svr_p;      /* to walk through Svrangelist */
+       struct RANGELIST *r_p;          /* to walk through staff range list */
+       short staffno;
+
+
+       debug(4, "attach_stuff");
+
+       /* make sure we've got STAFF structs for this measure */
+       create_staffs();
+
+       for (svr_p = Svrangelist_p; svr_p != (struct SVRANGELIST *) 0;
+                                               svr_p = svr_p->next) {
+               for (r_p = svr_p->stafflist_p; r_p != (struct RANGELIST *) 0;
+                                               r_p = r_p->next) {
+       
+                       for (staffno = r_p->begin; staffno <= r_p->end
+                                       && staffno <= MAXSTAFFS; staffno++) {
+                               do_attach(staffno, r_p->all, svr_p->vnolist_p);
+
+                               if (Place == PL_BETWEEN) {
+                                       /* between has 2 staffs in its range,
+                                        * but stuff is only associated
+                                        * with the top staff */
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       free_rlists();
+
+       /* have made copies of stuff for each staff that gets one, with
+        * the proper font/size etc, so need to free master stufflist copy */
+       free_stufflist(Head_stufflist_p);
+}
+\f
+
+/* Attach STUFF for a specific staff. */
+
+static void
+do_attach(staffno, all, vno_range_p)
+
+int staffno;
+int all;
+struct RANGELIST *vno_range_p;
+
+{
+       struct STAFF *staff_p;          /* where to attach STUFF */
+       struct STUFF *stufflist_p;      /* current copy of STUFF list */
+
+
+       if (staffno > Score.staffs) {
+               l_yyerror(Head_stufflist_p->inputfile,
+                               Head_stufflist_p->inputlineno,
+                               "staff number out of range");
+               return;
+       }
+
+       staff_p = Staffmap_p[staffno]->u.staff_p;
+
+       if (Place == PL_BETWEEN) {
+               if (staffno + 1 > Score.staffs) {
+                       /* will have already exclaimed about
+                        * this error before, so no need to print message,
+                        * but better skip next check */
+                       return;
+               }
+
+               /* if either staff of a between is invisible,
+                * throw this stuff away */
+               if (svpath(staffno, VISIBLE)->visible == NO ||
+                               svpath(staffno + 1,
+                               VISIBLE)->visible == NO) {
+                       return;
+               }
+       }
+
+       /* handle MIDI stuff specially */
+       if (Curr_stuff_type == ST_MIDI) {
+               if (all == YES) {
+                       /* need to find top visible staff/voice to attach to */
+                       int s;          /* staff number */
+                       int v;          /* voice number */
+                       struct RANGELIST range;
+       
+                       v = 1;  /* avoid bogus "used before set" warning */
+                       for (s = 1; s <= MAXSTAFFS; s++) {
+                               if (svpath(s, VISIBLE)->visible == YES) {
+                                       for (v = 1; v <= MAXVOICES; v++) {
+                                               if (vvpath(s, v, VISIBLE)->visible == YES) {
+                                                       break;
+                                               }
+                                       }
+                                       if (v <= MAXVOICES) {
+                                               break;
+                                       }
+                               }
+                       }
+                       if (s > MAXSTAFFS || v > MAXVOICES) {
+                               pfatal("failed to find top visible staff/voice");
+                       }
+                       /* make a special RANGELIST for this */
+                       range.begin = range.end = v;
+                       range.all = YES;
+                       range.next = 0;
+                       midi_attach(s, Staffmap_p[s]->u.staff_p, &range, all);
+               }
+               else {
+                       midi_attach(staffno, staff_p, vno_range_p, all);
+               }
+       }
+
+       else {
+               /* make the copy for this staff from master copy */
+               stufflist_p = clone_stufflist(Head_stufflist_p, staffno, all);
+
+               if (Curr_stuff_type == ST_PEDAL) {
+                       fix_pedal(staffno, stufflist_p);
+               }
+
+               connect_stuff(staff_p, stufflist_p);
+       }
+}
+\f
+
+/* attach MIDI stuff. This is slightly different than other stuff because
+ * it can be applied to one or both voices. */
+
+static void
+midi_attach(staffno, staff_p, vno_range_p, all)
+
+int staffno;           /* attach to this staff number */
+struct STAFF *staff_p; /* attach to this staff struct */
+struct RANGELIST *vno_range_p;
+int all;               /* if associated with "all" */
+
+{
+       struct RANGELIST *r_p;          /* walk through vno_range_p */
+       int vno;                        /* voice number */
+       struct STUFF *stufflist_p;      /* copy of stuff */
+       struct STUFF *st_p;             /* walk through stufflist_p */
+       short place;
+
+
+       /* do for each voice that MIDI stuff applies to */
+       for (r_p = vno_range_p; r_p != (struct RANGELIST *) 0; r_p = r_p->next) {
+               for (vno = r_p->begin; vno <= r_p->end; vno++) {
+
+                       /* make the copy for this staff from master copy */
+                       stufflist_p = clone_stufflist(Head_stufflist_p,
+                                               staffno, all);
+
+                       /* fix up place based on voice number */
+                       switch (vno) {
+                       case 1:
+                               place = PL_ABOVE;
+                               break;
+                       case 2:
+                               place = PL_BELOW;
+                               break;
+                       case 3:
+                               place = PL_BETWEEN;
+                               break;
+                       default:
+                               pfatal("illegal vno for midi");
+                               /*NOTREACHED*/
+                               place = PL_UNKNOWN;  /* avoid "used before set" warning */
+                               break;
+                       }
+                       for (st_p = stufflist_p; st_p != (struct STUFF *) 0;
+                                               st_p = st_p->next) {
+                               st_p->place = place;
+                       }
+
+                       connect_stuff(staff_p, stufflist_p);
+               }
+       }
+}
+\f
+
+/* connect a new stuff list into an existing stuff list. Add below stuff and
+ * MIDI stuff to the end of the list,
+ * and others to beginning of list, but make sure any
+ * "above all" comes after any above non-all, and that any below non-all
+ * comes before any "below all."
+ */
+
+void
+connect_stuff(staff_p, stufflist_p)
+
+struct STAFF *staff_p;         /* connect to stuff off of this staff */
+struct STUFF *stufflist_p;     /* connect this list  of stuff */
+
+{
+       struct STUFF *st_p;             /* to find link place in STUFF list */
+       struct STUFF *s_p;              /* to find end of stufflist_p */
+       struct STUFF **ins_p_p;         /* where to insert in list */
+
+
+       if (staff_p == (struct STAFF *) 0 || stufflist_p == (struct STUFF *) 0) {
+               return;
+       }
+
+       if (staff_p->stuff_p == (struct STUFF *) 0) {
+               /* no list before, so attach this one
+                * directly to STAFF */
+               staff_p->stuff_p = stufflist_p;
+       }
+
+       else if (Place == PL_BELOW || stufflist_p->stuff_type == ST_MIDI) {
+               /* if this set of stuff isn't associated with
+                * "all", then it goes before any below "all" stuff */
+               if (stufflist_p->all == NO) {
+                       for (ins_p_p = &(staff_p->stuff_p); 
+                                       *ins_p_p != (struct STUFF *) 0;
+                                       ins_p_p = &((*ins_p_p)->next)) {
+                               if ( (*ins_p_p)->place == PL_BELOW &&
+                                               (*ins_p_p)->all == YES) {
+                                       break;
+                               }
+                       }
+                       /* find end of list to be inserted */
+                       for (s_p = stufflist_p; s_p->next != (struct STUFF *) 0;
+                                                       s_p = s_p->next) {
+                               ;
+                       }
+
+                       /* insert */
+                       s_p->next = *ins_p_p;
+                       *ins_p_p = stufflist_p;
+               }
+
+               else {
+                       /* goes at end of list. find the end */
+                       for (st_p = staff_p->stuff_p;
+                                       st_p->next != (struct STUFF *)0;
+                                       st_p = st_p->next) {
+                               ;
+                       }
+
+                       /* connect in the new list */
+                       st_p->next = stufflist_p;
+               }
+       }
+       else {
+               /* find end of new list */
+               for (s_p = stufflist_p;
+                               s_p->next != (struct STUFF *) 0;
+                               s_p = s_p->next) {
+                       ;
+               }
+
+               if (stufflist_p->all == NO) {
+                       /* goes at the head of the list */
+                       s_p->next = staff_p->stuff_p;
+                       staff_p->stuff_p = stufflist_p;
+               }
+               else {
+                       /* goes before any existing above all */
+                       for (ins_p_p = &(staff_p->stuff_p); 
+                                       *ins_p_p != (struct STUFF *) 0;
+                                       ins_p_p = &((*ins_p_p)->next)) {
+                               if ( (*ins_p_p)->place == PL_ABOVE &&
+                                               (*ins_p_p)->all == YES) {
+                                       break;
+                               }
+                       }
+                       /* find end of list to be inserted */
+                       for (s_p = stufflist_p; s_p->next != (struct STUFF *) 0;
+                                                       s_p = s_p->next) {
+                               ;
+                       }
+
+                       /* insert */
+                       s_p->next = *ins_p_p;
+                       *ins_p_p = stufflist_p;
+               }
+       }
+}
+\f
+
+/* given a list of STUFF, return a clone of the list */
+
+static struct STUFF *
+clone_stufflist(stufflist_p, staffno, all)
+
+struct STUFF *stufflist_p;     /* what stuff to clone */
+int staffno;                   /* which staff, to get proper point size */
+int all;                       /* YES if was "above all" or "below all" */
+
+{
+       struct STUFF *new_p;    /* copy of STUFF */
+       char *newstring;        /* copy of text string */
+       int font;
+       int fontfamily;
+       int size;
+
+
+       if (stufflist_p == (struct STUFF *) 0) {
+               return( (struct STUFF *) 0 );
+       }
+
+       /* make copy of string with appropriate font and size */
+       if (stufflist_p->string != (char *) 0) {
+               switch(stufflist_p->stuff_type) {
+               case ST_BOLD:
+                       font = FONT_TB;
+                       break;
+               case ST_OCTAVE:
+                       Stuff_size = DFLT_SIZE;
+                       font = FONT_TI;
+                       break;
+               case ST_ITAL:
+                       font = FONT_TI;
+                       break;
+               case ST_BOLDITAL:
+                       font = FONT_TX;
+                       break;
+               default:
+                       font = FONT_TR;
+                       break;
+               }
+
+               /* figure out the proper size if not already determined */
+               if (Stuff_size  < 0) {
+                       if (all == YES) {
+                               size = Score.size;
+                       }
+                       else {
+                               size = svpath(staffno, SIZE)->size;
+                       }
+               }
+               else {
+                       size = Stuff_size;
+               }
+
+               /* determine fontfamily and font if not already known */
+               if (Curr_family == FAMILY_DFLT) {
+                       if (all == YES) {
+                               fontfamily = Score.fontfamily;
+                       }
+                       else {
+                               fontfamily = svpath(staffno, FONTFAMILY)->
+                                                       fontfamily;
+                       }
+               }
+               else {
+                       fontfamily = Curr_family;
+               }
+
+               /* clone text string */
+               newstring = copy_string(stufflist_p->string + 2, font, size);
+               if (IS_CHORDLIKE(Modifier)) {
+                       newstring = modify_chstr(newstring, Modifier);
+               }
+               fix_string(newstring, fontfamily + font, size,
+                       stufflist_p->inputfile, stufflist_p->inputlineno);
+               if (Modifier == TM_FIGBASS || Modifier == TM_ANALYSIS) {
+                       newstring = acc_trans(newstring);
+               }
+       }
+       else {
+               newstring = (char *) 0;
+       }
+       
+       /* create and fill in clone of stuff, then return it */
+       new_p = newSTUFF(newstring, stufflist_p->dist,
+                               stufflist_p->dist_usage,
+                               stufflist_p->start.count,
+                               stufflist_p->start.steps,
+                               stufflist_p->gracebackup,
+                               stufflist_p->end.bars, stufflist_p->end.count,
+                               stufflist_p->stuff_type, stufflist_p->modifier,
+                               stufflist_p->place, stufflist_p->inputfile,
+                               stufflist_p->inputlineno);
+       new_p->all = (short) all;
+       new_p->next = clone_stufflist(stufflist_p->next, staffno, all);
+       return(new_p);
+}
+\f
+
+/* allocate a STUFF and fill in all the values given. Initialize carry fields
+ * and "all" to NO. Leave coordinates and next link as 0.
+ * Note that the string pointer
+ * is copied; it does not make a copy of the string itself, so never call this
+ * function more than once with the same string--make a copy. */
+
+struct STUFF *
+newSTUFF(string, dist, dist_usage, start_count, start_steps, gracebackup, bars, count,
+               stuff_type, modifier, place, inputfile, inputlineno)
+
+char *string;          /* text string of stuff */
+int dist;              /* dist for this STUFF to override dist parameter */
+int dist_usage;                /* meaning of dist, SD_* */
+double start_count;    /* count at which to begin stuff */
+double start_steps;    /* offset by this many steps */
+int gracebackup;       /* how many grace notes to back up from start */
+int bars;              /* bars in "til" clasue */
+double count;          /* counts in "til" clause */
+int stuff_type;                /* ST_* */
+int modifier;          /* TM_* */
+int place;             /* PL_* */
+char *inputfile;       /* which file stuff was defined in */
+int inputlineno;       /* where stuff was defined in input file */
+
+{
+       struct STUFF *new_p;    /* the new STUFF to fill in */
+
+
+       CALLOC(STUFF, new_p, 1);
+       new_p->string = string;
+       new_p->start.count = start_count;
+       new_p->start.steps = start_steps;
+       new_p->gracebackup = (short) gracebackup;
+       new_p->dist = (short) dist;
+       new_p->dist_usage = (short) dist_usage;
+       new_p->end.bars = (short) bars;
+       new_p->end.count = count;
+       new_p->stuff_type = (short) stuff_type;
+       new_p->modifier = (short) modifier;
+       new_p->place = (short) place;
+       new_p->carryin = new_p->carryout = new_p->all = NO;
+       new_p->costuff_p = 0;
+       new_p->inputfile = inputfile;
+       new_p->inputlineno = (short) inputlineno;
+
+       return(new_p);
+}
+\f
+
+/* recursively free up a stufflist and any strings hanging off of it */
+
+static void
+free_stufflist(stuff_p)
+
+struct STUFF *stuff_p;
+
+{
+       if (stuff_p == (struct STUFF *) 0 ) {
+               return;
+       }
+
+       free_stufflist(stuff_p->next);
+       if (stuff_p->string != (char *) 0) {
+               FREE(stuff_p->string);
+       }
+       FREE(stuff_p);
+}
+\f
+
+/* at each bar line, see if there are any "til" clauses that are supposed
+ * to end in this measure. If so, make sure they end within the time
+ * signature for this measure. */
+
+void
+meas_stuff_chk()
+
+{
+       struct TIL_INFO *til_info_p;            /* to index thru list */
+       struct TIL_INFO **del_place_p_p;        /* for deleting from list */
+       struct TIL_INFO *one2free_p;            /* pointer to which element
+                                                * to free */
+
+       debug(2, "meas_chk_stuff");
+
+       /* update measure number to conpensate for any multirests */
+       Measnum += Multi_adjust;
+       Multi_adjust = 0;
+
+       /* go through list of in-progress til clauses */
+       for (til_info_p = Til_info_list_p, del_place_p_p = &Til_info_list_p;
+                               til_info_p != (struct TIL_INFO *) 0;  ) {
+
+               if (til_info_p->measnum == Measnum) {
+
+                       /* at measure where this til clause ends */
+                       /* check if within time signature */
+                       if (til_info_p->count > Score.timenum + 1.0) {
+                               l_yyerror(til_info_p->inputfile,
+                                       til_info_p->inputlineno,
+                                       "beats in 'til' clause must be <= numerator of time signature + 1 of the measure in which the 'til' clause ends (i.e., <= %d)",
+                                       Score.timenum);
+                       }
+
+                       /* this one has been taken care of: delete from list */
+                       *del_place_p_p = til_info_p->next;
+                       one2free_p = til_info_p;
+               }
+               else if (til_info_p->measnum < Measnum) {
+                       /* must have ended inside a multirest, so delete
+                        * from list */
+                       *del_place_p_p = til_info_p->next;
+                       one2free_p = til_info_p;
+               }
+               else {
+                       /* this one stays on the list for now, so move pointer
+                        * to where to potentially delete to next element */
+                       del_place_p_p = &(til_info_p->next);
+                       one2free_p = (struct TIL_INFO *) 0;
+               }
+
+               /* have to move to next element
+                * before freeing the current one */
+               til_info_p = til_info_p->next;
+
+               if (one2free_p != (struct TIL_INFO *) 0) {
+                       FREE(one2free_p);
+               }
+       }
+
+       /* update number of measures. */
+       Measnum++;
+
+       /* make sure pedal marks are in proper order */
+       ped_order_chk();
+}
+\f
+
+/* adjust number of measures to account for multirests. Called when there is
+ * a multirest. Saved the number of measures in the multirest (minus 1 since
+ * the barline at the end will count for one measure) */
+
+void
+multi_stuff(nmeas)
+
+int nmeas;     /* number of measures in multirest */
+
+{
+       /* subtract 1 to account for the fact that at the bar line at the
+        * end of the multirest we will peg the measure counter */
+       Multi_adjust = nmeas - 1;
+}
+\f
+
+/* handle pedal going into endings. When we hit a first ending, save the
+ * state of the pedal for all staffs. On subsequent endings in the set,
+ * reset the pedal state to what it was at the beginning of the first ending.
+ * At the endending, go back to normal operation. This is similar to
+ * the saveped() function used at print time. */
+
+void
+ped_endings(endingloc)
+
+int endingloc;         /* STARTITEM, INITEM, etc */
+
+{
+       register int s;         /* staff index */
+
+
+       if (endingloc == STARTITEM) {
+               if (Ped_snapshot[0] == YES) {
+
+                       /* starting 2nd ending: restore pedal state as it was
+                        * at beginning of first ending */
+                       for (s = 1; s <= MAXSTAFFS; s++) {
+                               Pedal_state[s] = Ped_snapshot[s];
+                       }
+               }
+
+               else {
+                       /* starting a set of endings,
+                        * need to save pedal state at this
+                        * point so we can carry it into subsequent endings */
+                       for (s = 1; s <= Score.staffs; s++) {
+                               Ped_snapshot[s] = Pedal_state[s];
+                       }
+                       /* make sure any remaining staffs are set to pedal off,
+                        * in case user increases the number of staffs
+                        * during the endings... */
+                       for (   ; s <= MAXSTAFFS; s++) {
+                               Ped_snapshot[s] = NO;
+                       }
+
+                       /* mark that we now have a snapshot */
+                       Ped_snapshot[0] = YES;
+               }
+       }
+
+       else if (endingloc == ENDITEM) {
+               /* at end of endings, discard snapshot of pedal states */
+               Ped_snapshot[0] = NO;
+       }
+}
+\f
+
+/* When all input has been processed, or when changing the number
+ * of staffs, we better not have any 'til' clauses
+ * still unfinished. If we do, print a warning message. */
+
+void
+chk4dangling_til_clauses(boundary_desc)
+
+char *boundary_desc;           /* "the end of the song" or
+                                * "a change in number of staffs" */
+
+{
+       struct TIL_INFO *til_info_p;
+
+
+       debug(2, "chk4dangling_til_clauses");
+
+       /* Go through the whole list of remaining til clauses,
+        * and print a warning message for each. */
+       for (til_info_p = Til_info_list_p; til_info_p != (struct TIL_INFO *) 0;
+                                       til_info_p = til_info_p->next) {
+
+               /* If right on the boundary or spills over only a very tiny
+                * amount, don't bother to complain */
+               if (til_info_p->measnum - Measnum == 0
+                                               && til_info_p->count < .001) {
+                       continue;
+               }
+
+               l_warning(til_info_p->inputfile, til_info_p->inputlineno,
+                               "'til' clause extends beyond %s by %dm + %.3f",
+                               boundary_desc, til_info_p->measnum - Measnum,
+                               til_info_p->count);
+       }
+
+       /* mop up. */
+       free_tils(Til_info_list_p);
+       Til_info_list_p = (struct TIL_INFO *) 0;
+}
+\f
+
+/* recursively free a list of TIL_INFO structs */
+
+static void
+free_tils(til_p)
+
+struct TIL_INFO *til_p;                /* free this list */
+
+{
+       if (til_p == (struct TIL_INFO *) 0) {
+               return;
+       }
+
+       free_tils(til_p->next);
+       FREE(til_p);
+}
+\f
+
+/* user only has to specify when pedal marks end. We deduce from current
+ * pedal state whether a pedal mark is begin or up/down. This gets called
+ * whenever we have a list of pedal STUFFs. Later we enforce that pedal
+ * marks are put in in ascending order only, so that if user enters more
+ * than one pedal line for the same staff, that will be handled properly. */
+
+static void
+fix_pedal(staffno, stuff_p)
+
+int staffno;                   /* pedal is for this staff */
+struct STUFF *stuff_p;         /* list of pedal mark info */
+
+{
+       /* walk through list of pedal marks */
+       for (  ; stuff_p != (struct STUFF *) 0; stuff_p = stuff_p->next) {
+
+               if (stuff_p->string == (char *) 0) {
+                       /* no star, so have to deduce state */
+
+                       if (Pedal_state[staffno] == NO) {
+                               /* pedal currently off, so begin pedal */
+                               Pedal_state[staffno] = YES;
+                               stuff_p->string = copy_string(Ped_begin_str + 2,
+                                       (int) Ped_begin_str[0],
+                                       (int) Ped_begin_str[1]);
+                       }
+                       else {
+                               /* pedal currently down, so pedal up/down */
+                               stuff_p->string = copy_string(Ped_up_down_str + 2,
+                                       (int) Ped_up_down_str[0],
+                                       (int) Ped_up_down_str[1]);
+                       }
+               }
+
+               else if (Pedal_state[staffno] == NO) {
+                       yyerror("can't end pedal -- none in progress");
+               }
+
+               else {
+                       /* user gave star, so end pedal */
+                       Pedal_state[staffno] = NO;
+               }
+       }
+}
+\f
+
+/* reset pedal states for all staffs. This should be called at init time
+ * and at any time when the number of staffs changes. This function also
+ * initializes the Ped_begin_str and Ped_up_down_str. */
+
+void
+reset_ped_state()
+
+{
+       static int first_time = YES;    /* flag if function called before */
+       register int s;                 /* index through staffs */
+
+
+       /* mark pedal off for all staffs */
+       for (s = 1; s <= Score.staffs; s++) {
+               Pedal_state[s] = NO;
+       }
+       Ped_snapshot[0] = NO;
+
+       /* the first time this function is called, initialize the strings
+        * for pedal begin and pedal end. We just have one copy of these
+        * and then make as many copies from these as necessary */
+       if (first_time == YES) {
+               first_time = NO;
+               Ped_begin_str = copy_string("\\(begped)", FONT_MUSIC,
+                                                       DFLT_SIZE);
+               Ped_up_down_str = copy_string("\\(pedal)", FONT_MUSIC,
+                                                       DFLT_SIZE);
+               fix_string(Ped_begin_str, FONT_MUSIC, DFLT_SIZE,
+                                       Curr_filename, -1);
+               fix_string(Ped_up_down_str, FONT_MUSIC, DFLT_SIZE,
+                                        Curr_filename, -1);
+       }
+}
+\f
+
+/* fill in rehearsal mark string. This doesn't go in a STUFF, but it's
+ * sort of like stuff and there didn't seem to be any more appropriate file for
+ * this function */
+
+
+static int Reh_let = 0;                /* current value of rehearsal letter. 0 == "A",
+                                * 25 == "Z", 26 == "AA", etc to 701 == "ZZ" */
+static int Reh_num = 1;                /* current value of rehearsal number */
+
+
+void
+set_reh_string(bar_p, fontfamily, font, size, string)
+
+struct BAR *bar_p;     /* which bar gets the rehearsal mark */
+int fontfamily;                /* what font family to use, or FAMILY_DFLT
+                        * if to use current default */
+int font;              /* what font to use, or FONT_UNKNOWN if to use the
+                        * current default font */
+int size;              /* font size to use, or -1 if to use current default */
+char *string;          /* string for rehearsal mark */
+
+{
+       char reh_str[12];       /* temporary buff for string version of
+                                * rehearsal number or letter */
+       static int reh_size = DFLT_SIZE;        /* size to use for reh marks */
+       static int reh_family = FAMILY_DFLT;    /* font family to use */
+       static int reh_font = FONT_TB;          /* font to use */
+
+
+       /* if first time through, init the font family to the score family */
+       if (reh_family == FAMILY_DFLT) {
+               reh_family = Score.fontfamily;
+       }
+
+       /* if user specified a new size, save that */
+       if (size != -1) {
+               if (size > 100) {
+                       yyerror("reh mark size too large");
+                       return;
+               }
+               else {
+                       reh_size = size;
+               }
+       }
+
+       /* if user specified new font or font family, save that */
+       if (font != FONT_UNKNOWN) {
+               reh_font = font;
+       }
+       if (fontfamily != FAMILY_DFLT) {
+               reh_family = fontfamily;
+       }
+
+       switch(bar_p->reh_type) {
+
+       case REH_NUM:
+               /* get string version of current rehearsal number, and
+                * incrment it */
+               bar_p->reh_string = copy_string(num2str(Reh_num++) + 2,
+                               reh_family + reh_font, reh_size);
+               break;
+
+       case REH_LET:
+               /* Get string version of current rehearsal letter.
+                * Start with A-Z, then AA, AB, AC, ... BA, BB, ... up to ZZ.
+                */
+               if (Reh_let < 26) {
+                       /* 1-letter long mark */
+                       (void) sprintf(reh_str, "%c", Reh_let + 'A');
+               }
+               else if (Reh_let < 27 * 26) {
+                       /* 2-letter long mark */
+                       (void) sprintf(reh_str, "%c%c",
+                               (Reh_let / 26) + 'A' - 1, (Reh_let % 26) + 'A');
+               }
+               else {
+                       ufatal("too many rehearsal letters!");
+               }
+               bar_p->reh_string = copy_string(reh_str,
+                               reh_family + reh_font, reh_size);
+               /* increment for next time around */
+               Reh_let++;
+               break;
+
+       case REH_MNUM:
+               /* get string version of current measure number */
+               bar_p->reh_string = copy_string(num2str(Meas_num) + 2,
+                               reh_family + reh_font, reh_size);
+               break;
+
+       case REH_STRING:
+               /* user-specified string */
+               bar_p->reh_string = fix_string(string,
+                               reh_family + reh_font, reh_size,
+                               Curr_filename, yylineno);
+               break;
+
+       case REH_NONE:
+               break;
+
+       default:
+               pfatal("set_reh_string passed bad value");
+               break;
+       }
+}
+\f
+
+/* Set rehearsal letter or number to user-specified value.
+ * If the current bar has a rehearsal mark of the type being changed,
+ * also replace its current mark with the changed one. This allows user
+ * to say either
+ *     reh num num=5
+ * or
+ *     num=5 reh num
+ * and get the same results, which is consistent with how mnum= setting
+ * had worked.
+ */
+
+void
+init_reh(rehnumber, rehletter, mainbar_p)
+
+int rehnumber;         /* New value for Reh_num or negative if setting Reh_let */
+char *rehletter;       /* "A" to "ZZ" or null if setting number */
+struct MAINLL *mainbar_p;      /* points to the current BAR */
+
+{
+       struct BAR *bar_p;
+       char *oldstr;           /* previous reh_string */
+
+       if (mainbar_p == 0 || mainbar_p->str != S_BAR) {
+               pfatal("bad mainbar_p passed to init_reh");
+       }
+       bar_p = mainbar_p->u.bar_p;
+       oldstr = bar_p->reh_string;
+
+       if (rehnumber >= 0) {
+               Reh_num = rehnumber;
+               /* If this bar has a rehearsal number on this bar,
+                * replace it, and free the old one. */
+               if (bar_p->reh_type == REH_NUM) {
+                       set_reh_string(bar_p, FAMILY_DFLT, FONT_UNKNOWN, -1,
+                                                               (char *) 0);
+                       FREE(oldstr);
+               }
+       }
+
+       if (rehletter != 0) {
+               /* Letter is stored internally as a number,
+                * which is then converted, so we have to convert in reverse.
+                * We only allow "A" through "ZZ" */
+               if (isupper(rehletter[0]) && rehletter[1] == '\0') {
+                       Reh_let = rehletter[0] - 'A';
+               }
+               else if (isupper(rehletter[0]) && isupper(rehletter[1])
+                                               && rehletter[2] == '\0') {
+                       Reh_let = 26 + (rehletter[1] - 'A')
+                                       + (rehletter[0] - 'A') * 26;
+               }
+               else {
+                       yyerror("rehearsal letter setting must be \"A\" through \"ZZ\"");
+                       return;
+               }
+               /* If this bar has a rehearsal letter on this bar,
+                * replace it, and free the old one. */
+               if (bar_p->reh_type == REH_LET) {
+                       set_reh_string(bar_p, FAMILY_DFLT, FONT_UNKNOWN, -1,
+                                                               (char *) 0);
+                       FREE(oldstr);
+               }
+       }
+}
+\f
+
+/* go through all stuff lists and verify that pedal marks are given in
+ * ascending order. If not, error. Some code in both parse and
+ * placement phases requires that pedal marks be in order. */
+
+static void
+ped_order_chk()
+
+{
+       int staffno;
+       struct STUFF *stuff_p;  /* walk through stuff list */
+       float last_ped_count;   /* count where last pedal occurred */
+       int last_backup;        /* gracebackup of last pedal */
+
+
+       /* check every staff */
+       for (staffno = 1; staffno <= Score.staffs; staffno++) {
+
+               /* initialize for current staff */
+               last_ped_count = -1.0;
+               last_backup = 0;
+
+               /* go through stuff list for current staff, looking for pedal */
+               for (stuff_p = Staffmap_p[staffno]->u.staff_p->stuff_p;
+                                       stuff_p != (struct STUFF *) 0;
+                                       stuff_p = stuff_p->next) {
+                       if (stuff_p->stuff_type == ST_PEDAL) {
+
+                               /* found a pedal. Make sure it is later than
+                                * the previous pedal */
+                               if (stuff_p->start.count < last_ped_count ||
+                                               (stuff_p->start.count
+                                               == last_ped_count
+                                               && stuff_p->gracebackup
+                                               > last_backup) ) {
+                                       l_yyerror(stuff_p->inputfile,
+                                               stuff_p->inputlineno,
+                                               "pedal must be specified in ascending order");
+                                       /* no need to print error more than
+                                        * once if multiple errors */
+                                       continue;
+                               }
+
+                               /* keep track of where this pedal is, for
+                                * comparing with the next one */
+                               last_ped_count = stuff_p->start.count;
+                               last_backup = stuff_p->gracebackup;
+                       }
+               }
+       }
+}
+\f
+
+/* Translate STUFF text modifier to a printable string. */
+
+char *
+stuff_modifier(modifier)
+
+int modifier;
+
+{
+       switch (modifier) {
+
+       case TM_CHORD:
+               return("chord");
+       case TM_ANALYSIS:
+               return("analysis");
+       case TM_FIGBASS:
+               return("figbass");
+       case TM_DYN:
+               return("dyn");
+       case TM_NONE:
+               return("(no modifier)");
+       default:
+               return("(invalid modifier)");
+       }
+}