chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / check.c
diff --git a/mup/mup/check.c b/mup/mup/check.c
new file mode 100644 (file)
index 0000000..e329b9c
--- /dev/null
@@ -0,0 +1,1994 @@
+
+/* Copyright (c) 1995, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+/* functions for checking for various classes of errors such as
+ * values out of range, or trying to do something in an illegal context.
+ * Also contains the code to combine multiple measures of rests into
+ * multirests. */
+
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+
+static struct MAINLL *add_pre_meas P((struct MAINLL *insert_p, int start,
+               int end, int add_bar));
+static int check_all_rests P((struct STAFF *staff_p, int count));
+static void do_combine P((struct MAINLL *begin_p, struct MAINLL *end_p,
+               int nummeas, int min_combine));
+static int valid_mark_item P((int mark, int place));
+static void mv_accs P((struct MAINLL *mll_p));
+static void move_xoct P((struct STUFF *stuff_p, struct MAINLL *newfirst_p,
+               int staffno, int bars, int start));
+static void addped P((struct STUFF *pedal_p, struct MAINLL *mll_p));
+
+
+\f
+
+/* give error message if given number is not within specified range. */
+/* return NO if out of range, YES if okay */
+
+/* once upon a time, there was the rangecheck function, and it got called
+ * many times. Then midi support was added and that code needed to do lots
+ * of rangechecks, but with the filename and line number something other
+ * than Curr_filename, and yylineno, so the l_rangecheck function was
+ * created, and rangecheck just calls that. There isn't an l_frangecheck
+ * to go with frangecheck, because nothing needed it. Another case where
+ * C++ would have been nice, so we could default added arguments....
+ */
+
+int
+rangecheck(n, min, max, name)
+
+int n;         /* the number to check */
+int min;       /* has to be at least this big */
+int max;       /* can be no bigger than this */
+char *name;    /* describes what n represents, to use in error message */
+
+{
+       return(l_rangecheck(n, min, max, name, Curr_filename, yylineno));
+}
+
+int
+l_rangecheck(n, min, max, name, fname, lineno)
+
+int n;         /* the number to check */
+int min;       /* has to be at least this big */
+int max;       /* can be no bigger than this */
+char *name;    /* describes what n represents, to use in error message */
+char *fname;   /* file name */
+int lineno;    /* line number */
+
+{
+       if ( (n < min) || (n > max) ) {
+               l_yyerror(fname, lineno,
+                               "%s (%d) is out of range (must be between %d and %d)",
+                               name, n, min, max);
+               return(NO);
+       }
+       return(YES);
+}
+
+
+/* This function is rather like rangecheck, except it also allows a special
+ * "empty" value. */
+
+int
+erangecheck(n, min, max, empty_value, name)
+
+int n;         /* the number to check */
+int min;       /* has to be at least this big */
+int max;       /* can be no bigger than this */
+int empty_value;       /* this is also a legal value */
+char *name;    /* describes what n represents, to use in error message */
+
+{
+       if (n == empty_value) {
+               /* value is okay--means user set to empty */
+               return(YES);
+       }
+       if ( (n < min) || (n > max) ) {
+               l_yyerror(Curr_filename, yylineno,
+                               "%s (%d) out of range (must be between %d to %d or set to nothing at all)",
+                               name, n, min, max);
+               return(NO);
+       }
+       return(YES);
+}
+\f
+
+/* just like rangecheck except for a float instead of int */
+
+int
+frangecheck(n, min, max, name)
+
+float n;       /* the number to check */
+float min;     /* has to be at least this big */
+float max;     /* can be no bigger than this */
+char *name;    /* describes what n represents, to use in error message */
+
+{
+       if ( (n < min) || (n > max) ) {
+               l_yyerror(Curr_filename, yylineno,
+                               "%s (%.3f) is out of range (must be between %.3f and %.3f)",
+                               name, n, min, max);
+               return(NO);
+       }
+       return(YES);
+}
+\f
+
+/* give error and return NO if given number is not a power of 2 */
+
+int
+power_of2check(n, name)
+
+int n;         /* number to verify */
+char *name;    /* what n represents, for error message */
+
+{
+       if ( (n <= 0) || ((n & (n - 1)) != 0)) {
+               l_yyerror(Curr_filename, yylineno,
+                               "%s (%d) not a power of 2", name, n);
+               return(NO);
+       }
+       return(YES);
+}
+\f
+
+/* check that current action is valid in current context. */
+/* If so, return YES, otherwise print message and return NO */
+
+int
+contextcheck(validcontext, action)
+
+int validcontext;      /* bitmap of valid contexts */
+char *action;          /* what action is to be done, for error messages */
+
+{
+       static int shouldBmusic; /* count of how many consecutive times
+                               * we were called when we should have been
+                               * in music context, but weren't
+                               */
+
+       /* Forgetting to say 'music' can cause tons of errors,
+        * which may confuse the user. So try to deduce what they meant.
+        */
+       if (validcontext == C_MUSIC) {
+               if (Context != C_MUSIC) {
+                       if (++shouldBmusic > 5) {
+                               
+                               l_yyerror(Curr_filename, yylineno, "guessing you forgot to specify 'music'; changing to music context to try to recover");
+                               Context = C_MUSIC;
+                       }
+               }
+               else {
+                       shouldBmusic = 0;
+               }
+       }
+       else {
+               shouldBmusic = 0;
+       }
+
+       if ((validcontext & Context) == 0) {
+               l_yyerror(Curr_filename, yylineno, "%s not valid in %s context",
+                       action, contextname(Context));
+               return(NO);
+       }
+       return(YES);
+}
+\f
+
+/* convert context number back to name */
+
+char *
+contextname(cont)
+
+int cont;      /* context number */
+
+{
+       switch(cont)  {
+       case C_MUSIC:
+               return("music");
+       case C_SCORE:
+               return("score");
+       case C_STAFF:
+               return("staff");
+       case C_VOICE:
+               return("voice");
+       case C_HEADER:
+               return("header");
+       case C_FOOTER:
+               return("footer");
+       case C_HEAD2:
+               return("header2");
+       case C_FOOT2:
+               return("footer2");
+       case C_TOP:
+               return("top");
+       case C_TOP2:
+               return("top2");
+       case C_BOT:
+               return("bottom");
+       case C_BOT2:
+               return("bottom2");
+       case C_BLOCK:
+               return("block");
+       default:
+               return("unknown");
+       }
+}
+\f
+
+/* check that at least one staff is visible, print error message if not */
+
+void
+check_at_least1visible()
+
+{
+       int staffno;
+
+       /* go through list of staffs, if we find a visible one, fine */
+       for (staffno = Score.staffs; staffno > 0; staffno--) {
+               if ( (svpath(staffno, VISIBLE))->visible == YES) {
+                       return;
+               }
+       }
+
+       yyerror("no staffs visible");
+       return;
+}
+\f
+
+/* if there is a change in visibility status, need to have a scorefeed after
+ * that. So go through main list. If there is a change in visibility, or
+ * in number of staffs, or stafflines, or staffscale, go
+ * backwards in list till hit FEED, BAR, or beginning of list. If hit FEED
+ * first, throw it away. Then search forward from the SSV until we hit
+ * FEED or STAFF.  If FEED, fine. If STAFF, insert
+ * a FEED. If we discarded the user FEED because it was in the wrong place,
+ * mark the pagefeed field as they had it.
+ * This function also adds a measure of space to the beginning of the song
+ * if we are doing MIDI. If a song begins with a grace note,
+ * we want to move that back into the "previous" measure, so this will
+ * create that measure. It's easier to deal with that here, before
+ * makechords() is called, than to try to add it in later.
+ */
+
+void
+chk_vis_feed()
+
+{
+       struct MAINLL *mll_p, *m_p;     /* to walk through main list */
+       struct MAINLL *new_feed_p;
+       short set_pagefeed = NO;        /* if to set pagefeed field */
+       short s;                        /* staff index */
+       short vis[MAXSTAFFS + 1];       /* which staffs are currently visible */
+       short stlines[MAXSTAFFS + 1];   /* stafflines for each staff */
+       float stscale[MAXSTAFFS + 1];   /* staffscale for each staff */
+       short num_staffs;               /* number of staffs */
+       short add_extra;                /* if to add extra space measure */
+
+
+       debug(4, "chk_vis_feed");
+
+       /* If doing MIDI, we want to add an extra space measure to the
+        * beginning */
+       add_extra = Doing_MIDI;
+
+       /* go through main list looking for visibility changes */
+       initstructs();
+       for (mll_p = Mainllhc_p; mll_p != (struct MAINLL *) 0;
+                                               mll_p = mll_p->next) {
+
+               if (mll_p->str == S_SSV) {
+                       /* SSV can only follow a BAR or BLOCKHEAD or another
+                        * SSV. If it follows a LINE/CURVE/PRHEAD,
+                        * then user must have entered music context,
+                        * but not entered any music,
+                        * just line/curve/print things. This violates our
+                        * mainlist rules, so we have to disallow */
+                       if (mll_p->prev != 0 && mll_p->prev->str != S_BAR &&
+                                       mll_p->prev->str != S_BLOCKHEAD &&
+                                       mll_p->prev->str != S_SSV) {
+                               l_yyerror(mll_p->inputfile, mll_p->inputlineno,
+                                       "music context containing only lines/curves/print statements is not allowed");
+                       }
+
+                       /* Only want to insert a FEED if there truly was
+                        * a change in the number of staffs to be printed.
+                        * User may have set visible when it was already set,
+                        * or just changed voice visibility which didn't
+                        * cause the visibily of staffs to change, or
+                        * something like that, which doesn't count.
+                        * So see what visiblity, stafflines,
+                        * and staffscale are set to now,
+                        * then assign the SSV and see if things changed.
+                        */
+                       for (s = 1; s <= MAXSTAFFS; s++) {
+                               /* save current values of interest */
+                               vis[s] = svpath(s, VISIBLE)->visible;
+                               stlines[s] = svpath(s, STAFFLINES)->stafflines;
+                               stscale[s] = svpath(s, STAFFSCALE)->staffscale;
+                       }
+                       num_staffs = Score.staffs;
+
+                       /* make any updates */
+                       asgnssv(mll_p->u.ssv_p);
+
+                       /* now compare with previous values */
+                       for (s = 1; s <= MAXSTAFFS; s++) {
+                               if (vis[s] != svpath(s, VISIBLE)->visible ||
+                                               stlines[s] != svpath(s,
+                                               STAFFLINES)->stafflines ||
+                                               stscale[s] != svpath(s,
+                                               STAFFSCALE)->staffscale) {
+                                       /* something changed */
+                                       break;
+                               }
+                       }
+
+                       if (s <= MAXSTAFFS || Score.staffs != num_staffs) {
+
+                               /* found a change. Go backwards. If find a
+                                * FEED, discard it. Otherwise ok */
+                               for (m_p = mll_p->prev;
+                                               m_p != (struct MAINLL *) 0;
+                                               m_p = m_p->prev) {
+
+                                       if (IS_CLEFSIG_FEED(m_p)) {
+                                               /* feed in wrong place. Discard
+                                                * this one. We'll put one in
+                                                * the proper place later */
+                                               set_pagefeed =
+                                                       m_p->u.feed_p->pagefeed;
+                                               unlinkMAINLL(m_p);
+                                               FREE(m_p);
+                                               break;
+                                       }
+
+                                       else if (m_p->str == S_BAR) {
+                                               break;
+                                       }
+                               }
+
+                               /* now look forwards. If find FEED, fine.
+                                * If not, insert one */
+                               for (m_p = mll_p->next;
+                                               m_p != (struct MAINLL *) 0;
+                                               m_p = m_p->next) {
+
+                                       if (m_p->str == S_FEED) {
+                                               /* user already put one in */
+                                               break;
+                                       }
+
+                                       else if (m_p->str == S_STAFF) {
+                                               /* user neglected to put in an
+                                                * explicit feed, so we add
+                                                * one for them */
+                                               new_feed_p =
+                                                       newMAINLLstruct(S_FEED,
+                                                       -1);
+                                               new_feed_p->u.feed_p->pagefeed
+                                                               = set_pagefeed;
+                                               insertMAINLL(new_feed_p,
+                                                               m_p->prev);
+                                               break;
+                                       }
+                               }
+                               set_pagefeed = NO;
+                       }
+               }
+
+               else if (add_extra == YES && mll_p->str == S_STAFF) {
+                       /* For MIDI purposes, add a measure space to the
+                        * beginning of the song, in case we need to move
+                        * grace notes back into it. Strictly speaking,
+                        * we probably don't need to do this unless there
+                        * truly is a grace note at the beginning, but
+                        * it should never hurt to add it, and it doesn't
+                        * seem worth the effort to check. */
+                       add_pre_meas(mll_p->prev, 1, Score.staffs, YES);
+
+                       add_extra = NO;
+               }
+       }
+}
+\f
+
+/* For MIDI, we add a measure of space preceeding what the user put in.
+ * This is used in case they start the piece with grace notes that we need
+ * to move back in the preceeding measure, since this guarantees there will
+ * be a preceeding measure. Also, for when taking a "slice" of the piece,
+ * skipping measures at the beginning, this is where we attach any MIDI
+ * STUFFs that happened during the skipped part.
+ * Returns the last MAINLL added.
+ */
+
+static struct MAINLL *
+add_pre_meas(insert_p, start, end, add_bar)
+
+struct MAINLL *insert_p;       /* insert after here */
+int start;                     /* staff number of first STAFF to create */
+int end;                       /* staff number of last STAFF to create */
+int add_bar;                   /* if YES, add an invisible bar too */
+
+{
+       int staff;              /* loop through staffs to be created */
+       struct MAINLL *new_p;   /* new STAFFs */
+       int numvoices;          /* number of voices on current staff */
+       int v;                  /* voice index */
+
+       /* Create a staff with measure space for all
+        * defined staffs/voices, and link onto main list */
+       for (staff = start; staff <= end; staff++) {
+
+               /* create the STAFF struct itself */
+               new_p = newMAINLLstruct(S_STAFF, -1);
+               new_p->u.staff_p->staffno = staff;
+               new_p->u.staff_p->visible = svpath(staff, VISIBLE)->visible;
+
+               numvoices = vscheme_voices(svpath(staff, VSCHEME)->vscheme);
+               for (v = 0; v < numvoices; v++) {
+                       add_meas_space( &(new_p->u.staff_p->groups_p[v]),
+                                               staff, v + 1);
+               }
+
+               /* link onto main list, and arrange to
+                * link the next thing after this one */
+               insertMAINLL(new_p, insert_p);
+               insert_p = new_p;
+       }
+       if (add_bar == YES) {
+               /* add an invisible bar line */
+               new_p = newMAINLLstruct(S_BAR, -1);
+               new_p->u.bar_p->bartype = INVISBAR;
+               insertMAINLL(new_p, insert_p);
+               insert_p = new_p;
+       }
+       return(insert_p);
+}
+\f
+
+/* check for valid interval. Unison, octave, fourths and fifths can not be
+ * major or minor. The others cannot be perfect. */
+
+void
+chk_interval(inttype, intnum)
+
+int inttype;   /* PERFECT, MINOR, etc */
+int intnum;    /* e.g., 4 for fourth */
+
+{
+       if (intnum <= 0) {
+               yyerror("transposition interval must be > 0");
+               return;
+       }
+
+       /* collapse into 1 octave. It's okay that a 7th will come out zero
+        * because of the way things are checked below. */
+       intnum %= 7;
+
+       switch (inttype) {
+
+       case  PERFECT:
+               switch (intnum) {
+               case 1: 
+               case 4:
+               case 5:
+                       break;
+               default:
+                       yyerror("specified interval cannot be perfect");
+                       break;
+               }
+               break;
+
+       case MAJOR:
+       case MINOR:
+               switch(intnum) {
+               case 1:
+               case 4:
+               case 5:
+                       yyerror("specified interval cannot be major or minor");
+                       break;
+               default:
+                       break;
+               }
+               break;
+
+       default:
+               /* everything else is okay */
+               break;
+       }
+}
+\f
+
+/* if specified used[] field is set to YES, print warning that its value is
+ * being overridden. This is to let user know they set the same parameter
+ * twice in the same SSV context */
+
+void
+used_check(mll_p, var, name)
+
+struct MAINLL *mll_p;  /* check used[] in the SSV pointed to by this */
+int var;               /* check this index in the used[] array */
+char *name;            /* name of variable, for warning message */
+
+{
+       if (mll_p == (struct MAINLL *) 0) {
+               l_yyerror(Curr_filename, yylineno,
+                       "can't set %s in %s context", name, contextname(Context));
+               return;
+       }
+
+       if (mll_p->str != S_SSV) {
+               pfatal("bad argument passed to used_check()");
+       }
+
+       if (mll_p->u.ssv_p->used[var] == YES) {
+               l_warning(Curr_filename, yylineno,
+                       "setting of '%s' parameter overrides previous setting",
+                       name);
+       }
+}
+\f
+
+/* go through list and combine multiple consecutive rests into multi-rests */
+
+void
+combine_rests(c)
+
+int c; /* argument to -c command line option;
+        * only combine when there are at least
+        * this many rest measures in a row.
+        * Set to NORESTCOMBINE if user didn't use -c option. */
+
+{
+       struct MAINLL *mll_p;   /* walk through main list */
+       struct MAINLL *begin_p = (struct MAINLL *) 0;   /* where section to
+                                                        * combine begins */
+       struct MAINLL *end_p = (struct MAINLL *) 0;
+       int all_rests = YES;
+       int n;                  /* how many measures minimum to combine */
+       int count = 0;          /* how many measures of all rests */
+       int begin_valid = NO;   /* if begin_p has been set. Can't just check to
+                                * see if it is null, because null is valid */
+       struct SSV *ssv_p;
+       char *timerep;          /* current time signature representation */
+
+
+       debug(2, "combine_rests");
+
+       /* go through main list */
+       initstructs();
+       n = c;  /* init to value of -c option */
+       for (mll_p = Mainllhc_p; mll_p != (struct MAINLL *) 0;
+                                               mll_p = mll_p->next) {
+
+               if (Doing_MIDI == YES && mll_p == Mainllhc_p) {
+                       /* Don't want to combine the "extra" measure
+                        * we added in for MIDI, so skip it. */
+                       for (  ; mll_p != 0; mll_p = mll_p->next) {
+                               if (mll_p->str == S_BAR) {
+                                       mll_p = mll_p->next;
+                                       break;
+                               }
+                       }
+                       if (mll_p == 0) {
+                               /* Must be no valid music data in the file.
+                                * check4barline_at_end() should have already
+                                * reported this to user, so we can just
+                                * return silently here. */
+                               return;
+                       }
+               }
+
+               /* for each STAFF that is visible, see if it is all rests */
+               if (mll_p->str == S_STAFF) {
+                       /* remember where we are, in case this is the beginning
+                        * of section that needs to be combined */
+                       if (begin_valid == NO) {
+                               begin_p = mll_p->prev;
+                               begin_valid = YES;
+                       }
+
+                       /* if we have all rests so far, check this staff */
+                       if (all_rests == YES) {
+                               if ((all_rests = check_all_rests
+                                               (mll_p->u.staff_p, count))
+                                               == NO) {
+                                       /* this measure was not all rests.
+                                        * Check to see if before that we
+                                        * had seen a run of
+                                        * all rests. If so, combine them */
+                                       do_combine(begin_p, end_p, count, n);
+                               }
+                       }
+               }
+
+               else if (mll_p->str == S_BAR) {
+                       if (all_rests == YES) {
+                               /* this measure was all rests, so
+                                * bump counter */
+                               if (mll_p->u.bar_p->bartype != INVISBAR) {
+                                       count++;
+                               }
+                               end_p = mll_p;
+                               /* if not an ordinary bar, end the combining */
+                               if ( (mll_p->u.bar_p->reh_type != REH_NONE) ||
+                                               (mll_p->u.bar_p->endingloc
+                                               != NOITEM &&
+                                               mll_p->u.bar_p->endingloc
+                                               != INITEM) ||
+                                               ( (mll_p->u.bar_p->bartype
+                                               != SINGLEBAR) &&
+                                               (mll_p->u.bar_p->bartype !=
+                                               INVISBAR) ) ) {
+                                       if (mll_p->u.bar_p->bartype == RESTART) {
+                                               /* There is an empty "measure"
+                                                * of space before restarts */
+                                               count--;
+                                               /* need to end combining at
+                                                * the previous bar line */
+                                               if (count >= n) {
+                                                       for (end_p = end_p->prev;
+                                                       end_p != 0 &&
+                                                       end_p->str != S_BAR;
+                                                       end_p = end_p->prev) {
+                                                               ;
+                                                       }
+                                               }
+                                       }
+
+                                       do_combine(begin_p, end_p, count, n);
+
+                                       /* re-initialize */
+                                       count = 0;
+                                       begin_p = (struct MAINLL *) 0;
+                                       all_rests = YES;
+                                       begin_valid = NO;
+                               }
+                       }
+                       else {
+                               /* re-init for next measure */
+                               all_rests = YES;
+                               count = 0;
+                               begin_p = (struct MAINLL *) 0;
+                               begin_valid = NO;
+                       }
+               }
+
+               else if (mll_p->str == S_SSV) {
+
+                       ssv_p = mll_p->u.ssv_p;
+
+                       /* If -c option was not used, we use the value of
+                        * the restcombine parameter. */
+                       if (c == NORESTCOMBINE &&
+                                       ssv_p->used[RESTCOMBINE] == YES) {
+                               n = ssv_p->restcombine;
+                       }
+
+                       /* if there is a change in visibility or a relevant
+                        * change on an already-visible score,
+                        * that is grounds to end the combination */
+                       if ( ssv_p->used[VISIBLE] == YES ||
+                                       ((((mll_p->u.ssv_p->staffno == 0) ||
+                                       (svpath(mll_p->u.ssv_p->staffno,
+                                       VISIBLE))->visible == YES)) &&
+                                       (ssv_p->used[CLEF] == YES
+                                       || ssv_p->used[SHARPS] == YES
+                                       || ssv_p->used[TIME] == YES
+                                       || ssv_p->used[TRANSPOSITION] == YES
+                                       || ssv_p->used[ADDTRANSPOSITION] == YES
+                                       || ssv_p->used[VISIBLE] == YES))) {
+                               do_combine(begin_p, end_p, count, n);
+
+                               /* re-initialize */
+                               count = 0;
+                               begin_p = (struct MAINLL *) 0;
+                               all_rests = YES;
+                               begin_valid = NO;
+                       }
+
+                       /* keep track of visibility */
+                       asgnssv(mll_p->u.ssv_p);
+               }
+               else if (mll_p->str == S_FEED) {
+                       do_combine(begin_p, end_p, count, n);
+                       count = 0;
+                       begin_p = (struct MAINLL *) 0;
+                       all_rests = YES;
+                       begin_valid = NO;
+               }
+       }
+
+       /* do final combination if any */
+       do_combine(begin_p, end_p, count, n);
+
+       /* If there were case of TS_ALWAYS with alternating time
+        * signatures, we had to save the entire time signature list for
+        * every measure in case it turned out to be a multirest.
+        * Now we can shorten down to one for those that aren't,
+        * and need to do this so later code works.
+        */
+       initstructs();
+       timerep = 0;
+       for (mll_p = Mainllhc_p; mll_p != 0; mll_p = mll_p->next) {
+               struct MAINLL * nmll_p;
+
+               if (mll_p->str != S_SSV) {
+                       continue;
+               }
+               asgnssv(mll_p->u.ssv_p);
+               if (Score.timevis != PTS_ALWAYS) {
+                       continue;
+               }
+
+               /* If we need to change the current timerep,
+                * need to do the permanent copy in the main list,
+                * not the one current in Score, so save pointer to current
+                * one in main list. */
+               if (mll_p->u.ssv_p->used[TIME]) {
+                       timerep = mll_p->u.ssv_p->timerep;
+               }
+
+               for (nmll_p = mll_p->next; nmll_p != 0;
+                                               nmll_p = nmll_p->next) {
+                       if (nmll_p->str == S_STAFF) {
+                               if (nmll_p->u.staff_p->groups_p[0]->basictime >= -1) {
+                                       /* not followed by multi-rest,
+                                        * can truncate time sig */
+                                       char * t;
+                                       for (t = timerep; t != 0 && *t != 0; t++) {
+                                               if (*t == TSR_ALTERNATING) {
+                                                       *t = TSR_END;
+                                                       break;
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+               }
+       }
+}
+\f
+
+/* given a STAFF, return NO if the staff is visible and has at least one
+ * voice which contains notes, or has lyrics or STUFF (except if stuff is on
+ * or before the first beat of the first measure. Otherwise return YES */
+
+static int
+check_all_rests(staff_p, count)
+
+struct STAFF *staff_p; /* which staff info to check */
+int count;             /* how many measures of all rest so far */
+
+{
+       int v;                  /* index through voices */
+       struct GRPSYL *gs_p;    /* walk through grpsyl list */
+       struct STUFF *stuff_p;  /* walk through stuff list */
+
+
+       /* if not visible, then okay to treat as all rests */
+       if ( (svpath(staff_p->staffno, VISIBLE))->visible == NO) {
+               return(YES);
+       }
+
+       /* if has lyrics, consider that not rests */
+       if (staff_p->nsyllists > 0) {
+               return(NO);
+       }
+
+       /* having STUFF is usually grounds for not being treated as all rests */
+       /***** what to do about earlier stuff that happens to spill into
+        *** this measure???????? *****/
+       if (staff_p->stuff_p != (struct STUFF *) 0) {
+               /* special case. If this is the first measure of rests, and     
+                * all STUFFs occur on or before beat 1 and have no til clause,
+                * then that's okay.
+                * This allows user to change tempo or something similar at
+                * the beginning of a combined multirest. */
+               if (count > 0) {
+                       /* Combining only applies to printing, not MIDI,
+                        * so MIDI STUFFs can be ignored */
+                       for (stuff_p = staff_p->stuff_p; stuff_p != 0;
+                                               stuff_p = stuff_p->next) {
+                               if (stuff_p->stuff_type != ST_MIDI) {
+                                       return(NO);
+                               }
+                       }
+                       /* all the STUFFs must have been MIDI */
+                       return(YES);
+               }
+               for (stuff_p = staff_p->stuff_p; stuff_p != (struct STUFF *) 0;
+                                       stuff_p = stuff_p->next) {
+                       if (stuff_p->stuff_type == ST_MIDI) {
+                               continue;
+                       }
+                       if (stuff_p->start.count > 1.0 || stuff_p->end.bars > 0
+                                       || stuff_p->end.count > 0.0) {
+                               return(NO);
+                       }
+               }
+       }
+
+       for (v = 0; v < MAXVOICES; v++) {
+               for (gs_p = staff_p->groups_p[v]; gs_p != (struct GRPSYL *) 0;
+                                                       gs_p = gs_p->next) {
+                       /* if voice is invisible, treat like all rests */
+                       if (vvpath(staff_p->staffno, v + 1, VISIBLE)->visible == NO) {
+                               continue;
+                       }
+
+                       if (gs_p->grpcont == GC_NOTES) {
+                               return(NO);
+                       }
+
+                       else if (gs_p->is_meas == NO) {
+                               /* We only combine mr and ms. If user entered
+                                * one or more rests/spaces that fill the
+                                * entire measure, we don't combine,
+                                * because the user may have had some reason
+                                * for explicitly specifying time values rather
+                                * than using a measure duration. */
+                               return(NO);
+                       }
+                       else if (gs_p->basictime < -1) {
+                               /* already multirest! */
+                               return(NO);
+                       }
+               }
+       }
+       return(YES);
+}
+\f
+
+/* effect the combination of several measures of rests into a multirest. */
+
+static void
+do_combine(begin_p, end_p, nummeas, min_combine)
+
+struct MAINLL *begin_p;        /* start combining from here */
+struct MAINLL *end_p;  /* stop here */
+int nummeas;           /* hom many measures are being combined */
+int min_combine;       /* minimum number to combine, or NORESTCOMBINE */
+
+{
+       struct MAINLL *new_p;
+       struct MAINLL *old_p;           /* first of items to discard */
+       struct GRPSYL *gs_p;            /* for multirest */
+       struct MAINLL *mll_p;           /* to find staff for transferring stuffs */
+       short s;                        /* index through staffs */
+       short numvoices;
+
+
+       if (min_combine == NORESTCOMBINE || nummeas < min_combine) {
+               /* don't bother to combine */
+               return;
+       }
+
+       /* discard everything in main list between the given points.
+        * It will be either STAFFs with all rests to be replaced or
+        * BARs to be discarded, or things associated with invisible staffs.
+        * I guess maybe we should free up the space rather than merely
+        * unhitching it from the list, but it hardly seems worth the bother,
+        * especially since we'd have to be careful not to delete the STUFFs
+        * on the first one in case they were needed at the end of this
+        * function.
+        */
+       if (begin_p == (struct MAINLL *) 0) {
+               old_p = Mainllhc_p;
+               Mainllhc_p = end_p;
+       }
+       else {
+               old_p = begin_p->next;
+               begin_p->next = end_p;
+       }
+       if (end_p != (struct MAINLL *) 0) {
+               end_p->prev = begin_p;
+       }
+
+       /* add multirest to list */
+       for (s = Score.staffs; s > 0; s--) {
+               new_p = newMAINLLstruct(S_STAFF, -1);
+               gs_p = newGRPSYL(GS_GROUP);
+               gs_p->grpcont = GC_REST;
+               gs_p->basictime = -nummeas;
+               gs_p->fulltime = Score.time;
+               gs_p->staffno = s;
+               gs_p->vno = 1;
+
+               new_p->u.staff_p->groups_p[0] = gs_p;
+               numvoices = vscheme_voices(svpath(s, VSCHEME)->vscheme);
+               if (numvoices > 1) {
+                       add_meas_space( &(new_p->u.staff_p->groups_p[1]), s, 2);
+                       new_p->u.staff_p->groups_p[1]->basictime = -nummeas;
+                       /* if the first voice was invisible,
+                        * but the second voice is visible, need to convert
+                        * the space just created into a rest. */
+                       if (vvpath(s, 1, VISIBLE)->visible == NO &&
+                                       vvpath(s, 2, VISIBLE)->visible == YES) {
+                               new_p->u.staff_p->groups_p[1]->grpcont = GC_REST;
+                       }
+               }
+               if (numvoices > 2) {
+                       add_meas_space( &(new_p->u.staff_p->groups_p[2]), s, 3);
+                       new_p->u.staff_p->groups_p[2]->basictime = -nummeas;
+                       /* if only the third voice is visible, need to convert
+                        * the space just created into a rest. */
+                       if (vvpath(s, 1, VISIBLE)->visible == NO &&
+                                       vvpath(s, 2, VISIBLE)->visible == NO &&
+                                       vvpath(s, 3, VISIBLE)->visible == YES) {
+                               new_p->u.staff_p->groups_p[2]->grpcont = GC_REST;
+                       }
+               }
+               new_p->u.staff_p->staffno = s;
+               new_p->u.staff_p->visible = svpath(s, VISIBLE)->visible;
+               insertMAINLL(new_p, begin_p);
+
+               /* if there were any STUFFs on or before the first beat of the
+                * first measure, we have to transfer them to the stufflist of
+                * the multirest. We can transfer the entire list, because if
+                * there were any items that shouldn't be transferred, we
+                * wouldn't have allowed the multirest combination in the
+                * first place. */
+               for (mll_p = old_p; mll_p != (struct MAINLL *) 0
+                               && mll_p != end_p; mll_p = mll_p->next) {
+                       if (mll_p->str == S_STAFF
+                                       && mll_p->u.staff_p->staffno == s) {
+                               new_p->u.staff_p->stuff_p =
+                                               mll_p->u.staff_p->stuff_p;
+                               break;
+                       }
+               }
+       }
+}
+\f
+
+/* translate MK_* to printable name */
+
+char *
+markname(mark)
+
+int mark;      /* MK_* value */
+
+{
+       switch(mark) {
+       case MK_MUSSYM:
+               return("mussym");
+       case MK_OCTAVE:
+               return("octave");
+       case MK_DYN:
+               return("dyn");
+       case MK_OTHERTEXT:
+               return("othertext");
+       case MK_CHORD:
+               return("chord");
+       case MK_LYRICS:
+               return("lyrics");
+       case MK_ENDING:
+               return("ending");
+       case MK_REHEARSAL:
+               return("rehearsal");
+       case MK_PEDAL:
+               return("pedal");
+       default:
+               pfatal("markname(): missing case");
+               /*NOTREACHED*/
+               return("");
+       }
+}
+\f
+
+/* verify that a mark order list is valid */
+
+void
+chk_order(ssv_p, place)
+
+struct SSV *ssv_p;     /* check the markorder list in here */
+int place;             /* PL_*, which list to check */
+
+{
+       int m, n;       /* index through MK_* */
+       int level;      /* value in markorder table */
+
+       for (m = 0; m < NUM_MARK; m++) {
+               if (ssv_p->markorder[place][m] == 0) {
+                       /* no level set for this mark, so skip it */
+                       continue;
+               }
+
+               /* some mark types cannot be equal with any other types */
+               switch (m) {
+
+               case MK_LYRICS:
+               case MK_ENDING:
+               case MK_REHEARSAL:
+               case MK_PEDAL:
+                       level = ssv_p->markorder[place][m];
+                       for (n = 0; n < NUM_MARK; n++) {
+                               if (n == m) {
+                                       continue;
+                               }
+                               if (ssv_p->markorder[place][n] == level) {
+                                       l_yyerror(Curr_filename, yylineno,
+                                               "%s cannot be at same level as %s",
+                                               markname(m), markname(n));
+                               }
+                       }
+                       break;
+
+               default:
+                       break;
+               }
+
+               if (valid_mark_item(m, place) == NO) {
+                       char *placename;
+                       switch (place) {
+                       case PL_ABOVE:
+                               placename = "above";
+                               break;
+                       case PL_BELOW:
+                               placename = "below";
+                               break;
+                       case PL_BETWEEN:
+                               placename = "between";
+                               break;
+                       default:
+                               pfatal("chk_order: invalid place %d", place);
+                               /* not reached; it just avoids bogus
+                                * "used before set" warning */
+                               placename = "";
+                       }
+                       l_warning(Curr_filename, yylineno,
+                                       "%s not valid in %sorder list",
+                                       markname(m), placename);
+               }
+       }
+}
+\f
+
+/* return YES if MK_* item is valid at given place, NO if not */
+
+static int
+valid_mark_item(mark, place)
+
+int mark;      /* MK_* */
+int place;     /* PL_* */
+
+{
+       if (mark == MK_OCTAVE && place == PL_BETWEEN) {
+               return(NO);
+       }
+       if ((mark == MK_ENDING || mark == MK_REHEARSAL)
+                                               && place != PL_ABOVE) {
+               return(NO);
+       }
+       if (mark == MK_PEDAL && place != PL_BELOW) {
+               return(NO);
+       }
+
+       /* everything else is okay */
+       return(YES);
+}
+\f
+
+/*
+ * User can specify that only a portion of the song is to be processed.
+ * This is done via one or two numbers, the first measure to include and
+ * the last.
+ * Positive numbers are relative to the beginning of the song, negative
+ * are relative to the end. So, as examples:
+ *     1       // the whole song (default)
+ *     1,-1    // another way to say the whole song
+ *     5       // start at measure 5, through the end
+ *     6,7     // only measures 6 and 7
+ *     1,10    // measures 1 through 10
+ *     1,-8    // skip the last 7 measures
+ *     -12,-3  // only process from 12 measures from the end through
+ *             // the third from the end
+ *
+ * When counting measures for this, invisbar bars do not count.
+ * It is measured by the number of bars encountered in input, not in
+ * performance: the bars are not double counted in sections
+ * between repeat signs. 
+ *
+ * A value of zero is not allowed.
+ * A positive start larger than the number of measures in the song
+ *     is a user error.
+ * A negative start that would result in starting before the beginning
+ *     starts at the beginning (with a warning)
+ * A positive end larger than the number of measures in the song goes to
+ *     end of song (with a warning)
+ * An end value that would result in starting before the beginning of the
+ *     song or before the start value is a user error.
+ *
+ * Only one slice is supported. I.e., you can't ask for something
+ * like measures 4-10, 17-24, and 46-80.
+ * You can only ask for one of those ranges.
+ * (You could get that effect by making 3 files and playing them one after
+ * another, although there might be slight pauses in between.)
+ *
+ * A possible future enhancement might be
+ * to also be able to specify by rehearsal mark.
+ * If a rehearsal mark string is specified rather than a number,
+ * the rehearsal mark  having that string (ASCII-ized by removing font, size,
+ * and other special things) would be used as the marked place. In this case,
+ * the end place would be only up to the rehearsal mark, not through the
+ * measure that starts there.
+ */
+
+void
+chk_x_arg(x_arg, start_p, end_p)
+
+char *x_arg;   /* arg to -x option specified by user */
+int *start_p;  /* start gets returned here */
+int *end_p;    /* end gets returned here */
+
+{
+       char *arg_p;    /* pointer to where end starts in x_arg */
+
+       /* set to defaults */
+       *start_p = 1;
+       *end_p = -1;
+
+       if (x_arg == 0 || *x_arg == '\0') {
+               /* No -x option, use whole song as normal */
+               return;
+       }
+
+       *start_p = (int) strtol(x_arg, &arg_p, 0);
+       if (arg_p == x_arg) {
+               if (Mupmate == YES) {
+                       l_yyerror(0, -1, "Run > Set Options > Extract Measures: value must be one or two numbers.");
+               }
+               else {
+                       l_yyerror(0, -1, "argument for %cx option must be one or two numbers", Optch);
+               }
+       }
+
+       /* If there is a comma, get the "end" argument as well */
+       if (*arg_p == ',') {
+               *end_p = (int) strtol(arg_p + 1, &arg_p, 0);
+       }
+
+       /* We should be at end of string, either after first arg if there
+        * was only one arg, or after second if there were two. */
+       if (*arg_p != '\0') {
+               if (Mupmate == YES) {
+                       l_yyerror(0, -1, "Run > Set Options > Extract Measures: value must be one or two numbers.");
+               }
+               else {
+                       l_yyerror(0, -1, "argument for %cx option must be one or two numbers", Optch);
+               }
+       }
+}
+\f
+
+/* This function does the slicing to extract selected measures from the input */
+
+void
+extract(start, end)
+
+int start;     /* Start at this measure number.  A negative
+                * number means count from the end of the piece,
+                * so -3 would mean the last 3 bars. */
+int end;       /* Play only through this measure number.
+                * Negative is relative to the end of the piece. */
+
+{
+       int pickup;                             /* YES if song begins
+                                                * with pickup measure */
+       int numbars;                            /* total number of bars */
+       int bars;                               /* how many processed so far */
+       int mrbars;                             /* how many bars of multirest */
+       struct MAINLL *topstaff_mll_p = 0;      /* "all" MIDI STUFFS will
+                                                * be attached here */
+       struct MAINLL *mll_p;                   /* loop through list */
+       struct MAINLL *m_p;                     /* to look ahead in list */
+       struct MAINLL *next_p;                  /* saved next */
+       struct STUFF *nextstuff_p;              /* saved next of a STUFF */
+       struct MAINLL *first_p;                 /* first STAFF at start */
+       struct STUFF *stuff_p;                  /* loop through STUFF list */
+       struct STUFF *pedal[MAXSTAFFS+1];       /* YES if pedal is down */
+       struct STUFF *saveped[MAXSTAFFS+1];     /* YES if pedal was down                                                         * at entry to set of endings */
+       int in_endings;                         /* YES if inside endings */
+       int i;                                  /* index */
+
+
+       pickup = has_pickup();
+       if ( ( (pickup == YES && start == 0) || (pickup == NO && start == 1) )
+                       && end == -1) {
+               /* Use whole song as normal; nothing to do here */
+               return;
+       }
+
+       /* If song has a pickup measure, compensate for that.
+        * This function treats the partial measure as a measure.
+        * So if user specified 0 for start, they want to start at the pickup,
+        * which will be effectively measure 1 here. If they specified 1,
+        * they want to skip the pickup, which means they want to start at
+        * what will be considered measure 2. And so forth.
+        */
+       if (pickup == YES) {
+               if (start >= 0) {
+                       start++;
+               }
+               if (end >= 0) {
+                       end++;
+               }
+       }
+       else {
+               /* It's not clear if these should be warnings or errors,
+                * but it seems friendlier to be just warnings.
+                * That way, if someone wants the beginning,
+                * but can't remember if the piece has a pickup or not,
+                * they can use 0, and it will always work,
+                * albeit possibly with a warning.
+                */
+               if (start == 0) {
+                       warning("x option start of 0 is only valid if there is a pickup measure; using 1");
+                       start = 1;
+               }
+               if (end == 0) {
+                       warning("x option end of 0 is only valid if there is a pickup measure; using 1");
+                       end = 1;
+               }
+       }
+
+       /* Count total number of bars in song */
+       for (numbars = 0, mll_p= Mainllhc_p; mll_p != 0; mll_p = mll_p->next) {
+               if (mll_p->str == S_BAR && mll_p->u.bar_p->bartype != INVISBAR) {
+                       numbars++;
+               }
+               /* Multirests count as multiple bars */
+               else if (mll_p->str == S_STAFF
+                               && mll_p->u.staff_p->groups_p[0] != 0) {
+                       if (mll_p->u.staff_p->groups_p[0]->basictime < -1) {
+                               numbars += -(mll_p->u.staff_p->groups_p[0]->basictime) - 1 ;
+                       }
+                       /* skip the rest of the STAFFs till BAR */
+                       while (mll_p->next != 0 && mll_p->next->str == S_STAFF) {
+                               mll_p = mll_p->next;
+                       }
+               }
+       }
+       if (numbars == 0) {
+               /* Only non-invisible bars count. If there aren't any
+                * identifiable measures, -x is pointless. */
+               ufatal("can't use -x on song with no visible bar lines");
+       }
+
+       /* If user specified things relative to the end, convert
+        * the relative-to-end negative values to relative-to-beginning
+        * positive values.
+        */
+       if (start < 0) {
+               start = numbars + start + 1;
+       }
+       if (end < 0) {
+               end = numbars + end + 1;
+       }
+
+       if (start > numbars) {
+               ufatal("Attempt to start beyond end of song");
+       }
+       if (end < start) {
+               ufatal("Attempt to end before start");
+       }
+       if (end <= 0) {
+               ufatal("Attempt to end before beginning of song");
+       }
+       if (start < 1) {
+               warning("attempt to start before beginning; ignoring");
+               start = 1;
+       }
+       if (end > numbars) {
+               warning("attempt to go past end; ignoring");
+               end = numbars;
+       }
+
+       if (start == 1 && end == numbars) {
+               /* After all the conversions, we ended up with
+                * the entire song. Nothing more to do here. */
+               return;
+       }
+       /* compensate for bar being at end of measure */
+       start--;
+
+
+       /* First find the bar where we're going to start.
+        * Find out if there are any notes tied into that measure
+        * that have an accidental. If so, move the accidental
+        * into the new starting measure.
+        * Note: the 'mll_p != 0' checks are defensive; shouldn't happen.
+        */
+       initstructs();
+       for (bars = 0, mll_p = Mainllhc_p; mll_p != 0 && bars < start;
+                                                       mll_p = mll_p->next){
+               if (mll_p->str == S_SSV) {
+                       /* need to keep things like keysig up to date */
+                       asgnssv(mll_p->u.ssv_p);
+               }
+               else if (mll_p->str == S_BAR && mll_p->u.bar_p->bartype != INVISBAR) {
+                       bars++;
+               }
+               else if (mll_p->str == S_STAFF
+                               && mll_p->u.staff_p->groups_p[0] != 0) {
+                       if (mll_p->u.staff_p->groups_p[0]->basictime < -1) {
+                               bars += -(mll_p->u.staff_p->groups_p[0]->basictime) - 1 ;
+                       }
+                       /* skip the rest of the STAFFs till BAR */
+                       while (mll_p->next->str == S_STAFF) {
+                               mll_p = mll_p->next;
+                       }
+               }
+       }
+       first_p = 0;
+       for (  ; mll_p != 0 && mll_p->str != S_BAR; mll_p = mll_p->next) {
+               if (mll_p->str == S_STAFF) {
+                       mv_accs(mll_p);
+                       if (first_p == 0) {
+                               first_p = mll_p;
+                       }
+               }
+       }
+       /* Fix up the measure number to account for what was chopped off.
+        * The pickup part may be a bit counter-intuitive...
+        * It's because when there was a pickup, we've already added 1 to
+        * "start" for that partial measure; with no pickup, we didn't.
+        * As an example, if user specified -x2, then the measure number
+        * at the barline at the end of the first printed measure should be 3.
+        * "start" will have value 1 if there was no pickup,
+        * but 2 if there was a pickup. So....
+        *
+        *                      start + 1 + (pickup ? 0 : 1);
+        *                      -----------------------------
+        *    no pickup:          1   + 1 +               1     = 3
+        *    pickup:             2   + 1 +           0         = 3
+        */
+       Meas_num = mll_p->u.bar_p->mnum = start + 1 + (pickup ? 0 : 1);
+
+       /* Find the top visible staff on the new effective "first" measure,
+        * We will be moving MIDI STUFFs into the special "extra"
+        * space measure that exists for MIDI right before the first real
+        * measure entered by the user.
+        * The current top visible tells us which staff to use for "all" items.
+        * We leave everything on the list up through the "extra" measure */
+       if (Doing_MIDI) {
+               int staffs_needed;      /* number of staffs at new first meas */
+               struct MAINLL *laststaffmll_p = 0;      /* last staff of the extra meas
+                        * added at the beginning for MIDI.
+                        * Initialization just to avoid "used before set." */
+
+               for (mll_p = Mainllhc_p; mll_p->str != S_BAR; mll_p = mll_p->next) {
+                       if (mll_p->str == S_STAFF && svpath(
+                                       mll_p->u.staff_p->staffno, VISIBLE)
+                                       ->visible == YES) {
+                               topstaff_mll_p = mll_p;
+                               break;
+                       }
+               }
+
+               /* The number of staffs in the "extra" added measure might be
+                * smaller than the number of staffs on the new
+                * effective first measure.
+                */
+               staffs_needed = Score.staffs;
+               for (mll_p = Mainllhc_p; mll_p->str != S_BAR; mll_p = mll_p->next){
+                       if (mll_p->str == S_STAFF) {
+                               laststaffmll_p = mll_p;
+                       }
+               }
+               if (laststaffmll_p == 0){
+                       pfatal("extract failed to find last staff in extra measure");
+               }
+               if (laststaffmll_p->u.staff_p->staffno < staffs_needed) {
+                       (void) add_pre_meas(laststaffmll_p,
+                                       laststaffmll_p->u.staff_p->staffno,
+                                       staffs_needed, NO);
+               }
+
+               /* We want to start discarding things after the extra measure,
+                * so skip past that. */
+               for (mll_p = Mainllhc_p; mll_p->str != S_BAR; mll_p = mll_p->next) {
+                       ;
+               }
+               mll_p = mll_p->next;
+       }
+       else {
+               mll_p = Mainllhc_p;
+       }
+
+       /* Pedal is off at the start */
+       for (i = 1; i <= MAXSTAFFS; i++) {
+               pedal[i] = 0;
+               saveped[i] = 0;
+       }
+
+       /* Now go through and discard anything irrelevant before the
+        * start measure. We save all SSVs. If doing MIDI, we save any
+        * MIDI directives that matter.
+        * Everything else gets discarded.
+        */
+       mrbars = 0;
+       next_p = 0;
+       in_endings = NO;
+       for (bars = 0; bars < start; mll_p = next_p) {
+               if (mll_p == 0) {
+                       pfatal("got null mll_p when finding starting measure");
+               }
+               next_p = mll_p->next;
+
+               switch (mll_p->str) {
+
+               case S_BAR:
+                       if (mll_p->u.bar_p->bartype != INVISBAR) {
+                               bars++;
+                       }
+                       if (mrbars > 0) {
+                               bars += mrbars - 1;
+                               if (bars > start || (mll_p->u.bar_p->bartype
+                                               == INVISBAR && bars == start)) {
+                                       /* New first bar is or was a multirest,
+                                        * so we need to retain this bar line.
+                                        */
+                                       continue;
+                               }
+                       }
+                       mrbars = 0;
+
+                       /* Keep track of if we're in a first ending. */
+                       if (mll_p->u.bar_p->endingloc == STARTITEM) {
+                               in_endings = YES;
+                       }
+                       else if (mll_p->u.bar_p->endingloc == ENDITEM) {
+                               in_endings = NO;
+                       }
+                       break;
+
+               case S_STAFF:
+                       /* Keep track of current pedal state for this staff.
+                        * If we find a C_ENDPED the pedal is up, otherwise
+                        * it's down (because even with a bounce, at the end
+                        * of the bounce, the pedal is down).
+                        * Also deal with an octave marks that might spill
+                        * over into the new first measure.
+                        */
+                       for (stuff_p = mll_p->u.staff_p->stuff_p; stuff_p != 0;
+                                               stuff_p = nextstuff_p) {
+                               /* In a previous implementation,
+                                * stuff_p->next could be invalid after call
+                                * to move_xoct, so we had to save it here.
+                                * With the current implementation, this
+                                * shouldn't be strictly necessary, but
+                                * it doesn't hurt. */
+                               nextstuff_p = stuff_p->next;
+
+                               /* Note that the only time string should be
+                                * null is on pedal carried over from a
+                                * previous score, so that isn't really a
+                                * change in pedal state, so we can ignore it.
+                                */
+                               if (stuff_p->stuff_type == ST_PEDAL &&
+                                                       stuff_p->string != 0) {
+                                       pedal[mll_p->u.staff_p->staffno] =
+                                                               stuff_p;
+                               }
+                               if (stuff_p->stuff_type == ST_OCTAVE) {
+                                       move_xoct(stuff_p, first_p,
+                                               mll_p->u.staff_p->staffno,
+                                               bars, start);
+                               }
+                       }
+
+                       if (Doing_MIDI) {
+                               mv_midi_items(mll_p, topstaff_mll_p);
+                       }
+
+                       /* Deal with multirest */
+                       if (mll_p->u.staff_p->groups_p[0]->basictime < -1) {
+                               mrbars = -(mll_p->u.staff_p->groups_p[0]->basictime);
+                               if (bars + mrbars > start) {
+                                       /* Slice starts in middle of multirest.
+                                        * This multirest will be the new
+                                        * first measure, but we need
+                                        * to adjust the number of
+                                        * measures worth of multirest to
+                                        * account for starting in the middle
+                                        * of it.
+                                        */
+                                   mll_p->u.staff_p->groups_p[0]->basictime =
+                                               -((bars + mrbars) - start);
+
+                                   /* If the end of the slice is also inside
+                                    * this same multirest, shorten it down
+                                    * to end there. It's slightly silly to
+                                    * be saving only a part of a multirest,
+                                    * but it's better to handle it than
+                                    * to core dump...
+                                    */
+                                  if (bars + mrbars > end) {
+                                       mll_p->u.staff_p->groups_p[0]->basictime
+                                               += (end - bars - mrbars + 2);
+                                  }
+
+                                   /* If down to single measure, have to
+                                    * convert from multirest to meas rest */
+                                   if (mll_p->u.staff_p->groups_p[0]->basictime == -1) {
+                                       mll_p->u.staff_p->groups_p[0]->is_meas = YES;
+                                       mll_p->u.staff_p->groups_p[0]->basictime = 1;
+                                       mll_p->u.staff_p->groups_p[0]->fulltime = Score.time;
+                                   }
+                                   continue;
+                               }
+                       }
+                       break;
+
+               case S_SSV:
+                       /* Need to keep all the SSVs. Not sure they
+                        * really need to be assigned, but shouldn't hurt. */
+                       asgnssv(mll_p->u.ssv_p);
+                       continue;
+
+               default:
+                       break;
+
+               }
+
+               /* Get rid of this MAINLL. User wants it skipped. */
+               unlinkMAINLL(mll_p);
+       }
+
+       /* Make sure there is a bar after where mll_p currently is.
+        * If song consists of just a multirest, followed by
+        * blocks, prints, lines, or curves, but no more music,
+        * we need to force mll_p to get set to the bar at the end of
+        * the multirest.
+        */
+       for (m_p = mll_p; m_p != 0 && m_p->str != S_BAR; m_p = m_p->next) {
+               ;
+       }
+       if (m_p == 0) {
+               /* No bar.  Force into hitting the next 'if' */
+               mll_p = 0;
+       }
+
+       /* If start was in a multirest, mll_p could be zero,
+        * so we have to use the beginning bar. */
+       if (mll_p == 0) {
+               for (mll_p = Mainllhc_p; mll_p != 0 && mll_p->str != S_BAR;
+                                                       mll_p = mll_p->next) {
+                       ;
+               }
+               if (mll_p == 0) {
+                       pfatal("-x option failed to find beginning bar correctly");
+               }
+       }
+
+       /* Add pedal starts if necessary. Go through the new effective
+        * first measure. For any staff that had the pedal down coming
+        * into that measure, make sure the measure starts with a BEGPED.
+        * Also, if there are any measure repeats, ufatal.
+        * Trying to start at a measure repeat doesn't really make a lot of
+        * sense, and it would be work to allow it, so don't bother.
+        * Using mrpt is discouraged anyway.
+        */
+       for ( ; mll_p->str != S_BAR; mll_p = mll_p->next) {
+               if (mll_p->str == S_STAFF) {
+                       int v;          /* voice index */
+                       /* check for mrpt */
+                       for (v = 0; v < MAXVOICES; v++) {
+                               if (is_mrpt(mll_p->u.staff_p->groups_p[v]) == YES) {
+                                       l_ufatal(mll_p->inputfile, mll_p->inputlineno,
+                                       "-x option not allowed to start at a mrpt");
+                               }
+                       }
+
+                       addped(pedal[mll_p->u.staff_p->staffno], mll_p);
+               }       
+       }
+
+       /* If slice starts inside an ending, we disallow that.
+        * It turns out there a number of pathological cases,
+        * particularly regarding pedal. If the pedal was down going into
+        * the first ending, but up at the beginning of the slice, then
+        * what should the pedal state be at the start of subsequent endings?
+        * It should normally be based on the state coming into the ending,
+        * but that no longer exists and conflicts with the new "beginning
+        * of the ending" state. And more fundamentally, where do you go back
+        * to when you reach the end of a non-final ending?
+        * There's nothing there to go back to!
+        * I suppose if someone has a really long ending and wants
+        * just the middle of it, this restriction will be annoying,
+        * but that shouldn't happen too often, and trying to handle this
+        * was just getting too complicated!
+        */
+       if (in_endings == YES) {
+               ufatal("-x section not allowed to begin inside an ending");
+       }
+
+       /* If the new first measure is in the middle of an ending,
+        * patch up the endingloc. */
+       if (mll_p->u.bar_p->endingloc == ENDITEM ||
+                                       mll_p->u.bar_p->endingloc == INITEM) {
+               struct MAINLL *prevmll_p;
+               struct MAINLL *topstaffmll_p = 0;
+
+               for (prevmll_p = mll_p->prev; prevmll_p != 0;
+                                               prevmll_p = prevmll_p->prev) {
+                       if (prevmll_p->str == S_STAFF) {
+                               topstaffmll_p = prevmll_p;
+                       }
+                       else if (prevmll_p->str == S_BAR) {
+                               prevmll_p->u.bar_p->endingloc = STARTITEM;
+                               break;
+                       }
+               }
+               if (prevmll_p == 0) {
+                       if (topstaffmll_p == 0) {
+                               pfatal("unexpected null topstaffmll_p");
+                       }
+                       prevmll_p = add_pre_meas(
+                               topstaffmll_p->prev, 1, Score.staffs, YES);
+                       prevmll_p->u.bar_p->endingloc = STARTITEM;
+               }
+       }
+
+       /* Now chop off end if needed */
+       if (end < numbars) {
+               for (  ; mll_p != 0 && (bars < end || mrbars > 0);
+                                                       mll_p = mll_p->next) {
+                       if (mll_p->str == S_BAR && mll_p->u.bar_p->bartype != INVISBAR) {
+                               bars++;
+                               mrbars = 0;
+                       }
+                       if (mll_p->str == S_STAFF &&
+                                       mll_p->u.staff_p->groups_p[0] != 0 &&
+                                       mll_p->u.staff_p->groups_p[0]->basictime < -1) {
+                               /* It's a multirest */
+                               mrbars = -(mll_p->u.staff_p->groups_p[0]->basictime);
+                               bars += mrbars - 1;
+                               if (bars >= end) {
+                                       /* Slice ends inside the multirest.
+                                        * Adjust its length. If down to a
+                                        * single measure, convert */
+                                       for (  ; mll_p->str == S_STAFF;
+                                                       mll_p = mll_p->next) {
+                                           mll_p->u.staff_p->groups_p[0]->basictime
+                                                       += bars - end + 1;
+                                           if (mll_p->u.staff_p->groups_p[0]->basictime == -1) {
+                                               mll_p->u.staff_p->groups_p[0]->is_meas = YES;
+                                               mll_p->u.staff_p->groups_p[0]->basictime = 1;
+                                               mll_p->u.staff_p->groups_p[0]->fulltime = Score.time;
+                                           }
+                                       }
+                                       bars -= bars - end;
+                               }
+                               /* multi-rest has been handled; skip over
+                                * the rest of the staffs in this measure */
+                               while (mll_p->next != 0 &&
+                                               mll_p->next->str == S_STAFF) {
+                                       mll_p = mll_p->next;
+                               }
+                       }
+               }
+               if (mll_p != 0) {
+                       /* If didn't get all the way to end of main list,
+                        * chop off the rest.
+                        * We don't bother to free the space.
+                        */
+                       mll_p->prev->next = 0;
+                       Mainlltc_p = mll_p->prev;
+               }
+
+               /* Remove ties/slurs going into the chopped-off part. */
+               for (mll_p = Mainlltc_p->prev; mll_p != 0; mll_p = mll_p->prev) {
+                       if (mll_p->str == S_STAFF) {
+                               int v;          /* voice index */
+                               for (v = 0; v < MAXVOICES; v++) {
+                                       struct GRPSYL *g_p;
+                                       int n;          /* note index */
+                                       if (mll_p->u.staff_p->groups_p[v] == 0) {
+                                               /* this voice doesn't exist */
+                                               continue;
+                                       }
+                                       /* find last group in this voice */
+                                       for (g_p = mll_p->u.staff_p->groups_p[v];
+                                                       g_p->next != 0;
+                                                       g_p = g_p->next) {
+                                               ;
+                                       } 
+                                       /* only notes can have tie/slur */
+                                       if (g_p->grpcont != GC_NOTES) {
+                                               continue;
+                                       }
+                                       g_p->tie = NO;
+                                       for (n = 0; n < g_p->nnotes; n++) {
+                                               g_p->notelist[n].tie = NO;
+                                               if (g_p->notelist[n].nslurto > 0) {
+                                                       g_p->notelist[n].nslurto = 0;
+                                                       FREE(g_p->notelist[n].slurtolist);
+                                               }
+                                       }
+                               }
+
+                               if (mll_p->u.staff_p->staffno == 1) {
+                                       /* No more staffs in the final bar */
+                                       break;
+                               }
+                       }
+               }
+
+               /* If end is in the middle of an ending, or with a restart,
+                * fix that */
+               for (mll_p = Mainlltc_p; mll_p != 0; mll_p = mll_p->prev) {
+                       if (mll_p->str == S_BAR) {
+                               if (mll_p->u.bar_p->endingloc == INITEM) {
+                                       mll_p->u.bar_p->endingloc = ENDITEM;
+                               }
+                               else if (mll_p->u.bar_p->endingloc == STARTITEM) {
+                                       struct MAINLL *pmll_p;
+
+                                       /* If there is a previous bar and it's
+                                        * in an ending, we need to change this
+                                        * one to an ENDITEM, else to NOITEM.
+                                        */
+                                       mll_p->u.bar_p->endingloc = NOITEM;
+                                       for (pmll_p = mll_p->prev; pmll_p != 0;
+                                                       pmll_p = pmll_p->prev) {
+                                               if (pmll_p->str == S_BAR) {
+                                                       if (pmll_p->u.bar_p->endingloc == INITEM ||
+                                                       pmll_p->u.bar_p->endingloc == STARTITEM) {
+                                                               mll_p->u.bar_p->endingloc = ENDITEM;
+                                                       }
+                                                       break;
+                                               }
+                                       }
+                               }
+                               if (mll_p->u.bar_p->bartype == RESTART) {
+                                       /* Not exactly clear what to do here.
+                                        * Could ufatal. We'll convert to
+                                        * an invisible bar. */
+                                       mll_p->u.bar_p->bartype = INVISBAR;
+                               }
+                               break;
+                       }
+               }
+               
+               /* Note: it shouldn't be necessary to do anything
+                * about location tags in chopped-off areas.
+                * Locvar code runs much later, so doesn't know
+                * about what was deleted, and that code has to be able
+                * to handle tags pointing to invisible things anyway
+                * for other reasons. Similarly, it is not necessary to
+                * shorten til clauses, since any that spill into the
+                * chopped-off part will still be handled okay, since a
+                * staff could be made invisible in the middle of a til
+                * clause, and this is a similar case.
+                */
+       }
+}
+\f
+
+/* When using -x option, if there is a tie across the beginning split point,
+ * we need to check if there was an accidental on the note being tied from,
+ * and if so, move the accidental to the new effective first measure.
+ */
+
+static void
+mv_accs(mll_p)
+
+struct MAINLL *mll_p;
+
+{
+       int v;                  /* voice index */
+       int n;                  /* note index */
+       int staffno;
+       int ea;                 /* effective accidental */
+       struct GRPSYL *gs_p;
+       struct NOTE *note_p;
+
+       staffno = mll_p->u.staff_p->staffno;
+       for (v = 0; v < MAXVOICES; v++) {
+               if ((gs_p = mll_p->u.staff_p->groups_p[v]) == 0) {
+                       /* voice doesn't exist on this staff */
+                       continue;
+               }
+               /* Only non-grace note groups can be tied to */
+               if (gs_p->grpcont == GC_NOTES && gs_p->grpvalue != GV_ZERO) {
+                       /* check all notes for being tied to */
+                       for (n = 0; n < gs_p->nnotes; n++) {
+                               note_p = &(gs_p->notelist[n]);
+                               /* Only need to check if doesn't already
+                                * have an accidental. If effective accidental
+                                * differs from what note would get from        
+                                * key sig, need to add explicit accidental. */
+                               if (note_p->accidental == '\0') {
+                                       ea = eff_acc(gs_p, note_p, mll_p);
+                                       if (ea != acc_from_keysig(
+                                         note_p->letter, staffno, mll_p)) {
+                                               note_p->accidental
+                                                       = Acclets[ea+2];
+                                       }
+                               }
+                       }
+               }
+       }
+}
+\f
+
+/* If there is an octave mark whose til clause goes into the new
+ * effective first measure due to -x option, we need to move that octave mark
+ * to that new first measure, with its til clause appropriately shortened.
+ */
+
+static void
+move_xoct(stuff_p, newfirst_p, staffno, bars, start)
+
+struct STUFF *stuff_p;         /* the STUFF to potentially move */
+struct MAINLL *newfirst_p;     /* points to first STAFF in new first meas */
+int staffno;                   /* which STAFF this STUFF is for */
+int bars;                      /* how many bars into song the STUFF is */
+int start;                     /* start bar number from -x option */
+
+{
+       struct STAFF *staff_p;  /* where to move the STUFF */
+       struct MAINLL *mll_p;   /* to search for proper STAFF */
+       struct STUFF *newstuff_p;       /* moved STUFF */
+
+
+       /* See if this STUFF is an octave that spills into new first measure */
+       if (stuff_p->stuff_type == ST_OCTAVE &&
+                                       bars + stuff_p->end.bars >= start) {
+               /* Make a new STUFF, adjusting length of til clause
+                * to compensate for -x, and starting at count 1 */
+               newstuff_p = newSTUFF(stuff_p->string,
+                                       stuff_p->dist,
+                                       stuff_p->dist_usage,
+                                       1.0, 0.0, 0,
+                                       bars + stuff_p->end.bars - start,
+                                       stuff_p->end.count,
+                                       ST_OCTAVE,
+                                       stuff_p->modifier,
+                                       stuff_p->place,
+                                       stuff_p->inputfile,
+                                       stuff_p->inputlineno);
+
+               /* Find corresponding STAFF in new first measure */
+               staff_p = 0;
+               for (mll_p = newfirst_p; mll_p != 0 && mll_p->str == S_STAFF;
+                                                       mll_p = mll_p->next) {
+                       if (mll_p->u.staff_p->staffno == staffno) {
+                               staff_p = mll_p->u.staff_p;
+                               break;
+                       }
+               }
+               if (staff_p == 0) {
+                       /* Staff apparently doesn't exist here,
+                        * so we can ignore the octave mark. */
+                       return;
+               }
+
+               /* Make the octave stuff into a 1-element stufflist,
+                * and call the function to merge that list with the
+                * existing list. */
+               newstuff_p->next = 0;
+               connect_stuff(staff_p, newstuff_p);
+       }
+}
+\f
+
+/* If the passed-in pedal_p points to a pedal stuff that indicates the
+ * pedal state needs to be altered at the beginning of the STAFF passed in,
+ * then do that. This is for -x, for like when the slice begins where the
+ * pedal was held down from the previous measure, so we have to add a begped.
+ */
+
+static void
+addped(pedal_p, mll_p)
+
+struct STUFF *pedal_p; /* last pedal state of previous measures */
+struct MAINLL *mll_p;  /* points to a STAFF, to add pedal to */
+
+{
+       int s;          /* staff number */
+       struct STUFF *stuff_p;  /* walk through stuff list */
+       struct STUFF **prev_p_p; /* This holds the address
+                                * of the previous' next,
+                                * which is where we will need
+                                * to update if we need to delete
+                                * or insert a STUFF.
+                                */
+
+       s = mll_p->u.staff_p->staffno;
+       if (pedal_p == 0 || pedal_p->string == 0) {
+               /* There is no pedal carrying over into this measure,
+                * so no need to add anything. */
+               return;
+       }
+
+       /* If last pedal mark was an endped, we don't need to move one in,
+        * because pedal is off; otherwise we do. */
+       if (pedal_p->string[4] != C_ENDPED) {
+
+               /* Need to add a pedal STUFF at count 1 if
+                * there isn't already one at or before there.
+                * See if there's already one there.
+                */
+               for (stuff_p = mll_p->u.staff_p->stuff_p,
+                               prev_p_p = &(mll_p->u.staff_p->stuff_p);
+                               stuff_p != 0;
+                               prev_p_p = &(stuff_p->next),
+                               stuff_p = stuff_p->next) {
+                       if (stuff_p->stuff_type == ST_PEDAL &&
+                                       stuff_p->start.count
+                                       <= 1.0) {
+                               /* Already had a pedal at beginning of measure.
+                                * If it's an ENDPED,
+                                * that negates the one coming in,
+                                * and it ought to be removed.
+                                * If it's a PEDAL, need to change to a BEGPED.
+                                * In any case, we don't need
+                                * to check any more STUFFs for this STAFF.
+                                */
+                               if (stuff_p->string[4] == C_ENDPED) {
+                                       /* delete it */
+                                       *prev_p_p = stuff_p->next;
+                               }
+                               else if (stuff_p->string[4] == C_PEDAL) {
+                                       stuff_p->string[4] = C_BEGPED;
+                               }
+                               break;
+                       }
+               }
+               if (stuff_p == 0) {
+                       /* There wasn't a pedal at the
+                        * beginning of the new first measure,
+                        * so we will need to add one.
+                        */
+                       struct STUFF *newped_p;
+
+                       newped_p = newSTUFF(pedal_p->string,
+                                       pedal_p->dist,
+                                       pedal_p->dist_usage,
+                                       1.0, 0.0, 0, 0, 0.0,
+                                       ST_PEDAL,
+                                       pedal_p->modifier,
+                                       pedal_p->place,
+                                       pedal_p->inputfile,
+                                       pedal_p->inputlineno);
+                       newped_p->string[4] = C_BEGPED;
+
+                       /* figure out where to insert it */
+                       for (stuff_p = mll_p->u.staff_p->stuff_p,
+                                       prev_p_p = &(mll_p->u.staff_p->stuff_p);
+                                       stuff_p != 0;
+                                       prev_p_p = &(stuff_p->next),
+                                       stuff_p = stuff_p->next) {
+                               if (stuff_p->place == PL_ABOVE) {
+                                       /* not far enough yet */
+                                       continue;
+                               }
+                               if (pedal_p->all == YES && stuff_p->all == NO){
+                                       continue;
+                               }
+                               if (stuff_p->start.count < 1.0) {
+                                       continue;
+                               }
+                               /* found right place */
+                               break;
+                       }
+                       if (prev_p_p == 0) {
+                               pfatal("failed to find place to insert pedal for -x");
+                       }
+
+                       /* insert into STUFF list */
+                       newped_p->next = *prev_p_p;
+                       *prev_p_p = newped_p;
+               }
+       }
+}