chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / relvert.c
diff --git a/mup/mup/relvert.c b/mup/mup/relvert.c
new file mode 100644 (file)
index 0000000..f0b623e
--- /dev/null
@@ -0,0 +1,2758 @@
+/* Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 by Arkkra Enterprises */
+/* All rights reserved */
+/*
+ * Name:       relvert.c
+ *
+ * Description:        This file contains functions for setting all remaining
+ *             relative vertical coordinates.
+ */
+
+#include <string.h>
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+/* how many rectangles to malloc initially and at each realloc if needed */
+#define RECTCHUNK      (100)
+
+/* rectangle structure; see procscore() prologue for explanation of its use */
+struct RECTAB {
+       float n, s, e, w;       /* boundaries of a rectangle */
+                               /* horz coords are absolute; vertical coords */
+                               /*  are relative to center staff line */
+                               /*  (baseline for "between") */
+       short relevant;         /* is rectangle relevant? */
+       short tried;            /* have we tried this one yet? */
+};
+static struct RECTAB *rectab;  /* ptr to malloc'ed and realloc'ed array */
+
+/* this fudge factor prevents roundoff error from causing overlap */
+#define FUDGE  (0.001)
+
+/* these symbols tell certain subroutines which things to work on */
+#define        DO_OTHERS       0       /* default */
+#define        DO_PHRASE       1
+
+
+static int reclim;             /* index after last rectangle in rectab */
+
+static void procstaff P((struct MAINLL *mainll_p, int s)); 
+static void dostaff P((int s, int place));
+static void dogroups P((struct MAINLL *start_p, int s, int place));
+static void llgrps P((struct STAFF *staff_p, struct GRPSYL *gs_p, int place));
+static void dobeamalt P((struct MAINLL *start_p, int s, int place));
+static void onebeamalt P((struct GRPSYL *gs_p));
+static double getstemendvert P((struct GRPSYL *gs_p));
+static void linerects P((double x1, double y1, double x2, double y2, int side,
+               double halfstaff));
+static void docurve P((struct MAINLL *start_p, int s, int place,
+               int do_which));
+static void curverect P((int s, struct STUFF *stuff_p, double halfstaff));
+static void curvepiecerect P((double x1, double y1, double x2, double y2,
+               double halfstaff));
+static void dotuplet P((struct MAINLL *start_p, int s, int place));
+static void onetuplet P((struct STAFF *staff_p, struct GRPSYL *start_p,
+               int place));
+static void domiscstuff P((struct MAINLL *start_p, int s, int place,
+               unsigned long do_which));
+static void dolyrics P((struct MAINLL *start_p, int s, int place));
+static void getvsize P((struct MAINLL *start_p, int s, int place, int v,
+               float *asc_p, float *des_p));
+static void setsylvert P((struct MAINLL *start_p, int s, int place, int v,
+               double baseline));
+static void dopedal P((struct MAINLL *start_p, int s));
+static void doendings P((struct MAINLL *start_p, int s));
+static void storeend P((struct MAINLL *start_p, struct MAINLL *end_p, int s));
+static void dorehears P((struct MAINLL *start_p, int s));
+static double stackit P((double west, double east, double height, double dist,
+               int place));
+static void inc_reclim P((void));
+\f
+/*
+ * Name:        relvert()
+ *
+ * Abstract:    Set all relative vertical coords not already set.
+ *
+ * Returns:     void
+ *
+ * Description: This function sets all remaining relative vertical coords.
+ *             It calls procstaff() once for each staff in each score to
+ *             do this.
+ */
+
+void
+relvert()
+
+{
+       struct MAINLL *mainll_p;        /* point along main linked list */
+       struct MAINLL *end_p;           /* point at end of a piece of MLL */
+       struct MAINLL *m2_p;            /* another pointer along MLL */
+       int s;                          /* staff number */
+       int gotbar;                     /* was a bar found in this chunk? */
+
+
+       debug(16, "relvert");
+       /*
+        * Find each section of the main linked list, delimited by FEEDs.
+        * For each such section, call procstaff() for each visible staff.
+        * Keep SSVs up to date so that we always know what staffs are visible.
+        */
+       initstructs();                  /* clean out old SSV info */
+
+       /* skip anything before first FEED first */
+       for (mainll_p = Mainllhc_p; mainll_p->str != S_FEED;
+                       mainll_p = mainll_p->next) {
+               if (mainll_p->str == S_SSV)
+                       asgnssv(mainll_p->u.ssv_p);
+       }
+
+       /*
+        * Initially allocate RECTCHUNK rectangles.  If we find we need more at
+        * some point, we'll realloc to get more.
+        */
+       MALLOC(RECTAB, rectab, RECTCHUNK);
+
+       for (;;) {
+               /*
+                * Find end of this chunk.  If it has no bars in it, this must
+                * either be the end of the MLL and there was a final feed
+                * after all the music data, or else this is a block.  Either
+                * way, there is no need to process this chunk.
+                */
+               gotbar = NO;
+               for (end_p = mainll_p->next; end_p != 0 &&
+                               end_p->str != S_FEED; end_p = end_p->next) {
+                       if (end_p->str == S_BAR)
+                               gotbar = YES;
+               }
+               if (gotbar == NO) {
+                       if (end_p == 0)
+                               break;          /* end of MLL, get out */
+
+                       /* update SSVs to beginning of next score */
+                       for (m2_p = mainll_p->next; m2_p != end_p;
+                                               m2_p = m2_p->next) {
+                               if (m2_p->str == S_SSV)
+                                       asgnssv(m2_p->u.ssv_p);
+                       }
+
+                       mainll_p = end_p;       /* block, skip by it */
+                       continue;
+               }
+
+               for (s = 1; s <= Score.staffs; s++) {
+                       if (svpath(s, VISIBLE)->visible == YES)
+                               procstaff(mainll_p, s);
+               }
+
+               /* update SSVs to beginning of next score */
+               for (m2_p = mainll_p->next; m2_p != end_p; m2_p = m2_p->next) {
+                       if (m2_p->str == S_SSV)
+                               asgnssv(m2_p->u.ssv_p);
+               }
+
+               if (end_p == 0)
+                       break;
+               mainll_p = end_p;
+       }
+
+       FREE(rectab);
+}
+\f
+/*
+ * Name:        procstaff()
+ *
+ * Abstract:    Set all relative vertical coords for a staff in one score.
+ *
+ * Returns:     void
+ *
+ * Description: This function sets all remaining relative vertical coords
+ *             for a given staff of a given score.
+ */
+
+static void
+procstaff(start_p, s)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* the staff we are to work on */
+
+{
+       struct MAINLL *mainll_p;/* point along main linked list */
+       char *order;            /* point at a subarray in markorder */
+       int stk;                /* stacking order number */
+       int mk;                 /* mark type */
+       unsigned long do_which; /* bit map of which mark types to do */
+       float north, south;     /* relative coords of staff */
+       float hb;               /* height of "between" objects */
+       int k;                  /* loop variable */
+
+
+       debug(32, "procstaff file=%s line=%d s=%d", start_p->inputfile,
+                       start_p->inputlineno, s);
+
+       /* set globals like Staffscale for use by the rest of the file */
+       set_staffscale(s);
+
+       /*
+        * Each structure in rectab[] represents something to be drawn that
+        * is associated with this staff, beginning with the staff itself.
+        * The coordinates define the rectangle that surrounds the object.
+        * The rectangles' edges are horizontal and vertical.  So if an object
+        * (like a slanted beam) doesn't fit well in such a recangle, multiple
+        * rectangles are used to enclose pieces of it, as in integration in
+        * calculus.
+        *
+        * The first part of this function does this for things that are above
+        * the staff.  The second part does it for things that are below it.
+        * The third part does it for items that are to be centered (if
+        * possible) between two staffs.  In the first two parts, rectangles
+        * are added to the table one at a time, working outwards from the
+        * staff.  In the third part, they are piled on an imaginary baseline.
+        *
+        * Some objects (like note groups) already have an assigned position.
+        * and their rectangles are simply added to the table, regardless of
+        * whether they overlap preexisting rectangles.
+        *
+        * Some objects (like phrase marks) get their positions figured out
+        * now, by some unique algorithm that doesn't make use of the table of
+        * rectangles, and then their rectangles are added to the table, again
+        * not worrying about overlap with preexisting rectangles.
+        *
+        * Some objects (like "stuff" to be printed) make use of the table to
+        * figure out where their rectangles should be placed.  They are placed
+        * as close to the staff (or baseline, for "between") as is possible
+        * without overlapping preexisting rectangles (or, in the case of
+        * chords, getting closer to the staff than allowed by "chorddist"; or
+        * in the case of rom, ital, bold, boldital, or rehearsal marks, closer
+        * than "dist"; or in the case of dynamics, closer than "dyndist").
+        * (And some things have their own "dist" to override these parameters,
+        * and the optional ability to force a distance regardless of overlap.)
+        * To see if the rectangle being added overlaps, first its east and
+        * west are tested.  All previous rectangles that are "out of its way"
+        * horizontally are marked not "relevant"; the others are marked
+        * "relevant".  As positions are tried, working outwards, positions
+        * that fail to avoid overlap are marked "tried".  (For chords, and
+        * rom/ital/bold/boldital, previous rectangles that are closer to the
+        * staff than the stuff is allowed to come anyhow are pre-marked as if
+        * "tried".)
+        */
+
+       /*
+        * Fill rectab for the objects above this staff.
+        */
+       reclim = 0;                     /* rectab is initially empty */
+
+       dostaff(s, PL_ABOVE);
+       dogroups(start_p, s, PL_ABOVE);
+       dobeamalt(start_p, s, PL_ABOVE);
+       docurve(start_p, s, PL_ABOVE, DO_OTHERS);
+       dotuplet(start_p, s, PL_ABOVE);
+       docurve(start_p, s, PL_ABOVE, DO_PHRASE);
+
+       /* get stacking order of the user-controllable mark types */
+       order = svpath(s, ABOVEORDER)->markorder[PL_ABOVE];
+
+       /* loop on each possible stacking order number */
+       for (stk = 1; stk <= NUM_MARK; stk++) {
+
+               /* set bit map for each mark type that has this order number */
+               do_which = 0;
+               for (mk = 0; mk < NUM_MARK; mk++) {
+                       if (order[mk] == stk) {
+                               do_which |= (1L << mk);
+                       }
+               }
+               /* if no marks, we're done; stacking orders are contiguous */
+               if (do_which == 0)
+                       break;
+
+               /*
+                * Some mark types must have a unique order number, not shared
+                * with any others.  For each of them, do a case statement to
+                * call their subroutine.  The other ones all share the same
+                * subroutine, so call it in the default to do the mark types
+                * listed in the bit map.
+                */
+               switch (do_which) {
+               case 1L << MK_LYRICS:
+                       dolyrics(start_p, s, PL_ABOVE);
+                       break;
+               case 1L << MK_ENDING:
+                       doendings(start_p, s);
+                       break;
+               case 1L << MK_REHEARSAL:
+                       dorehears(start_p, s);
+                       break;
+               case 1L << MK_PEDAL:
+                       break;  /* ignore for above */
+               default:
+                       domiscstuff(start_p, s, PL_ABOVE, do_which);
+                       break;
+               }
+       }
+
+       /*
+        * Find the northernmost rectangle, for setting the staff's north.
+        * But don't let north be so close that things sticking out might
+        * almost touch another staff.  Staffs smaller than a regular 5 line
+        * staff will still be given as much space.  In any case, we want at
+        * least 3 stepsizes of white space.
+        */
+       north = staffvertspace(s) / 2.0 + 3.0 * Stepsize;
+       for (k = 0; k < reclim; k++) {
+               if (rectab[k].n > north)
+                       north = rectab[k].n;
+       }
+
+       /*
+        * Fill rectab for the objects below this staff.
+        */
+       reclim = 0;                     /* rectab is initially empty */
+
+       dostaff(s, PL_BELOW);
+       dogroups(start_p, s, PL_BELOW);
+       dobeamalt(start_p, s, PL_BELOW);
+       docurve(start_p, s, PL_BELOW, DO_OTHERS);
+       dotuplet(start_p, s, PL_BELOW);
+       docurve(start_p, s, PL_BELOW, DO_PHRASE);
+
+       /* get stacking order of the user-controllable mark types */
+       order = svpath(s, BELOWORDER)->markorder[PL_BELOW];
+
+       /* loop on each possible stacking order number */
+       for (stk = 1; stk <= NUM_MARK; stk++) {
+
+               /* set bit map for each mark type that has this order number */
+               do_which = 0;
+               for (mk = 0; mk < NUM_MARK; mk++) {
+                       if (order[mk] == stk) {
+                               do_which |= (1L << mk);
+                       }
+               }
+               /* if no marks, we're done; stacking orders are contiguous */
+               if (do_which == 0)
+                       break;
+
+               /*
+                * Some mark types must have a unique order number, not shared
+                * with any others.  For each of them, do a case statement to
+                * call their subroutine.  The other ones all share the same
+                * subroutine, so call it in the default to do the mark types
+                * listed in the bit map.
+                */
+               switch (do_which) {
+               case 1L << MK_LYRICS:
+                       dolyrics(start_p, s, PL_BELOW);
+                       break;
+               case 1L << MK_ENDING:
+               case 1L << MK_REHEARSAL:
+                       break;  /* ignore for below */
+               case 1L << MK_PEDAL:
+                       dopedal(start_p, s);
+                       break;
+               default:
+                       domiscstuff(start_p, s, PL_BELOW, do_which);
+                       break;
+               }
+       }
+
+       /*
+        * Find the southernmost rectangle, for setting the staff's south.
+        * But don't let south be so close that things sticking out might
+        * almost touch another staff.  Staffs smaller than a regular 5 line
+        * staff will still be given as much space.  In any case, we want at
+        * least 3 stepsizes of white space.
+        */
+       south = -(staffvertspace(s) / 2.0 + 3.0 * Stepsize);
+       for (k = 0; k < reclim; k++) {
+               if (rectab[k].s < south)
+                       south = rectab[k].s;
+       }
+
+       /*
+        * Fill rectab for the objects between this staff and the one below.
+        */
+       reclim = 0;                     /* rectab is initially empty */
+
+       /* set up baseline, a rectangle of height 0 spanning the page */
+       rectab[reclim].w = 0;
+       rectab[reclim].e = PGWIDTH;
+       rectab[reclim].n = 0;
+       rectab[reclim].s = 0;
+       inc_reclim();
+
+
+       /* get stacking order of the user-controllable mark types */
+       order = svpath(s, BETWEENORDER)->markorder[PL_BETWEEN];
+
+       /* loop on each possible stacking order number */
+       for (stk = 1; stk <= NUM_MARK; stk++) {
+
+               /* set bit map for each mark type that has this order number */
+               do_which = 0;
+               for (mk = 0; mk < NUM_MARK; mk++) {
+                       if (order[mk] == stk) {
+                               do_which |= (1L << mk);
+                       }
+               }
+               /* if no marks, we're done; stacking orders are contiguous */
+               if (do_which == 0)
+                       break;
+
+               /*
+                * Some mark types must have a unique order number, not shared
+                * with any others.  For each of them, do a case statement to
+                * call their subroutine.  The other ones all share the same
+                * subroutine, so call it in the default to do the mark types
+                * listed in the bit map.
+                */
+               switch (do_which) {
+               case 1L << MK_LYRICS:
+                       dolyrics(start_p, s, PL_BETWEEN);
+                       break;
+               case 1L << MK_ENDING:
+               case 1L << MK_REHEARSAL:
+               case 1L << MK_PEDAL:
+                       break;  /* ignore for between */
+               default:
+                       domiscstuff(start_p, s, PL_BETWEEN, do_which);
+                       break;
+               }
+       }
+
+       /*
+        * Find the northernmost rectangle, for finding the height of these
+        * objects between.
+        */
+       hb = 0;
+       for (k = 0; k < reclim; k++) {
+               if (rectab[k].n > hb)
+                       hb = rectab[k].n;
+       }
+
+       /*
+        * Set the relative north and south of every STAFF structure for this
+        * staff number on this score.  (There's one per measure.)  While
+        * we're at it, set RX to 0, in case anyone cares.  Set the height of
+        * "between" objects in each STAFF, too.
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+
+               if (mainll_p->str == S_STAFF &&
+                               mainll_p->u.staff_p->staffno == s) {
+
+                       mainll_p->u.staff_p->c[RN] = north;
+                       mainll_p->u.staff_p->c[RX] = 0;
+                       mainll_p->u.staff_p->c[RS] = south;
+                       mainll_p->u.staff_p->heightbetween = hb;
+               }
+       }
+}
+\f
+/*
+ * Name:        dostaff()
+ *
+ * Abstract:    Set up the rectangle for the staff itself.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab the rectangle for the staff
+ *             itself.  The staff's relative vertical coords are not set now,
+ *             though, because they must later be set to include all the
+ *             objects associated with the staff.
+ */
+
+static void
+dostaff(s, place)
+
+int s;                         /* staff number */
+int place;                     /* above or below? */
+
+{
+       debug(32, "dostaff s=%d place=%d", s, place);
+       /*
+        * Use the full page width, even though the staff will not actually
+        * reach the edges, due to margins, etc.  This way nothing will ever
+        * fall beyond this base rectangle.  Put a STDPAD of padding around
+        * it vertically.
+        */
+       rectab[reclim].w = 0;
+       rectab[reclim].e = PGWIDTH;
+
+       if (place == PL_ABOVE) {
+               rectab[reclim].n = halfstaffhi(s) + Stdpad;
+               rectab[reclim].s = 0;
+       } else {        /* PL_BELOW */
+               rectab[reclim].n = 0;
+               rectab[reclim].s = -(halfstaffhi(s) + Stdpad);
+       }
+
+       inc_reclim();
+}
+\f
+/*
+ * Name:        dogroups()
+ *
+ * Abstract:    Set up rectangles & relative vert coords for staff's groups.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab the rectangles for each group on
+ *             this staff.  The groups' relative vertical coords were already
+ *             set in proclist() in beamstem.c.
+ */
+
+static void
+dogroups(start_p, s, place)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above or below? */
+
+{
+       struct MAINLL *mainll_p;        /* point along main linked list */
+       int v;                          /* voice number */
+
+
+       debug(32, "dogroups file=%s line=%d s=%d place=%d", start_p->inputfile,
+                       start_p->inputlineno, s, place);
+       /*
+        * Loop through this score's part of the MLL.
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+               /*
+                * Whenever we find a structure for this staff (another
+                * measure of this staff), call llgrps() for each voice.
+                * If some voice doesn't exist, llgrps() will get a
+                * null pointer and just return.
+                */
+               if (mainll_p->str == S_STAFF &&
+                               mainll_p->u.staff_p->staffno == s) {
+
+                       for (v = 0; v < MAXVOICES; v++)
+                               llgrps(mainll_p->u.staff_p,
+                                      mainll_p->u.staff_p->groups_p[v], place);
+               }
+       }
+}
+\f
+/*
+ * Name:        llgrps()
+ *
+ * Abstract:    Set up rectangles for note and rest groups.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts rectangles into rectab for all groups in
+ *             this measure of this voice, for groups consisting of notes or
+ *             rests.
+ */
+
+static void
+llgrps(staff_p, first_p, place)
+
+struct STAFF *staff_p;         /* point to the staff */
+struct GRPSYL *first_p;                /* point to first group */
+int place;                     /* above or below? */
+
+{
+       struct GRPSYL *gs_p;            /* point at a group */
+       struct NOTE *note_p;            /* point at a note */
+       double mx, my_offset, mheight, mwidth;  /* multirest number coords */
+       int n;                          /* loop through notelist */
+       float asc, des, wid;            /* ascent, descent, and width of acc */
+
+
+       /*
+        * For each group that is notes or a rest, put a rectangle into rectab.
+        * However, on tablature staffs, don't do this for rests, since they
+        * aren't printed there.
+        */
+       for (gs_p = first_p; gs_p != 0; gs_p = gs_p->next) {
+               if (gs_p->grpcont == GC_SPACE)
+                       continue;
+
+               if (gs_p->grpcont == GC_REST && is_tab_staff(gs_p->staffno))
+                       continue;
+
+               if (place == PL_ABOVE && (
+                       gs_p->basictime < -1 && svpath(staff_p->staffno,
+                                       PRINTMULTNUM)->printmultnum == YES ||
+                       is_mrpt(gs_p) && svpath(staff_p->staffno,
+                                       NUMBERMRPT)->numbermrpt == YES
+               )) {
+                       /*
+                        * Special case for multirests and measure repeats.
+                        * The rest or mrpt symbol itself is inside the staff,
+                        * so we don't have to worry about it.  But we need to
+                        * make a rectangle for the number, if the number is
+                        * to be printed.
+                        */
+                       (void)mrnum(staff_p, &mx, &my_offset, &mheight,
+                                       &mwidth);
+                       rectab[reclim].w = mx;
+                       rectab[reclim].e = mx + mwidth;
+                       rectab[reclim].n = my_offset + mheight;
+                       rectab[reclim].s = 0;
+
+                       inc_reclim();
+                       continue;
+               }
+
+               /* for "below", no rectangles are needed for multirests */
+               if (gs_p->basictime < -1)
+                       continue;
+
+               /*
+                * We have a normal note or rest group.  Make a rectangle for
+                * it, making sure it reaches the center staff line.
+                */
+               rectab[reclim].w = gs_p->c[AW];
+               rectab[reclim].e = gs_p->c[AE];
+
+               if (place == PL_ABOVE) {
+                       rectab[reclim].n = MAX(gs_p->c[RN], 0);
+                       rectab[reclim].s = 0;
+               } else {        /* PL_BELOW */
+                       rectab[reclim].n = 0;
+                       rectab[reclim].s = MIN(gs_p->c[RS], 0);
+               }
+
+               inc_reclim();
+
+               /* if a clef precedes this group, make a rectangle for it */
+               if (gs_p->clef != NOCLEF) {
+                       float north, south;     /* clef coords */
+
+                       rectab[reclim].e = gs_p->c[AW] - Staffscale * CLEFPAD;
+                       rectab[reclim].w = rectab[reclim].e - Staffscale *
+                                       clefwidth(gs_p->clef, YES);
+                       (void)clefvert(gs_p->clef, YES, &north, &south);
+                       rectab[reclim].n = north * Staffscale;
+                       rectab[reclim].s = south * Staffscale;
+
+                       inc_reclim();
+               }
+
+               /*
+                * An additional rectangle is needed for each note that has an
+                * accidental.  This is because although the east/west group
+                * boundaries include any accidentals, the north/south
+                * boundaries ingore them.  It needs to be this way because,
+                * for other reasons, like ties, we want the north/south group
+                * boundaries to consider only the note heads.  But for general
+                * stuff, the accidentals should also be considered.  The
+                * rectangles added below take care of this.
+                * Similarly, if the top or bottom note is on a line and has a
+                * dot in the space away from the group, it needs a rectangle.
+                */
+               if (gs_p->grpcont == GC_NOTES &&
+                                       ! is_tab_staff(gs_p->staffno)) {
+                       for (n = 0; n < gs_p->nnotes; n++) {
+                               note_p = &gs_p->notelist[n];
+
+                               if (gs_p->dots != 0 &&
+                               note_p->stepsup % 2 == 0 &&
+                               (n == 0 && note_p->ydotr > 0.0 ||
+                                n == gs_p->nnotes - 1 && note_p->ydotr < 0.0)){
+                                       float radius;   /* of a dot, + pad */
+                                       radius = Stdpad + Staffscale *
+                                               ascent(FONT_MUSIC, (note_p->
+                                               notesize == GS_NORMAL ?
+                                               DFLT_SIZE : SMALLSIZE), C_DOT);
+                                       rectab[reclim].n = gs_p->c[RY] +
+                                               note_p->ydotr + radius;
+                                       rectab[reclim].s = gs_p->c[RY] +
+                                               note_p->ydotr - radius;
+                                       rectab[reclim].w = gs_p->c[AX] +
+                                               gs_p->xdotr - radius;
+                                       rectab[reclim].e = gs_p->c[AX] +
+                                               gs_p->xdotr + radius +
+                                               (gs_p->dots - 1) * 2.0 *
+                                               (radius + Stdpad);
+                                       inc_reclim();
+                               }
+
+                               if (note_p->accidental == '\0')
+                                       continue;
+
+                               /* this note has an acc; create a rectangle */
+                               accdimen(note_p, &asc, &des, &wid);
+                               asc *= Staffscale;
+                               des *= Staffscale;
+                               wid *= Staffscale;
+
+                               rectab[reclim].w = gs_p->c[AX] + note_p->waccr;
+                               rectab[reclim].e = rectab[reclim].w + wid;
+                               rectab[reclim].n = note_p->c[RY] + asc;
+                               rectab[reclim].s = note_p->c[RY] - des;
+
+                               inc_reclim();
+                       }
+               }
+       }
+}
+\f
+/*
+ * Name:        dobeamalt()
+ *
+ * Abstract:    Set up rectangles for beams and alternation bars.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab rectangles for each beam or
+ *             alternation bar on this staff in this score, where the thing
+ *             is on the "place" side of the notes.
+ */
+
+static void
+dobeamalt(start_p, s, place)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above or below? */
+
+{
+       struct MAINLL *mainll_p;        /* point along main linked list */
+       struct GRPSYL *gs_p;            /* point along a GRPSYL linked list */
+       int v;                          /* voice number */
+
+
+       debug(32, "dobeamalt file=%s line=%d s=%d place=%d", start_p->inputfile,
+                       start_p->inputlineno, s, place);
+       /*
+        * Loop through this score's part of the MLL.
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+               /*
+                * Whenever we find a structure for this staff (another
+                * measure of this staff), loop through its voices.
+                */
+               if (mainll_p->str == S_STAFF &&
+                               mainll_p->u.staff_p->staffno == s) {
+
+                       for (v = 0; v < MAXVOICES; v++) {
+                               for (gs_p = mainll_p->u.staff_p->groups_p[v];
+                                               gs_p != 0; gs_p = gs_p->next) {
+                                       /*
+                                        * Whenever we find the first group of
+                                        * a nongrace beamed or alted set with
+                                        * the stem direction on the side we
+                                        * are dealing with, call onebeamalt()
+                                        * to put rectangle(s) in rectab.
+                                        * But not for cross staff beams.
+                                        * Grace groups are included in the
+                                        * following nongrace group's rectangle
+                                        * already.
+                                        */
+                                       if (gs_p->grpcont == GC_NOTES &&
+                                           gs_p->grpvalue == GV_NORMAL &&
+                                           gs_p->beamloc == STARTITEM &&
+                                           gs_p->beamto == CS_SAME) {
+
+                                               if (place == PL_ABOVE &&
+                                                   gs_p->stemdir == UP ||
+                                                   place == PL_BELOW &&
+                                                   gs_p->stemdir == DOWN)
+
+                                                       onebeamalt(gs_p);
+                                       }
+                               }
+                       }
+               }
+       }
+}
+\f
+/*
+ * Name:        onebeamalt()
+ *
+ * Abstract:    Set up rectangle(s) for one beam or alternation bar.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts zero or more rectangles in rectab for the
+ *             beam or alternation that starts at the given group.  The longer
+ *             and more slanted the beam/alternation is, the more rectangles
+ *             will be necessary to enclose it without wasting a lot of space.
+ *             If the beam/alt lies within the staff, there's no need to make
+ *             any rectangles.  All rectangles' inner edges are the center
+ *             staff line.
+ */
+
+static void
+onebeamalt(gs_p)
+
+struct GRPSYL *gs_p;           /* initially points to first group */
+
+{
+       float stemshift;        /* how far a stem is from its group's X */
+       float x1, y1;           /* coords of left end of beam/alt */
+       float x2, y2;           /* coords of right end of beam/alt */
+
+
+       /*
+        * Set coords of the ends of the beam/alt.  We are given the first
+        * group, but must search forward to the end to find the last group,
+        * being careful to ignore embedded grace groups.  We adjust the X
+        * coords (for groups that can have stems) because stems are offset
+        * from their group's X.  The Y coords can't always be based on the
+        * group boundaries, because there might be "with" lists on the
+        * abnormal (beam) side, and they don't affect the position of the beam.
+        */
+       x1 = gs_p->c[AX];
+       y1 = getstemendvert(gs_p);
+
+       while (gs_p != 0 && (gs_p->grpvalue == GV_ZERO ||
+                            gs_p->beamloc != ENDITEM))
+               gs_p = gs_p->next;
+       if (gs_p == 0)
+               pfatal("beam or alt group has no ENDITEM");
+
+       x2 = gs_p->c[AX];
+       y2 = getstemendvert(gs_p);
+
+       stemshift = getstemshift(gs_p);
+
+       if (gs_p->basictime >= 2) {
+               /* the groups have stems (if first one does, others must too)*/
+               if (gs_p->stemdir == UP) {
+                       x1 += stemshift;
+                       x2 += stemshift;
+               } else {
+                       x1 -= stemshift;
+                       x2 -= stemshift;
+               }
+       }
+
+       /* make zero or more rectangles for this beam/alt */
+       linerects(x1, y1, x2, y2, gs_p->stemdir, halfstaffhi(gs_p->staffno));
+}
+\f
+/*
+ * Name:        getstemendvert()
+ *
+ * Abstract:    Find the vertical coord of the end of a stem.
+ *
+ * Returns:     void
+ *
+ * Description: This function is given a GRPSYL of a group that has either a
+ *             real, visible stem, or an invisible one (alt).  If finds
+ *             the relative vertical coordinate of the end of the stems
+ *             farthest from the note head(s).
+ */
+
+static double
+getstemendvert(gs_p)
+
+struct GRPSYL *gs_p;   /* the group in question */
+
+{
+       double y;       /* the answer */
+
+
+       if (gs_p->nwith == 0 || gs_p->normwith == YES) {
+               /*
+                * Either there is no "with" list, or it's on the notes' end
+                * of the stem.  So we can use the group boundary.
+                */
+               y = gs_p->stemdir == UP ? gs_p->c[RN] : gs_p->c[RS];
+       } else {
+               /*
+                * There is a "with" list at this end of the stem.  Find where
+                * the end of the stem is by applying the stem's length to the
+                * farthest note on the opposite side.
+                */
+               if (gs_p->stemdir == UP)
+                       y = gs_p->notelist[ gs_p->nnotes - 1 ].c[RY] +
+                                       gs_p->stemlen;
+               else
+                       y = gs_p->notelist[ 0 ].c[RY] - gs_p->stemlen;
+       }
+
+       /* counteract the stem shortening that was done in finalstemadjust() */
+       if (gs_p->beamloc != NOITEM) {
+               if (gs_p->stemdir == UP) {
+                       y += (W_WIDE * Stdpad / 2.0);
+               } else {
+                       y -= (W_WIDE * Stdpad / 2.0);
+               }
+       }
+
+       return (y);
+}
+\f
+/*
+ * Name:        linerects()
+ *
+ * Abstract:    Set up rectangle(s) to contain a (possibly) slanted line.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts zero or more rectangles in rectab to contain
+ *             a (possibly) slanted line.  The longer and more slanted the
+ *             line is, the more rectangles will be necessary to enclose it
+ *             without wasting a lot of space.  If the line lies within the
+ *             staff, there's no need to make any rectangles.  All rectangles'
+ *             inner edges are the center staff line.
+ */
+
+static void
+linerects(x1, y1, x2, y2, side, halfstaff)
+
+double x1, y1;         /* coords of left end of line */
+double x2, y2;         /* coords of right end of line */
+int side;              /* side to favor, UP or DOWN */
+double halfstaff;      /* half the staff height */
+
+{
+       float slope, yintercept;/* of a line a STDPAD beyond beam/alt */
+       float deltax;           /* width of one rectangle */
+       float leftx, rightx;    /* X coord of sides of a rectangle */
+
+
+       /* if line is within staff, no need for any rectangles */
+       if (fabs(y1) < halfstaff && fabs(y2) < halfstaff)
+               return;
+
+       /*
+        * If this beam/alt is level, make one big rectangle, and get out.
+        */
+       if (y1 == y2) {
+               rectab[reclim].w = x1;
+               rectab[reclim].e = x2;
+               if (side == UP) {
+                       rectab[reclim].n = y1;
+                       rectab[reclim].s = 0;
+               } else {
+                       rectab[reclim].n = 0;
+                       rectab[reclim].s = y1;
+               }
+               inc_reclim();
+               return;
+       }
+
+       /*
+        * We may need multiple rectangles.  Make them narrow enough so that
+        * the change in Y across the width of one is one STEPSIZE.  The
+        * rightmost one will probably be narrower, using whatever room
+        * remains.  The equation of our line is  y = slope * x + yintercept.
+        */
+       slope = (y1 - y2) / (x1 - x2);
+       yintercept = y1 - slope * x1;
+       deltax = Stepsize / fabs(slope);
+
+       for (leftx = x1; leftx < x2; leftx += deltax) {
+               rightx = MIN(x2, leftx + deltax);
+               rectab[reclim].w = leftx;
+               rectab[reclim].e = rightx;
+               if (side == UP) {
+                       rectab[reclim].n = slope * (slope > 0 ? rightx : leftx)
+                                       + yintercept;
+                       rectab[reclim].s = 0;
+               } else {
+                       rectab[reclim].n = 0;
+                       rectab[reclim].s = slope * (slope > 0 ? leftx : rightx)
+                                       + yintercept;
+               }
+               inc_reclim();
+       }
+}
+\f
+/*
+ * Name:        docurve()
+ *
+ * Abstract:    Get point list and set up rectangles for tie/slur/bend/phrase.
+ *
+ * Returns:     void
+ *
+ * Description: This function goes through all ties, slurs, bends, phrases for
+ *             staff.  The first time it is called for a staff (which is for
+ *             place "above") it calls a function to set up the curve list.
+ *             Whichever time it is called, it calls a function to put
+ *             rectangles in rectab.
+ */
+
+static void
+docurve(start_p, s, place, do_which)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above or below? */
+int do_which;                  /* which stuff types are to be handled */
+
+{
+       struct MAINLL *mainll_p;        /* loop through main linked list */
+       struct STUFF *stuff_p;          /* point along a STUFF list */
+       float halfstaff;                /* half the staff height */
+
+
+       debug(32, "docurve file=%s line=%d s=%d place=%d do_which=%d",
+               start_p->inputfile, start_p->inputlineno, s, place, do_which);
+       halfstaff = halfstaffhi(s);
+
+       /*
+        * Loop through this score's part of the MLL, looking for matching
+        * staffs.
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+
+               if (mainll_p->str != S_STAFF ||
+                               mainll_p->u.staff_p->staffno != s)
+                       continue;
+
+               /* loop through each stuff of the indicated type */
+               for (stuff_p = mainll_p->u.staff_p->stuff_p;
+                               stuff_p != 0; stuff_p = stuff_p->next){
+
+                       switch (stuff_p->stuff_type) {
+                       case ST_TIESLUR:
+                       case ST_TABSLUR:
+                       case ST_BEND:
+                       case ST_PHRASE:
+                               break;          /* docurve works on these */
+                       default:
+                               continue;       /* for some other function */
+                       }
+
+                       /*
+                        * If we are to do phrases and this is not a phrase, or
+                        * vice versa, skip this.
+                        */
+                       if ((do_which == DO_PHRASE) !=
+                           (stuff_p->stuff_type == ST_PHRASE))
+                               continue;
+
+                       /*
+                        * When we're in here the first time (for PL_ABOVE),
+                        * call a function to set up the curve list.  For
+                        * everything but ST_PHRASE it also sets "place".
+                        */
+                       if (place == PL_ABOVE) {
+                               switch (stuff_p->stuff_type) {
+                               case ST_TIESLUR:
+                                       /* don't call tieslur_points now if the
+                                        * positions of the tie/slur's endpoints
+                                        * would change later due to CSS */
+                                       if (css_affects_tieslurbend(stuff_p,
+                                                       mainll_p) == YES) {
+                                               break;
+                                       }
+                                       tieslur_points(mainll_p, stuff_p);
+                                       break;
+                               case ST_TABSLUR:
+                                       tabslur_points(mainll_p, stuff_p);
+                                       break;
+                               case ST_BEND:
+                                       /* don't call bend_points now if the
+                                        * positions of the bend's endpoints
+                                        * would change later due to CSS */
+                                       if (css_affects_tieslurbend(stuff_p,
+                                                       mainll_p) == YES) {
+                                               break;
+                                       }
+                                       bend_points(mainll_p, stuff_p);
+                                       break;
+                               case ST_PHRASE:
+                                       /* don't call phrase_points now if the
+                                        * positions of the phrase's endpoints
+                                        * would change later due to CSS */
+                                       if (css_affects_phrase(stuff_p,
+                                                       mainll_p) == YES) {
+                                               break;
+                                       }
+                                       phrase_points(mainll_p, stuff_p);
+                                       break;
+                               }
+                       }
+
+                       /*
+                        * Make rectangles no matter what side of the staff the
+                        * curve is supposed to be on, because, depending on
+                        * how high or low the notes are, rectangles may be
+                        * needed even on the opposite side you'd expect.
+                        */
+                       if (stuff_p->crvlist_p != 0) {
+                               curverect(s, stuff_p, halfstaff);
+                       }
+               }
+       }
+}
+\f
+/*
+ * Name:        curverect()
+ *
+ * Abstract:    Put rectangles in rectab for a tie, slur, bend, or phrase.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts rectangles in rectab for a tie, slur, bend,
+ *             or phrase.  Each segment of the curve gets one or more
+ *             rectangles, depending on how long and how slanted it is.  To do
+ *             this, we call curvepiecerect().
+ */
+
+static void
+curverect(s, stuff_p, halfstaff)
+
+int s;                         /* staff number */
+struct STUFF *stuff_p;         /* the curve's STUFF */
+double halfstaff;              /* half the staff height */
+
+{
+       struct CRVLIST *point_p; /* point at a phrase point */
+       float x1, y1;           /* coords of left end of a segment */
+       float x2, y2;           /* coords of right end of a segment */
+       float midx, midy;       /* middle of one segment of a curve */
+
+
+       /*
+        * Loop through the curve list.  For each pair of neighboring points,
+        * there is a segment of the curve.  For items that are actually
+        * straight line segments, call curvepiecerect() once.  But for actual
+        * curves, find the midpoint, and call curvepiecerect() for each half.
+        * This way we more closely approximate the real curve.
+        */
+       for (point_p = stuff_p->crvlist_p; point_p->next != 0;
+                       point_p = point_p->next) {
+
+               x1 = point_p->x;
+               y1 = point_p->y;
+               x2 = point_p->next->x;
+               y2 = point_p->next->y;
+
+               if (stuff_p->stuff_type == ST_BEND ||
+                   stuff_p->stuff_type == ST_TABSLUR) {
+                       /* bend, or slur on tab or tabnote */
+                       curvepiecerect(x1, y1, x2, y2, halfstaff);
+               } else {
+                       /* a real curve */
+                       midx = (x1 + x2) / 2.0;
+                       midy = curve_y_at_x(stuff_p->crvlist_p, midx);
+                       curvepiecerect(x1, y1, midx, midy, halfstaff);
+                       curvepiecerect(midx, midy, x2, y2, halfstaff);
+               }
+       }
+}
+\f
+/*
+ * Name:        curvepiecerect()
+ *
+ * Abstract:    Put rects in rectab for a piece of a tie, slur, bend, or phrase.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts rectangles in rectab for one piece of a
+ *             curve.  The piece gets one or more rectangles, depending on how
+ *             long and how slanted it is.
+ */
+
+static void
+curvepiecerect(x1, y1, x2, y2, halfstaff)
+
+double x1, y1;                 /* coords of left end of the piece */
+double x2, y2;                 /* coords of right end of the piece */
+double halfstaff;              /* half the staff height */
+
+{
+       float slope, yintercept;/* of a line a segment */
+       float deltax;           /* width of one rectangle */
+       float leftx, rightx;    /* X coord of sides of a rectangle */
+
+
+       /* if whole piece is within the staff, no rectangles are needed */
+       if (fabs(y1) < halfstaff && fabs(y2) < halfstaff)
+               return;
+
+       /*
+        * If this piece is level, make 1 big rectangle, and continue.
+        */
+       if (y1 == y2) {
+               rectab[reclim].w = x1;
+               rectab[reclim].e = x2;
+               rectab[reclim].n = MAX(y1 + 2 * Stdpad, 0.0);
+               rectab[reclim].s = MIN(y1 - 2 * Stdpad, 0.0);
+               inc_reclim();
+               return;
+       }
+
+       /*
+        * We may need multiple rectangles.  Make them narrow enough so that
+        * the change in Y across the width of one is one Stepsize.  The
+        * rightmost one will probably be narrower, using whatever room
+        * remains.  The equation of our line is
+        *      y = slope * x + yintercept
+        * Initially each rectangle only includes its segment (plus padding),
+        * but then we extend it to reach the center line of the staff.
+        */
+       slope = (y1 - y2) / (x1 - x2);
+       yintercept = y1 - slope * x1;
+       deltax = Stepsize / fabs(slope);
+
+       for (leftx = x1; leftx < x2; leftx += deltax) {
+               rightx = MIN(x2, leftx + deltax);
+
+               rectab[reclim].w = leftx;
+               rectab[reclim].e = rightx;
+
+               /*
+                * For north and south boundaries, use the side of the rect
+                * that sticks out more, to err on the side of making the rect
+                * big enough.  Also add in padding, to 1) allow for the fact
+                * that the real curve probably bulges out beyond our segment
+                * approximation, and 2) because we don't want anything
+                * actually touching the curve.
+                */
+               rectab[reclim].n = slope * (slope > 0.0 ?  rightx : leftx) +
+                               yintercept + 2.0 * Stdpad;
+               rectab[reclim].s = slope * (slope < 0.0 ?  rightx : leftx) +
+                               yintercept - 2.0 * Stdpad;
+
+               /* rectangle must reach the center line of the staff */
+               if (rectab[reclim].n < 0.0)
+                       rectab[reclim].n = 0.0;
+               if (rectab[reclim].s > 0.0)
+                       rectab[reclim].s = 0.0;
+
+               inc_reclim();
+       }
+}
+\f
+/*
+ * Name:        dotuplet()
+ *
+ * Abstract:    Set up rectangles for tuplet brackets.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab rectangles for each tuplet
+ *             bracket on this staff in this score, where the thing is on
+ *             the "place" side of the notes.
+ */
+
+
+static void
+dotuplet(start_p, s, place)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above or below? */
+
+{
+       struct MAINLL *mainll_p;        /* point along main linked list */
+       struct GRPSYL *gs_p;            /* point along a GRPSYL linked list */
+       int v;                          /* voice number */
+
+
+       debug(32, "dotuplet file=%s line=%d s=%d place=%d", start_p->inputfile,
+                       start_p->inputlineno, s, place);
+
+       /* tuplet brackets are never printed on tablature staffs */
+       if (is_tab_staff(s))
+               return;
+
+       /*
+        * Loop through this score's part of the MLL.
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+               /*
+                * Whenever we find a structure for this staff (another
+                * measure of this staff), loop through its voices.
+                */
+               if (mainll_p->str == S_STAFF &&
+                               mainll_p->u.staff_p->staffno == s) {
+
+                       for (v = 0; v < MAXVOICES; v++) {
+                               for (gs_p = mainll_p->u.staff_p->groups_p[v];
+                                               gs_p != 0; gs_p = gs_p->next) {
+                                       /*
+                                        * Whenever we find the first group of
+                                        * a tuplet with a bracket on the
+                                        * "place" side of the group, call
+                                        * onetuplet() to put rectangle(s) in
+                                        * rectab.
+                                        */
+                                       if ((gs_p->tuploc == STARTITEM ||
+                                            gs_p->tuploc == LONEITEM) &&
+                                           gs_p->printtup != PT_NEITHER) {
+
+                                               if (tupdir(gs_p, mainll_p->u.
+                                                       staff_p) == place)
+
+                                                       onetuplet(mainll_p->u.
+                                                       staff_p, gs_p, place);
+                                       }
+                               }
+                       }
+               }
+       }
+}
+\f
+/*
+ * Name:        onetuplet()
+ *
+ * Abstract:    Set up rectangle(s) for one tuplet bracket or number.
+ *
+ * Returns:     void
+ *
+ * Description:        If this tuplet is not going to be given a bracket (like because
+ *             its notes are already beamed), this function just makes one
+ *             rectangle, for the number.  Otherwise, this function puts zero
+ *             or more rectangles in rectab for the tuplet that starts at the
+ *             given group.  The longer and more slanted the tuplet bracket
+ *             is, the more rectangles will be necessary to enclose it without
+ *             wasting a lot of space.  All rectangles' inner edges are the
+ *             center staff line.
+ */
+
+static void
+onetuplet(staff_p, start_p, place)
+
+struct STAFF *staff_p;         /* point to the staff we're on */
+struct GRPSYL *start_p;                /* points to first group in tuplet */
+int place;                     /* above or below? */
+
+{
+       struct GRPSYL *gs_p;    /* point to a group in tuplet */
+       float stemshift;        /* how far a stem is from its group's X */
+       float x1, y1;           /* coords of left end of beam/alt */
+       float x2, y2;           /* coords of right end of beam/alt */
+       float numeast, numwest; /* horizontal coords of the tuplet number */
+       float height;           /* height of the tuplet number */
+
+
+       /*
+        * Set coords of the ends of the tuplet.  We are given the first
+        * group, but must search forward to the end to find the last group,
+        * being careful to ignore embedded grace groups.  We adjust the X
+        * coords because brackets reach beyond their group's X.
+        */
+       x1 = start_p->c[AX];
+       y1 = (place == PL_ABOVE ? start_p->c[RN] : start_p->c[RS])
+                       + start_p->tupextend;
+
+       for (gs_p = start_p; gs_p != 0 && (gs_p->grpvalue == GV_ZERO ||
+                       gs_p->tuploc != ENDITEM && gs_p->tuploc != LONEITEM);
+                       gs_p = gs_p->next)
+               ;
+       if (gs_p == 0)
+               pfatal("tuplet has no ENDITEM");
+
+       x2 = gs_p->c[AX];
+       y2 = (place == PL_ABOVE ? gs_p->c[RN] : gs_p->c[RS]) + gs_p->tupextend;
+
+       /*
+        * If there is not going to be a bracket, create one rectangle for the
+        * tuplet number, and return.
+        */
+       if (tupgetsbrack(start_p) == NO) {
+               (void)tupnumsize(start_p, &numwest, &numeast, &height, staff_p);
+               rectab[reclim].n = (y1 + y2) / 2 + height / 2;
+               rectab[reclim].s = (y1 + y2) / 2 - height / 2;
+               rectab[reclim].w = numwest;
+               rectab[reclim].e = numeast;
+
+               inc_reclim();
+               return;
+       }
+
+       /* there is going to be a bracket; extend x coords to reach to end */
+       stemshift = getstemshift(gs_p);
+
+       x1 -= stemshift;
+       x2 += stemshift;
+
+       /* make zero or more rectangles for this bracket */
+       linerects(x1, y1, x2, y2, place == PL_ABOVE ? UP : DOWN,
+                       halfstaffhi(gs_p->staffno));
+}
+\f
+/*
+ * Name:        domiscstuff()
+ *
+ * Abstract:    Set up rectangles and vert coords for miscellaneous STUFF.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab a rectangle for each STUFF
+ *             structure in the "place" relationship to the given staff on
+ *             this score, except for stuff types that have special,
+ *             dedicated functions for their type.  It also sets their
+ *             relative vertical coordinates.
+ */
+
+static void
+domiscstuff(start_p, s, place, do_which)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above, below, or between? */
+unsigned long do_which;                /* which stuff types are to be handled */
+
+{
+       struct MAINLL *mainll_p;        /* loop through main linked list */
+       struct STUFF *stuff_p;          /* point along a STUFF list */
+       float high;                     /* height of a rectangle */
+       float len;                      /* length of a cresc/descresc */
+       float lowpart;                  /* dist between stuff's Y and S */
+       float dist;                     /* how close chord can get to staff */
+       int stype;                      /* stuff type */
+
+
+       debug(32, "domiscstuff file=%s line=%d s=%d place=%d do_which=%ld",
+               start_p->inputfile, start_p->inputlineno, s, place, do_which);
+       /*
+        * Loop through this score's part of the MLL.  Whenever we find a
+        * structure for this staff (another measure), loop through its
+        * STUFF list, dealing with each STUFF that is above, below, or
+        * between, as specified by "place".
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+
+               if (mainll_p->str != S_STAFF ||
+                               mainll_p->u.staff_p->staffno != s) {
+                       continue;
+               }
+
+               for (stuff_p = mainll_p->u.staff_p->stuff_p;
+                               stuff_p != 0; stuff_p = stuff_p->next) {
+
+                       if (stuff_p->place != place) {
+                               continue;
+                       }
+
+                       stype = stuff_p->stuff_type;
+
+                       /* if wrong type for this pass, exit */
+                       if (stype == ST_MUSSYM) {
+                               if ((do_which & (1L << MK_MUSSYM)) == 0)
+                                       continue;
+                       } else if (stype == ST_OCTAVE) {
+                               if ((do_which & (1L << MK_OCTAVE)) == 0)
+                                       continue;
+                       } else if (stype != ST_PHRASE &&
+                                       stuff_p->modifier == TM_DYN) {
+                               if ((do_which & (1L << MK_DYN)) == 0)
+                                       continue;
+                       } else if (stype != ST_PHRASE &&
+                                       IS_CHORDLIKE(stuff_p->modifier)) {
+                               if ((do_which & (1L << MK_CHORD)) == 0)
+                                       continue;
+                       } else if (IS_TEXT(stype)) {
+                               if ((do_which & (1L << MK_OTHERTEXT)) == 0)
+                                       continue;
+                       }
+
+                       /*
+                        * We found a "stuff" that needs to be positioned.
+                        * First find its total height, and the height of the
+                        * part of it below its Y coord.
+                        */
+                       /* avoid 'used before set' warning */
+                       high = lowpart = 0.0;
+
+                       /* handle various types differently */
+                       switch (stype) {
+                       case ST_PEDAL:
+                       case ST_PHRASE:
+                       case ST_TIESLUR:
+                       case ST_TABSLUR:
+                       case ST_BEND:
+                       case ST_MIDI:
+                               /* don't handle these types here; */
+                               /* they have their own subroutines */
+                               continue;
+
+                       case ST_OCTAVE:
+                       case ST_ROM:
+                       case ST_BOLD:
+                       case ST_ITAL:
+                       case ST_BOLDITAL:
+                       case ST_MUSSYM:
+                               /* high is string's height */
+                               high = strheight( stuff_p->string);
+                               lowpart = strdescent( stuff_p->string);
+
+                               /*
+                                * If a chord grid is to be printed under the
+                                * string, the Y and N of the stuff remain
+                                * unchanged, but its S is lowered by the total
+                                * height of the grid.  So add its height to
+                                * both "high" and "lowpart".
+                                */
+                               if (stuff_p->modifier == TM_CHORD && svpath(s,
+                               GRIDSWHEREUSED)->gridswhereused == YES) {
+                                       struct GRID *grid_p;
+                                       float gnorth, gsouth;
+
+                                       grid_p = findgrid(stuff_p->string);
+                                       /* if none, skip this; stuff.c warned*/
+                                       if (grid_p == 0)
+                                               break;
+
+                                       gridsize(grid_p, stuff_p->all ? 0 :
+                                               mainll_p->u.staff_p->staffno,
+                                               &gnorth, &gsouth,
+                                               (float *)0, (float *)0);
+
+                                       high += gnorth - gsouth;
+                                       lowpart += gnorth - gsouth;
+                               }
+                               break;
+
+                       case ST_CRESC:
+                       case ST_DECRESC:
+                               /* height depends on length */
+                               len = stuff_p->c[AE] - stuff_p->c[AW];
+
+                               if (len < 0.5)
+                                       high = 2.00 * STEPSIZE + 2 * STDPAD;
+                               else if (len < 2.0)
+                                       high = 2.67 * STEPSIZE + 2 * STDPAD;
+                               else
+                                       high = 3.33 * STEPSIZE + 2 * STDPAD;
+
+                               if (stuff_p->all)
+                                       high *= Score.staffscale;
+                               else
+                                       high *= Staffscale;
+
+                               lowpart = high / 2;
+
+                               break;
+
+                       default:
+                               pfatal("unknown stuff type (%d)", stype);
+                       }
+
+                       /*
+                        * Now find "dist", the minimum distance it should be
+                        * put from the staff.
+                        */
+                       if (stuff_p->dist_usage == SD_NONE) {
+                               /*
+                                * The user didn't specify the dist, so we get
+                                * it from the appropriate parameter or hard-
+                                * coded value, as the case may be.  For
+                                * parameters, if the stuff belongs to the
+                                * score as a whole ("all"), use the Score
+                                 * value instead of svpath.
+                                */
+                               /* if "dyn", fake to use same logic as cresc */
+                               if (stuff_p->modifier == TM_DYN)
+                                       stype = ST_CRESC;
+                               switch (stype) {
+                               case ST_ROM:
+                               case ST_BOLD:
+                               case ST_ITAL:
+                               case ST_BOLDITAL:
+                                       if (stuff_p->all) {
+                                               if (IS_CHORDLIKE(
+                                               stuff_p->modifier)) {
+                                                       dist =
+                                                       halfstaffhi(s) +
+                                                       STEPSIZE *
+                                                       Score.staffscale *
+                                                       Score.chorddist;
+                                               } else {
+                                                       dist =
+                                                       halfstaffhi(s) +
+                                                       STEPSIZE *
+                                                       Score.staffscale *
+                                                       Score.dist;
+                                               }
+                                       } else {
+                                               if (IS_CHORDLIKE(
+                                               stuff_p->modifier)) {
+                                                       dist =
+                                                       halfstaffhi(s) +
+                                                       Stepsize *
+                                                       svpath(s, CHORDDIST)->
+                                                       chorddist;
+                                               } else {
+                                                       dist =
+                                                       halfstaffhi(s) +
+                                                       Stepsize *
+                                                       svpath(s, DIST)->dist;
+                                               }
+                                       }
+                                       break;
+                               case ST_CRESC:
+                               case ST_DECRESC:
+                                       if (stuff_p->all) {
+                                               dist = halfstaffhi(s) +
+                                               STEPSIZE * Score.staffscale *
+                                               Score.dyndist;
+                                       } else {
+                                               dist = halfstaffhi(s) +
+                                               Stepsize * svpath(s,
+                                               DYNDIST)->dyndist;
+                                       }
+                                       break;
+                               default:
+                                       dist = 0;
+                                       break;
+                               }
+                       } else {
+                               /* the user specified the dist, so use that */
+                               if (stuff_p->all) {
+                                       dist = halfstaffhi(s) +
+                                               STEPSIZE * stuff_p->dist;
+                               } else {
+                                       dist = halfstaffhi(s) +
+                                               Stepsize * stuff_p->dist;
+                               }
+                       }
+
+                       if (stuff_p->dist_usage == SD_FORCE) {
+                               /*
+                                * The user is forcing this dist, so don't
+                                * stack; just put it there.  Note: the user
+                                * cannot specify "dist" for "between" items.
+                                */
+                               if (stuff_p->place == PL_ABOVE) {
+                                       rectab[reclim].n = dist + high;
+                                       rectab[reclim].s = dist;
+                                       stuff_p->c[RS] = dist;
+                               } else {        /* PL_BELOW */
+                                       rectab[reclim].n = -dist;
+                                       rectab[reclim].s = -dist - high;
+                                       stuff_p->c[RS] = -dist - high;
+                               }
+                               rectab[reclim].e = stuff_p->c[AE];
+                               rectab[reclim].w = stuff_p->c[AW];
+                               inc_reclim();
+                       } else {
+                               /*
+                                * Stack the usual way.  For the case of
+                                * "between", stackit() will ignore "dist".
+                                */
+                               stuff_p->c[RS] = stackit(stuff_p->c[AW],
+                                       stuff_p->c[AE], high, dist, place);
+                       }
+
+                       stuff_p->c[RN] = stuff_p->c[RS] + high;
+                       stuff_p->c[RY] = stuff_p->c[RS] + lowpart;
+               }
+       }
+}
+\f
+/*
+ * Name:        dolyrics()
+ *
+ * Abstract:    Set up rectangles and vert coords for lyrics.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab a rectangle for each verse in
+ *             the "place" relationship to the given staff on this score.
+ */
+
+static void
+dolyrics(start_p, s, place)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above, below, or between? */
+
+{
+       int *versenums;         /* malloc'ed array of verse numbers in score */
+       struct MAINLL *mainll_p;/* point along main linked list */
+       struct STAFF *staff_p;  /* point at a staff structure */
+       struct GRPSYL *gs_p;    /* point at a syllable */
+       float protrude;         /* farthest protrusion of rectangle */
+       int vfound;             /* number of verse numbers found in score */
+       int v;                  /* verse number */
+       int begin, end, delta;  /* for looping over verses in proper order */
+       float dist;             /* how close lyrics can get to staff */
+       float farwest, fareast; /* farthest east and west of any syllable */
+       float baseline;         /* baseline of a verse of syllables */
+       float maxasc, maxdes;   /* max ascent & descent of syllables */
+       int gotverse0;          /* is there a verse 0 (centered verse)? */
+       int gototherverse;      /* is there a normal verse (not 0)? */
+       int n, k, j;            /* loop variables */
+
+
+       debug(32, "dolyrics file=%s line=%d s=%d place=%d", start_p->inputfile,
+                       start_p->inputlineno, s, place);
+       /* if there are no lyrics in this song, get out now */
+       if (Maxverses == 0)
+               return;
+
+       /*
+        * Allocate an array containing room for all the verse numbers used in
+        * this score.  Maxverses is the number of verse numbers used in the
+        * whole user input, so this will certainly be enough.
+        */
+       MALLOCA(int, versenums, Maxverses);
+
+       /*
+        * Loop through this score's part of the MLL, noting whether verse 0
+        * (the centered verse) and/or other verses exist on the "place" side
+        * of the staff.  We have to find this out before actually processing
+        * the verses, because verse 0 is to be treated as a normal verse if
+        * and only if there are no other verses.
+        */
+       gotverse0 = NO;
+       gototherverse = NO;
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+               /*
+                * Whenever we find a structure for this staff (another
+                * measure of this staff), loop through its verse headcells.
+                */
+               if (mainll_p->str == S_STAFF &&
+                               mainll_p->u.staff_p->staffno == s) {
+                       staff_p = mainll_p->u.staff_p;
+                       for (n = 0; n < staff_p->nsyllists; n++) {
+                               if (staff_p->sylplace[n] == place) {
+                                       if (staff_p->syls_p[n]->vno == 0)
+                                               gotverse0 = YES;
+                                       else
+                                               gototherverse = YES;
+                               }
+                       }
+               }
+       }
+
+       /* if no verses, get out now */
+       if (gotverse0 == NO && gototherverse == 0) {
+               FREE(versenums);
+               return;
+       }
+
+       /*
+        * Loop through this score's part of the MLL, recording all the verse
+        * numbers that occur on the "place" side of the staff in versenums[].
+        * Verse 0 may or may not be included, depending on the above results.
+        * Also set farwest and fareast.
+        */
+       vfound = 0;                     /* no verses have been found yet */
+       farwest = PGWIDTH;              /* init it all the way east */
+       fareast = 0;                    /* init it all the way west */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+               /*
+                * Whenever we find a structure for this staff (another
+                * measure of this staff), loop through its verse headcells.
+                */
+               if (mainll_p->str == S_STAFF &&
+                               mainll_p->u.staff_p->staffno == s) {
+
+                       staff_p = mainll_p->u.staff_p;
+
+                       for (n = 0; n < staff_p->nsyllists; n++) {
+
+                               if (staff_p->sylplace[n] == place) {
+                                       /*
+                                        * We found a verse number.  Search the
+                                        * the array to see if it's already
+                                        * been found.  If not, insert it into
+                                        * versenums[] in the right place, so
+                                        * that they'll end up being in order
+                                        * (actually, reverse order).
+                                        */
+                                       v = staff_p->syls_p[n]->vno;
+                                       /* ignore verse 0 if others exist */
+                                       if (v == 0 && gototherverse == YES)
+                                               continue;
+                                       for (k = 0; k < vfound &&
+                                                   v < versenums[k]; k++) {
+                                               ;
+                                       }
+                                       if (k == vfound || v > versenums[k]) {
+                                               for (j = vfound; j > k; j--) {
+                                                       versenums[j] =
+                                                       versenums[j-1];
+                                               }
+                                               versenums[k] = v;
+                                               vfound++;  /* found one more */
+                                       }
+
+                                       /*
+                                        * If any syl sticks out farther than
+                                        * any previous one, extend farwest or
+                                        * fareast.
+                                        */
+                                       for (gs_p = staff_p->syls_p[n];
+                                            gs_p != 0; gs_p = gs_p->next) {
+
+                                               if (gs_p->c[AW] < farwest)
+                                                       farwest = gs_p->c[AW];
+                                               if (gs_p->c[AE] > fareast)
+                                                       fareast = gs_p->c[AE];
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /*
+        * Enclose all the syllables of all the verses (of this place) in one
+        * big rectangle.  Pad on west and east by 8 step sizes.  Pretend the
+        * rectangle is PGHEIGHT high.  We don't actually know yet how high
+        * it is, and this will prevent it from getting between the staff and
+        * anything else.  Later in this function we will correct the entry
+        * that stackit put in rectab, to reflect the true height.  For above
+        * and below cases, don't let it get any closer than 2 step sizes to
+        * the staff.  The half-height of a one-line staff is regarded as 1
+        * instead of the true 0, to give a little breathing room.
+        */
+       if (place == PL_BETWEEN)
+               dist = 0;
+       else
+               dist = halfstaffhi(s) + 2.0 * Stepsize;
+
+       (void)stackit(farwest - 8 * STEPSIZE, fareast + 8 * STEPSIZE, PGHEIGHT,
+                       dist, place);
+
+       /*
+        * Find the greatest protrusion of any currently existing rectangle
+        * that horizontally is within the span of our new rectangle.  That's
+        * the same as the top or bottom of the new rectangle.
+        */
+       if (place == PL_BELOW)
+               protrude = rectab[reclim - 1].n;
+       else
+               protrude = rectab[reclim - 1].s;
+
+       /*
+        * Loop through the verses, from the inside out. setting the relative
+        * vertical coords of their syllables.  When necessary, we also insert
+        * new syllables on the next score for continuing underscores.
+        */
+       if (place == PL_BELOW) {        /* work downward from staff */
+               begin = vfound - 1;     /* first verse number */
+               end = -1;               /* beyond last verse number */
+               delta = -1;
+       } else {        /* above and between both work upwards from bottom */
+               begin = 0;              /* last verse number */
+               end = vfound;           /* before first verse number */
+               delta = 1;
+       }
+       for (n = begin; n != end; n += delta) {
+               /*
+                * Find the farthest any syllable ascends and descends from the
+                * baseline of the verse.
+                */
+               getvsize(start_p, s, place, versenums[n], &maxasc, &maxdes);
+
+               /*
+                * Set the baseline for this verse, based on where we're
+                * pushing up against (the last verse we did, or earlier
+                * things), and how far this verse sticks out.
+                */
+               if (place == PL_BELOW)
+                       baseline = protrude - maxasc;
+               else    /* above or between */
+                       baseline = protrude + maxdes;
+
+               /* set syllables' vertical coords; continue underscores */
+               setsylvert(start_p, s, place, versenums[n], baseline);
+
+               /* set new lower bound, for next time through loop */
+               if (place == PL_BELOW)
+                       protrude = baseline - maxdes;
+               else    /* above or between */
+                       protrude = baseline + maxasc;
+
+       } /* for every verse */
+
+       /*
+        * If there was a verse 0 (centered verse) and also normal verses, then
+        * in the above code we have handled only the normal verses, and we now
+        * need to handle verse 0.
+        */
+       if (gotverse0 == YES && gototherverse == YES) {
+               float mid;      /* RY of the middle of the normal verses */
+               struct RECTAB rec;      /* one rectangle */
+
+               /* get ascent and descent of verse 0 */
+               getvsize(start_p, s, place, 0, &maxasc, &maxdes);
+
+               /*
+                * We will use stackit's "dist" mechanism to try to get verse 0
+                * to line up with the center of the other verses.  The last
+                * rectangle in rectab is currently the normal verses', but the
+                * one coord isn't really set right yet.  Fortunately, the
+                * "protrude" variable is what we need for that coord.
+                */
+               if (place == PL_BELOW) {
+                       mid = (rectab[reclim - 1].n + protrude) / 2.0;
+                       dist = -mid - (maxasc + maxdes) / 2.0;
+               } else {
+                       mid = (protrude + rectab[reclim - 1].s) / 2.0;
+                       dist = mid - (maxasc + maxdes) / 2.0;
+               }
+
+               /*
+                * Find the easternmost and westernmost points of verse 0.
+                * It's easier to loop through all the syllables than to try to
+                * find the first and last syllables on the line.
+                */
+               farwest = PGWIDTH;              /* init it all the way east */
+               fareast = 0;                    /* init it all the way west */
+               for (mainll_p = start_p->next;
+                               mainll_p != 0 && mainll_p->str != S_FEED;
+                               mainll_p = mainll_p->next) {
+
+                       if (mainll_p->str != S_STAFF ||
+                                       mainll_p->u.staff_p->staffno != s)
+                               continue;
+
+                       staff_p = mainll_p->u.staff_p;
+                       for (n = 0; n < staff_p->nsyllists; n++) {
+                               if (staff_p->sylplace[n] == place &&
+                                               staff_p->syls_p[n]->vno == 0) {
+                                       for (gs_p = staff_p->syls_p[n];
+                                            gs_p != 0; gs_p = gs_p->next) {
+
+                                               if (gs_p->c[AW] < farwest)
+                                                       farwest = gs_p->c[AW];
+                                               if (gs_p->c[AE] > fareast)
+                                                       fareast = gs_p->c[AE];
+                                       }
+                               }
+                       }
+               }
+
+               /*
+                * Squeeze the regular verses' rectangle to zero so that it
+                * won't affect verse 0's.  We hope they wouldn't interfere
+                * anyway, but the +8 and -8 might make them.  The regular
+                * verses' rectangle will be corrected later anyway.
+                */
+               rectab[reclim - 1].n = rectab[reclim - 1].s = 0;
+
+               /*
+                * Stack verse 0's rectangle and set its baseline.  We have to
+                * play games with "place", because for "between" stackit
+                * ignores "dist", but we need it to use "dist".
+                */
+               baseline = stackit(farwest - 8 * STEPSIZE,
+                       fareast + 8 * STEPSIZE, maxasc + maxdes, dist,
+                       place == PL_BETWEEN ? PL_ABOVE : place) + maxdes;
+
+               /*
+                * Switch verse 0's rectangle and the normal verses' so that
+                * the later code can always use reclim-1 for the normal.
+                */
+               rec = rectab[reclim - 2];
+               rectab[reclim - 2] = rectab[reclim - 1];
+               rectab[reclim - 1] = rec;
+
+               setsylvert(start_p, s, place, 0, baseline);
+       }
+
+       /*
+        * Now that we know how high this rectangle really is, correct it in
+        * rectab.  Make it reach the center of the staff/baseline, to prevent
+        * anything later from getting in between there.
+        */
+       if (place == PL_BELOW) {
+               rectab[reclim - 1].n = 0;
+               rectab[reclim - 1].s = protrude;
+       } else {        /* above or between */
+               rectab[reclim - 1].n = protrude;
+               rectab[reclim - 1].s = 0;
+       }
+
+       FREE(versenums);
+}
+\f
+/*
+ * Name:        getvsize()
+ *
+ * Abstract:    Get the maximum ascent and descent for a verse on a score.
+ *
+ * Returns:     void
+ *
+ * Description: This function returns (through pointers) the maximum ascent and
+ *             descent of a verse on this score.  Usually this is the standard
+ *             ascent and descent of the font, but it could be greater if
+ *             there are font or size changes inside some syllable.
+ */
+
+static void
+getvsize(start_p, s, place, v, maxasc_p, maxdes_p)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above, below, or between? */
+int v;                         /* verse number */
+float *maxasc_p, *maxdes_p;    /* ascent and descent to be returned */
+
+{
+       int lyricsfont;         /* that is set for this staff */
+       int lyricssize;         /* that is set for this staff */
+       float asc, des;         /* max ascent & descent of syllables */
+       struct MAINLL *mainll_p;/* point along main linked list */
+       struct STAFF *staff_p;  /* point at a staff structure */
+       struct GRPSYL *gs_p;    /* point at a syllable */
+       int k;                  /* loop variable */
+
+
+       /*
+        * Get the standard max ascent and descent for any syllable.
+        */
+       lyricsfont = svpath(s, LYRICSFONT)->lyricsfont;
+       lyricssize = svpath(s, LYRICSSIZE)->lyricssize;
+       *maxasc_p = fontascent(lyricsfont, lyricssize) * Staffscale;
+       *maxdes_p = fontdescent(lyricsfont, lyricssize) * Staffscale;
+
+       /*
+        * Find the farthest any syllable ascends and descends from the
+        * baseline of the verse.  Start with the standard amount for this font
+        * size.  If the loop finds any weird syllable with bigger characters
+        * embedded, they will be increased.
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 && mainll_p->str
+                               != S_FEED; mainll_p = mainll_p->next) {
+
+               if (mainll_p->str != S_STAFF ||
+                               mainll_p->u.staff_p->staffno != s)
+                       continue;
+
+               /* found a STAFF of the number we're dealing with */
+               staff_p = mainll_p->u.staff_p;
+
+               /*
+                * See if this verse is present in this staff,
+                * and if so, loop through it.
+                */
+               for (k = 0; k < staff_p->nsyllists; k++) {
+
+                       if (staff_p->sylplace[k] == place &&
+                                       staff_p->syls_p[k]->vno == v) {
+
+                               for (gs_p = staff_p->syls_p[k]; gs_p != 0;
+                                               gs_p = gs_p->next) {
+                                       /*
+                                        * If asc or des is greater
+                                        * for this syl, save it.
+                                        */
+                                       asc = strascent(gs_p->syl);
+
+                                       des = strdescent(gs_p->syl);
+
+                                       if (asc > *maxasc_p)
+                                               *maxasc_p = asc;
+                                       if (des > *maxdes_p)
+                                               *maxdes_p = des;
+                               }
+
+                               /* no need to look any more */
+                               break;
+                       }
+               }
+       } /* for every MLL stucture in score */
+}
+\f
+/*
+ * Name:        setsylvert()
+ *
+ * Abstract:    Set the maximum ascent and descent for a verse on a score.
+ *
+ * Returns:     void
+ *
+ * Description: This function, using the given baseline, sets the relative
+ *             vertical coords of each syllable in the verse on this score.
+ *             If there are any nonnull syllables, it calls a function to
+ *             continue underscores if need be.
+ */
+
+static void
+setsylvert(start_p, s, place, v, baseline)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+int place;                     /* above, below, or between? */
+int v;                         /* verse number */
+double baseline;               /* baseline of a verse of syllables */
+
+{
+       struct MAINLL *mainll_p;/* point along main linked list */
+       struct STAFF *staff_p;  /* point at a staff structure */
+       struct GRPSYL *gs_p;    /* point at a syllable */
+       struct MAINLL *laststaff_p; /* point last staff that has a syllable */
+       struct GRPSYL *lastgs_p;/* point at last nonnull syllable in a verse */
+       int k;                  /* loop variable */
+
+
+       /*
+        * Loop through all these syllables as before, setting their relative
+        * vertical coords.
+        */
+       lastgs_p = 0;           /* set later to last nonnull syl, if exists */
+       laststaff_p = 0;        /* set later to staff containing lastgs_p */
+
+       for (mainll_p = start_p->next; mainll_p != 0 && mainll_p->str
+                               != S_FEED; mainll_p = mainll_p->next) {
+
+               if (mainll_p->str != S_STAFF ||
+                               mainll_p->u.staff_p->staffno != s)
+                       continue;
+
+               /* found a STAFF of the number we're dealing with */
+               staff_p = mainll_p->u.staff_p;
+
+               /*
+                * See if this verse is present in this staff,
+                * and if so, loop through it.
+                */
+               for (k = 0; k < staff_p->nsyllists; k++) {
+
+                       if (staff_p->sylplace[k] == place &&
+                                       staff_p->syls_p[k]->vno == v) {
+
+                               for (gs_p = staff_p->syls_p[k]; gs_p != 0;
+                                               gs_p = gs_p->next) {
+
+                                       if (gs_p->syl == 0) {
+                                               continue;
+                                       }
+
+                                       gs_p->c[RY] = baseline;
+
+                                       gs_p->c[RN] = baseline
+                                               + strascent(gs_p->syl);
+
+                                       gs_p->c[RS] = baseline
+                                               - strdescent(gs_p->syl);
+
+                                       /* remember last nonnull syl */
+                                       if (gs_p->syl[0] != '\0') {
+                                               lastgs_p = gs_p;
+                                               laststaff_p = mainll_p;
+                                       }
+                               }
+                       }
+               }
+       } /* for every MLL stucture in score */
+
+       /*
+        * At this point, if this score has any nonnull syllables for
+        * this verse, lastgs_p points at the last one and laststaff_p
+        * points at its STAFF.  If that last syllable ends in '_' or
+        * '-', we may need to continue this character onto the next
+        * score, so call a function to do that.
+        */
+       if (lastgs_p != 0 && has_extender(lastgs_p->syl))
+               cont_extender(laststaff_p, place, v);
+}
+\f
+/*
+ * Name:        dopedal()
+ *
+ * Abstract:    Set a rectangle for pedal marks, if there are any.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts a rectangle into rectab for pedal marks, if
+ *             there are any on this score.  It also sets their relative
+ *             vertical coordinates.
+ */
+
+static void
+dopedal(start_p, s)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+
+{
+       struct MAINLL *mainll_p;        /* loop through main linked list */
+       struct STUFF *stuff_p;          /* point along a STUFF list */
+       float protrude;                 /* farthest protrusion of rectangle */
+       float lowpoint;                 /* the lowest any mark goes */
+       float asc;                      /* ascent of a pedal mark */
+       float hi;                       /* height of a pedal mark */
+       int k;                          /* loop variable */
+
+
+       debug(32, "dopedal file=%s line=%d s=%d", start_p->inputfile,
+                       start_p->inputlineno, s);
+       /*
+        * Find the greatest protrusion of any currently existing rectangle.
+        */
+       protrude = 0;
+       for (k = 0; k < reclim; k++) {
+               if (rectab[k].s < protrude)
+                       protrude = rectab[k].s;
+       }
+
+       lowpoint = 0;
+
+       /*
+        * Loop through this score's part of the MLL.  Whenever we find a
+        * structure for this staff (another measure), loop through its
+        * STUFF list, setting coords for each pedal mark.
+        */
+       for (mainll_p = start_p->next; mainll_p != 0 &&
+                       mainll_p->str != S_FEED; mainll_p = mainll_p->next) {
+
+               if (mainll_p->str != S_STAFF ||
+                               mainll_p->u.staff_p->staffno != s)
+                       continue;
+
+               for (stuff_p = mainll_p->u.staff_p->stuff_p;
+                               stuff_p != 0; stuff_p = stuff_p->next) {
+
+                       if (stuff_p->stuff_type != ST_PEDAL)
+                               continue;
+
+                       /*
+                        * Whichever pedal character this is, always use
+                        * C_BEGPED if pedstyle is P_LINE and the "Ped." string
+                        * for the other cases.  For the former, all three
+                        * characters are the same height; and for the latter,
+                        * this string is taller than the "*".  This also
+                        * handles the pedal continuation situation.
+                        */
+                       stuff_p->c[RN] = protrude;
+                       if (svpath(s, PEDSTYLE)->pedstyle == P_LINE) {
+                               asc = ascent(FONT_MUSIC, DFLT_SIZE, C_BEGPED);
+                               hi  = height(FONT_MUSIC, DFLT_SIZE, C_BEGPED);
+                       } else { /* P_PEDSTAR or P_ALTPEDSTAR */
+                               asc = strascent(Ped_start);
+                               hi  = strheight(Ped_start);
+                       }
+                       if (stuff_p->all) {
+                               asc *= Score.staffscale;
+                               hi  *= Score.staffscale;
+                       } else {
+                               asc *= Staffscale;
+                               hi  *= Staffscale;
+                       }
+                       stuff_p->c[RY] = protrude - asc;
+                       stuff_p->c[RS] = protrude - hi;
+
+                       if (stuff_p->c[RS] < lowpoint)
+                               lowpoint = stuff_p->c[RS];
+               }
+       }
+
+       /*
+        * If we found pedal mark(s), put one big rectangle in rectab, spanning
+        * the width of the page.
+        */
+       if (lowpoint < 0) {
+               rectab[reclim].n = protrude;
+               rectab[reclim].s = lowpoint;
+               rectab[reclim].w = 0;
+               rectab[reclim].e = PGWIDTH;
+
+               inc_reclim();
+       }
+}
+\f
+/*
+ * Name:        doendings()
+ *
+ * Abstract:    Set up rectangles and vert coords for ending marks.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab rectangles for ending marks.
+ *             Also, MARKCOORD structures get linked to BARs for them.
+ */
+
+static void
+doendings(start_p, s)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+
+{
+       struct MAINLL *mainll_p;/* point along main linked list */
+       struct BAR *bar_p;      /* point at a bar or pseudobar on this score */
+
+
+       debug(32, "doendings file=%s line=%d s=%d", start_p->inputfile,
+                       start_p->inputlineno, s);
+       /* if endings are not to be drawn over this staff, get out */
+       if (has_ending(s) == NO)
+               return;
+
+       /* point at pseudobar in clefsig that immediately follows this feed */
+       mainll_p = start_p->next;
+       bar_p = mainll_p->u.clefsig_p->bar_p;
+
+       /*
+        * If an ending starts at the pseudobar, or is continuing on from the
+        * previous score, handle it, along with any following continguous ones.
+        */
+       if (bar_p->endingloc != NOITEM) {
+               /*
+                * Search forward for the end of this ending (or following
+                * contiguous ones), or the end of the score, whichever comes
+                * first.
+                */
+               while ( ! (mainll_p->str == S_BAR &&
+                                       mainll_p->u.bar_p->endingloc == ENDITEM)
+                               && mainll_p->str != S_FEED) {
+
+                       mainll_p = mainll_p->next;
+               }
+
+               /* handle ending(s) from start to this bar or feed */
+               storeend(start_p, mainll_p, s);
+
+               /* if feed, there's nothing more to look for */
+               if (mainll_p->str == S_FEED)
+                       return;
+
+               /* point after this bar at end of this ending(s) */
+               mainll_p = mainll_p->next;
+       }
+
+       /*
+        * Search the rest of the score for contiguous groups of endings.
+        */
+       while (mainll_p != 0 && mainll_p->str != S_FEED) {
+
+               /* find another bar; return if there aren't any more */
+               while (mainll_p != 0 && mainll_p->str != S_BAR &&
+                                       mainll_p->str != S_FEED)
+                       mainll_p = mainll_p->next;
+               if (mainll_p == 0 || mainll_p->str == S_FEED)
+                       return;
+
+               /*
+                * We found another bar.  If it isn't associated with an
+                * ending, point beyond it and continue to go look for the
+                * next bar.
+                */
+               if (mainll_p->u.bar_p->endingloc == NOITEM) {
+                       mainll_p = mainll_p->next;
+                       continue;
+               }
+
+               /*
+                * This bar is the start of an ending.  Search forward for the
+                * end of this ending (or following contiguous ones), or the
+                * end of the score, whichever comes first.
+                */
+               start_p = mainll_p;
+               while ( ! (mainll_p->str == S_BAR &&
+                                       mainll_p->u.bar_p->endingloc == ENDITEM)
+                               && mainll_p->str != S_FEED) {
+
+                       mainll_p = mainll_p->next;
+               }
+
+               /* handle ending(s) from start to this bar or feed */
+               storeend(start_p, mainll_p, s);
+
+               /* if feed, there's nothing more to look for */
+               if (mainll_p->str == S_FEED)
+                       return;
+
+               /* point after this bar at end of this ending */
+               mainll_p = mainll_p->next;
+       }
+}
+\f
+/*
+ * Name:        storeend()
+ *
+ * Abstract:    Set up rectangles and vert coords for contiguous endings.
+ *
+ * Returns:     void
+ *
+ * Description: This function is given the starting and ending bars of a group
+ *             of continguous ending marks on a staff.  The starting "bar"
+ *             may be the pseudobar at the start of the score; and the ending
+ *             bar may be the end of the score.  This function applies stackit
+ *             to them as a unit.  It adds another rectangle to rectab to
+ *             prevent anything later from getting in between the ending(s)
+ *             and the staff.  Then, for the starting bar of each ending in
+ *             the group, it allocates a MARKCOORD structure.
+ */
+
+static void
+storeend(start_p, end_p, s)
+
+struct MAINLL *start_p;                /* the start of these ending(s) */
+struct MAINLL *end_p;          /* the end of these ending(s) */
+int s;                         /* staff number */
+
+{
+       struct MAINLL *mainll_p;/* point along main linked list */
+       struct BAR *bar_p;      /* point at a bar or pseudobar on this score */
+       struct MARKCOORD *mark_p; /* we allocate these for bars to point at */
+       float west, east;       /* extremities of group of ending(s) */
+       float south;            /* their bottom boundary */
+
+
+       /*
+        * Find the west and east boundaries of the ending(s).
+        */
+       if (start_p->str == S_FEED)
+               west = start_p->next->u.clefsig_p->bar_p->c[AX]; /* pseudobar */
+       else
+               west = start_p->u.bar_p->c[AX];         /* normal bar */
+
+       if (end_p->str == S_FEED)
+               east = PGWIDTH - eff_rightmargin(end_p); /* end of score */
+       else
+               east = end_p->u.bar_p->c[AX];           /* normal bar */
+
+       /* make a rectangle out of the ending(s) and find where they go */
+       south = stackit(west, east, ENDINGHEIGHT, (double)0.0, PL_ABOVE);
+
+       /*
+        * Superimpose another rectangle on top of the one stackit put there;
+        * one that reaches down to the staff.  This ensures that nothing later
+        * will get between the ending(s) and the staff.
+        */
+       rectab[reclim].n = south + ENDINGHEIGHT;
+       rectab[reclim].s = 0;
+       rectab[reclim].e = east;
+       rectab[reclim].w = west;
+       inc_reclim();
+
+       /*
+        * If the pseudobar has an ending, calloc a markcoord structure and put
+        * it in the pseudobar's linked list of them.
+        */
+       if (start_p->str == S_FEED) {
+               bar_p = start_p->next->u.clefsig_p->bar_p;
+               CALLOC(MARKCOORD, mark_p, 1);
+               mark_p->next = bar_p->ending_p;
+               bar_p->ending_p = mark_p;
+               mark_p->staffno = (short)s;
+               mark_p->ry = south;
+       }
+
+       /*
+        * Loop through this part of the score.  Wherever there is a bar that
+        * is the start of an ending, calloc a markcoord structure and put it
+        * in the bar's linked list of them.
+        */
+       for (mainll_p = start_p; mainll_p != end_p; mainll_p = mainll_p->next) {
+               if (mainll_p->str != S_BAR)
+                       continue;
+               bar_p = mainll_p->u.bar_p;
+               if (bar_p->endingloc != STARTITEM)
+                       continue;
+               CALLOC(MARKCOORD, mark_p, 1);
+               mark_p->next = bar_p->ending_p;
+               bar_p->ending_p = mark_p;
+               mark_p->staffno = (short)s;
+               mark_p->ry = south;
+       }
+}
+\f
+/*
+ * Name:        dorehears()
+ *
+ * Abstract:    Set up rectangles and vert coords for rehearsal marks.
+ *
+ * Returns:     void
+ *
+ * Description: This function puts into rectab rectangles for rehearsal marks.
+ *             Also, MARKCOORD structures get linked to BARs for them.
+ */
+
+static void
+dorehears(start_p, s)
+
+struct MAINLL *start_p;                /* FEED at the start of this score */
+int s;                         /* staff number */
+
+{
+       struct MAINLL *mainll_p;/* point along main linked list */
+       struct BAR *bar_p;      /* point at a bar or pseudobar on this score */
+       struct MARKCOORD *mark_p; /* we allocate these for bars to point at */
+       float west, east;       /* of a rehearsal mark */
+       float south;            /* of a rehearsal mark */
+       float height;           /* of a rehearsal mark */
+       float dist;             /* distance from center of staff */
+       int dopseudo;           /* do the pseudobar's rehearsal mark? */
+       char *reh_string;       /* string for the reh mark */
+
+
+       debug(32, "dorehears file=%s line=%d s=%d", start_p->inputfile,
+                       start_p->inputlineno, s);
+       /* if rehearsal marks are not to be drawn over this staff, get out */
+       if (has_ending(s) == NO)
+               return;
+
+       /* point at pseudobar in clefsig that immediately follows this feed */
+       mainll_p = start_p->next;
+       bar_p = mainll_p->u.clefsig_p->bar_p;
+
+       /* if there's a rehearsal mark at the pseudobar, note that fact */
+       if (bar_p->reh_type != REH_NONE)
+               dopseudo = YES;
+       else
+               dopseudo = NO;
+
+       /*
+        * Loop through the score, dealing with the pseudobar (if it has a
+        * rehearsal mark), and all real bars that have a rehearsal mark.
+        */
+       for ( ; mainll_p != 0 && mainll_p->str != S_FEED;
+                               mainll_p = mainll_p->next) {
+
+               if (dopseudo == YES || mainll_p->str == S_BAR &&
+                                      mainll_p->u.bar_p->reh_type != REH_NONE){
+                       if (dopseudo == YES)
+                               dopseudo = NO;
+                       else
+                               bar_p = mainll_p->u.bar_p;
+
+                       /*
+                        * Find the size of the rehearsal label, including 6
+                        * more points to allow for the box around it.  Make
+                        * its first character be centered over the bar line.
+                        * Place it by using stackit.
+                        */
+                       reh_string = get_reh_string(bar_p->reh_string, s);
+                       height = strheight(reh_string);
+                       west = bar_p->c[AX] - left_width(reh_string);
+                       east = west + strwidth(reh_string);
+
+                       if (bar_p->dist_usage == SD_NONE) {
+                               /* get the usual dist */
+                               dist = svpath(s, DIST)->dist;
+                       } else {
+                               /* override with this bar's dist */
+                               dist = bar_p->dist;
+                       }
+                       /* convert to inches from center of staff */
+                       dist = halfstaffhi(s) + STEPSIZE * dist;
+
+                       if (bar_p->dist_usage == SD_FORCE) {
+                               /*
+                                * The user is forcing this dist, so don't
+                                * stack; just put it there.
+                                */
+                               south = dist;
+                               rectab[reclim].n = south + height;
+                               rectab[reclim].s = south;
+                               rectab[reclim].e = east;
+                               rectab[reclim].w = west;
+                               inc_reclim();
+                       } else {
+                               /* stack the usual way */
+                               south = stackit(west, east, height, dist,
+                                               PL_ABOVE);
+                       }
+
+                       /*
+                        * Allocate and link a MARKCOORD, and put the necessary
+                        * info in it.
+                        */
+                       CALLOC(MARKCOORD, mark_p, 1);
+                       mark_p->next = bar_p->reh_p;
+                       bar_p->reh_p = mark_p;
+                       mark_p->staffno = (short)s;
+                       mark_p->ry = south + strdescent(reh_string);
+               }
+       }
+}
+\f
+/*
+ * Name:        stackit()
+ *
+ * Abstract:    Place a rectangle and add it to rectab.
+ *
+ * Returns:     south boundary of the new rectangle
+ *
+ * Description: This function puts the given rectangle into rectab.  It is put
+ *             as close to the staff or baseline as is possible without
+ *             overlapping rectangles already in rectab, and without letting
+ *             it get any closer to the staff/baseline than "dist" STEPSIZE.
+ */
+
+static double
+stackit(west, east, height, dist, place)
+
+double west;                   /* west edge of the new rectangle */
+double east;                   /* east edge of the new rectangle */
+double height;                 /* height of the new rectangle */
+double dist;                   /* min dist from item to center line of staff*/
+int place;                     /* above, below, or between? */
+
+{
+       float north, south;     /* trial boundaries for new rectangle */
+       int try;                /* which element of rectab to try */
+       int overlap;            /* does our rectangle overlap existing ones? */
+       int j;                  /* loop variable */
+
+
+       /*
+        * For each rectangle in rectab, decide whether (based on
+        * its horizontal coords) it could possibly overlap with our
+        * new rectangle.  If it's totally left or right of ours, it
+        * can't.  We allow a slight overlap (FUDGE) so that round
+        * off errors don't stop us from packing things as tightly
+        * as possible.
+        */
+       for (j = 0; j < reclim; j++) {
+               if (rectab[j].w + FUDGE > east ||
+                   rectab[j].e < west + FUDGE)
+                       rectab[j].relevant = NO;
+               else
+                       rectab[j].relevant = YES;
+       }
+
+       /*
+        * Set up first trial position for this rectangle:  "dist" inches
+        * away from the center line of the staff.  For "between", it always
+        * starts at the baseline.
+        */
+       north = south = 0.0;    /* prevent useless 'used before set' warning */
+       switch (place) {
+       case PL_BELOW:
+               /* work downward from staff, allowing "dist" distance */
+               north = -dist;
+               south = north - height;
+               break;
+       case PL_ABOVE:
+               /* work upward from staff, allowing "dist" distance */
+               south = dist;
+               north = south + height;
+               break;
+       case PL_BETWEEN:
+               /* work upward from baseline */
+               south = 0;
+               north = height;
+               break;
+       }
+
+       /*
+        * Mark the "tried" field for all relevant rectangles.  This says
+        * whether we have already tried using their boundaries for positioning
+        * our rectangle.  Any rectangle that is closer to the staff/baseline
+        * than we want to allow, we mark as if we have tried it already.
+        */
+       for (j = 0; j < reclim; j++) {
+               if (rectab[j].relevant == YES) {
+                       if (place == PL_BELOW && rectab[j].s > north ||
+                           place != PL_BELOW && rectab[j].n < south)
+                               rectab[j].tried = YES;
+                       else
+                               rectab[j].tried = NO;
+               }
+       }
+
+       /*
+        * Keep trying positions for this rectangle, working outwards from the
+        * first trial position.  When we find one that doesn't overlap an
+        * existing rectangle, break.  This has to succeed at some point, at
+        * at the outermost rectangle position if not earlier.
+        */
+       for (;;) {
+               overlap = NO;
+               for (j = 0; j < reclim; j++) {
+                       /* ignore ones too far east or west */
+                       if (rectab[j].relevant == NO)
+                               continue;
+
+                       /* if all south or north, okay; else overlap */
+                       if (rectab[j].s + FUDGE <= north &&
+                           rectab[j].n >= south + FUDGE) {
+                               overlap = YES;
+                               break;
+                       }
+               }
+
+               /* if no rectangle overlapped, we found a valid place */
+               if (overlap == NO)
+                       break;
+
+               /*
+                * Something overlapped, so we have to try again.  Find the
+                * innermost relevant outer rectangle boundary that hasn't been
+                * tried already, to use as the next trial position for our
+                * rectangle's inner boundary.
+                */
+               try = -1;
+               for (j = 0; j < reclim; j++) {
+                       /* ignore ones too far east or west */
+                       if (rectab[j].relevant == NO || rectab[j].tried == YES)
+                               continue;
+
+                       /*
+                        * If this is the first relevant one we haven't tried,
+                        * or if this is farther in than the innermost so far,
+                        * save it as being the new innermost so far.
+                        */
+                       if (place == PL_BELOW) {
+                               if (try == -1 || rectab[j].s > rectab[try].s)
+                                       try = j;
+                       } else {
+                               if (try == -1 || rectab[j].n < rectab[try].n)
+                                       try = j;
+                       }
+               }
+
+               if (try == -1)
+                       pfatal("bug in stackit()");
+
+               /*
+                * Mark this one as having been tried (for next time around, if
+                * necessary).  Set new trial values for north and south of our
+                * rectangle.
+                */
+               rectab[try].tried = YES;
+               if (place == PL_BELOW) {
+                       north = rectab[try].s;
+                       south = north - height;
+               } else {
+                       south = rectab[try].n;
+                       north = south + height;
+               }
+
+       } /* end of while loop trying positions for this rectangle */
+
+       /*
+        * We found the correct position for the new rectangle.  Enter it
+        * into rectab.
+        */
+       rectab[reclim].n = north;
+       rectab[reclim].s = south;
+       rectab[reclim].e = east;
+       rectab[reclim].w = west;
+
+       inc_reclim();
+
+       return (south);
+}
+\f
+/*
+ * Name:        inc_reclim()
+ *
+ * Abstract:    Increment no. of rectangles, and realloc more if we run out.
+ *
+ * Returns:     void
+ *
+ * Description: This function increments reclim, the index into rectab.  If it
+ *             finds that rectab[reclim] is now beyond the end of the space
+ *             that's been allocated, it does a realloc to get more space.
+ */
+
+static void
+inc_reclim()
+{
+       /* when first called, relvert will have allocated this many */
+       static int rectabsize = RECTCHUNK;
+
+
+       reclim++;
+
+       /* if rectab[reclim] is still valid, no need to allocate more */
+       if (reclim < rectabsize)
+               return;
+
+       /* must allocate another chunk of rectangles */
+       rectabsize += RECTCHUNK;
+       REALLOC(RECTAB, rectab, rectabsize);
+}