chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / setgrps.c
diff --git a/mup/mup/setgrps.c b/mup/mup/setgrps.c
new file mode 100644 (file)
index 0000000..3f520bf
--- /dev/null
@@ -0,0 +1,2920 @@
+/* Copyright (c) 1995, 1997, 1998, 1999, 2000, 2001, 2004, 2005, 2006
+ * by Arkkra Enterprises */
+/* All rights reserved */
+/*
+ * Name:       setgrps.c
+ *
+ * Description:        This file contains functions for setting the relative
+ *             horizontal coordinates of all groups that contain notes
+ *             (grpcont == GC_NOTES) and of all objects in these groups.
+ *             It also sets relative vertical coordinates for the dots
+ *             after notes.
+ */
+
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+struct NOTEPTRS {
+       struct NOTE *top_p;     /* point at a note in top group */
+       struct NOTE *bot_p;     /* point at same note in bottom group*/
+       float wid;              /* width of the note head */
+};
+
+static struct GRPSYL *procallvoices P((struct MAINLL *mll_p,
+               struct GRPSYL *gs_p));
+static void proc1or2voices P((struct MAINLL *mll_p, struct STAFF *staff_p,
+               struct GRPSYL *gs1_p, struct GRPSYL *gs2_p));
+static int compat P((struct NOTEPTRS noteptrs[], struct GRPSYL *gs1_p,
+               struct GRPSYL *gs2_p));
+static int can_overlap P((struct GRPSYL *gs1_p, struct GRPSYL *gs2_p));
+static void procsome P((struct NOTEPTRS noteptrs[], struct MAINLL *mll_p,
+               struct STAFF *staff_p, struct GRPSYL *gs1_p,
+               struct GRPSYL *gs2_p));
+static void procgrace P((struct NOTEPTRS noteptrs[], struct MAINLL *mll_p,
+               struct STAFF *staff_p, struct GRPSYL *gsnorm_p));
+static void procbunch P((struct NOTEPTRS noteptrs[], struct MAINLL *mll_p,
+               struct STAFF *staff_p, struct GRPSYL *gs1_p,
+               struct GRPSYL *gs2_p));
+static void doacc P((struct NOTEPTRS noteptrs[], double halfwide,
+               double halfhigh, int collinear));
+static int nextacc P((struct NOTEPTRS noteptrs[], int found));
+static void dodot P((struct STAFF *staff_p, struct GRPSYL *gs1_p,
+               struct GRPSYL *gs2_p, double halfwide, int collinear));
+static void dogrpdot P((struct STAFF *staff_p, struct GRPSYL *gs_p,
+               struct GRPSYL *ogs_p, double halfwide, int uppermost,
+               int lowermost, int push));
+static void westwith P((struct GRPSYL *gs_p));
+static void eastwith P((struct GRPSYL *gs_p));
+static void csbstempad P((struct MAINLL *mll_p, struct GRPSYL *gs_p));
+static void proctab P((struct MAINLL *mll_p, struct STAFF *staff_p,
+               struct GRPSYL *gs1_p));
+static void noterparen P((struct NOTEPTRS noteptrs[], struct GRPSYL *gs1_p,
+               struct GRPSYL *gs2_p, double halfwide, double halfhigh,
+               int collinear));
+\f
+/*
+ * Name:        setgrps()
+ *
+ * Abstract:   Find first group on each staff & call procallvoices to process.
+ *
+ * Returns:     void
+ *
+ * Description: This function goes through the chord lists, and for each chord,
+ *             the list of GRPSYLs hanging off it.  It finds the first group
+ *             on each staff, and calls procallvoices() to set the relative
+ *             horizontal coordinates of all the note groups on that staff.
+ */
+
+void
+setgrps()
+
+{
+       struct CHORD *ch_p;             /* point at a chord */
+       struct GRPSYL *gs1_p;           /* point at a group */
+       struct MAINLL *mainll_p;        /* point at items in main linked list*/
+       struct MAINLL *mstaff_p;        /* for looking for staff */
+
+
+       debug(16, "setgrps");
+       initstructs();          /* clean out old SSV info */
+
+       /*
+        * Loop down the main linked list looking for each chord list
+        * headcell.
+        */
+       for (mainll_p = Mainllhc_p; mainll_p != 0; mainll_p = mainll_p->next) {
+
+               /* keep SSVs up to date */
+               if (mainll_p->str == S_SSV)
+                       asgnssv(mainll_p->u.ssv_p);
+
+               if (mainll_p->str != S_CHHEAD)
+                       continue;       /* skip everything but chord HC */
+
+               /*
+                * Loop through each chord in this list.
+                */
+               for (ch_p = mainll_p->u.chhead_p->ch_p; ch_p != 0;
+                                       ch_p = ch_p->ch_p) {
+                       /*
+                        * Loop through the linked list of GRPSYLs hanging off
+                        * this chord.  Skip the syllables; just deal with the
+                        * groups.  Upon finding the first group on a staff
+                        * (which could be for any of the voices, since not all
+                        * might be present in this chord), call procallvoices
+                        * to process all the note groups.
+                        */
+                       gs1_p = ch_p->gs_p;
+                       for (;;) {
+                               /* find first group on a staff */
+                               while (gs1_p != 0 &&
+                                               gs1_p->grpsyl == GS_SYLLABLE)
+                                       gs1_p = gs1_p->gs_p;
+                               if (gs1_p == 0)
+                                       break;
+
+                               /* find the staff's MLL structure */
+                               mstaff_p = chmgrp2staffm(mainll_p, gs1_p);
+
+                               /* set gs1_p to after this staff's groups */
+                               gs1_p = procallvoices(mstaff_p, gs1_p);
+                       }
+               }
+       }
+}
+\f
+/*
+ * Name:        procallvoices()
+ *
+ * Abstract:    Process the groups for all the voices on one staff in a chord.
+ *
+ * Returns:     pointer to the first GRPSYL after these groups, 0 if none
+ *
+ * Description: This function is given the GRPSYL for the first (topmost) voice
+ *             that is on this staff in this chord.  It finds what other
+ *             GRPSYLs exist.  For each of them that is for notes (not rests
+ *             or spaces), it calls proc1or2voices() to process them together
+ *             and/or separately, as needed.  This file generally deals only
+ *             with notes, not rests or spaces.  But this function also deals
+ *             with rests to the following extent:  For both notes and rests,
+ *             there are situations where voice 3 should "stand in" for voice 1
+ *             or voice 2.  This function makes those decisions, and sets pvno.
+ */
+
+static struct GRPSYL *
+procallvoices(mll_p, gs_p)
+
+struct MAINLL *mll_p;          /* the MLL item the group is connected to */
+struct GRPSYL *gs_p;           /* point at first voice on this staff */
+
+{
+       struct STAFF *staff_p;          /* point at staff */
+       struct GRPSYL *g_p[MAXVOICES];  /* point at note groups */
+       struct GRPSYL *last_p;          /* point at last note group */
+       struct GRPSYL *g2_p[MAXVOICES]; /* point at note and rest groups */
+       struct GRPSYL *gs1_p;           /* remember first group */
+       struct GRPSYL *gs2_p;           /* another GRPSYL pointer */
+       int numnonspace;                /* number of nonspace GRPSYLs */
+       int numgrps;                    /* how many note groups are here */
+       int n;                          /* loop variable, voices processed */
+
+
+       staff_p = mll_p->u.staff_p;
+       numgrps = 0;                    /* no groups found yet */
+       last_p = 0;                     /* no note groups yet */
+       gs1_p = gs_p;                   /* remember first group */
+
+       /* find all groups in this chord on this staff; remember note groups */
+       while (gs_p != 0 && gs_p->staffno == staff_p->staffno &&
+                           gs_p->grpsyl == GS_GROUP) {
+               gs_p->pvno = gs_p->vno; /* init pseudo voice no. to voice no.*/
+               if (gs_p->grpcont == GC_NOTES) {
+                       g_p[numgrps++] = gs_p;
+                       last_p = gs_p;
+               }
+               gs_p = gs_p->gs_p;
+       }
+
+       /*
+        * Before continuing on to process note groups, change voice 3's pvno
+        * when appropriate.  First find all nonspace groups.
+        */
+       numnonspace = 0;                /* no nonspace groups found yet */
+       gs2_p = gs1_p;
+
+       /* find all nonspace groups in this chord on this staff */
+       while (gs2_p != 0 && gs2_p->staffno == staff_p->staffno &&
+                           gs2_p->grpsyl == GS_GROUP) {
+               if (gs2_p->grpcont != GC_SPACE) {
+                       g2_p[numnonspace++] = gs2_p;
+               } else {
+                       /*
+                        * This is a convenient, though somewhat inappropriate,
+                        * place to process grace groups that precede a space
+                        * group.  Ones that precede notes groups will be
+                        * processed in the normal flow, called from procsome.
+                        * They are not allowed before rest groups.
+                        */
+                       struct NOTEPTRS noteptrs[MAXHAND + 1];
+                       procgrace(noteptrs, mll_p, staff_p, gs2_p);
+               }
+               gs2_p = gs2_p->gs_p;
+       }
+
+       /*
+        * If the only nonspace voices are 1 and 3, or 2 and 3, and at least
+        * one of them is a rest and this is not a tab staff and "ho" was not
+        * used for either . . .
+        */
+       if (numnonspace == 2 && g2_p[1]->vno == 3 &&
+          (g2_p[0]->grpcont == GC_REST || g2_p[1]->grpcont == GC_REST) &&
+          ! is_tab_staff(staff_p->staffno) && g2_p[0]->ho_usage == HO_NONE &&
+          g2_p[1]->ho_usage == HO_NONE) {
+               /*
+                * If v1 is either a rest or stem-up notes and v3 is a rest or
+                * stem-down notes, let v3 stand in for v2.
+                */
+               if (g2_p[0]->vno == 1 && (g2_p[0]->grpcont == GC_NOTES &&
+                   g2_p[0]->stemdir == UP || g2_p[0]->grpcont == GC_REST) &&
+                   (g2_p[1]->grpcont == GC_NOTES && g2_p[1]->stemdir == DOWN ||
+                   g2_p[1]->grpcont == GC_REST)) {
+                       g2_p[1]->pvno = 2;
+               }
+               /*
+                * If v2 is either a rest or stem-down notes and v3 is a rest or
+                * stem-up notes, let v3 stand in for v1.
+                */
+               if (g2_p[0]->vno == 2 && (g2_p[0]->grpcont == GC_NOTES &&
+                   g2_p[0]->stemdir == DOWN || g2_p[0]->grpcont == GC_REST) &&
+                   (g2_p[1]->grpcont == GC_NOTES && g2_p[1]->stemdir == UP ||
+                   g2_p[1]->grpcont == GC_REST)) {
+                       g2_p[1]->pvno = 1;
+               }
+       }
+
+       /* if there were no note groups on this staff, nothing more to do */
+       if (numgrps == 0)
+               return (gs_p);
+
+       n = 0;          /* number of voices processed so far */
+
+       /*
+        * If voices 1 and 2 exist and are notes and do not have user specified
+        * horizontal offsets and this is not a tab staff, handle them together.
+        * If both voices 1 and 2 have a group here, they will be the first two
+        * found.  Tab staffs should be handled separately because their voices
+        * never conflict with each other (because of chktabcollision() in
+        * in setnotes.c).  Before checking the offsets, verify that they are
+        * legal and fix if not.
+        */
+       if (numgrps >= 2 && g_p[0]->vno == 1 && g_p[1]->vno == 2 &&
+                       ! is_tab_staff(staff_p->staffno)) {
+
+               vfyoffset(g_p);         /* verify and fix */
+
+               if (g_p[0]->ho_usage == HO_NONE && g_p[1]->ho_usage == HO_NONE){
+                       proc1or2voices(mll_p, staff_p, g_p[0], g_p[1]);
+                       n = 2;          /* processed 2 voices */
+               }
+       }
+
+       /*
+        * Else, if v1 and v3, or v2 and v3, are notes, and only those two
+        * exist, and they do not have user specified horizontal offsets and
+        * this is not a tab staff, and v3's stem dir is compatible, let v3
+        * "stand in" for v1 or v2, as the case may be.  Handle the two voices
+        * together.
+        */
+       else if (numgrps == 2 && numnonspace == 2 &&
+                       ! is_tab_staff(staff_p->staffno) && g_p[0]->ho_usage ==
+                       HO_NONE && g_p[1]->ho_usage == HO_NONE) {
+
+               if (g_p[0]->vno == 1 && g_p[0]->stemdir == UP &&
+                   g_p[1]->vno == 3 && g_p[1]->stemdir == DOWN) {
+
+                       g_p[1]->pvno = 2;
+                       proc1or2voices(mll_p, staff_p, g_p[0], g_p[1]);
+                       n = 2;          /* processed 2 voices */
+
+               } else if (g_p[0]->vno == 2 && g_p[0]->stemdir == DOWN &&
+                          g_p[1]->vno == 3 && g_p[1]->stemdir == UP) {
+
+                       g_p[1]->pvno = 1;
+                       proc1or2voices(mll_p, staff_p, g_p[1], g_p[0]);
+                       n = 2;          /* processed 2 voices */
+               }
+       }
+
+       /* process any remaining voices individually */
+       for ( ; n < numgrps; n++) {
+               proc1or2voices(mll_p, staff_p, g_p[n], (struct GRPSYL *)0);
+       }
+
+       /* return the first GRPSYL after the groups we processed */
+       return (gs_p);
+}
+\f
+/*
+ * Name:        proc1or2voices()
+ *
+ * Abstract:    Process a single voice, or voices 1 and 2 together.
+ *
+ * Returns:     void
+ *
+ * Description: This function is given pointers to one or two groups on a
+ *             staff.  If it's just one (the second one is a null pointer),
+ *             that group is to be handled alone.  If it is two, they are
+ *             voices 1 and 2, since voice 3 is always handled separately.
+ *             (Except that voice 3 can sometimes "stand in" for v1 or v2.)
+ *             In any case, these are always note groups, not rest or space.
+ *
+ *             The function sets up an array (noteptrs) to point at each
+ *             note in the group(s), figuring out whether the groups overlap
+ *             and, if so, if they are compatible (see below for definition).
+ *             It calls procsome() to set relative horizontal coordinates for
+ *             some notes, which is done either separately for each group or
+ *             both at once, depending on the situation.
+ */
+
+static void
+proc1or2voices(mll_p, staff_p, gs1_p, gs2_p)
+
+struct MAINLL *mll_p;          /* the MLL item the group is connected to */
+struct STAFF *staff_p;                 /* the staff the groups are on */
+register struct GRPSYL *gs1_p, *gs2_p; /* point at groups in this hand */
+
+{
+       /*
+        * Each structure in this array points at a note.  Notes from gs1_p
+        * are pointed at by top_p, and, when both groups exist, notes
+        * from gs2_p are pointed at by bot_p.  If there's no overlap
+        * between the groups, there won't be any here either.  But if
+        * the groups "share" notes, the shared notes will be pointed
+        * at by both.  If the groups are "incompatible" (must be
+        * drawn shifted horizontally to avoid interference), they will
+        * be done separately and use this array separately, one at a time.
+        * And in that case, notes from both gs1_p and gs2_p will use top_p,
+        * in turn.
+        */
+       struct NOTEPTRS noteptrs[MAXHAND + 1];
+
+       float offset;           /* how far to offset incompatible groups */
+       int num1;               /* number of notes in top group */
+       int n;                  /* loop variable */
+       int incompat;           /* are groups incompatible (special case) */
+
+
+       /*
+        * For mrpt, we have nothing to do except set the horizontal group
+        * coordinates.  If the first group is a measure repeat, so is the
+        * second one, if it exists at all.  We set a very small width, as a
+        * placeholder, because if other staffs have normal notes, we don't
+        * want the first chord to be abnormally wide because of the mrpt
+        * symbol.  (It will be centered in the measure.)  If all the staffs
+        * have mrpt, abshorz.c will ensure that enough space is left for
+        * these symbols.
+        */
+       if (is_mrpt(gs1_p)) {
+               gs1_p->c[RX] = 0;
+               gs1_p->c[RE] = TEMPMRPTWIDTH / 2.0;
+               gs1_p->c[RW] = -TEMPMRPTWIDTH / 2.0;
+
+               if (gs2_p != 0) {
+                       gs2_p->c[RX] = 0;
+                       gs2_p->c[RE] = TEMPMRPTWIDTH / 2.0;
+                       gs2_p->c[RW] = -TEMPMRPTWIDTH / 2.0;
+               }
+               return;
+       }
+
+       /* clear out the array */
+       for (n = 0; n < NUMELEM(noteptrs); n++) {
+               noteptrs[n].top_p = 0;
+               noteptrs[n].bot_p = 0;
+               noteptrs[n].wid = 0.0;
+       }
+
+       num1 = gs1_p->nnotes;
+
+       /* set all the "top" group pointers */
+       for (n = 0; n < num1; n++)
+               noteptrs[n].top_p = &gs1_p->notelist[n];
+
+       /* if there is no "bottom" group, process the first bunch and quit */
+       if (gs2_p == 0) {
+               procsome(noteptrs, mll_p, staff_p, gs1_p, (struct GRPSYL *)0);
+
+               /* if group is rolled, allow room for the roll */
+               if (gs1_p->roll != NOITEM)
+                       gs1_p->c[RW] -= ROLLPADDING;
+               return;
+       }
+
+       /*
+        * If the lowest note of the top group is higher than the highest
+        * note of the bottom group, point at all the bottom notes,
+        * process both, and quit.  Exception:  if the inner notes of the
+        * two groups are on neighboring steps, and the top note of the
+        * bottom group is on a line and has a dot, and the top group has
+        * no dots, the groups are to be regarded as if overlapping and
+        * incompatible.  This is because there is no decent way to place
+        * the dots in this case otherwise.  But if, in this neighboring note
+        * situation, there are no problems with dots, the groups can still be
+        * handled together here; their stems will be made collinear.  When
+        * the notes are two or more steps apart, there's no problem at all,
+        * and the groups' X coordinates will line up and equal the chord's.
+        * Another exception ("else if") is that when the stem of either group
+        * has been forced the "wrong way" by the user, we require more
+        * vertical space between the groups.  Since we don't know the stem
+        * lengths yet, we can't do the full job, though.  The user may have to
+        * use "len" or "ho" to avoid a collision.
+        */
+       incompat = NO;
+       if (noteptrs[num1-1].top_p->stepsup > gs2_p->notelist[0].stepsup) {
+               if (noteptrs[num1-1].top_p->stepsup ==
+                               gs2_p->notelist[0].stepsup + 1 &&
+                               gs2_p->notelist[0].stepsup % 2 == 0 &&
+                               gs2_p->dots == 0 &&
+                               gs1_p->dots > 0) {
+                       incompat = YES;
+               } else if ((gs1_p->stemdir == DOWN || gs2_p->stemdir == UP) &&
+                               noteptrs[num1-1].top_p->stepsup <
+                               gs2_p->notelist[0].stepsup + 3) {
+                       incompat = YES;
+               } else {
+                       for (n = 0; n < gs2_p->nnotes; n++)
+                               noteptrs[num1+n].bot_p = &gs2_p->notelist[n];
+                       procsome(noteptrs, mll_p, staff_p, gs1_p, gs2_p);
+
+                       /* if a group is rolled, allow room for the roll */
+                       if (gs1_p->roll != NOITEM)
+                               gs1_p->c[RW] -= ROLLPADDING;
+                       if (gs2_p->roll != NOITEM)
+                               gs2_p->c[RW] -= ROLLPADDING;
+                       return;
+               }
+       }
+
+       /*
+        * There is overlap between the two groups.  See if they are
+        * compatible (also fills in group 2 in noteptrs).  If so,
+        * process the groups together, and return.
+        */
+       if (incompat == NO && compat(noteptrs, gs1_p, gs2_p) == YES) {
+               procsome(noteptrs, mll_p, staff_p, gs1_p, gs2_p);
+
+               /* if a group is rolled, allow room for the roll */
+               if (gs1_p->roll != NOITEM)
+                       gs1_p->c[RW] -= ROLLPADDING;
+               if (gs2_p->roll != NOITEM)
+                       gs2_p->c[RW] -= ROLLPADDING;
+               return;
+       }
+
+       /*
+        * The fact that we are here means the two groups are not compatible,
+        * meaning they overlap but can't share note heads.  Clear the array
+        * of any notes from the second group, in case compat() put some there.
+        */
+       for (n = 0; n < NUMELEM(noteptrs); n++)
+               noteptrs[n].bot_p = 0;
+
+       /*
+        * It is possible that the groups can at least be given collinear
+        * stems.  For this to be allowed, it must be that the bottom note of
+        * the top group is on the same step as the top note of the bottom
+        * group.  The top group's note can't have dots, the bottom group's
+        * can't have accidentals or a roll, and neither can have parentheses,
+        * because they couldn't be drawn decently.  Neither note can have
+        * another note on a neighboring step.
+        */
+       if (noteptrs[num1-1].top_p->stepsup == gs2_p->notelist[0].stepsup &&
+
+                       gs1_p->dots == 0 &&
+
+                       gs2_p->notelist[0].accidental == '\0' &&
+
+                       gs2_p->roll == NOITEM &&
+
+                       noteptrs[num1-1].top_p->note_has_paren == NO &&
+                       gs2_p->notelist[0].note_has_paren == NO &&
+
+                       (num1 == 1 || noteptrs[num1-2].top_p->stepsup
+                               > noteptrs[num1-1].top_p->stepsup + 1) &&
+
+                       (gs2_p->nnotes == 1 || gs2_p->notelist[0].stepsup
+                               > gs2_p->notelist[1].stepsup + 1) ) {
+               /*
+                * Since we are not sharing noteheads, the notes of the bottom
+                * group must be put after the notes of the top group in the
+                * noteptrs table.  Then process them together.
+                */
+               for (n = 0; n < gs2_p->nnotes; n++)
+                       noteptrs[num1+n].bot_p = &gs2_p->notelist[n];
+               procsome(noteptrs, mll_p, staff_p, gs1_p, gs2_p);
+
+               /* if top group is rolled, allow room for the roll */
+               if (gs1_p->roll != NOITEM)
+                       gs1_p->c[RW] -= ROLLPADDING;
+               return;
+       }
+
+       /*
+        * At this point we know we have to handle the groups separately, and
+        * then place them.  Process the top group now.
+        */
+       procsome(noteptrs, mll_p, staff_p, gs1_p, (struct GRPSYL *)0);
+
+       /*
+        * Clear the top group out of the array, and fill it with just the
+        * bottom group, to process them.  But mark them as if "top", to
+        * simplify procsome().
+        */
+       for (n = 0; n < NUMELEM(noteptrs); n++)
+               noteptrs[n].top_p = 0;
+
+       /* set all the "top" group pointers even though this is group 2 */
+       for (n = 0; n < gs2_p->nnotes; n++)
+               noteptrs[n].top_p = &gs2_p->notelist[n];
+
+       procsome(noteptrs, mll_p, staff_p, gs2_p, (struct GRPSYL *)0);
+
+       /*
+        * Now that we've figured out all the relative horizontal coords for
+        * the two groups (and everything in them) separately, we need to
+        * decide how to offset them so they don't overlap.  We'll offset
+        * each the same distance, one right and one left, and apply that
+        * offset to every horizontal coord of the groups.
+        */
+       /*
+        * If the groups can be placed so that their rectangles overlap, do it.
+        * Else if one of the groups is to be rolled and the other is not, the
+        * one to be rolled must be put on the left.  Otherwise, find which
+        * direction gives minimal offset, but bias the results (0.1) to favor
+        * putting the top group towards the left, so that the stems will be
+        * closer to lining up.  Set "offset" to the offset to be applied to
+        * group 1.  Group 2's will be -offset.
+        */
+       if (can_overlap(gs1_p, gs2_p) == YES) {
+               /* top group goes on right; top's offset > 0 */
+               if (allsmall(gs1_p, gs1_p) == allsmall(gs2_p, gs2_p)) {
+                       offset = 0.50 * STEPSIZE;
+               } else {
+                       offset = 0.75 * STEPSIZE;
+               }
+               if (gs2_p->roll != NOITEM)
+                       gs2_p->c[RW] -= ROLLPADDING;
+       } else if (gs1_p->roll != NOITEM && gs2_p->roll == NOITEM) {
+               /* only top group is rolled; it goes on left; its offset < 0 */
+               offset = ( gs2_p->c[RW] - gs1_p->c[RE] ) / 2;
+               gs1_p->c[RW] -= ROLLPADDING;
+       } else if (gs1_p->roll == NOITEM && gs2_p->roll != NOITEM) {
+               /* only bottom is rolled; top goes on right; top's offset > 0 */
+               offset = ( gs2_p->c[RE] - gs1_p->c[RW] ) / 2;
+               gs2_p->c[RW] -= ROLLPADDING;
+       } else {
+               /* either both are rolled or neither is; use other criterion */
+               if (gs1_p->c[RE] - gs2_p->c[RW] <
+                                       gs2_p->c[RE] - gs1_p->c[RW] + 0.1) {
+                       /* top group goes on left; its offset is negative */
+                       offset = ( gs2_p->c[RW] - gs1_p->c[RE] ) / 2;
+                       if (gs1_p->roll != NOITEM)
+                               gs1_p->c[RW] -= ROLLPADDING;
+               } else {
+                       /* top group goes on right; its offset is positive */
+                       offset = ( gs2_p->c[RE] - gs1_p->c[RW] ) / 2;
+                       if (gs2_p->roll != NOITEM)
+                               gs2_p->c[RW] -= ROLLPADDING;
+               }
+       }
+
+       /* apply offset to the groups and any preceding grace groups */
+       shiftgs(gs1_p, offset);
+       shiftgs(gs2_p, -offset);
+}
+\f
+/*
+ * Name:        compat()
+ *
+ * Abstract:    Determine whether two groups in a hand are "compatible".
+ *
+ * Returns:     YES or NO
+ *
+ * Description: This function is given pointers to the two groups in a hand,
+ *             in a situation where they overlap.  The noteptrs array has
+ *             just the top group filled in at this point.  The function
+ *             figures out whether the two groups are compatible (see block
+ *             comment below), or whether they must be drawn separately and
+ *             offset horizontally.  While doing this, it fills in the bottom
+ *             group part of noteptrs.  If it returns YES, this has been
+ *             completed.  If it returns NO, this may be partially done,
+ *             and the caller should clear out the partially complete bot_p
+ *             part of noteptrs.
+ */
+
+static int
+compat(noteptrs, gs1_p, gs2_p)
+
+struct NOTEPTRS noteptrs[];            /* array of ptrs to notes to process */
+register struct GRPSYL *gs1_p, *gs2_p; /* point at groups in this hand */
+
+{
+       int num1;               /* number of notes in top group */
+       register int n, k;      /* loop variables */
+
+
+       num1 = gs1_p->nnotes;
+
+       /*
+        * There is overlap between the two groups.  Try to match the bottom
+        * N notes of the top group with the top N notes of the bottom group.
+        * If all N are "compatible", we can "share" these notes.  For two
+        * groups to be compatible, they must meet the following conditions:
+        *      1) both basic time values must be half notes, or both must be
+        *         shorter than half notes
+        *      2) both have no dots or the same number of dots
+        *      3) the bottom N notes of the top group are the same letters
+        *         and octaves as the top N notes of the bottom group
+        *      4) no two of these N notes can be on neighboring letters
+        *      5) for each of the N pairs, the two notes have no accidental
+        *         or the same accidental
+        *      6) for each of the N pairs, the two notes must have the same
+        *         size and headshape
+        */
+       /* check rule 1 */
+       if (gs1_p->basictime < 2  || gs2_p->basictime < 2)
+               return (NO);
+       if (gs1_p->basictime == 2 && gs2_p->basictime != 2)
+               return (NO);
+       if (gs1_p->basictime != 2 && gs2_p->basictime == 2)
+               return (NO);
+
+       /* check rule 2 */
+       if (gs1_p->dots != gs2_p->dots)
+               return (NO);
+
+       /* check rules 3, 4, 5, and 6 together */
+       /* see if any note in the top group matches the top note in the other*/
+       for (n = 0; n < num1; n++) {
+               if (noteptrs[n].top_p->stepsup == gs2_p->notelist[0].stepsup)
+                       break;
+       }
+       if (n == num1)
+               return (NO);            /* didn't find any match */
+
+       /* starting with this note, verify that it and the rest match */
+       for (k = 0; n < num1; k++, n++) {
+               if (k >= gs2_p->nnotes) /* not enough notes in group 2? */
+                       return (NO);
+               if (gs2_p->notelist[k].stepsup != noteptrs[n].top_p->stepsup)
+                       return (NO);
+               if (k > 0 &&
+               gs2_p->notelist[k-1].stepsup - 1 == gs2_p->notelist[k].stepsup)
+                       return (NO);
+               if (gs2_p->notelist[k].accidental != noteptrs[n].top_p->accidental)
+                       return (NO);
+               if (gs2_p->notelist[k].notesize != noteptrs[n].top_p->notesize)
+                       return (NO);
+               if (gs2_p->notelist[k].headshape != noteptrs[n].top_p->headshape)
+                       return (NO);
+
+               /* this note matches; set up noteptrs */
+               noteptrs[n].bot_p = &gs2_p->notelist[k];
+       }
+
+       /*
+        * The fact that we made it to here means all the overlapping notes
+        * matched.  So fill the rest of group 2's note pointers.
+        */
+       for ( ; k < gs2_p->nnotes; k++, n++)
+               noteptrs[n].bot_p = &gs2_p->notelist[k];
+       /*
+        * It is possible that, although the overlapping notes' headshapes
+        * match, some of the characters are mirrors of each other due to the
+        * opposite stem dir.  In these cases, group 2 rules.  So overwrite the
+        * notes in group 1.  If the lowest note in group 1 has to be changed,
+        * that could affect the RS of group 1, so change that too.
+        * Also, while doing this, if any of these notes or their accs have
+        * parens in one group but not the other, erase those parens.
+        */
+       n -= k;
+       for (k = 0; n < num1; k++, n++) {
+               gs1_p->notelist[n].headchar = gs2_p->notelist[k].headchar;
+               gs1_p->notelist[n].headfont = gs2_p->notelist[k].headfont;
+               gs1_p->notelist[n].c[RN] = gs2_p->notelist[k].c[RN];
+               gs1_p->notelist[n].c[RS] = gs2_p->notelist[k].c[RS];
+
+               if (gs1_p->notelist[n].note_has_paren !=
+                   gs2_p->notelist[k].note_has_paren) {
+                       gs1_p->notelist[n].note_has_paren = NO;
+                       gs2_p->notelist[k].note_has_paren = NO;
+               }
+               if (gs1_p->notelist[n].acc_has_paren !=
+                   gs2_p->notelist[k].acc_has_paren) {
+                       gs1_p->notelist[n].acc_has_paren = NO;
+                       gs2_p->notelist[k].acc_has_paren = NO;
+               }
+       }
+       gs1_p->c[RS] = gs2_p->notelist[k - 1].c[RS];
+
+       return (YES);
+}
+\f
+/*
+ * Name:        can_overlap()
+ *
+ * Abstract:    Decides whether incompatible groups' rectangles can overlap.
+ *
+ * Returns:     YES or NO
+ *
+ * Description: This function is given two incompatible groups in a hand.  It
+ *             decides whether they can be placed such that their rectangles
+ *             overlap.  This arrangement is where the first group is to the
+ *             right of the second group, and the stems are about 3 stepsizes
+ *             apart.  The noteheads must be separated enough vertically so
+ *             that they don't collide, and various other things must also be
+ *             true for this to work.
+ */
+
+static int
+can_overlap(gs1_p, gs2_p)
+
+struct GRPSYL *gs1_p, *gs2_p;  /* point at group(s) in this hand */
+
+{
+       int notedist;           /* steps between two notes (absolute value) */
+       int n, k;               /* loop counters */
+
+
+       /*
+        * First, ensure that no note heads would collide.  We don't yet know
+        * whether any will be on the "wrong" side of their stem.  This is not
+        * too common and would rarely help things, so for now we assume the
+        * worst case, which is that all are on the "correct" side and thus
+        * have the potential of colliding with the other group's notes.
+        */
+       for (n = 0; n < gs1_p->nnotes; n++) {
+               for (k = 0; k < gs2_p->nnotes; k++) {
+                       notedist = abs(gs1_p->notelist[n].stepsup -
+                                      gs2_p->notelist[k].stepsup);
+
+                       /* never allow closer than 2 steps */
+                       if (notedist < 2)
+                               return (NO);
+
+                       /* if either is double whole, don't allow less than 3 */
+                       if ((gs1_p->basictime == 0 || gs2_p->basictime == 0) &&
+                                       notedist < 3)
+                               return (NO);
+               }
+       }
+
+       /* neither group can have slashes */
+       if (gs1_p->slash_alt > 0 || gs2_p->slash_alt > 0)
+               return (NO);
+
+       /* the first group can't have accidentals */
+       for (n = 0; n < gs1_p->nnotes; n++) {
+               if (gs1_p->notelist[n].accidental != '\0')
+                       return (NO);
+       }
+
+       /* the first group can't any preceding grace groups */
+       if (gs1_p->prev != 0 && gs1_p->prev->grpvalue == GV_ZERO)
+               return (NO);
+
+       /* the first group can't have a roll unless the second group has one */
+       if (gs1_p->roll != NOITEM && gs2_p->roll == NOITEM)
+               return (NO);
+
+       /* the second group can't have any dots */
+       if (gs2_p->dots > 0)
+               return (NO);
+
+       /* the second group can't have any flags */
+       if (gs2_p->basictime >= 8 && gs2_p->beamloc == NOITEM)
+               return (NO);
+
+       /* neither group can have a stem forced the "wrong" way */
+       if (gs1_p->stemdir == DOWN || gs2_p->stemdir == UP)
+               return (NO);
+
+       /*
+        * At this point we know we can overlap.
+        */
+       return (YES);
+}
+\f
+/*
+ * Name:        procsome()
+ *
+ * Abstract:    Sets coords for group(s) and their associated grace groups.
+ *
+ * Returns:     void
+ *
+ * Description: This function calls procbunch() to set the horizontal coords
+ *             for the given group(s) and their notes, etc.  Then it calls
+ *             procgrace() to deal with any grace groups preceding these
+ *             group(s) and adjust the main group(s)' west coordinates to.
+ *             contain the grace groups.
+ */
+
+static void
+procsome(noteptrs, mll_p, staff_p, gs1_p, gs2_p)
+
+struct NOTEPTRS noteptrs[];    /* array of ptrs to notes to process */
+struct MAINLL *mll_p;          /* the MLL item the group is connected to */
+struct STAFF *staff_p;         /* the staff the groups are connected to */
+struct GRPSYL *gs1_p, *gs2_p;  /* point at group(s) in this hand */
+
+{
+       /* process the normal group(s) */
+       procbunch(noteptrs, mll_p, staff_p, gs1_p, gs2_p);
+
+       /* process any grace groups preceding first normal group */
+       procgrace(noteptrs, mll_p, staff_p, gs1_p);
+
+       /* process any grace groups preceding second normal group, if exists */
+       if (gs2_p != 0)
+               procgrace(noteptrs, mll_p, staff_p, gs2_p);
+}
+\f
+/*
+ * Name:        procgrace()
+ *
+ * Abstract:    Sets coords for grace groups and adjusts normal group's west.
+ *
+ * Returns:     void
+ *
+ * Description: This function loops leftward from the given normal group,
+ *             calling procbunch() for each grace group, and adjusting the
+ *             normal group's west coordinate accordingly.
+ */
+
+static void
+procgrace(noteptrs, mll_p, staff_p, gsnorm_p)
+
+struct NOTEPTRS noteptrs[];    /* array of ptrs to notes to process */
+struct MAINLL *mll_p;          /* the MLL item the group is connected to */
+struct STAFF *staff_p;         /* the staff the groups are connected to */
+struct GRPSYL *gsnorm_p;       /* point at the normal group to start from */
+
+{
+       struct GRPSYL *gs_p;    /* point at a grace group */
+       struct GRPSYL *right_p; /* point at the group to the right of this */
+       int n;                  /* loop variable */
+
+
+       /*
+        * Loop through any grace groups preceding the normal group, working
+        * right to left.  Call procbunch() for each.  Upon return, set
+        * the grace group's x,e,w relative to the normal group's x, and
+        * alter the west coordinate of the normal group to include them.
+        */
+       right_p = gsnorm_p;
+       for (gs_p = gsnorm_p->prev; gs_p != 0 && gs_p->grpvalue == GV_ZERO;
+                               gs_p = gs_p->prev) {
+               /* clear noteptrs, and resetup for this grace group */
+               /* note:  grace groups are always notes, not rests or spaces */
+               for (n = 0; n < MAXHAND + 1; n++) {
+                       noteptrs[n].top_p = 0;
+                       noteptrs[n].bot_p = 0;
+               }
+               /* set all the "top" group pointers */
+               for (n = 0; n < gs_p->nnotes; n++)
+                       noteptrs[n].top_p = &gs_p->notelist[n];
+
+               procbunch(noteptrs, mll_p, staff_p, gs_p, (struct GRPSYL *)0);
+
+               gs_p->c[RX] = right_p->c[RW] - gs_p->c[RE];
+               gs_p->c[RW] += gs_p->c[RX];
+               gs_p->c[RE] += gs_p->c[RX];
+
+               gsnorm_p->c[RW] = gs_p->c[RW];
+               right_p = gs_p;
+       }
+}
+\f
+/*
+ * Name:        procbunch()
+ *
+ * Abstract:    Sets relative horizontal coords of note heads, accs, & dots.
+ *
+ * Returns:     void
+ *
+ * Description: This function figures out which note heads in the given
+ *             group(s) need to be put on the "wrong" side of the stem to
+ *             avoid overlapping.  Then it sets all note heads' horizontal
+ *             coords.  It calls doacc() to find and store the positions
+ *             for the accidentals, dodot() for the dots.  It sets RW and
+ *             RE for the group(s), also taking flags into consideration.
+ */
+
+/*
+ * This macro checks the n'th structure in noteptrs.  If the top group has
+ * a note there, it returns a pointer to that note, else it returns the
+ * bottom pointer, which may or may not be 0.
+ */
+#define        GETPTR(n)       (noteptrs[n].top_p != 0 ?               \
+                       noteptrs[n].top_p : noteptrs[n].bot_p)
+
+static void
+procbunch(noteptrs, mll_p, staff_p, gs1_p, gs2_p)
+
+struct NOTEPTRS noteptrs[];    /* array of ptrs to notes to process */
+struct MAINLL *mll_p;          /* the MLL item the group is connected to */
+struct STAFF *staff_p;         /* the staff the groups are connected to */
+struct GRPSYL *gs1_p, *gs2_p;  /* point at group(s) in this hand */
+
+{
+       int normhead[MAXHAND + 1];      /* position of note heads */
+       float gwide;                    /* width of any note in these groups */
+       float nwide;                    /* width of a particular note */
+       float maxwide;                  /* max of gwide for the two groups */
+       float ghigh;                    /* height of any note in these groups*/
+       float nhigh;                    /* height of a particular note */
+       float g1wide, g2wide;           /* gwide for the two groups */
+       float maxhigh;                  /* max of ghigh for the two groups */
+       float flagwidth;                /* width of a flag */
+       float rh;                       /* relative horizontal of a note */
+       int collinear;                  /* are the 2 groups' stems collinear? */
+       register int k, n;              /* loop variables */
+       int size;
+
+
+       /*
+        * If this is a tablature staff, call a special function to handle it,
+        * and return.  Voices on tab staffs are handled one at a time, so
+        * gs2_p will never be used for them.
+        */
+       if (is_tab_staff(staff_p->staffno)) {
+               proctab(mll_p, staff_p, gs1_p);
+               return;
+       }
+
+       collinear = NO;                 /* assume not collinear stems */
+
+       /*
+        * "Normal" position of a note head means to the left of the stem
+        * for an upward stem, and right for downward.  When two notes in a
+        * group are on neighboring letters, one of the note heads has to be
+        * in "abnormal" position so that they don't collide.  Shared
+        * note heads must always be in normal position.  (The fact
+        * that no two of them can be on neighboring letters is enforced
+        * when checking for compatibility of groups.)
+        */
+       /*
+        * See if there are any shared notes first.
+        */
+       for (n = 0; noteptrs[n].top_p != 0; n++) {
+               if (noteptrs[n].bot_p != 0)
+                       break;          /* found a shared note */
+       }
+
+       if (noteptrs[n].top_p != 0) {
+               /*
+                * There are shared notes, and n indexes to the first one
+                * (starting from the top).  Set this first one to normal.
+                * First work upwards from there, reversing normality
+                * whenever there are neighboring notes, setting back to
+                * normal otherwise.  Then work downwards from there, doing
+                * the same.
+                */
+               normhead[n] = YES;
+               for (k = n - 1 ; k >= 0; k--) {
+                       if (noteptrs[k+1].top_p->stepsup ==
+                           noteptrs[ k ].top_p->stepsup - 1)
+                               normhead[k] = ! normhead[k+1];
+                       else
+                               normhead[k] = YES;
+               }
+               for (k = n + 1 ; noteptrs[k].bot_p != 0; k++) {
+                       if (noteptrs[k-1].bot_p->stepsup ==
+                           noteptrs[ k ].bot_p->stepsup + 1)
+                               normhead[k] = ! normhead[k-1];
+                       else
+                               normhead[k] = YES;
+               }
+       } else {
+               /*
+                * There are no shared notes.  It may even be that there's only
+                * one group.  In each group, the note that's opposite the stem
+                * must be normal, and then we go down the list of other notes
+                * in the group, reversing normality whenever there are
+                * neighboring notes, and setting back to normal otherwise.
+                * There's a special concern if the bottom note of the top
+                * group is on the neighboring letter to the top note of the
+                * bottom group, or if it is on the same letter.  In that case,
+                * we want to offset the groups slightly, such that their stems
+                * are collinear, so set that flag.
+                */
+               /* the first group's stem could go either way */
+               if (gs1_p->stemdir == UP) {
+                       normhead[n-1] = YES;    /* bottom note normal */
+                       for (k = n - 2; k >= 0; k--) {
+                               if (noteptrs[k+1].top_p->stepsup ==
+                                   noteptrs[ k ].top_p->stepsup - 1)
+                                       normhead[k] = ! normhead[k+1];
+                               else
+                                       normhead[k] = YES;
+                       }
+               } else {        /* stemdir == DOWN */
+                       normhead[0] = YES;      /* top note normal */
+                       for (k = 1; k < n; k++) {
+                               if (noteptrs[k-1].top_p->stepsup ==
+                                   noteptrs[ k ].top_p->stepsup + 1)
+                                       normhead[k] = ! normhead[k-1];
+                               else
+                                       normhead[k] = YES;
+                       }
+               }
+
+               /* the second group's stem (if it exists) must go down */
+               if (gs2_p != 0) {
+                       normhead[n] = YES;      /* top note normal */
+                       for (k = n + 1; noteptrs[k].bot_p != 0; k++) {
+                               if (noteptrs[k-1].bot_p->stepsup ==
+                                   noteptrs[ k ].bot_p->stepsup + 1)
+                                       normhead[k] = ! normhead[k-1];
+                               else
+                                       normhead[k] = YES;
+                       }
+
+                       collinear = (noteptrs[n-1].top_p->stepsup <=
+                                    noteptrs[ n ].bot_p->stepsup + 1);
+               }
+       }
+
+       /*
+        * Set gwide and ghigh to be the biggest values of any note in the top
+        * group, also storing the width of each note for later use.
+        */
+       gwide = ghigh = 0.0;
+       for (n = 0; noteptrs[n].top_p != 0; n++) {
+               size = noteptrs[n].top_p->notesize == GS_NORMAL ?
+                               DFLT_SIZE : SMALLSIZE;
+               nwide = width(noteptrs[n].top_p->headfont, size,
+                               noteptrs[n].top_p->headchar);
+               noteptrs[n].wid = nwide;
+               if (nwide > gwide) {
+                       gwide = nwide;
+               }
+               nhigh = height(noteptrs[n].top_p->headfont, size,
+                               noteptrs[n].top_p->headchar);
+               if (nhigh > ghigh) {
+                       ghigh = nhigh;
+               }
+       }
+
+       /* remember these values, for comparing to the other group (if any) */
+       maxwide = g1wide = gwide;       /* widest group so far */
+       maxhigh = ghigh;                /* highest group so far */
+
+       if (gs1_p->basictime <= 1) {
+               gs1_p->stemx = 0.0;     /* center the imaginary stem */
+       } else {
+               gs1_p->stemx = gs1_p->stemdir == UP ? gwide / 2 : -gwide / 2;
+       }
+
+       for (n = 0; noteptrs[n].top_p != 0; n++) {
+               nwide = noteptrs[n].wid;
+
+               if (normhead[n] == YES) {
+                       /*
+                        * The note head is in normal position, so usually its
+                        * relative x coord is 0, and west and east are half a
+                        * width off.  But if the note is smaller than the
+                        * group's max, and there is a stem, and the note is
+                        * not shared by the other group, the note needs to
+                        * be off center so that it touches the stem.
+                        */
+                       if (nwide != gwide && gs1_p->basictime >= 2 &&
+                                       noteptrs[n].bot_p == 0) {
+                               if (gs1_p->stemdir == UP) {
+                                       noteptrs[n].top_p->c[RE] = gwide / 2;
+                                       noteptrs[n].top_p->c[RX] =
+                                                       gwide / 2 - nwide / 2;
+                                       noteptrs[n].top_p->c[RW] =
+                                                       gwide / 2 - nwide;
+                               } else {        /* DOWN */
+                                       noteptrs[n].top_p->c[RW] = -gwide / 2;
+                                       noteptrs[n].top_p->c[RX] =
+                                                       -gwide / 2 + nwide / 2;
+                                       noteptrs[n].top_p->c[RE] =
+                                                       -gwide / 2 + nwide;
+                               }
+                       } else {
+                               noteptrs[n].top_p->c[RX] = 0;
+                               noteptrs[n].top_p->c[RW] = -nwide / 2;
+                               noteptrs[n].top_p->c[RE] = nwide / 2;
+                       }
+               } else {
+                       /*
+                        * The note head is in abnormal position.  Its relative
+                        * x coord, and west and east, depend on which way the
+                        * stem is going.  Smaller than normal notes need to
+                        * be placed differently regardless of whether stemed.
+                        * In all case, adjust by W_NORMAL*POINT, the width of
+                        * the stem, so that the note overlays the stem.
+                        */
+                       if (nwide != gwide) {
+                               if (gs1_p->stemdir == UP) {
+                                       noteptrs[n].top_p->c[RW] =
+                                               gwide / 2 - W_NORMAL * POINT;
+                                       noteptrs[n].top_p->c[RX] =
+                                               gwide / 2 + nwide / 2
+                                               - W_NORMAL * POINT;
+                                       noteptrs[n].top_p->c[RE] =
+                                               gwide / 2 + nwide
+                                               - W_NORMAL * POINT;
+                               } else {        /* DOWN */
+                                       noteptrs[n].top_p->c[RE] =
+                                               W_NORMAL * POINT - gwide / 2;
+                                       noteptrs[n].top_p->c[RX] =
+                                               W_NORMAL * POINT
+                                               - gwide / 2 - nwide /2;
+                                       noteptrs[n].top_p->c[RW] =
+                                               W_NORMAL * POINT
+                                               - gwide / 2 - nwide;
+                               }
+                       } else {
+                               if (gs1_p->stemdir == UP) {
+                                       noteptrs[n].top_p->c[RX] =
+                                               nwide - W_NORMAL * POINT;
+                                       noteptrs[n].top_p->c[RW] =
+                                               nwide * 0.5 - W_NORMAL * POINT;
+                                       noteptrs[n].top_p->c[RE] =
+                                               nwide * 1.5 - W_NORMAL * POINT;
+                               } else {        /* DOWN */
+                                       noteptrs[n].top_p->c[RX] =
+                                               W_NORMAL * POINT - nwide;
+                                       noteptrs[n].top_p->c[RW] =
+                                               W_NORMAL * POINT - nwide * 1.5;
+                                       noteptrs[n].top_p->c[RE] =
+                                               W_NORMAL * POINT - nwide * 0.5;
+                               }
+                       }
+               }
+       }
+
+       /*
+        * If there is a bottom group, get note head character width for
+        * it, find where in noteptrs that group starts, then loop through
+        * it, setting coords.  While doing this, set the group's
+        * horizontal coords.
+        */
+       g2wide = 0.0;   /* to avoid useless 'used before set' warning */
+       if (gs2_p != 0) {
+               /* skip by notes that are only in the top group */
+               for (n = 0; noteptrs[n].bot_p == 0; n++)
+                       ;
+               /*
+                * Set gwide and ghigh to be the biggest values of any note in
+                * the bottom group, also storing the width of each note for
+                * later use.  If the note is shared between groups, the width
+                * has already been stored in noteptrs[].wid, so we don't have
+                * to recalculate it.
+                */
+               gwide = ghigh = 0.0;
+               for ( ; noteptrs[n].bot_p != 0; n++) {
+                       size = noteptrs[n].bot_p->notesize == GS_NORMAL ?
+                                       DFLT_SIZE : SMALLSIZE;
+                       if (noteptrs[n].wid == 0.0) {
+                               nwide = width(noteptrs[n].bot_p->headfont, size,
+                                               noteptrs[n].bot_p->headchar);
+                               noteptrs[n].wid = nwide;
+                       } else {
+                               nwide = noteptrs[n].wid;
+                       }
+                       if (nwide > gwide) {
+                               gwide = nwide;
+                       }
+                       nhigh = height(noteptrs[n].bot_p->headfont, size,
+                                       noteptrs[n].bot_p->headchar);
+                       if (nhigh > ghigh) {
+                               ghigh = nhigh;
+                       }
+               }
+               g2wide = gwide;
+               if (gs2_p->basictime <= 1) {
+                       gs2_p->stemx = 0.0;     /* center the imaginary stem */
+               } else {
+                       gs2_p->stemx = gs2_p->stemdir == UP ? gwide / 2
+                                                          : -gwide / 2;
+               }
+
+               /* if groups have different note head sizes, adjust maxes */
+               if (gwide > maxwide)
+                       maxwide = gwide;
+               if (ghigh > maxhigh)
+                       maxhigh = ghigh;
+
+               for (n = 0; noteptrs[n].bot_p == 0; n++)
+                       ;
+               for ( ; noteptrs[n].bot_p != 0; n++) {
+                       nwide = noteptrs[n].wid;
+
+                       if (normhead[n] == YES) {
+                               /*
+                                * The note head is in normal position, so its
+                                * relative x coord is 0, and west and east are
+                                * half a width off.  But if the note is smaller
+                                * than the widest note in the group and there
+                                * is a stem, and the note is not shared by the
+                                * other group, the note needs to be off center
+                                * so that it touches the stem.
+                                */
+                               if (nwide != gwide && gs2_p->basictime >= 2 &&
+                                               noteptrs[n].top_p == 0) {
+                                       noteptrs[n].bot_p->c[RW] = -gwide / 2;
+                                       noteptrs[n].bot_p->c[RX] =
+                                                       -gwide / 2 + nwide / 2;
+                                       noteptrs[n].bot_p->c[RE] =
+                                                       -gwide / 2 + nwide;
+                               } else {
+                                       noteptrs[n].bot_p->c[RX] = 0;
+                                       noteptrs[n].bot_p->c[RW] = -nwide * 0.5;
+                                       noteptrs[n].bot_p->c[RE] = nwide * 0.5;
+                               }
+                       } else {
+                               /*
+                                * The note head is in abnormal position.  Its
+                                * relative x coord, and west and east, depend
+                                * on which way the stem is going, but the
+                                * stem must always be down in group 2.  Smaller
+                                * than normal notes need to be placed
+                                * differently regardless of whether stemed.
+                                */
+                               if (nwide != gwide) {
+                                       noteptrs[n].bot_p->c[RE] =
+                                               W_NORMAL * POINT - gwide / 2;
+                                       noteptrs[n].bot_p->c[RX] =
+                                               W_NORMAL * POINT
+                                               - gwide / 2 - nwide /2;
+                                       noteptrs[n].bot_p->c[RW] =
+                                               W_NORMAL * POINT
+                                               - gwide / 2 - nwide;
+                               } else {
+                                       noteptrs[n].bot_p->c[RX] =
+                                               W_NORMAL * POINT - nwide;
+                                       noteptrs[n].bot_p->c[RW] =
+                                               W_NORMAL * POINT - nwide * 1.5;
+                                       noteptrs[n].bot_p->c[RE] =
+                                               W_NORMAL * POINT - nwide * 0.5;
+                               }
+                       }
+               }
+       }
+
+       /* find position of accidentals */
+       doacc(noteptrs, maxwide / 2, maxhigh / 2, collinear);
+
+       /* find position of dots after notes */
+       dodot(staff_p, gs1_p, gs2_p, maxwide / 2, collinear);
+
+       /* find position of right parentheses around notes */
+       noterparen(noteptrs, gs1_p, gs2_p, maxwide/2, maxhigh/2, collinear);
+
+       /*
+        * Set RX for the group(s) to 0 for now if stems are offset (the
+        * normal case), or to the appropriate value if stems are collinear.
+        * If we only have one group it will thus be set to 0 now, though
+        * later, if there's an incompatible group next to it, this coord
+        * and all others will be adjusted.
+        */
+       if (collinear) {
+               gs1_p->c[RX] = (W_NORMAL * POINT - maxwide) / 2;
+               gs2_p->c[RX] = (maxwide - W_NORMAL * POINT) / 2;
+       } else {
+               gs1_p->c[RX] = 0;
+               if (gs2_p != 0)
+                       gs2_p->c[RX] = 0;
+       }
+
+       /*
+        * Set the western boundaries for the group(s).
+        */
+       /*
+        * Init the group's RW to 0.  Then loop through the notes, finding the
+        * westernmost thing associated with a note, and leaving the group's RW
+        * set to that.
+        */
+       gs1_p->c[RW] = 0;
+       for (k = 0; k < gs1_p->nnotes; k++) {
+               rh = notehorz(gs1_p, &gs1_p->notelist[k], RW);
+               if (rh < gs1_p->c[RW])
+                       gs1_p->c[RW] = rh;
+       }
+       /*
+        * If the stem is down on a half note or shorter that is to have
+        * slashes through its stem, make sure there is room for the slashes.
+        */
+       if (gs1_p->slash_alt > 0 && gs1_p->stemdir == DOWN &&
+                       gs1_p->basictime >= 2) {
+               gwide = g1wide;
+               /* if position of stem minus slash room < current west . . . */
+               if (-gwide / 2 - SLASHPAD < gs1_p->c[RW])
+                       gs1_p->c[RW] = -gwide / 2 - SLASHPAD;
+       }
+       westwith(gs1_p);                /* expand RW for "with" list if needbe*/
+       gs1_p->c[RW] -= gs1_p->padding; /* add user requested padding */
+
+       /* add the pad parameter that user wants for this voice */
+       gs1_p->c[RW] -= vvpath(gs1_p->staffno, gs1_p->vno, PAD)->pad;
+
+       csbstempad(mll_p, gs1_p);       /* cross staff beaming may need space */
+       gs1_p->c[RW] += gs1_p->c[RX];   /* shift by RX, in case RX isn't 0 */
+
+       /*
+        * If group 2 exists, do the same for it.  However, in the slash
+        * section, we know the stem must be down, so no need to check that.
+        */
+       if (gs2_p != 0) {
+               gs2_p->c[RW] = 0;
+               for (k = 0; k < gs2_p->nnotes; k++) {
+                       rh = notehorz(gs2_p, &gs2_p->notelist[k], RW);
+                       if (rh < gs2_p->c[RW])
+                               gs2_p->c[RW] = rh;
+               }
+               if (gs2_p->slash_alt > 0 && gs2_p->basictime >= 2) {
+                       gwide = g2wide;
+                       /* if pos of stem minus slash room < current west . .*/
+                       if (-gwide / 2 - SLASHPAD < gs2_p->c[RW])
+                               gs2_p->c[RW] = -gwide / 2 - SLASHPAD;
+               }
+               westwith(gs2_p);
+               gs2_p->c[RW] -= gs2_p->padding;
+               gs2_p->c[RW] -= vvpath(gs2_p->staffno, gs2_p->vno, PAD)->pad;
+               csbstempad(mll_p, gs2_p);
+               gs2_p->c[RW] += gs2_p->c[RX];
+       }
+
+       /*
+        * Set the eastern boundaries for the group(s).
+        */
+       /*
+        * Init the group's RE to 0.  Then loop through the notes, finding the
+        * easternmost thing associated with a note, and leaving the group's RE
+        * set to that.
+        */
+       gs1_p->c[RE] = 0;
+       for (k = 0; k < gs1_p->nnotes; k++) {
+               rh = notehorz(gs1_p, &gs1_p->notelist[k], RE);
+               if (rh > gs1_p->c[RE])
+                       gs1_p->c[RE] = rh;
+       }
+       /*
+        * Add in any padding needed for ties, slurs, and bends.  Also add room
+        * for alternations if there are any.
+        */
+       gs1_p->c[RE] += tieslurpad(staff_p, gs1_p);
+       if (gs1_p->slash_alt < 0 && gs1_p->beamloc == STARTITEM)
+               gs1_p->c[RE] += ALTPAD;
+       /*
+        * If the stem is up and a flag is needed, and the east boundary
+        * doesn't yet contain it, adjust the east boundary so the flag will
+        * fit.
+        */
+       if (gs1_p->stemdir == UP && gs1_p->basictime >= 8 &&
+                               gs1_p->beamloc == NOITEM) {
+               flagwidth = width(FONT_MUSIC, gs1_p->grpsize == GS_NORMAL ?
+                       DFLT_SIZE : SMALLSIZE, C_UPFLAG);
+               if (gs1_p->notelist[0].c[RE] + flagwidth > gs1_p->c[RE])
+                       gs1_p->c[RE] = gs1_p->notelist[0].c[RE] + flagwidth;
+       }
+       /*
+        * If the stem is up on a half note or shorter that is to have slashes
+        * through its stem, make sure there's room for the slashes.
+        */
+       if (gs1_p->slash_alt > 0 && gs1_p->stemdir == UP &&
+                       gs1_p->basictime >= 2) {
+               gwide = g1wide;
+               /* if position of stem plus slash room > current east . . . */
+               if (gwide / 2 + SLASHPAD > gs1_p->c[RE])
+                       gs1_p->c[RE] = gwide / 2 + SLASHPAD;
+       }
+       /*
+        * Expand RE some more if need be to accommodate the "with" list.  Then
+        * shift it over by RX, in case RX isn't 0.
+        */
+       eastwith(gs1_p);
+       gs1_p->c[RE] += gs1_p->c[RX];
+
+       /*
+        * If group 2 exists, do the same for it.  However, the stem is always
+        * down, so any flags will always already fit.  For the same reason,
+        * slashes don't need to be considered.
+        */
+       if (gs2_p != 0) {
+               gs2_p->c[RE] = 0;
+               for (k = 0; k < gs2_p->nnotes; k++) {
+                       rh = notehorz(gs2_p, &gs2_p->notelist[k], RE);
+                       if (rh > gs2_p->c[RE])
+                               gs2_p->c[RE] = rh;
+               }
+               gs2_p->c[RE] += tieslurpad(staff_p, gs2_p);
+               if (gs2_p->slash_alt < 0 && gs2_p->beamloc == STARTITEM)
+                       gs2_p->c[RE] += ALTPAD;
+               eastwith(gs2_p);
+               gs2_p->c[RE] += gs2_p->c[RX];
+       }
+}
+\f
+/*
+ * Name:        doacc()
+ *
+ * Abstract:    Finds horizontal position for each accidental in group(s).
+ *
+ * Returns:     void
+ *
+ * Description: This function loops through all the accidentals belonging
+ *             to notes in the group(s) it is given.  It figures out where
+ *             to place them horizontally to avoid overlap, and stores the
+ *             relative west coord of each in NOTE.waccr.  For each group,
+ *             it uses the appropriate size of accidentals (based on normal
+ *             versus cue/grace), and places them appropriately, considering
+ *             also the size of the notes.  However, if there are two groups,
+ *             the note head sizes could be different.  The halfwide and
+ *             halfhigh passed in are supposed to be the right size for the
+ *             bigger of the two sizes, and accidentals will not be packed
+ *             as tightly against the other notes.  This doesn't hurt, and
+ *             isn't worth the trouble to do it "right".
+ *
+ *             This function takes into account parentheses around accidentals.
+ *             Its algorithm treats them as part of the accidental.  Also, when
+ *             there are parentheses around the note, it handles the left
+ *             parentheses the same way:  if there is also an accidental, it
+ *             treats it as part of it; otherwise the paren is handled like an
+ *             accidental itself.
+ */
+
+/* this fudge factor prevents roundoff error from causing overlap */
+#define        FUDGE           (.01)
+
+/* when CSS applies to a note or acc, move it by this much */
+#define CSS_OFF                (CSS_STEPS * STEPSIZE)
+
+static void
+doacc(noteptrs, halfwide, halfhigh, collinear)
+
+struct NOTEPTRS noteptrs[];    /* array of ptrs to notes to process */
+double halfwide;               /* half of max of width & height of (notes */
+double halfhigh;               /*  in group 1, notes in group 2) */
+int collinear;                 /* are stems collinear? */
+
+{
+       /*
+        * Each structure in this table represents either a note head that
+        * is farther left than normal, or an accidental.  A note head
+        * could be too far left for one of two reasons: either it was
+        * forced to be on the left ("wrong") side of a stem that points
+        * down, or it is a normal note in the top group when the stems are
+        * collinear.  In the collinear case, to make this function easier,
+        * we start out regarding the bottom group as being normal, and
+        * the top group as being shifted left one note head, and we figure
+        * everything relative to the bottom group.  But at the end we adjust
+        * waccr so that every accidental is relative to its own group, like
+        * it's supposed to be.
+        *
+        * The coordinates define the rectangle that surrounds the note or acc,
+        * including standard padding, even on note heads, which don't
+        * normally have padding.  First the notes are put into this table;
+        * then the accidentals, one at a time, making sure they don't
+        * overlap things already in the table.
+        * To see if the accidental being added overlaps, first its north
+        * and south are tested.  All previous rectangles that are "out of
+        * its way" vertically are marked not "relevant"; the others are
+        * marked "relevant".  As positions are tried, right to left, positions
+        * that fail to avoid overlap are marked "tried".
+        *
+        * After the correct position is found for an accidental, there is a
+        * special case for flats and double flats to take advantage of their
+        * shape and let them pack tighter.
+        */
+       struct {
+               float n, s, e, w;       /* boundaries of a rectangle */
+               short relevant;         /* is rectangle relevant? */
+               short tried;            /* have we tried this one yet? */
+       } rectab[2 * MAXHAND + 1];      /* enough for all notes & accidentals*/
+
+       struct NOTE *note_p;            /* point at a note */
+       int reclim;                     /* index after last rectangle in tab */
+       float north, south, east, west; /* relative coords of new accidental */
+       float accasc, accdesc;          /* ascent & descent of accidental */
+       float accwidth;                 /* width of new accidental */
+       float parenwidth;               /* width of note's left parenthesis */
+       float parenv;                   /* half the vertical size of paren */
+       float totwidth;                 /* width of acc plus paren */
+       int overlap;                    /* does our acc overlap existing ones*/
+       int try;                        /* which element of rectab to try */
+       int found;                      /* accs/parens found so far */
+       int k, j;                       /* loop variables */
+       int size;
+       float horfn, verfn;             /* horz & vert flat/nat notch sizes */
+       float savehorfn;                /* save original horfn */
+
+
+       reclim = 0;                     /* table initially empty */
+
+       /*
+        * Loop through noteptrs, finding all notes that are left of normal
+        * position, entering them in rectab.  Include padding around them.
+        * First loop through all notes, finding ones that are on the left
+        * side of a down stem; then, if stems are collinear, loop through
+        * the top group, finding all normal notes.
+        */
+       for (k = 0; (note_p = GETPTR(k)) != 0; k++) {
+               if (note_p->c[RX] < 0) {
+                       rectab[reclim].n = note_p->c[RY] + halfhigh + STDPAD;
+                       rectab[reclim].s = note_p->c[RY] - halfhigh - STDPAD;
+                       rectab[reclim].e = note_p->c[RE] + STDPAD;
+                       rectab[reclim].w = note_p->c[RW] - STDPAD;
+                       if (note_p->stepsup >= CSS_STEPS / 2) {
+                               rectab[reclim].n += CSS_OFF;
+                               rectab[reclim].s += CSS_OFF;
+                       } else if (note_p->stepsup <= -CSS_STEPS / 2) {
+                               rectab[reclim].n -= CSS_OFF;
+                               rectab[reclim].s -= CSS_OFF;
+                       }
+                       reclim++;
+               }
+       }
+       if (collinear) {
+               for (k = 0; (note_p = noteptrs[k].top_p) != 0; k++) {
+                       if (note_p->c[RX] == 0) {
+                               rectab[reclim].n = note_p->c[RY] + halfhigh
+                                               + STDPAD;
+                               rectab[reclim].s = note_p->c[RY] - halfhigh
+                                               - STDPAD;
+                               rectab[reclim].e = W_NORMAL * POINT
+                                               - halfwide + STDPAD;
+                               rectab[reclim].w = W_NORMAL * POINT
+                                               - 3 * halfwide - STDPAD;
+                               if (note_p->stepsup >= CSS_STEPS / 2) {
+                                       rectab[reclim].n += CSS_OFF;
+                                       rectab[reclim].s += CSS_OFF;
+                               } else if (note_p->stepsup <= -CSS_STEPS / 2) {
+                                       rectab[reclim].n -= CSS_OFF;
+                                       rectab[reclim].s -= CSS_OFF;
+                               }
+                               reclim++;
+                       }
+               }
+       }
+
+       /* prevent false "may be used before set" lint warning */
+       verfn = savehorfn = 0.0;
+
+       /*
+        * Loop through all notes, find the ones with accs or parens.  Find
+        * where the accs and parens will fit, storing that info in waccr, and
+        * adding them to rectab.  Call a function so that we loop in the
+        * proper order.
+        */
+       for (found = 0, k = nextacc(noteptrs, found);  k != -1;
+                               found++, k = nextacc(noteptrs, found)) {
+               note_p = GETPTR(k);
+               /* get dimensions of accidental if there is one */
+               if (note_p->accidental != '\0') {
+                       accdimen(note_p, &accasc, &accdesc, &accwidth);
+               } else {
+                       accwidth = accasc = accdesc = 0.0;
+               }
+               /* get dimensions of note's left paren, if there is one */
+               if (note_p->note_has_paren == YES) {
+                       size = (note_p->notesize == GS_NORMAL ?
+                                       DFLT_SIZE : SMALLSIZE);
+                       parenwidth = width(FONT_TR, size, '(');
+                       parenv = height(FONT_TR, size, '(') / 2.0;
+               } else {
+                       parenwidth = parenv = 0.0;
+               }
+               /* set the north, south, and width of what we have found */
+               north = note_p->c[RY] + MAX(accasc, parenv);
+               south = note_p->c[RY] - MAX(accdesc, parenv);
+               if (note_p->stepsup >= CSS_STEPS / 2) {
+                       north += CSS_OFF;
+                       south += CSS_OFF;
+               } else if (note_p->stepsup <= -CSS_STEPS / 2) {
+                       north -= CSS_OFF;
+                       south -= CSS_OFF;
+               }
+               totwidth = accwidth + parenwidth;
+
+               /*
+                * For each rectangle in rectab, decide whether (based on
+                * its vertical coords) it could possibly overlap with our
+                * new accidental.  If it's totally above or below 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].s + FUDGE > north ||
+                           rectab[j].n < south + FUDGE)
+                               rectab[j].relevant = NO;
+                       else
+                               rectab[j].relevant = YES;
+               }
+
+               /*
+                * Mark that none of the relevant rectangles' boundaries have
+                * been tried yet for positioning our acc.
+                */
+               for (j = 0; j < reclim; j++) {
+                       if (rectab[j].relevant == YES)
+                               rectab[j].tried = NO;
+               }
+
+               /*
+                * Set up first trial position for this acc., just to the
+                * left of normal notes, allowing padding.
+                */
+               east = - halfwide - STDPAD;
+               west = east - totwidth;
+
+               /*
+                * Keep trying positions for this acc, working right to
+                * left.  When we find one that doesn't overlap an existing
+                * rectangle, break.  This has to succeed at some point,
+                * at the leftmost rectangle position if not earlier.
+                */
+               for (;;) {
+                       overlap = NO;
+                       for (j = 0; j < reclim; j++) {
+                               /* ignore ones too far north or south */
+                               if (rectab[j].relevant == NO)
+                                       continue;
+
+                               /* if all west or east, okay; else overlap */
+                               if (rectab[j].w + FUDGE <= east &&
+                                   rectab[j].e >= west + 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 eastermost relevant west rectangle boundary
+                        * that hasn't been tried already, to use as the next
+                        * trial position for our acc's east.
+                        */
+                       try = -1;
+                       for (j = 0; j < reclim; j++) {
+                               /* ignore ones too far north or south */
+                               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 east than the
+                                * easternmost so far, save it as being the
+                                * new easternmost so far.
+                                */
+                               if (try == -1 || rectab[j].w > rectab[try].w)
+                                       try = j;
+                       }
+
+                       if (try == -1)
+                               pfatal("bug in doacc()");
+
+                       /*
+                        * Mark this one as having been tried (for next time
+                        * around, if necessary).  Set new trial values for
+                        * east and west of our acc.
+                        */
+                       rectab[try].tried = YES;
+                       east = rectab[try].w;
+                       west = east - totwidth;
+
+               } /* end of while loop trying positions for this acc */
+
+               /*
+                * We found the correct position for the new acc.  However, for
+                * flats, double flats & nats, we would like a notch to be taken
+                * out of the upper right corner of their rectangle, in effect,
+                * since there's nothing there but white space.  This can only
+                * be done if the acc is not already right next to the group.
+                */
+               if (note_p->accidental == '&' || note_p->accidental == 'B' ||
+                                       note_p->accidental == 'n') {
+                       /* get notch size; if paren, add width to horz */
+                       if (note_p->accidental == 'n') {
+                               horfn = 1.4 * STEPSIZE; /* horizontal notch */
+                               verfn = 1.6 * STEPSIZE; /* vertical notch */
+                       } else {
+                               horfn = 1.5 * STEPSIZE; /* horizontal notch */
+                               verfn = 2.8 * STEPSIZE; /* vertical notch */
+                       }
+                       if (note_p->notesize == GS_SMALL) {
+                               horfn *= SM_FACTOR;
+                               verfn *= SM_FACTOR;
+                       }
+                       if (note_p->acc_has_paren) {
+                               size = (note_p->notesize == GS_NORMAL ?
+                                               DFLT_SIZE : SMALLSIZE);
+                               horfn += width(FONT_TR, size, ')');
+                       }
+                       savehorfn = horfn;      /* may need it later */
+                       /*
+                        * If notch width is bigger than the max possible dist
+                        * we could move the acc (we would overwrite the note),
+                        * reduce it to be the space available.
+                        */
+                       if (horfn > - east - halfwide - STDPAD)
+                               horfn = - east - halfwide - STDPAD;
+
+                       /* only attempt the shift if > 0 width available */
+                       if (horfn > 0.0) {
+                               /*
+                                * The useable notch size is horfn by verfn.
+                                * We'd like to move the acc to the right by
+                                * horfn.  We can only do this if the space is
+                                * unoccupied that is immediately to the right
+                                * of the acc, of width = horfn and height =
+                                * (height of acc) - verfn.  (If only part of
+                                * that space is available, we won't bother
+                                * trying to use it.)  So check whether any
+                                * existing rectangle overlaps that space.
+                                */
+                               overlap = NO;
+                               for (j = 0; j < reclim; j++) {
+                                       if (rectab[j].s + FUDGE <= north - verfn &&
+                                           rectab[j].n - FUDGE >= south &&
+                                           rectab[j].w + FUDGE <= east + horfn &&
+                                           rectab[j].e - FUDGE >= east) {
+                                               overlap = YES;
+                                               break;
+                                       }
+                               }
+                               /*
+                                * If the space is free, move the acc to the
+                                * right by HORFN.
+                                */
+                               if (overlap == NO) {
+                                       west += horfn;
+                                       east += horfn;
+                               } else {
+                                       /*
+                                        * All right, let's try again with 1/2
+                                        * of the previous horfn.
+                                        */
+                                       horfn /= 2.0;
+                                       overlap = NO;
+                                       for (j = 0; j < reclim; j++) {
+                                               if (rectab[j].s + FUDGE <= north - verfn &&
+                                                   rectab[j].n - FUDGE >= south &&
+                                                   rectab[j].w + FUDGE <= east + horfn &&
+                                                   rectab[j].e - FUDGE >= east) {
+                                                       overlap = YES;
+                                                       break;
+                                               }
+                                       }
+                                       if (overlap == NO) {
+                                               west += horfn;
+                                               east += horfn;
+                                       }
+                               }
+                       }
+               }
+
+               /*
+                * We have the final position for the new acc.  Enter it into
+                * rectab.  But for naturals, we don't want to reserve the
+                * lower left corner, where there is nothing but white space;
+                * so in that case, put two overlapping entries in rectab to
+                * account for the rest of the space.  Naturals are symmetrical,
+                * so we can use the same horfn and verfn as were calculated
+                * above for the upper right corner.
+                */
+               if (note_p->accidental == 'n') {
+                       /* upper part of natural */
+                       rectab[reclim].n = north;
+                       rectab[reclim].s = south + verfn;
+                       rectab[reclim].e = east;
+                       rectab[reclim].w = west;
+                       reclim++;
+
+                       /* right hand part of natural */
+                       rectab[reclim].n = north;
+                       rectab[reclim].s = south;
+                       rectab[reclim].e = east;
+                       rectab[reclim].w = west + savehorfn;
+               } else {
+                       /* some other accidental; reserve the whole rectangle*/
+                       rectab[reclim].n = north;
+                       rectab[reclim].s = south;
+                       rectab[reclim].e = east;
+                       rectab[reclim].w = west;
+               }
+               reclim++;
+
+               /*
+                * Store the acc's west in waccr in the NOTE structure for
+                * whichever groups have this note.  Store wlparen when there
+                * is a left paren on the note.
+                */
+               if (noteptrs[k].top_p != 0) {
+                       if (note_p->note_has_paren == YES)
+                               noteptrs[k].top_p->wlparen = west;
+                       if (note_p->accidental != '\0')
+                               noteptrs[k].top_p->waccr = west + parenwidth;
+               }
+               if (noteptrs[k].bot_p != 0) {
+                       if (note_p->note_has_paren == YES)
+                               noteptrs[k].bot_p->wlparen = west;
+                       if (note_p->accidental != '\0')
+                               noteptrs[k].bot_p->waccr = west + parenwidth;
+               }
+
+       } /* end of loop for each accidental */
+
+       /*
+        * Finally, if the stems were collinear, we have to adjust waccr for
+        * all the notes of the top group, so that it's relative to the top
+        * group instead of the bottom group.
+        */
+       if (collinear) {
+               for (k = 0; noteptrs[k].top_p != 0; k++) {
+                       if (noteptrs[k].top_p->note_has_paren == YES)
+                               noteptrs[k].top_p->wlparen += 2 * halfwide
+                                               - W_NORMAL * POINT;
+                       if (noteptrs[k].top_p->accidental != '\0')
+                               noteptrs[k].top_p->waccr += 2 * halfwide
+                                               - W_NORMAL * POINT;
+               }
+       }
+}
+\f
+/*
+ * Name:       nextacc()
+ *
+ * Abstract:   Find the next note that has an accidental to be processed.
+ *
+ * Returns:    Index to the NOTE, or -1 if no more.
+ *
+ * Description:        This function is called by doacc(), to return in the correct
+ *             order the notes that have accidentals to be processed.
+ *             (Actually, a note is to be processed not only if it has an
+ *             accidental, but also if it has parentheses.)  The first time in
+ *             here, count is 0, and it looks for the first eligible note (top
+ *             down).  The next time, count is 1, and it looks for the bottom-
+ *             most eligible note.  After that, it goes through the inner
+ *             notes, top down.  In the great majority of cases, this will
+ *             result in the most desirable packing of accidentals.
+ */
+
+static int
+nextacc(noteptrs, found)
+
+struct NOTEPTRS noteptrs[];    /* array of ptrs to notes to process */
+int found;                     /* no. of accidentals found already */
+
+{
+       struct NOTE *note_p;    /* point at a note */
+       static int previdx;     /* idx to note chosen the last time in here */
+       static int lastidx;     /* idx to the bottommost note chosen */
+       int n;                  /* loop counter */
+
+
+       /*
+        * If this is the first call for this group(s), find the topmost
+        * eligible note.
+        */
+       if (found == 0) {
+               for (n = 0; (note_p = GETPTR(n)) != 0; n++) {
+                       if (note_p->accidental != '\0' ||
+                                       note_p->note_has_paren == YES) {
+                               previdx = n;    /* remember it for next time */
+                               return (n);
+                       }
+               }
+               return (-1);    /* no notes have acc or parens */
+       }
+
+       /*
+        * If this is the second call, find the bottom of the list, then look
+        * backwards for the last eligible note.  Stop before finding the first
+        * note again.
+        */
+       if (found == 1) {
+               /* find the slot beyond the last note */
+               for (n = 0; (note_p = GETPTR(n)) != 0; n++) {
+                       ;
+               }
+               /* search from last note going backwards */
+               for (n-- ; n > previdx; n--) {
+                       note_p = GETPTR(n);
+                       if (note_p->accidental != '\0' ||
+                                       note_p->note_has_paren == YES) {
+                               lastidx = n;    /* remember it for next time */
+                               return (n);
+                       }
+               }
+               return (-1);    /* only 1 note has acc or parens */
+       }
+
+       /*
+        * Third or later call:  Scan inner notes top to bottom.
+        */
+       for (n = previdx + 1; n < lastidx; n++) {
+               note_p = GETPTR(n);
+               if (note_p->accidental != '\0' ||
+                               note_p->note_has_paren == YES) {
+                       previdx = n;
+                       return (n);
+               }
+       }
+       return (-1);            /* all eligible notes were already found */
+}
+\f
+/*
+ * Name:        dodot()
+ *
+ * Abstract:    Finds horizontal and vertical positions of dots.
+ *
+ * Returns:     void
+ *
+ * Description: This function figures out the limitations on where dots
+ *             can be put, for each group, and calls dogrpdot() for each
+ *             group that has dots, to figure their positions.
+ */
+
+static void
+dodot(staff_p, gs1_p, gs2_p, halfwide, collinear)
+
+struct STAFF *staff_p;         /* the staff the groups are connected to */
+register struct GRPSYL *gs1_p, *gs2_p; /* point at group(s) in this hand */
+double halfwide;                       /* half of max of width of notes */
+int collinear;                         /* are stems collinear? */
+
+{
+       /* the highest and lowest values of steps above the middle staff */
+       /* line that a dot is allowed to be for the given group */
+       int uppermost, lowermost;
+
+       int lowtopidx;          /* index to lowest note of top group */
+       int push;               /* steps to protruding note */
+       register int k;         /* loop variable */
+
+
+       lowtopidx = gs1_p->nnotes - 1;  /* for convenience */
+
+       /*
+        * For each group that needs dots, set the outer limits of where
+        * they are allowed.  If the other group doesn't need dots, we
+        * have to be careful to keep them out of its way.  Otherwise,
+        * don't worry about that; let them fall on top of each other if
+        * that would happen.
+        */
+
+       /*
+        * If the first group needs dots, find out how high and low they are
+        * allowed to be.  Also find out if nearby notes in the other group
+        * could be in the way of dots.  Call dogrpdot() with this info to
+        * find their positions.
+        */
+       if (gs1_p->dots > 0) {
+               /* upper limit is always as described above */
+               uppermost = gs1_p->notelist[0].stepsup;
+               if (uppermost % 2 == 0)         /* line note */
+                       uppermost++;
+
+               /* set lower limit as if no other group */
+               lowermost = gs1_p->notelist[lowtopidx].stepsup;
+               if (lowermost % 2 == 0)         /* line note */
+                       lowermost--;
+
+               /* but adjust if the other group exists & would interfere */
+               if (gs2_p != 0 && gs2_p->dots == 0 || collinear) {
+                       if (lowermost <= gs2_p->notelist[0].stepsup)
+                               lowermost += 2;
+               }
+
+               /*
+                * If the stems are collinear, bottom group notes that are
+                * in normal position for that group protrude to the right
+                * relative to the top group.  From top down, search for notes
+                * in the bottom group that are like this.  Set push to the
+                * first one.  If none are found, let push be 1000 to be out of
+                * the way.  In setting horizontal dot positions, dogrpdot()
+                * needs to know this.
+                */
+               push = 1000;
+               if ( gs2_p != 0 && collinear ) {
+                       for (k = 0; k < gs2_p->nnotes; k++) {
+                               if (gs2_p->notelist[k].c[RX] == 0) {
+                                       push = gs2_p->notelist[k].stepsup;
+                                       break;
+                               }
+                       }
+               }
+
+               /* do top group's dots */
+               dogrpdot(staff_p, gs1_p, (struct GRPSYL *)0, halfwide,
+                               uppermost, lowermost, push);
+       }
+
+       /*
+        * If the second group exists and needs dots, find out how high and
+        * low they are allowed to be, and find their positions.
+        */
+       if (gs2_p != 0 && gs2_p->dots > 0) {
+               /* set upper limit as if no other group */
+               uppermost = gs2_p->notelist[0].stepsup;
+               if (uppermost % 2 == 0)         /* line note */
+                       uppermost++;
+
+               /* but adjust if the other group would interfere */
+               if (gs1_p->dots == 0 || collinear) {
+                       if (uppermost >= gs1_p->notelist[lowtopidx].stepsup)
+                               uppermost -= 2;
+               }
+
+               /* lower limit is always as described above */
+               lowermost = gs2_p->notelist[ gs2_p->nnotes - 1 ].stepsup;
+               if (lowermost % 2 == 0)         /* line note */
+                       lowermost--;
+
+               /*
+                * Unless the stems are collinear, in which case no problem,
+                * from bottom up, search for notes in the top group that
+                * protrude towards the right.  Set push to the first one.
+                * If none are found, let push be 1000 to be out of the way.
+                * In setting horizontal dot positions, dogrpdot() needs to
+                * know this.
+                */
+               push = 1000;
+               if ( ! collinear ) {
+                       for (k = lowtopidx; k >= 0; k--) {
+                               if (gs1_p->notelist[k].c[RX] > 0) {
+                                       push = gs1_p->notelist[k].stepsup;
+                                       break;
+                               }
+                       }
+               }
+
+               /* do bottom group's dots */
+               dogrpdot(staff_p, gs2_p, gs1_p, halfwide, uppermost, lowermost,
+                               push);
+       }
+}
+\f
+/*
+ * Name:        dogrpdot()
+ *
+ * Abstract:    Finds horizontal and vertical positions of dots for one group.
+ *
+ * Returns:     void
+ *
+ * Description: This function loops through all the notes belonging to the
+ *             given group, setting the coords of the dots relative to it.
+ */
+
+/* recover dotsteps from ydotr, avoiding roundoff error */
+#define        DOTSTEPS(ydotr) (                               \
+       ydotr > 0.0 ?                                   \
+               (int)((ydotr + 0.001) / STEPSIZE)       \
+       :                                               \
+               -(int)((-ydotr + 0.001) / STEPSIZE)     \
+)
+
+static void
+dogrpdot(staff_p, gs_p, ogs_p, halfwide, uppermost, lowermost, push)
+
+struct STAFF *staff_p;         /* the staff the groups are connected to */
+register struct GRPSYL *gs_p;  /* point at group */
+struct GRPSYL *ogs_p;          /* if we're doing group 1 and 2 together, and
+                                * gs_p is group 2, ogs_p is group 1, else 0 */
+double halfwide;               /* half of max of width of notes */
+int uppermost;                 /* highest step where a dot is permitted */
+int lowermost;                 /* lowest step where a dot is permitted */
+int push;                      /* avoid protruding note at this position */
+
+{
+       float dotwidth;         /* width of a dot (includes padding) */
+       int normhorz;           /* use normal horizontal dot position? */
+       int notesteps;          /* steps note is above center line of staff */
+       int dotsteps;           /* steps dot is above center line of staff */
+       register int n, k;      /* loop variables */
+
+
+       /* until proven otherwise, assume normal horizontal dot position */
+       normhorz = YES;
+
+       /*
+        * The rules for vertical positioning of dots are as follows.
+        * For space notes, dots will be put in the same space.  For line
+        * notes we'd like them to be in the space directly above, except for
+        * voice 2 in vscheme=2o,3o or 2f,3f when voice 1 is not space, in
+        * which case we'd like them to be in the space below.  But if notes in
+        * a group are jammed onto neighboring steps, we may need to put some
+        * line note dots on the space below regardless; and we may
+        * even have to let some dots land on top of each other.  But in
+        * any case, never exceed the uppermost/lowermost bounds, which
+        * would interfere with the other group.
+        *
+        * The rules for horizontal positioning of dots are as follows.
+        * If the note on the dot's space, or either neighboring line,
+        * is in abnormal position to the right, the dot must be put
+        * farther right than normal.  The parameter "push" is the nearest
+        * note from the other group that protrudes this way.  And the dots
+        * of all the notes have to line up, so if any one has this problem,
+        * they must all be moved.
+        */
+
+       /*
+        * Loop through all notes in the group, setting dot positions.  At
+        * the top of the loop, "dotsteps" is the previous dot, but by the
+        * end it gets set to the current dot.
+        */
+       dotsteps = uppermost + 2;       /* pretend previous dot was here */
+
+       for (n = 0; n < gs_p->nnotes; n++) {
+
+               notesteps = gs_p->notelist[n].stepsup;
+
+               if (notesteps % 2 == 0) {
+                       /*
+                        * This note is on a line.  If the dot cannot be put
+                        * above the line, or if doing that would overlay the
+                        * previous dot and we are allowed to put it below
+                        * the line, then put it below the line.  Else, put
+                        * it above the line.  Notice that we're putting the
+                        * dot in the space above if at all possible; later on,
+                        * we'll make adjustments for voice 2 if appropriate.
+                        */
+                       if (notesteps + 1 > uppermost ||
+                          (notesteps + 1 == dotsteps &&
+                           notesteps - 1 >= lowermost)) {
+                               dotsteps = notesteps - 1;
+                       } else {
+                               dotsteps = notesteps + 1;
+                       }
+               } else {
+                       /*
+                        * This note is on a space.  The dot must be put in
+                        * this same space, regardless of anything else.
+                        */
+                       dotsteps = notesteps;
+               }
+
+               /* set relative y coord based on step position */
+               gs_p->notelist[n].ydotr = dotsteps * STEPSIZE;
+
+               /*
+                * Now see if this dot forces abnormal positioning.  "Push" may
+                * indicate a protruding note in the other group.  If this
+                * note is within 1 step of our dot, use abnormal positioning
+                * for the dot.  Else if the stem is down, all dots can be
+                * normal.  Else, we have to search for protruding notes to
+                * see where the dot can be.
+                */
+               if (normhorz == YES) {
+                       if (abs(dotsteps - push) <= 1) {
+                               normhorz = NO;
+                       } else if (gs_p->stemdir == UP) {
+                               for (k = 0; k < gs_p->nnotes; k++) {
+                                       notesteps = gs_p->notelist[k].stepsup;
+
+                                       if (gs_p->notelist[k].c[RE] >halfwide &&
+                                           notesteps <= dotsteps + 1 &&
+                                           notesteps >= dotsteps - 1) {
+
+                                               normhorz = NO;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /*
+        * Set horizontal dot positions, relative to the group.  STDPAD is
+        * needed because notehead characters don't include padding.  The
+        * abnormal case adds in one more notehead width, minus the width
+        * of the stem.  Since the dots for all notes line up vertically,
+        * xdotr is in GRPSYL instead of in each NOTE.
+        */
+       dotwidth = width(FONT_MUSIC, DFLT_SIZE, C_DOT);
+       gs_p->xdotr = halfwide + STDPAD + dotwidth / 2;
+       if (normhorz == NO) {
+               gs_p->xdotr += 2 * halfwide - W_NORMAL * POINT;
+       }
+
+       /*
+        * If this is voice 2, we may need to adjust the vertical position of
+        * nonshared line notes.  The same should happen if this is voice 3
+        * "standing in" for voice 2.
+        */
+       if (gs_p->pvno == 2) {
+               int trymove;            /* try to move dots? */
+               int vscheme;            /* voice scheme */
+               RATIONAL vtime;         /* time so far in this measure */
+               int prevdotsteps;       /* Y distance of prev note's dot */
+               struct GRPSYL *pgs_p;   /* point along GRPSYL list */
+               int onotesteps;         /* lowest note of voice 1 */
+
+               trymove = NO;           /* first assume leave them alone */
+               vscheme = svpath(gs_p->staffno, VSCHEME)->vscheme;
+               if (vscheme == V_2OPSTEM || vscheme == V_3OPSTEM) {
+                       /* always try to move if 2o or 3o */
+                       trymove = YES;
+               } else {
+                       /* 2f or 3f; move iff voice 1 is not all spaces here */
+                       vtime = Zero;   /* add up time of preceding groups */
+                       for (pgs_p = gs_p->prev; pgs_p != 0;
+                                       pgs_p = pgs_p->prev) {
+                               vtime = radd(vtime, pgs_p->fulltime);
+                       }
+                       if ( ! hasspace(staff_p->groups_p[0], vtime,
+                                       radd(vtime, gs_p->fulltime))) {
+                               /* not all space during duration of our group*/
+                               trymove = YES;
+                       }
+               }
+
+               if (trymove == YES) {
+                       /*
+                        * We need to try to move the dots of line notes from
+                        * the space above them to the space below them.  We
+                        * will work from bottom to top.  Initially, pretend
+                        * that the previous note is way low out of the way.
+                        * If a voice 1 group was being handled along with our
+                        * group, find the stepsup of its lowest note.
+                        */
+                       prevdotsteps = -1000;
+                       if (ogs_p != 0) {
+                               onotesteps = ogs_p->notelist[
+                                               ogs_p->nnotes - 1].stepsup;
+                       } else {
+                               onotesteps = 0; /* for lint; set before used */
+                       }
+                       for (n = gs_p->nnotes - 1; n >= 0; n--) {
+                               notesteps = gs_p->notelist[n].stepsup;
+                               /*
+                                * We want to stop if we run into notes shared
+                                * by group 1 if it exists.  ( > is defensive).
+                                */
+                               if (ogs_p != 0 && notesteps >= onotesteps)
+                                       break;
+                               /*
+                                * Recover our dotsteps from our dots coord
+                                * calculated earlier in this function.  Then,
+                                * consider moving our dot only if we are a
+                                * line note and our dot is currently in the
+                                * space above.  (It could already be below,
+                                * do to tightly packed notes.)
+                                */
+                               dotsteps = DOTSTEPS(gs_p->notelist[n].ydotr);
+                               if (notesteps % 2 == 0 &&
+                                               dotsteps == notesteps + 1) {
+                                       /*
+                                        * If the previous (lower) note is at
+                                        * least 2 steps away, we can certainly
+                                        * move our dot.  But also move it if
+                                        * we are the top note of group 2, and
+                                        * group 1 exists and has a note 2 steps
+                                        * away, and they don't have a dot at
+                                        * the same horz position; because our
+                                        * dot would be confusing if above.  If
+                                        * it make our dot land on top of the
+                                        * previous note's dot, tough.
+                                        */
+                                       if (prevdotsteps < notesteps - 1 ||
+                                           n == 0 && ogs_p != 0 &&
+                                           notesteps + 2 == onotesteps &&
+                                           ogs_p->xdotr != gs_p->xdotr) {
+
+                                               dotsteps -= 2;
+                                               gs_p->notelist[n].ydotr -=
+                                                       2.0 * STEPSIZE;
+                                       }
+                               }
+                               prevdotsteps = dotsteps;
+                       }
+               }
+       }
+}
+\f
+/*
+ * Name:        westwith()
+ *
+ * Abstract:    Adjust west coord of a group to allow for its "with" lists.
+ *
+ * Returns:     void
+ *
+ * Description: This function is given a GRPSYL whose relative horizontal
+ *             coords are set, relative to the center of the group, except
+ *             that "with" lists have not yet been considered.  It alters
+ *             gs_p->c[RW] if need be so that the group's rectangle includes
+ *             all "with" lists.
+ */
+
+static void
+westwith(gs_p)
+
+struct GRPSYL *gs_p;           /* point at this group */
+
+{
+       int n;                  /* loop through the "with" list items */
+       int font, size;         /* of the chars in the "with" list item */
+       int first_char;         /* first char of string to print */
+       char *str_p;            /* point into the item */
+       float x_offset;         /* half the width of the first char in item */
+
+
+       for (n = 0; n < gs_p->nwith; n++) {
+               /* should center first character on x */
+               font = gs_p->withlist[n][0];
+               size = gs_p->withlist[n][1];
+               str_p = gs_p->withlist[n] + 2;
+               first_char = next_str_char(&str_p, &font, &size);
+               x_offset = width(font, size, first_char) / 2.0;
+               if (-x_offset < gs_p->c[RW])
+                       gs_p->c[RW] = -x_offset;
+       }
+}
+\f
+/*
+ * Name:        eastwith()
+ *
+ * Abstract:    Adjust east coord of a group to allow for its "with" lists.
+ *
+ * Returns:     void
+ *
+ * Description: This function is given a GRPSYL whose relative horizontal
+ *             coords are set, relative to the center of the group, except
+ *             that "with" lists have not yet been considered.  It alters
+ *             gs_p->c[RE] if need be so that the group's rectangle includes
+ *             all "with" lists.
+ */
+
+static void
+eastwith(gs_p)
+
+struct GRPSYL *gs_p;           /* point at this group */
+
+{
+       int n;                  /* loop through the "with" list items */
+       int font, size;         /* of the chars in the "with" list item */
+       int first_char;         /* first char of string to print */
+       char *str_p;            /* point into the item */
+       float x_offset;         /* half the width of the first char in item */
+
+
+       for (n = 0; n < gs_p->nwith; n++) {
+               /* should center first character on x */
+               font = gs_p->withlist[n][0];
+               size = gs_p->withlist[n][1];
+               str_p = gs_p->withlist[n] + 2;
+               first_char = next_str_char(&str_p, &font, &size);
+               x_offset = strwidth(gs_p->withlist[n]) -
+                               width(font, size, first_char) / 2.0;
+               if (x_offset > gs_p->c[RE])
+                       gs_p->c[RE] = x_offset;
+       }
+}
+\f
+/*
+ * Name:        csbstempad()
+ *
+ * Abstract:    Pad a group's RW for cross staff beaming if need be.
+ *
+ * Returns:     void
+ *
+ * Description: In cross staff beamed groups, where the beams are between the
+ *             staffs, and a note on the bottom staff is followed by a note on
+ *             the top staff, and the first note has no dots or anything else
+ *             that would force more space after it, and the top note has no
+ *             accidentals, graces, or anything that would force more space
+ *             before it, the stems of the two groups can be very close
+ *             together, too close.  This function checks for that case, and
+ *             when found, adds padding to the left of the top group.
+ */
+
+static void
+csbstempad(mll_p, gs_p)
+
+struct MAINLL *mll_p;          /* the MLL item the group is connected to */
+struct GRPSYL *gs_p;           /* point at the top staff's group */
+
+{
+       struct GRPSYL *gs2_p;           /* point at various GRPSYLs */
+       struct CHORD *ch_p, *pch_p;     /* our chord and preceding chord */
+       struct MAINLL *m2_p;            /* loop through MLL */
+       int k;                          /* loop through notelist */
+       int found;                      /* have we found our group? */
+
+
+       /* if this group is not a candidate for this, return */
+       if (gs_p->beamto != CS_BELOW)   /* must be CSB beamed with below */
+               return;
+       if (gs_p->stemdir == UP)        /* stem must be down */
+               return;
+       if (gs_p->beamloc == STARTITEM) /* must not be first item in CSB */
+               return;
+       if (gs_p->prev == 0)            /* (defensive) */
+               return;
+       if (gs_p->prev->grpcont != GC_SPACE)    /* prev must be a space */
+               return;
+
+       /*
+        * The notes should all have the same RW (even cues) unless a note is
+        * on the "wrong" side of the stem, because they are all supposed to
+        * touch the stem.  In the latter case, there's already enough space in
+        * the group to the left of the stem, so return.
+        */
+       for (k = 1; k < gs_p->nnotes; k++) {
+               if (ABSDIFF(gs_p->notelist[k].c[RW], gs_p->notelist[0].c[RW])
+                               > FUDGE)
+                       return;
+       }
+
+       /*
+        * If there's anything to the left of the notes' RWs (the stem
+        * position), it should be enough space, so return.
+        */
+       if (gs_p->c[RW] < gs_p->notelist[0].c[RW] - STDPAD - FUDGE)
+               return;
+
+       /* find the chord headcell for this measure */
+       for (m2_p = mll_p->prev; m2_p->str != S_CHHEAD; m2_p = m2_p->prev)
+               ;
+       /*
+        * Loop through the chords.  For each chord, loop through all its
+        * groups, trying to find our group.  It should be found.  At the point
+        * it is found, pch_p will point to the chord preceding the one that
+        * contains our group.
+        */
+       found = NO;
+       pch_p = 0;      /* to avoid useless 'used before set' warning */
+       for (ch_p = m2_p->u.chhead_p->ch_p; ch_p != 0;
+                               pch_p = ch_p, ch_p = ch_p->ch_p) {
+               for (gs2_p = ch_p->gs_p; gs2_p != 0; gs2_p = gs2_p->gs_p) {
+                       if (gs2_p == gs_p) {
+                               found = YES;
+                               break;
+                       }
+               }
+               if (found == YES)
+                       break;
+       }
+       if (found == NO)        /* defensive; this should never happen */
+               return;
+
+       /* find next visible staff after our staff */
+       for (m2_p = mll_p->next; m2_p->str == S_STAFF &&
+                       m2_p->u.staff_p->visible == NO; m2_p = m2_p->next)
+               ;
+       if (m2_p->str != S_STAFF)       /* defensive; should not happen */
+               return;
+
+       /*
+        * Loop down the preceding chord, looking for a group that is on the
+        * next visible staff after our staff and is CSB'ed to the staff above.
+        */
+       for (gs2_p = pch_p->gs_p; gs2_p != 0; gs2_p = gs2_p->gs_p) {
+
+               if (gs2_p->staffno == m2_p->u.staff_p->staffno &&
+                               gs2_p->beamto == CS_ABOVE) {
+                       /*
+                        * We found such a group; it must be the only one.
+                        * Check that it meets the conditions.
+                        */
+                       if (gs2_p->stemdir == DOWN)
+                               return;
+                       /*
+                        * The notes need to all have the same RE, analogous to
+                        * the earlier check on gs_p's RW.
+                        */
+                       for (k = 1; k < gs2_p->nnotes; k++) {
+                               if (ABSDIFF(gs2_p->notelist[k].c[RE], gs2_p->
+                                               notelist[0].c[RE]) > FUDGE)
+                                       return;
+                       }
+                       /*
+                        * If there's anything to the right of the notes' REs,
+                        * there's already enough space.
+                        */
+                       if (gs2_p->c[RE] > gs2_p->notelist[0].c[RE] +
+                                       STDPAD + FUDGE)
+                               return;
+
+                       /*
+                        * FINALLY!  We have established the need for more
+                        * space.  Append it to our group's RW.
+                        */
+                       gs_p->c[RW] -= STEPSIZE;
+                       return;
+               }
+       }
+
+       /* didn't find one; shouldn't happen, but just return */
+}
+\f
+/*
+ * Name:        proctab()
+ *
+ * Abstract:    Sets relative horizontal coords of fret numbers.
+ *
+ * Returns:     void
+ *
+ * Description: This function sets all the horizontal coords of "notes" on a
+ *             tablature staff, which are actually fret numbers.  It sets RW
+ *             and RE for the group, too.  They also take bends into account.
+ */
+
+static void
+proctab(mll_p, staff_p, gs_p)
+
+struct MAINLL *mll_p;          /* the MLL item the group is connected to */
+struct STAFF *staff_p;         /* the staff the group is connected to */
+struct GRPSYL *gs_p;           /* point at this group */
+
+{
+       int n;                  /* loop through the "notes" in the group */
+       float halfwide;         /* half the width of a fret or bend number */
+       float maxhalffret;      /* half the max width of a fret number */
+       float maxhalfbend;      /* half the max width of a bend number */
+       float maxbend;          /* width of a bend number that sticks right */
+       struct GRPSYL *prevgs_p;/* point at previous group */
+       int center;             /* should bend string be centered? */
+       int k;                  /* loop variable */
+
+
+       maxhalffret = 0.0;
+       maxhalfbend = 0.0;
+       maxbend = 0.0;
+
+       prevgs_p = prevgrpsyl(gs_p, &mll_p);    /* in case we need it */
+
+       /* loop though all frets and bends in this group */
+       for (n = 0; n < gs_p->nnotes; n++) {
+               /*
+                * If there is a fret, find half the width of that number.  It
+                * should be centered on the center of the group.  Keep track
+                * of the maximum width so far.  Allow 1.5*STDPAD on each side
+                * of the fret number, since we don't ever want the numbers so
+                * close that they look like one number.
+                */
+               if (gs_p->notelist[n].FRETNO != NOFRET) {
+                       halfwide = strwidth(fret_string(&gs_p->notelist[n],
+                                       gs_p)) / 2.0;
+                       gs_p->notelist[n].c[RX] = 0.0;
+                       gs_p->notelist[n].c[RE] = halfwide;
+                       gs_p->notelist[n].c[RW] = -halfwide;
+                       maxhalffret = MAX(halfwide + 1.5*STDPAD, maxhalffret);
+               }
+
+               /*
+                * If there is a bend, figure out if it's the normal situation
+                * (centered on the group's X) or the the case where its left
+                * edge should be at the group's X (the case of a continuation
+                * bend where the previous group's bend was higher).  In the
+                * latter case, the string had to be shifted to avoid colliding
+                * with the arrow coming down from the previous group.
+                */
+               if (HASREALBEND(gs_p->notelist[n])) {
+                       center = YES;   /* first assume normal */
+
+                       /* search previous group, if any, for a bend */
+                       if (prevgs_p != 0) {
+                               for (k = 0; k < prevgs_p->nnotes; k++) {
+                                       if (HASREALBEND(prevgs_p->notelist[k]))
+                                               break;
+                               }
+                               /*
+                                * If previous group had a bend and its
+                                * distance was higher than the current group,
+                                * we have the special case.
+                                */
+                               if (k < prevgs_p->nnotes &&
+                                   GT( ratbend(&prevgs_p->notelist[k]),
+                                   ratbend(&gs_p->notelist[n]) ) ) {
+                                       center = NO;
+                               }
+                       }
+                       if (center == YES) {
+                               /*
+                                * Normal case of a bend string: centered at
+                                * group's X.  Maintain maxhalfbend as the
+                                * the widest so far.
+                                */
+                               halfwide = strwidth(bend_string(
+                                               &gs_p->notelist[n])) / 2.0;
+                               maxhalfbend = MAX(halfwide, maxhalfbend);
+                       } else {
+                               /*
+                                * A bend string that has its left edge at the
+                                * group's X.  There can only be one such,
+                                * since multiple continuation bends are not
+                                * allowed (other than releases).
+                                */
+                               maxbend = strwidth(bend_string(
+                                               &gs_p->notelist[n]));
+                       }
+               }
+       }
+
+       /*
+        * Set the group's relative horizontal coordinates.  On the east, add
+        * extra room if there are ties or slurs.  On the west, add any user
+        * requested padding.  Also adjust for "with" lists.  They can extend
+        * into tie/slur padding, but not into user requested padding.
+        */
+       gs_p->c[RX] = 0.0;
+
+       gs_p->c[RW] = -MAX(maxhalffret, maxhalfbend);
+       westwith(gs_p);
+       gs_p->c[RW] -= gs_p->padding;
+       gs_p->c[RW] -= vvpath(gs_p->staffno, gs_p->vno, PAD)->pad;
+
+       maxhalffret += tieslurpad(staff_p, gs_p);
+       gs_p->c[RE] = MAX(MAX(maxhalffret, maxhalfbend), maxbend);
+       eastwith(gs_p);
+}
+\f
+/*
+ * Name:        noterparen()
+ *
+ * Abstract:    Finds horizontal position notes' right parentheses.
+ *
+ * Returns:     void
+ *
+ * Description: If any of the notes in the given group(s) are to have
+ *             parentheses around them, this function finds the horizontal
+ *             positions of the right parentheses.  The left ones were done
+ *             in doacc() along with accidentals.  For each group, it uses
+ *             the appropriate size of parentheses (based on normal versus
+ *             cue/grace), and places them appropriately, considering also
+ *             the size of the notes.  However, if there are two groups,
+ *             the note head sizes could be different.  The halfwide and
+ *             halfhigh passed in are supposed to be the right size for the
+ *             bigger of the two sizes, and accidentals will not be packed
+ *             as tightly against the other notes.  This doesn't hurt, and
+ *             isn't worth the trouble to do it "right".
+ */
+
+static void
+noterparen(noteptrs, gs1_p, gs2_p, halfwide, halfhigh, collinear)
+
+struct NOTEPTRS noteptrs[];    /* array of ptrs to notes to process */
+struct GRPSYL *gs1_p, *gs2_p;  /* point at group(s) in this hand */
+double halfwide;               /* half of max of width & height of (notes */
+double halfhigh;               /*  in group 1, notes in group 2) */
+int collinear;                 /* are stems collinear? */
+
+{
+       /*
+        * Each structure in this table represents either a note head that is
+        * farther right than normal, note dot(s), or right paren.  A note head
+        * could be too far right for one of two reasons: either it was
+        * forced to be on the right ("wrong") side of a stem that points
+        * up, or it is a normal note in the bottom group when the stems are
+        * collinear.  In the collinear case, to make this function easier,
+        * we start out regarding the top group as being normal, and
+        * the bottom group as being shifted right one note head, and we figure
+        * everything relative to the top group.  But at the end we adjust
+        * so that every parenthesis is relative to its own group, like
+        * it's supposed to be.
+        *
+        * The coordinates define the rectangle that surrounds the note, dot(s),
+        * or paren, including standard padding, even on note heads, which don't
+        * normally have padding.  First the notes and dots are put into this
+        * table, just one rectangle for a sequence of dots; then the right
+        * parens one at a time, making sure they don't overlap things already
+        * in the table.
+        *
+        * To see if the parenthesis being added overlaps, first its north
+        * and south are tested.  All previous rectangles that are "out of
+        * its way" vertically are marked not "relevant"; the others are
+        * marked "relevant".  As positions are tried, left to right, positions
+        * that fail to avoid overlap are marked "tried".
+        */
+       struct {
+               float n, s, e, w;       /* boundaries of a rectangle */
+               short relevant;         /* is rectangle relevant? */
+               short tried;            /* have we tried this one yet? */
+       } rectab[2 * MAXHAND + 1];      /* enough for all notes & accidentals*/
+
+       struct NOTE *note_p;            /* point at a note */
+       int reclim;                     /* index after last rectangle in tab */
+       int parensexist;                /* does any note have parens? */
+       float north, south, east, west; /* relative coords of new accidental */
+       float parenwidth;               /* width of note's left parenthesis */
+       float parenv;                   /* half the vertical size of paren */
+       float dotoff;                   /* additional offset caused by dots */
+       float dotoff1, dotoff2;         /* same, for groups 1 and 2 */
+       int overlap;                    /* does our acc overlap existing ones*/
+       int try;                        /* which element of rectab to try */
+       int k, j;                       /* loop variables */
+       int size;
+
+
+       /*
+        * If no notes have parentheses, we can get out because there is
+        * nothing to do.
+        */
+       parensexist = NO;               /* init to no parens */
+       for (k = 0; (note_p = GETPTR(k)) != 0; k++) {
+               if (note_p->note_has_paren == YES)
+                       parensexist = YES;
+       }
+       if (parensexist == NO)
+               return;
+
+       reclim = 0;                     /* table initially empty */
+
+       /* set up dot offsets for both groups, zero if no dots */
+       dotoff1 = gs1_p->dots * (width(FONT_MUSIC,DFLT_SIZE,C_DOT) + 2*STDPAD);
+       dotoff2 = 0.0;          /* prevent useless 'used before set' warning */
+       if (gs2_p != 0) {
+               dotoff2 = gs2_p->dots * (width(FONT_MUSIC, DFLT_SIZE, C_DOT) +
+                               2 * STDPAD);
+       }
+
+       /*
+        * Loop through noteptrs, loading rectab with all the things that are
+        * already present that are to the right of the baseline.
+        */
+       for (k = 0; (note_p = GETPTR(k)) != 0; k++) {
+               /*
+                * If note exists in top group, use its dot offset, else use
+                * bottom's.  If it's in both, the results would be the same.
+                */
+               if (noteptrs[k].top_p != 0)
+                       dotoff = dotoff1;
+               else
+                       dotoff = dotoff2;
+
+               /* if note is right of normal position, put it in the table */
+               if (note_p->c[RX] > 0) {
+                       rectab[reclim].n = note_p->c[RY] + halfhigh + STDPAD;
+                       rectab[reclim].s = note_p->c[RY] - halfhigh - STDPAD;
+                       rectab[reclim].e = note_p->c[RE] + STDPAD;
+                       rectab[reclim].w = note_p->c[RW] - STDPAD;
+                       reclim++;
+               }
+
+               /* if collinear, bottom group's notes go into table if normal */
+               if (collinear && noteptrs[k].bot_p != 0) {
+                       if (note_p->c[RX] == 0) {
+                               rectab[reclim].n = note_p->c[RY] + halfhigh
+                                               + STDPAD;
+                               rectab[reclim].s = note_p->c[RY] - halfhigh
+                                               - STDPAD;
+                               rectab[reclim].e = W_NORMAL * POINT
+                                               + 3 * halfwide + STDPAD;
+                               rectab[reclim].w = W_NORMAL * POINT
+                                               + halfwide - STDPAD;
+                               reclim++;
+                       }
+               }
+
+               /* if this group has dots, do rectangle for dots */
+               if (dotoff > 0) {
+                       rectab[reclim].n = note_p->ydotr + STDPAD;
+                       rectab[reclim].s = note_p->ydotr - STDPAD;
+                       if (noteptrs[k].top_p != 0)
+                               rectab[reclim].e = gs1_p->xdotr + dotoff;
+                       else
+                               rectab[reclim].e = gs2_p->xdotr + dotoff;
+                       rectab[reclim].w = 0;
+                       reclim++;
+               }
+       }
+
+       /*
+        * Loop through all parentheses, finding where they will fit, storing
+        * that info in erparen, and adding them to rectab.
+        */
+       for (k = 0; (note_p = GETPTR(k)) != 0; k++) {
+
+               /* if no parens around the note, skip the note */
+               if (note_p->note_has_paren == NO)
+                       continue;
+
+               /* get dimensions of note's right paren */
+               size = (note_p->notesize == GS_NORMAL ? DFLT_SIZE : SMALLSIZE);
+               parenwidth = width(FONT_TR, size, ')');
+               parenv = height(FONT_TR, size, ')') / 2.0;
+
+               /* set the north and south of the paren */
+               north = note_p->c[RY] + parenv;
+               south = note_p->c[RY] - parenv;
+
+               /*
+                * For each rectangle in rectab, decide whether (based on
+                * its vertical coords) it could possibly overlap with our
+                * new paren.  If it's totally above or below 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].s + FUDGE > north ||
+                           rectab[j].n < south + FUDGE)
+                               rectab[j].relevant = NO;
+                       else
+                               rectab[j].relevant = YES;
+               }
+
+               /*
+                * Mark that none of the relevant rectangles' boundaries have
+                * been tried yet for positioning our paren.
+                */
+               for (j = 0; j < reclim; j++) {
+                       if (rectab[j].relevant == YES)
+                               rectab[j].tried = NO;
+               }
+
+               /*
+                * Set up first trial position for this paren, just to the
+                * right of normal notes, allowing padding.
+                */
+               west = halfwide + STDPAD;
+               east = west + parenwidth;
+
+               /*
+                * Keep trying positions for this paren, working left to
+                * right.  When we find one that doesn't overlap an existing
+                * rectangle, break.  This has to succeed at some point,
+                * at the rightmost rectangle position if not earlier.
+                */
+               for (;;) {
+                       overlap = NO;
+                       for (j = 0; j < reclim; j++) {
+                               /* ignore ones too far north or south */
+                               if (rectab[j].relevant == NO)
+                                       continue;
+
+                               /* if all west or east, okay; else overlap */
+                               if (rectab[j].w + FUDGE <= east &&
+                                   rectab[j].e >= west + 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 westermost relevant east rectangle boundary
+                        * that hasn't been tried already, to use as the next
+                        * trial position for our paren's west.
+                        */
+                       try = -1;
+                       for (j = 0; j < reclim; j++) {
+                               /* ignore ones too far north or south */
+                               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 west than the
+                                * westernmost so far, save it as being the
+                                * new westernmost so far.
+                                */
+                               if (try == -1 || rectab[j].e < rectab[try].e)
+                                       try = j;
+                       }
+
+                       if (try == -1)
+                               pfatal("bug in noterparen()");
+
+                       /*
+                        * Mark this one as having been tried (for next time
+                        * around, if necessary).  Set new trial values for
+                        * east and west of our paren.
+                        */
+                       rectab[try].tried = YES;
+                       west = rectab[try].e;
+                       east = west + parenwidth;
+
+               } /* end of while loop trying positions for this acc */
+
+               /*
+                * We have the final position for the new paren.  Enter it into
+                * rectab.  Store its east in erparen in the NOTE structure for
+                * whichever groups have this note.
+                */
+               rectab[reclim].n = north;
+               rectab[reclim].s = south;
+               rectab[reclim].e = east;
+               rectab[reclim].w = west;
+               reclim++;
+               if (noteptrs[k].top_p != 0) {
+                       noteptrs[k].top_p->erparen = east;
+               }
+               if (noteptrs[k].bot_p != 0) {
+                       noteptrs[k].bot_p->erparen = east;
+               }
+
+       } /* end of loop for each accidental */
+
+       /*
+        * Finally, if the stems were collinear, we have to adjust erparen for
+        * all the notes of the bottom group, so that it's relative to the
+        * bottom group instead of the top group.
+        */
+       if (collinear) {
+               for (k = 0; (note_p = GETPTR(k)) != 0; k++) {
+                       if (noteptrs[k].bot_p != 0) {
+                               noteptrs[k].bot_p->erparen -= 2 * halfwide
+                                               - W_NORMAL * POINT;
+                       }
+               }
+       }
+}