chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / prntdata.c
diff --git a/mup/mup/prntdata.c b/mup/mup/prntdata.c
new file mode 100644 (file)
index 0000000..5258a18
--- /dev/null
@@ -0,0 +1,3668 @@
+
+/* Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+/* functions for printing things off of STAFF structs: notes, stems,
+ * rests, flags, beams, etc */
+
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+
+/* This struct is used to build up a mesh that represents cross staff beams.
+ * This is used to figure out how far from the stem end to offset
+ * the end of a beam.
+ * There are a row of these linked horizontally via "next" for each beam.
+ * The stems are linked vertically via the above_p and below_p pointers.
+ * To get the stem offset for a given beam,
+ * the code finds the desired basictime on the appropriate stem,
+ * and counts how many beams that is from the end of the stem.
+ */
+struct CSBINFO {
+       struct CSBINFO *next;           /* for next group in same beam */
+       struct CSBINFO *above_p;        /* beams above this beam */
+       struct CSBINFO *below_p;        /* beams below this beam */
+       struct GRPSYL *gs_p;            /* group this info is associated with.
+                                        * This is only used on the 8th beam,
+                                        * and is just for convenience,
+                                        * to save us from having to figure
+                                        * it out again later.
+                                        */
+       int basictime;                  /* 8, 16, 32, etc represented by beam */
+};
+
+/* static functions */
+static void do_syl_joins P((char *syl, double west, double y));
+static void pr_stuff P((struct STUFF *stufflist_p, int staffno,
+               struct MAINLL *mll_p));
+static int pr_grid P((struct STUFF *stuff_p, int staffnum));
+static void pr_tieslur P((struct STUFF *stuff_p, struct MAINLL *mll_p,
+               int staffno));
+static int get_ts_style P((struct STUFF *stuff_p, struct MAINLL *mll_p));
+static void pr_rest P((struct GRPSYL *gs_p, struct STAFF *staff_p));
+static double mr_y_loc P((int staffno));
+static void pr_note_dots P((struct NOTE *noteinfo_p, int numdots,
+               double xdotr, double group_x, double group_y));
+static void pr_parens P((struct NOTE *note_p, struct GRPSYL *gs_p));
+static void pr_stems P((struct GRPSYL *grpsyl_p));
+static double slash_xlen P((struct GRPSYL *grpsyl_p));
+static void pr_flags P((struct GRPSYL *grpsyl_p, double x, double y));
+static void pr_accidental P((struct NOTE *noteinfo_p, struct GRPSYL *grpsyl_p));
+static void pr_leger P((struct NOTE *noteinfo_p, struct GRPSYL *gs_p,
+               int staffno));
+static int numlegers P((struct NOTE *noteinfo_p));
+static double leger_length P((struct NOTE *noteinfo_p, struct GRPSYL *othergs_p,
+               int lines, int other_is_prev, int is_intermediate));
+static void pr_tupnums P((struct GRPSYL *gs_p, struct STAFF *staff_p));
+static void pr_beams P((struct GRPSYL *grpsyl_p, int grpvalue, int grpsize));
+static struct CSBINFO *mkcsbmesh P((struct GRPSYL *begin_p,
+               struct GRPSYL *end_p));
+static int draw_beams P((struct GRPSYL *gs_p, struct GRPSYL *endbeam_p,
+               int basictime, int grpsize, int grpvalue));
+static double beam_offset P((int nbeams, int gsize, int stemdir));
+static struct GRPSYL *neighboring_note_beam_group P((struct GRPSYL *gs_p,
+               struct GRPSYL *first_p, int backwards) );
+static int chkgroupings P((int *side_p, struct GRPSYL *thisgs_p));
+static void do_beam P((double x1, double y1, double x2, double y2,
+               double halfwidth));
+static void pr_cresc P((struct STUFF *stuff_p));
+static void extend P((struct STUFF *stuff_p));
+static int tupdir1voice P((struct GRPSYL *gs_p));
+static int mirror P((char *str, int ch, int font));
+\f
+
+/* print things off of STAFF struct */
+
+void
+pr_staff(mll_p)
+
+struct MAINLL *mll_p;  /* which main list struct holds the STAFF struct */
+
+{
+       struct STAFF *staff_p;  /* mll_p->u.staff_p */
+       struct GRPSYL *grpsyl_p;/* current grpsyl */
+       struct MAINLL *barmll_p;/* to find TIMEDSSVs */
+       struct TIMEDSSV *tssv_p;/* for mid-measure parameter changes */
+       struct TIMEDSSV *t_p;   /* walk through the mid-measure changes */
+       RATIONAL now;           /* how far we are into measure */
+       char *savedlyr;         /* saved copy of lyric syllable */
+       register int n;         /* index thru notes in a group */
+       struct NOTE *noteinfo_p;/* current note */
+       int otherstaff;         /* staff number for cross-staff stems */
+       int v;                  /* walk through voices or verses on the staff */
+       int size;
+
+
+       debug(512, "pr_staff file=%s lineno=%d staff=%d", mll_p->inputfile,
+                       mll_p->inputlineno, mll_p->u.staff_p->staffno);
+
+       staff_p = mll_p->u.staff_p;
+
+       if ( svpath(staff_p->staffno, VISIBLE)->visible == NO) {
+               /* invisible staffs are easy to print... */
+               return;
+       }
+
+       /* do any syllables */
+       for (v = 0; v < staff_p->nsyllists; v++) {
+
+               /* if bottom staff of "between" lyric is invisible,
+                * the lyric silently disappears from output */
+               if (staff_p->sylplace[v] == PL_BETWEEN &&
+                               svpath(staff_p->staffno + 1, VISIBLE)->visible
+                               == NO) {
+                       continue;
+               }
+
+               if (staff_p->syls_p[v] != (struct GRPSYL *) 0 &&
+                                       staff_p->syls_p[v]->inputlineno > 0) {
+                       /* tell PostScript about user input line reference */
+                       pr_linenum(staff_p->syls_p[v]->inputfile,
+                                       staff_p->syls_p[v]->inputlineno);
+               }
+
+               /* do all syllables for current verse/place */
+               for (grpsyl_p = staff_p->syls_p[v];
+                                       grpsyl_p != (struct GRPSYL *) 0;
+                                       grpsyl_p = grpsyl_p->next) {
+
+                       if ( grpsyl_p->syl != (char *) 0) {
+
+                               /* if <...> before or after syllable that
+                                * were not used for placement, need to
+                                * compensate for that */
+                               lyr_compensate(grpsyl_p);
+
+                               /* Extender printing can alter the lyrics
+                                * string to get rid of the extender so it
+                                * won't print with the syllable. But if we
+                                * are printing pages using -o option we
+                                * may need to have the original
+                                * string preserved, because we may do this
+                                * page again. So make a copy.
+                                */
+                               if ((savedlyr = malloc(strlen(grpsyl_p->syl) + 1))
+                                                       == 0) {
+                                       l_no_mem(__FILE__, __LINE__);
+                               }
+                               strcpy(savedlyr, grpsyl_p->syl);
+                               
+                               /* if syllable ends with a dash or underscore,
+                                * they have to be spread between this syllable
+                                * and the next */
+                               (void) spread_extender(grpsyl_p, mll_p,
+                                               grpsyl_p->vno,
+                                               staff_p->sylplace[v], YES);
+
+                               /* now print the syllable itself */
+                               pr_string(grpsyl_p->c[AW], grpsyl_p->c[AY],
+                                               grpsyl_p->syl, J_LEFT,
+                                               grpsyl_p->inputfile,
+                                               grpsyl_p->inputlineno);
+
+                               /* handle multiple syllables on one chord */
+                               do_syl_joins(grpsyl_p->syl,
+                                               (double) grpsyl_p->c[AW],
+                                               (double) grpsyl_p->c[AY]);
+                               /* if string was altered, put original back */
+                               if (strcmp(grpsyl_p->syl, savedlyr) != 0) {
+                                       FREE(grpsyl_p->syl);
+                                       grpsyl_p->syl = savedlyr;
+                               }
+                               else {
+                                       FREE(savedlyr);
+                               }
+                       }
+               }
+       }
+
+       /* Find the BAR that would point to any TIMEDSSVs for this measure. */
+       for (barmll_p = mll_p->next; barmll_p->str != S_BAR; barmll_p = barmll_p->next) {
+               ;
+       }
+       t_p = tssv_p = barmll_p->u.bar_p->timedssv_p;
+
+       /* do notes, etc for each voice on the staff */
+       for (v = 0; v < MAXVOICES; v++) {
+
+               if (staff_p->groups_p[v] == 0) {
+                       continue;
+               }
+
+               /* tab staff notes are handled differently */
+               if (is_tab_staff(staff_p->staffno) == YES) {
+                       pr_tab_groups(staff_p->groups_p[v], mll_p);
+                       continue;
+               }
+
+               /* Set up to handle mid-measure changes, if any */
+               if (tssv_p != 0) {
+                       setssvstate(mll_p);
+               }
+               t_p = tssv_p;
+               now = Zero;
+
+               /* for each GRPSYL in the list for current voice */
+               for ( grpsyl_p = staff_p->groups_p[v];
+                                       grpsyl_p != (struct GRPSYL *) 0;
+                                       grpsyl_p = grpsyl_p->next) {
+
+                       /* Apply any timed SSVs */
+                       while (t_p != 0 && LE(t_p->time_off, now) ) {
+                               asgnssv(&t_p->ssv);
+                               t_p = t_p->next;
+                       }
+                       now = radd(now, grpsyl_p->fulltime);
+
+                       if (grpsyl_p->clef != NOCLEF) {
+                               float widthclef;
+                               int clefsize;
+                               clefsize = (3 * DFLT_SIZE) / 4;
+                               widthclef = width(FONT_MUSIC, clefsize,
+                                       clefchar(grpsyl_p->clef));
+                               pr_clef(grpsyl_p->staffno,
+                                       grpsyl_p->c[AW] -
+                                       (widthclef + CLEFPAD) * Staffscale,
+                                       YES, clefsize);
+                       }
+                       if (grpsyl_p->grpcont == GC_SPACE) {
+                               /* very easy to print a space -- do nothing! */
+                               continue;
+                       }
+
+                       if (grpsyl_p->grpcont == GC_REST) {
+                               pr_rest(grpsyl_p, staff_p);
+                               continue;
+                       }
+
+                       if (is_mrpt(grpsyl_p) == YES) {
+                               pr_mrpt(grpsyl_p, staff_p);
+                               continue;
+                       }
+
+                       /* If group has a cross-staff stem,
+                        * figure out which is the other staff */
+                       if (grpsyl_p->stemto == CS_ABOVE) {
+                               for (otherstaff = grpsyl_p->staffno - 1;
+                                               otherstaff >= 1; otherstaff--) {
+                                       if (svpath(otherstaff, VISIBLE)->visible
+                                                       == YES) {
+                                               break;
+                                       }
+                               }
+                       }
+                       else if (grpsyl_p->stemto == CS_BELOW) {
+                               for (otherstaff = grpsyl_p->staffno + 1;
+                                               otherstaff <= Score.staffs;
+                                               otherstaff++) {
+                                       if (svpath(otherstaff, VISIBLE)->visible
+                                                       == YES) {
+                                               break;
+                                       }
+                               }
+                       }
+                       else {
+                               otherstaff = grpsyl_p->staffno;
+                       }
+                       if (otherstaff < 1 || otherstaff > Score.staffs) {
+                               pfatal("failed to find other score for cross-staff stems for leger lines");
+                       }
+
+                       /* do each note in the group */
+                       for (n = 0; n < grpsyl_p->nnotes; n++) {
+
+                               size = (grpsyl_p->notelist[n].notesize ==
+                                               GS_NORMAL ? DFLT_SIZE :
+                                               SMALLSIZE);
+
+                               /* we're going to need the NOTE info a lot;
+                                * get its address */
+                               noteinfo_p = &(grpsyl_p->notelist[n]);
+
+                               /* do the note head */
+                               pr_muschar(noteinfo_p->c[AX],
+                                               noteinfo_p->c[AY],
+                                               noteinfo_p->headchar,
+                                               size,
+                                               noteinfo_p->headfont);
+                       
+                               /* do any accidental */
+                               pr_accidental(noteinfo_p, grpsyl_p);
+
+                               /* do any dots */
+                               pr_note_dots(noteinfo_p, grpsyl_p->dots,
+                                               grpsyl_p->xdotr,
+                                               (double) grpsyl_p->c[AX],
+                                               (double) grpsyl_p->c[AY]);
+
+                               /* print parentheses around note if any*/
+                               if (noteinfo_p->note_has_paren == YES) {
+                                       pr_parens(noteinfo_p, grpsyl_p);
+                               }
+
+                               /* print small curve for 1/4 bends */
+                               if (noteinfo_p->smallbend == YES) {
+                                       float adjust;
+
+                                       /* may have to move slightly to avoid
+                                        * flag. This is true if group is an
+                                        * unbeamed, stem-up group of 8th note
+                                        * or shorter duration */
+                                       if (grpsyl_p->basictime >= 8 &&
+                                                       grpsyl_p->stemdir == UP
+                                                       && grpsyl_p->beamloc
+                                                       == NOITEM) {
+                                               adjust = 2.0 * STEPSIZE;
+                                       }
+                                       else {
+                                               adjust = STEPSIZE;
+                                       }
+                                       pr_sm_bend( (double)
+                                               noteinfo_p->c[AE] + adjust,
+                                               (double)
+                                               noteinfo_p->c[AY] + 0.5 * STEPSIZE);
+                               }
+
+                               /* do any leger lines */
+                               if (grpsyl_p->stemto == CS_SAME ||
+                                               (n >= FNNI(grpsyl_p) &&
+                                               n <= LNNI(grpsyl_p) )) {
+                                       pr_leger(noteinfo_p, grpsyl_p,
+                                                       grpsyl_p->staffno);
+                               }
+                               else {
+                                       /* notes are on a different staff */
+                                       pr_leger(noteinfo_p, grpsyl_p,
+                                                       otherstaff);
+                               }
+                       }
+
+                       /* do "with" lists */
+                       pr_withlist(grpsyl_p);
+
+                       /* do stems, flags, slash, and alt */
+                       pr_stems(grpsyl_p);
+
+                       /* print rolls */
+                       if (gets_roll(grpsyl_p, staff_p, v) == YES) {
+                               print_roll(grpsyl_p);
+                       }
+               }
+
+               /* assign anything that happened after start of last group */
+               while (t_p != 0) {
+                       asgnssv(&t_p->ssv);
+                       t_p = t_p->next;
+               }
+
+               /* print tuplet numbers if any */
+               pr_tupnums(staff_p->groups_p[v], staff_p);
+
+               /* draw beams */
+               pr_beams(staff_p->groups_p[v], GV_NORMAL, GS_NORMAL);
+               pr_beams(staff_p->groups_p[v], GV_ZERO, GS_SMALL);
+               pr_beams(staff_p->groups_p[v], GV_NORMAL, GS_SMALL);
+       }
+
+       /* now do any associated STUFFs */
+       pr_stuff(staff_p->stuff_p, staff_p->staffno, mll_p);
+}
+\f
+
+/* if two syllables are to be joined, draw a little curved line between them */
+
+static void
+do_syl_joins (syl, west, y)
+
+char *syl;     /* syllable string */
+double west;   /* where syllable was printed */
+double y;      /* where syllable was printed */
+
+{
+       int font, size;
+       char *p;                        /* pointer into syllable string */
+       float wid;                      /* of syllable up to space */
+       double x, east;                 /* of curved line */
+       double xinc, yinc;              /* increment to move when doing curve */
+       double spacewid;                /* width of ' ' */
+
+
+       int skipover = NO;
+       
+       /* skip past any <...> */
+       font = syl[0];
+       size = syl[1];
+       for (p = syl + 2; *p != '\0'; p++) {
+               switch ( (unsigned) *p & 0xff) {
+               case STR_PRE:
+               case STR_U_PRE:
+               case STR_PST:
+               case STR_U_PST:
+                       skipover = YES;
+                       break;
+               case STR_PRE_END:
+               case STR_PST_END:
+                       skipover = NO;
+                       break;
+               case STR_MUS_CHAR:
+                       p += 2;
+                       break;
+               case STR_FONT:
+                       font = *(p+1);
+                       /*FALLTHRU*/
+               case STR_SIZE:
+               case STR_BACKSPACE:
+               case STR_PAGENUM:
+               case STR_NUMPAGES:
+                       p++;
+                       break;
+               case ' ':
+                       if (skipover == NO && font <= EXT_FONT_OFFSET) {
+                               /* temporarily shorten string to just before
+                                * the space to get width of string up to
+                                * that point */
+                               *p = '\0';
+                               wid = strwidth(syl);
+                               *p = ' ';
+
+                               /* Calculate dimensions
+                                * and location of curve to be drawn. */
+                               spacewid = width(font, size, ' ');
+                               xinc = spacewid * 0.3;
+                               yinc = spacewid * 0.15;
+                               x = west + wid - STDPAD;
+                               east = x + spacewid;
+
+                               do_linetype(L_NORMAL);
+                               do_moveto(x, y);
+                               do_curveto(x + xinc, y - yinc,
+                                       east - xinc, y - yinc, east, y);
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+}
+\f
+
+/* print things in STUFF list */
+
+static void
+pr_stuff (stufflist_p, staffno, mll_p)
+
+struct STUFF *stufflist_p;     /* which list of STUFF */
+int staffno;                   /* which staff the stuff is for */
+struct MAINLL *mll_p;
+
+{
+       char lch;       /* last character in string */
+
+
+       /* do each item in stuff list */
+       for (   ; stufflist_p != (struct STUFF *) 0;
+                                       stufflist_p = stufflist_p->next) {
+
+               set_staffscale( (stufflist_p->all == YES) ? 0 : staffno);
+
+               switch (stufflist_p->stuff_type) {
+
+               case ST_MUSSYM:
+               case ST_OCTAVE:
+               case ST_ROM:
+               case ST_BOLD:
+               case ST_ITAL:
+               case ST_BOLDITAL:
+                       /* do 'til' clause if any */
+                       extend(stufflist_p);
+
+                       /* if special case of ending in ~ or _, don't print the
+                        * ~ or _ itself */
+                       if ((lch = last_char(stufflist_p->string)) == '~' ||
+                                       lch == '_') {
+                               stufflist_p->string[strlen(stufflist_p->string)
+                                               -1] = '\0';
+                       }
+
+                       /* print the string at specified place */
+                       if (stufflist_p->string != (char *) 0) {
+
+
+                               /* print grid if appropriate,
+                                * otherwise just the string. */
+                               if (stufflist_p->modifier != TM_CHORD ||
+                                               svpath(staffno, GRIDSWHEREUSED)
+                                               ->gridswhereused == NO ||
+                                               pr_grid(stufflist_p,
+                                               (stufflist_p->all == YES ?
+                                               0 : staffno))
+                                               == NO) {
+                                       pr_string (stufflist_p->c[AW],
+                                               stufflist_p->c[AY],
+                                               stufflist_p->string, J_LEFT,
+                                               stufflist_p->inputfile,
+                                               stufflist_p->inputlineno);
+                               }
+                       }
+
+                       break;
+
+               case ST_CRESC:
+               case ST_DECRESC:
+                       pr_cresc(stufflist_p);
+                       break;
+
+               case ST_PEDAL:
+                       pr_ped_char(stufflist_p, staffno);
+                       break;
+
+               case ST_PHRASE:
+                       pr_phrase(stufflist_p->crvlist_p, stufflist_p->modifier,
+                               (stufflist_p->modifier == L_NORMAL ? YES : NO),
+                               staffno);
+                       break;
+
+               case ST_TIESLUR:
+                       pr_tieslur(stufflist_p, mll_p, staffno);
+                       break;
+               
+               case ST_BEND:
+                       pr_bend(stufflist_p->crvlist_p);
+                       break;
+
+               case ST_TABSLUR:
+                       pr_tabslur(stufflist_p->crvlist_p,
+                                       get_ts_style(stufflist_p, mll_p));
+                       break;
+
+               case ST_MIDI:
+                       break;
+
+               default:
+                       pfatal("unknown stuff type");
+                       break;
+               }
+       }
+}
+\f
+
+/* Print a guitar grid. Return YES if grid was found and printed, else NO. */
+
+static int
+pr_grid(stuff_p, staffnum)
+
+struct STUFF *stuff_p;
+int staffnum;
+
+{
+       struct GRID *grid_p;
+       double space;
+       float north, south;
+
+
+       if ((grid_p = findgrid(stuff_p->string)) == 0) {
+               /* placement phase should have printed a warning already */
+               return(NO);
+       }
+
+       /* print the grid name */
+       pr_string(stuff_p->c[AX] - strwidth(grid_p->name) / 2.0,
+                       stuff_p->c[AY], grid_p->name, J_LEFT,
+                       stuff_p->inputfile, stuff_p->inputlineno);
+
+       space = gridspace(staffnum);
+       gridsize(grid_p, staffnum, &north, &south, (float *) 0, (float *) 0);
+
+       do_grid(stuff_p->c[AX] - space * (grid_p->numstr - 1) / 2.0,
+                       stuff_p->c[AS] - south,
+                       space, grid_p, staffnum);
+       return(YES);
+}
+\f
+
+/* print ties and slurs */
+
+static void
+pr_tieslur(stuff_p, mll_p, staffno)
+
+struct STUFF *stuff_p;
+struct MAINLL *mll_p;
+int staffno;
+
+{
+       int ts_style;           /* tie/slur style (L_DOTTED or L_DASHED) */
+
+
+       ts_style = get_ts_style(stuff_p, mll_p);
+
+       /* If tabslur, do that */
+       if ( stuff_p->curveno >= 0 && stuff_p->begnote_p->nslurto > 0
+                       && IS_NOWHERE(stuff_p-> begnote_p->slurtolist
+                       [stuff_p->curveno].octave)) {
+               pr_tabslur(stuff_p->crvlist_p, ts_style);
+               return;
+       }
+
+       /* print a regular tie/slur curve */
+       pr_phrase(stuff_p->crvlist_p, ts_style,
+                               (ts_style == L_NORMAL ? YES : NO), staffno );
+}
+\f
+
+/* given a TIESLUR STUFF, return the line type to use for it */
+
+static int
+get_ts_style(stuff_p, mll_p)
+
+struct STUFF *stuff_p;
+struct MAINLL *mll_p;
+
+{
+       struct GRPSYL *prevgrp_p;       /* for carryins */
+       int n;                          /* notelist index */
+
+
+       if (stuff_p->carryin == YES) {
+               prevgrp_p = prevgrpsyl(stuff_p->beggrp_p, &mll_p);
+               if (stuff_p->curveno >= 0) {
+                       /* a carried-in slur. Need to find a note
+                        * in previous group that is slurred to this one,
+                        * and use its slurstyle. There is some chance
+                        * that there could be more than one slur to this
+                        * note from the same curveno
+                        * and each slur could have a different style,
+                        * in which case we no longer have enough information
+                        * to know which to use, so we just use the first
+                        * we find. */
+                       for (n = 0; n < prevgrp_p->nnotes; n++) {
+
+                               if (prevgrp_p->notelist[n].nslurto
+                                                       <= stuff_p->curveno) {
+                                       /* couldn't have come from this grp */
+                                       continue;
+                               }
+
+                               if (prevgrp_p->notelist[n].slurtolist
+                                               [stuff_p->curveno].letter
+                                               == stuff_p->begnote_p->letter
+                                               && prevgrp_p->notelist[n]
+                                               .slurtolist[stuff_p->curveno].octave
+                                               == stuff_p->begnote_p->octave) {
+
+                                       return (prevgrp_p->notelist[n].
+                                               slurtolist[stuff_p->curveno]
+                                               .slurstyle);
+                               }
+                       }
+               }
+               else {
+                       /* a carried-in tie. Need to find matching note
+                        * in previous group, and use its tiestyle. */
+                       for (n = 0; n < prevgrp_p->nnotes; n++) {
+                               if (prevgrp_p->notelist[n].letter ==
+                                               stuff_p->begnote_p->letter &&
+                                               prevgrp_p->notelist[n].octave
+                                               == stuff_p->begnote_p->octave) {
+                                       return(prevgrp_p->notelist[n].tiestyle);
+                               }
+                       }
+               }
+       }
+
+       else {
+               if (stuff_p->curveno >= 0) {
+                       /* a non-carried-in slur, use slurstyle */
+                       return(stuff_p->begnote_p->slurtolist
+                                               [stuff_p->curveno].slurstyle);
+               }
+               else {
+                       /* a non-carried-in tie, use tiestyle */
+                       return(stuff_p->begnote_p->tiestyle);
+               }
+       }
+
+       /* if none of those cases applied, use normal */
+       return(L_NORMAL);
+}
+\f
+
+/* print a rest symbol */
+
+static void
+pr_rest(gs_p, staff_p)
+
+struct GRPSYL *gs_p;   /* information about the rest to be printed */
+struct STAFF *staff_p;
+
+{
+       int muschar;    /* which type of rest character to print */
+       int d;          /* number of dots */
+       float adjust;   /* to space dots properly */
+       float y;        /* vertical location of rest */
+       int size;
+
+
+       if (gs_p->basictime < -1) {
+               /* multirest are a special case */
+               pr_multirest(gs_p, staff_p);
+               return;
+       }
+
+       /* draw the rest */
+       muschar = restchar(gs_p->basictime);
+       /* Half and whole rests outside the staff need to use the version
+        * that includes a ledger line. So check for that case. 
+        * We used to use characters with ledgers all the time,
+        * but Ghostscript then sometimes seemed to misplace them
+        * by one pixel at certain magnifications, which looked bad. */
+       if (muschar == C_LL1REST || muschar == C_LL2REST) {
+               double halfst;
+               if (svpath(staff_p->staffno, STAFFLINES)->stafflines > 1) {
+                       halfst = halfstaffhi(staff_p->staffno);
+               }
+               else {
+                       halfst = 0.0;
+               }
+               /* The adjustments to halfst are chosen so that both half
+                * and whole rests will properly get leger lines when they
+                * are outside the staff, but not when inside.
+                */
+               if ( (gs_p->c[AN] > (staff_p->c[AY] + halfst + 1.7 * Stepsize)) ||
+                               (gs_p->c[AN] < (staff_p->c[AY] - halfst - Stdpad)) ) {
+                       muschar = (muschar == C_LL1REST ? C_1REST : C_2REST);
+               }
+       }
+       size = (gs_p->grpsize == GS_NORMAL ? DFLT_SIZE : SMALLSIZE);
+       if (gs_p->is_meas == YES) {
+               /* measure rest is special case, have to move to middle */
+               pr_muschar( (gs_p->c[AW] + gs_p->c[AE]) / 2.0,
+                                       gs_p->c[AY], muschar, size, FONT_MUSIC);
+       }
+       else {
+               pr_muschar(gs_p->c[AX], gs_p->c[AY], muschar, size, FONT_MUSIC);
+       }
+
+       /* get ready to print any dots */
+       adjust = width(FONT_MUSIC, adj_size(size, Staffscale, (char *) 0,
+                               -1), C_DOT) / 2.0;
+       y = _Cur[AY] + Stepsize;
+
+       /* print any dots after the rest */
+       for (d = 0; d < gs_p->dots; d++) {
+               /* each time we print a dot, the current location will get
+                * moved to just beyond that one */
+               pr_muschar(_Cur[AX] + adjust + (2.0 * Stdpad), y, C_DOT, size,
+                                                               FONT_MUSIC);
+       }
+}
+\f
+
+/* print a measure repeat */
+
+void
+pr_mrpt(gs_p, staff_p)
+
+struct GRPSYL *gs_p;
+struct STAFF *staff_p;
+
+{
+       double x;               /* horizontal position of number string */
+       double y, y_offset;     /* vertical location */
+       double height, width;   /* of meas num string */
+       char *numstr;           /* ASCII version of numbers of measures */
+
+
+       /* measure repeat has to be moved to the middle of the measure */
+       pr_muschar( (gs_p->c[AW] + gs_p->c[AE]) / 2.0,
+               mr_y_loc(gs_p->staffno), C_MEASRPT, DFLT_SIZE, FONT_MUSIC);
+
+       if (svpath(gs_p->staffno, NUMBERMRPT)->numbermrpt == YES) {
+               /* print number above the staff */
+               y = Staffs_y[gs_p->staffno];
+               numstr = mrnum(staff_p, &x, &y_offset, &height, &width);
+               pr_string(x, y + y_offset, numstr, J_LEFT, (char *) 0, -1);
+       }
+}
+\f
+
+/* given a staff number, return the y at which to print the measure repeat
+ * or multirest symbols. If the number of staff lines is odd, this is the
+ * middle line, otherwise the line just above the middle. */
+
+static double
+mr_y_loc(staffno)
+
+int staffno;
+
+{
+       double y;
+
+       y = Staffs_y[staffno];
+       /* if even number of staff lines, move up a stepsize */
+       if ( (svpath(staffno, STAFFLINES)->stafflines & 1) == 0) {
+               y += Stepsize * (is_tab_staff(staffno) ? TABRATIO : 1.0);
+       }
+       return(y);
+}
+\f
+
+/* print the dots for dotted notes */
+
+static void
+pr_note_dots(noteinfo_p, numdots, xdotr, group_x, group_y)
+
+struct NOTE *noteinfo_p;       /* which note to dot */
+int numdots;           /* how many dots to print */
+double xdotr;          /* relative x distance from note to print the dots */
+double group_x;
+double group_y;                /* coord of group, dots are relative to this */
+
+{
+       float adjust;   /* to place dots with proper spacing */
+
+
+       /* if note isn't dotted, nothing to do */
+       if (numdots <= 0) {
+               return;
+       }
+
+       adjust = width(FONT_MUSIC, adj_size(DFLT_SIZE, Staffscale,
+                               (char *) 0, -1), C_DOT) / 2.0;
+
+       /* go to where first dot belongs */
+       set_cur(group_x + xdotr - adjust, group_y + noteinfo_p->ydotr);
+       
+       /* print as many dots as necessary */
+       for (  ; numdots > 0; numdots--) {
+               pr_muschar(_Cur[AX] + adjust + (2.0 * Stdpad),
+                               _Cur[AY], C_DOT, DFLT_SIZE, FONT_MUSIC);
+       }
+}
+\f
+
+/* print parentheses around a note. Should only be called if note_has_paren
+ * is YES */
+
+static void
+pr_parens(note_p, gs_p)
+
+struct NOTE *note_p;
+struct GRPSYL * gs_p;
+
+{
+       char paren_string[4];
+       double y;
+
+
+       /* make a parentheses string of proper size in internal string format */
+       (void) sprintf(paren_string, "%c%c(", FONT_TR,
+               adj_size((note_p->notesize == GS_NORMAL ? DFLT_SIZE : SMALLSIZE),
+                               Staffscale, (char *) 0, -1));
+
+       /* center the parentheses vertically on the Y on the note */
+       y = note_p->c[AY] - (strascent(paren_string)
+                                       - (strheight(paren_string) / 2.0));
+
+       /* print the left parenthesis */
+       pr_string(gs_p->c[AX] + note_p->wlparen, y,
+                               paren_string, J_LEFT, (char *) 0, -1);
+
+       /* now do the right parenthesis */
+       paren_string[2] = ')';
+       pr_string(gs_p->c[AX] + note_p->erparen - strwidth(paren_string), y,
+                               paren_string, J_LEFT, (char *) 0, -1);
+}
+\f
+
+/* print "with" lists */
+
+void
+pr_withlist(gs_p)
+
+struct GRPSYL *gs_p;   /* GRPSYL that might have with lists */
+
+{
+       float y;                /* where to start from */
+       float x;
+       float  y_offset, sign;
+       float x_offset;         /* to center first character of item on note */
+       float yposition;        /* y coordinate at which to print */
+       float item_height;      /* height of with list item */
+       int first_char;         /* first char of string to print */
+       char *str_p;            /* pointer into string to print */
+       int font, size;
+       int index;              /* offset into with list */
+       int alternate;          /* upside version of music symbol */
+       float ystaff;           /* y of middle of staff */
+       float yline;            /* y value of staff line */
+       float top, bot;         /* top and bottom of item to be printed */
+       float pad;              /* vertical padding around short items */
+       int sl;                 /* staff line index */
+       float adjusted_stepsize;        /* STEPSIZE or STEPSIZE * TABRATIO
+                                * depending on whether tab staff or not */
+       int stafflines;         /* how many lines in current staff */
+       float minwithheight;    /* MINWITHHEIGHT * Staffscale */
+
+
+       if (gs_p->nnotes == 0) {
+               return;
+       }
+
+       /* with goes forward from note opposite stem */
+       if (gs_p->normwith == YES) {
+               if (gs_p->stemdir == UP) {
+                       y = gs_p->notelist [gs_p->nnotes - 1] .c[AS];
+                       x = gs_p->notelist [gs_p->nnotes - 1] .c[AX];
+                       sign = -1.0;
+               }
+               else {
+                       y = gs_p->notelist[0].c[AN];
+                       x = gs_p->notelist[0].c[AX];
+                       sign = 1.0;
+               }
+       }
+       else {
+               /* with goes on opposite side than normal */
+               y = find_y_stem(gs_p);
+               if (gs_p->stemdir == DOWN) {
+                       sign = -1.0;
+                       /* whole notes /double wholes may have
+                        * zero length stems so have to adjust */
+                       if (gs_p->stemlen <= 0.0) {
+                               y = gs_p->notelist[gs_p->nnotes - 1] .c[AS];
+                       }
+                       /* beamed notes stems effective stick out a little
+                        * farther, so compensate for that */
+                       if (gs_p->beamloc != NOITEM) {
+                               y -= POINT;
+                       }
+               }
+               else {
+                       sign = 1.0;
+                       if (gs_p->stemlen <= 0.0) {
+                               y = gs_p->notelist[0].c[AN];
+                       }
+                       if (gs_p->beamloc != NOITEM) {
+                               y += POINT;
+                       }
+               }
+               x = gs_p->c[AX];
+       }
+
+       /* If a dot, wedge, and uwedge is the only item in the list,
+        * and it's on the stem side of a group with a stem, it is supposed
+        * to be aligned with the stem. */
+       if (gs_p->normwith == NO && gs_p->nwith == 1 &&
+                               gs_p->basictime > 1 && gs_p->stemlen > 0.0 &&
+                               is_music_symbol(gs_p->withlist[0]) == YES) {
+               font = gs_p->withlist[0][0];
+               size = gs_p->withlist[0][1];
+               str_p = gs_p->withlist[0] + 2;
+               first_char = next_str_char(&str_p, &font, &size);
+               if (first_char == C_DOT || first_char == C_WEDGE ||
+                                       first_char == C_UWEDGE) {
+                       x = find_x_stem(gs_p);
+               }
+       }
+
+       y_offset = 0.0;
+       minwithheight = MINWITHHEIGHT * Staffscale;
+
+       /* do each item in with list */
+       for (index = 0; index < gs_p->nwith; index++) {
+
+               /* should center first character on x */
+               font = gs_p->withlist[index][0];
+               size = gs_p->withlist[index][1];
+               str_p = gs_p->withlist[index] + 2;
+               first_char = next_str_char(&str_p, &font, &size);
+
+               /* get upside down version if necessary */
+               if (sign == -1.0 && IS_MUSIC_FONT(font)) {
+                       if ((alternate = mirror(gs_p->withlist[index],
+                                       first_char, font)) != first_char) {
+                               *(str_p - 1) = (char) alternate;
+                       }
+               }
+               
+               x_offset = left_width( &(gs_p->withlist[index][0]) );
+
+               /* get height of item to print */
+               item_height = strheight(gs_p->withlist[index]);
+
+               /* if string is so short vertically
+                * it could get swallowed up in a staff
+                * line, adjust to fall in a space. Placement phase will have
+                * allowed MINWITHHEIGHT, so put in middle of that area unless
+                * that would fall on a line, in which case move somewhat */
+               if (item_height < minwithheight) {
+                       /* need to adjust this one. Start out by putting in
+                        * middle vertically of reserved area */
+                       yposition = y + y_offset + sign * minwithheight / 2.0;
+
+                       /* no reason to adjust further for 1-line staffs */
+                       if ((stafflines = svpath(gs_p->staffno,
+                                               STAFFLINES)->stafflines) > 1) {
+
+                               /* get stepsize distance based on whether it
+                                * is a tab staff or not */
+                               adjusted_stepsize = (is_tab_staff(gs_p->staffno)
+                                       == YES ? Stepsize * TABRATIO : Stepsize);
+
+                               /* find y of middle of staff */
+                               ystaff = gs_p->notelist[0].c[AY]
+                                       - (gs_p->notelist[0].stepsup
+                                       * adjusted_stepsize);
+
+                               /* take the extra vertical space alloted to this
+                                * with list item, and add 1/4 of it on top
+                                * and bottom as padding. If no staff line is
+                                * in between the boundaries of the item after
+                                * adding that padding, it's good enough where
+                                * it is. Otherwise, if a staff line falls above
+                                * the middle of the item, move the item
+                                * down into space. Otherwise move it
+                                * up into space.
+                                */
+                               pad = (minwithheight - item_height) / 4.0;
+                               top = yposition + (item_height / 2.0) + pad;
+                               bot = yposition - (item_height / 2.0) - pad;
+
+                               /* check each staff line for collisions, from
+                                * bottom to top */
+                               for (sl = -(stafflines - 1);
+                                               sl <= (stafflines - 1);
+                                               sl += 2) {
+
+                                       /* find y of current staff line */
+                                       yline = ystaff + (sl * adjusted_stepsize);
+
+                                       /* check if current staff line goes
+                                        * through the item
+                                        * as currently placed */
+                                       if (yline < top && yline > bot) {
+                                               /* collides--need to move */
+
+                                               if ((top - yline) >
+                                                               (yline - bot)) {
+                                                       /* move up to area
+                                                        * above the line */
+                                                       yposition += 2.0 * pad;
+                                                       /* if overdid the move,
+                                                        * move back a bit */
+                                                       if (yposition - yline -
+                                                       (item_height / 2.0)
+                                                       > 0.7 * adjusted_stepsize) {
+                                                               yposition -=
+                                                                0.4 * adjusted_stepsize;
+                                                       }
+                                               }
+                                               else {
+                                                       /* move down to area
+                                                        * below the line */
+                                                       yposition -= 2.0 * pad;
+                                                       if (yline - yposition -
+                                                       (item_height / 2.0)
+                                                       > 0.7 * adjusted_stepsize) {
+                                                               yposition +=
+                                                                0.4 * adjusted_stepsize;
+                                                       }
+                                               }
+
+                                               /* only 1 staff line can
+                                                * possibly interfere,
+                                                * and we've found that one, so
+                                                * can jump out of loop */
+                                               break;
+                                       }
+                               }
+                       }
+
+                       /* adjust y_offset to include the area taken by item */
+                       y_offset += minwithheight * sign;
+
+                       /* up to now, we've been using the center of the item,
+                        * so now adjust to baseline */
+                       if (sign > 0.0) {
+                               yposition += (item_height / 2.0)
+                                       - strascent(gs_p->withlist[index]);
+                       }
+                       else {
+                               yposition -= (item_height / 2.0)
+                                       - strdescent(gs_p->withlist[index]);
+                       }
+               }
+               else {
+                       /* not too short, handle normally */
+                       y_offset += item_height * sign;
+                       yposition = y + y_offset;
+
+                       /* adjust to get to baseline of string from top or
+                        * bottom that we've used up to this point */
+                       if (sign > 0.0) {
+                               yposition -= strascent(gs_p->withlist[index]);
+                       }
+                       else {
+                               yposition += strdescent(gs_p->withlist[index]);
+                       }
+               }
+
+               pr_string(x - x_offset, yposition, gs_p->withlist[index],
+                               J_CENTER, gs_p->inputfile, gs_p->inputlineno);
+       }
+}
+\f
+
+/* print note stems and flags. Also print any slashes and alt lines */
+
+static void
+pr_stems(grpsyl_p)
+
+struct GRPSYL *grpsyl_p;       /* which group's stem to print */
+
+{
+       float x, y1, y2;
+       float sign;     /* 1 or -1 direction for moving to draw slashes */
+       float y_offset, offset, spacing;        /* for where to draw slashes */
+       float y_tilt;           /* how much to move in y direction to get
+                                * proper tilt on slashes */
+       float halfwidth;        /* half width of slash or alt line */
+       struct GRPSYL *first_p, *last_p;        /* beginning and ending group
+                                                * of beam group */
+       int grpsize;            /* grpsize field of grpsyl_p */
+       int grpvalue;           /* grpvalue field of grpsyl_p */
+       int slash;              /* to count number of slashes drawn */
+       struct NOTE *note_p;
+
+
+       /* if no stem, nothing to do */
+       if ( grpsyl_p->stemlen <= 0 && grpsyl_p->slash_alt == 0) {
+               return;
+       }
+
+       /* figure out x coordinate of stem */
+       x = find_x_stem(grpsyl_p);
+
+       /* if stem is up, start at bottom note, if down, at top */
+       if (grpsyl_p->stemdir == UP) {
+               note_p = &(grpsyl_p->notelist[ grpsyl_p->nnotes - 1]);
+               y1 = note_p->c[AY];
+               y2 = find_y_stem(grpsyl_p);
+               sign = -1;
+       }
+       else {
+               note_p = &(grpsyl_p->notelist [0]);
+               y1 = note_p->c[AY];
+               y2 = find_y_stem(grpsyl_p);
+               sign = 1;
+       }
+
+       if (note_p->headchar != 0) {
+               y1 += stem_yoff(note_p->headchar, note_p->headfont,
+                       grpsyl_p->stemdir)
+                       * (note_p->notesize == GS_NORMAL
+                       ? Stepsize : Stepsize * SM_FACTOR);
+       }
+
+       if (grpsyl_p->basictime >= 2) {
+               /* print the stem */
+               do_linetype(L_NORMAL);
+
+               draw_line(x, y1, x, y2);
+
+               /* attach any flags as appropriate */
+               pr_flags(grpsyl_p, (double) x, (double) y2);
+       }
+
+       /* print any slashes */
+       if (grpsyl_p->slash_alt > 0) {
+
+               /* adjust for flags or beams. */
+               if (grpsyl_p->basictime >= 8) {
+                       offset = (numbeams(grpsyl_p->basictime) - 1) *
+                               (grpsyl_p->grpsize == GS_NORMAL ? 5.0 : 4.0)
+                               * Stdpad;
+                       if (grpsyl_p->beamloc == NOITEM) {
+                               if (grpsyl_p->grpsize == GS_NORMAL) {
+                                       offset += 8.0 * Stdpad;
+                               }
+                               else if (grpsyl_p->basictime != 16) {
+                                       /* 16th small notes don't have any extra
+                                        * stem to account for extra flag */
+                                       offset += 3.0 * Stdpad;
+                               }
+                       }
+               }
+               else {
+                       offset = 0.0;
+               }
+
+               if ( grpsyl_p->beamloc == NOITEM) {
+                       /* unbeamed things get hard-coded tilt value */
+                       if (grpsyl_p->grpvalue == GV_ZERO) {
+                               y_tilt = (grpsyl_p->stemdir == UP ? 3.5 : -3.5)
+                                                       * Stdpad;
+                       }
+                       else {
+                               y_tilt = 2.2 * Stdpad;
+                       }
+               }
+
+               else {
+                       /* beamed. Need to slant slashes the same as beam */
+
+                       grpsize = grpsyl_p->grpsize;
+                       grpvalue = grpsyl_p->grpvalue;
+
+                       /* find beginning and ending stems */
+                       for (first_p = grpsyl_p; (first_p->beamloc != STARTITEM)
+                                       || (first_p->grpsize != grpsize)
+                                       || (first_p->grpvalue != grpvalue);
+                                       first_p = first_p->prev) {
+                               ;
+                       }
+
+                       for (last_p = grpsyl_p; (last_p->beamloc != ENDITEM)
+                                       || (last_p->grpsize != grpsize)
+                                       || (last_p->grpvalue != grpvalue);
+                                       last_p = last_p->next) {
+                               ;
+                       }
+
+                       /* calculate slope from them. We find the ratio of
+                        * y to x of the beam and apply that proportion to
+                        * the known x length of the slash to get the y height
+                        * of the slash, then divide by 2 to get the y distance
+                        * on either side of the stem. */
+                       y_tilt = (((find_y_stem(last_p) - find_y_stem(first_p))
+                                       * (2.0 * slash_xlen(grpsyl_p)))
+                                       / (find_x_stem(last_p)
+                                       - find_x_stem(first_p))) / 2.0;
+                       y1 = find_y_stem(first_p);
+               }
+
+               /* draw the slashes */
+               pr_slashes(grpsyl_p, (double) x, (double) y2, (double) sign,
+                               (double) offset, (double) y_tilt);
+       }
+
+       /* print alt group lines if any */
+       if (grpsyl_p->slash_alt < 0) {
+               struct GRPSYL *grpsyl2_p;
+               float grp2x, grp2y;     /* stem of second group */
+               float grp1y_offset, grp2y_offset;
+
+
+               if (grpsyl_p->next == (struct GRPSYL *) 0) {
+                       pfatal("missing second group in alt pair");
+               }
+
+               /* figure out how wide to draw the lines and how far apart
+                * to make them */
+               if (grpsyl_p->grpsize == GS_NORMAL) {
+                       halfwidth = W_WIDE * Staffscale / PPI / 2.0;
+                       spacing = 5.0 * Stdpad;
+               }
+               else {
+                       halfwidth = W_MEDIUM * Staffscale / PPI / 2.0;
+                       spacing = 3.0 * Stdpad;
+               }
+
+               /* find the stem coordinates of the second group */
+               grpsyl2_p = grpsyl_p->next;
+               grp2x = find_x_stem(grpsyl2_p);
+               grp2y = find_y_stem(grpsyl2_p);
+
+               /* on notes shorter than half note, the lines don't go all the
+                * way to the stems */
+               if ( grpsyl_p->basictime >= 4) {
+                       /* figure out where the y of the end of the line is
+                        * by multiplying the x value by the tangent of the
+                        * angle of the line that would go all the way
+                        * between the stems */
+                       grp2y_offset = (grp2x - x - (6.0 * Stdpad))
+                                               * ((grp2y - y2) / (grp2x - x));
+                       grp1y_offset = (6.0 * Stdpad)
+                                       * ((grp2y - y2) / (grp2x - x));
+                       /* if 8th notes or shorter, get out of way of beams */
+                       offset = numbeams(grpsyl_p->basictime) * spacing;
+                       x += (6.0 * Stdpad);
+                       grp2x -= (6.0 * Stdpad);
+               }
+               else {
+                       grp1y_offset = 0.0;
+                       grp2y_offset = grp2y - y2;
+                       offset = 0.0;
+               }
+
+               /* draw the alt lines */
+               for (slash = -(grpsyl_p->slash_alt) - 1; slash >= 0; slash--) {
+                       y_offset = sign * slash * spacing + (sign * offset);
+                       do_newpath();
+                       do_moveto(x, y2 + y_offset + grp1y_offset - halfwidth);
+                       do_line(x, y2 + y_offset + grp1y_offset + halfwidth);
+                       do_line(grp2x, y2 + y_offset + grp2y_offset
+                                                               + halfwidth);
+                       do_line(grp2x, y2 + y_offset + grp2y_offset
+                                                               - halfwidth);
+                       do_closepath();
+                       do_fill();
+               }
+
+               /* earlier phase wanted both groups in alt pair to have
+                * slash_alt set, but now we've printed this one, so clear
+                * the one on the following group, so it won't try to
+                * print another alt group */
+               grpsyl2_p->slash_alt = 0;
+       }
+}
+\f
+
+void
+pr_slashes(grpsyl_p, x, y, sign, offset, y_tilt)
+
+struct GRPSYL *grpsyl_p;
+double x;
+double y;
+double sign;
+double offset;
+double y_tilt;
+
+{
+       int slash;
+       double xlen;
+       float y_offset;
+       float spacing;
+       float halfwidth;
+
+
+       /* get length based on note head size */
+       xlen = slash_xlen(grpsyl_p);
+
+       /* figure out how wide to make the slashes and how far apart
+       * to space them */
+       if (grpsyl_p->grpsize == GS_NORMAL) {
+               halfwidth = W_WIDE * Staffscale / PPI / 2.0;
+               spacing = 5 * Stdpad;
+       }
+       else {
+               halfwidth = W_MEDIUM * Staffscale / PPI / 2.0;
+               spacing = 4 * Stdpad;
+       }
+
+       for (slash = grpsyl_p->slash_alt; slash > 0; slash--) {
+               y_offset = y + sign * (offset + (spacing * slash));
+
+               /* draw filled parallelogram */
+               do_newpath();
+               do_moveto(x - xlen, y_offset - y_tilt - halfwidth);
+               do_line(x - xlen, y_offset - y_tilt + halfwidth);
+               do_line(x + xlen, y_offset + y_tilt + halfwidth);
+               do_line(x + xlen, y_offset + y_tilt - halfwidth);
+               do_closepath();
+               do_fill();
+       }
+}
+
+static double
+slash_xlen(grpsyl_p)
+
+struct GRPSYL *grpsyl_p;
+
+{
+       return (SLASHHORZ * Stepsize *
+                       (grpsyl_p->grpsize == GS_NORMAL ? 1.0 : SM_FACTOR));
+}
+\f
+
+/* print flags on 8th and shorter notes */
+
+static void
+pr_flags(grpsyl_p, x, y)
+
+struct GRPSYL *grpsyl_p;       /* group for which to draw flags */
+double x;
+double y;                      /* coord of end of stem */
+
+{
+       int muschar;    /* what kind of flag to print */
+       float y_offset; /* from end of stem */
+       int f;          /* how many flags */
+       int size;
+
+
+       /* only 8th and shorter notes might have flags */
+       if (grpsyl_p->basictime < 8) {
+               return;
+       }
+
+       /* if not a note, no flag */
+       if (grpsyl_p->grpcont != GC_NOTES) {
+               return;
+       }
+
+       /* if beamed, no flag */
+       if (grpsyl_p->beamloc != NOITEM) {
+               return;
+       }
+
+       /* figure out if up/down and whether small/reg */
+       muschar = (grpsyl_p->stemdir == UP ? C_DNFLAG : C_UPFLAG);
+       size = (grpsyl_p->grpsize == GS_NORMAL ? DFLT_SIZE : SMALLSIZE);
+
+       /* do for each flag. f == 1 less than the number of flags, and is
+        * how much to multiply the y_offset by for each flag */
+       for ( f = numbeams(grpsyl_p->basictime) - 1; f >= 0; f--) {
+
+               switch (muschar) {
+
+               case C_UPFLAG:
+                       y_offset = f * (grpsyl_p->grpsize == GS_NORMAL ?
+                                               FLAGSEP : SMFLAGSEP);
+                       break;
+               case C_DNFLAG:
+                       y_offset = -f * (grpsyl_p->grpsize == GS_NORMAL ?
+                                               FLAGSEP : SMFLAGSEP);
+                       break;
+               default:
+                       pfatal("bad flag type");
+                       /*NOTREACHED*/
+                       return; /* to shut up compiler warning about unused */
+               }
+
+               y_offset *= Staffscale;
+
+               /* now that we know where to place the flag, print it */
+               pr_muschar(x + width(FONT_MUSIC,
+                               adj_size(size, Staffscale, (char *) 0, -1),
+                               muschar) / 2.0,
+                               y + y_offset, muschar, size, FONT_MUSIC);
+       }
+}
+\f
+
+/* print any accidental */
+
+static void
+pr_accidental(noteinfo_p, grpsyl_p)
+
+struct NOTE *noteinfo_p;       /* info about the note being printed */
+struct GRPSYL *grpsyl_p;       /* info about the group conatining the note */
+
+{
+       int muschar;    /* which accidental symbol to draw */
+       int size;
+       int a_size;     /* size adjusted for Staffscale */
+
+
+       /* figure out which accidental symbol to use */
+       muschar = acc2char(noteinfo_p->accidental);
+
+       /* if there is an accidental, print it at specified place */
+       if (muschar != '\0') {
+               size = (noteinfo_p->notesize == GS_NORMAL
+                                               ? DFLT_SIZE : SMALLSIZE);
+               a_size = adj_size(size, Staffscale, (char *) 0, -1);
+               if (noteinfo_p->acc_has_paren == NO) {
+                       pr_muschar(grpsyl_p->c[AX] + noteinfo_p->waccr
+                               + width(FONT_MUSIC, a_size, muschar) / 2.0,
+                               noteinfo_p->c[AY], muschar, size, FONT_MUSIC);
+               }
+               else {
+                       /* have to print parentheses in addition to the
+                        * symbol for the accidental */
+                       char paren_string[4];   /* "(" or ")" in internal format */
+                       double offset;          /* y adjustment of ( ) */
+
+                       /* create string for "(" */
+                       (void) sprintf(paren_string, "%c%c%c",
+                                                       FONT_TR, a_size, '(');
+
+                       /* to center things vertically on the note, need to
+                        * adjust parentheses downward by difference between
+                        * the ascent and half the height of the parenthesis */
+                       offset = strascent(paren_string) -
+                                       (strheight(paren_string) / 2.0);
+
+                       /* print the '(', the accidental, and the ')' */
+                       pr_string(grpsyl_p->c[AX] + noteinfo_p->waccr,
+                                       noteinfo_p->c[AY] - offset,
+                                       paren_string, J_LEFT,
+                                       grpsyl_p->inputfile,
+                                       grpsyl_p->inputlineno);
+
+                       pr_muschar(_Cur[AX] +
+                                       width(FONT_MUSIC, a_size, muschar) / 2.0,
+                                       noteinfo_p->c[AY], muschar, size,
+                                       FONT_MUSIC);
+
+                       (void) sprintf(paren_string, "%c%c%c",
+                                                       FONT_TR, a_size, ')');
+                       pr_string(_Cur[AX], noteinfo_p->c[AY] - offset,
+                                       paren_string, J_LEFT,
+                                       grpsyl_p->inputfile,
+                                       grpsyl_p->inputlineno);
+               }
+       }
+}
+\f
+
+/* print appropriate number of leger lines */
+
+static void
+pr_leger(noteinfo_p, gs_p, staffno)
+
+struct NOTE *noteinfo_p;       /* info about current note */
+struct GRPSYL *gs_p;           /* which group contains the note */
+int staffno;                   /* which staff to draw relative to */
+
+{
+       register int lines2draw;        /* how many leger lines are needed */
+       float sign;                     /* 1 for above or -1 for below staff */
+       float y;                        /* vertical position */
+       float left_leger, right_leger;  /* how far legers stick out from note */
+       int is_intermediate;            /* YES if inner, NO if outermost */
+       int on_other_side;              /* YES if on "wrong" side of stem */
+
+
+       if ((lines2draw = numlegers(noteinfo_p)) < 1) {
+               /* No legers needed for this note */
+               return;
+       }
+
+       /* Is note above or below the middle of the staff? */
+       sign = noteinfo_p->stepsup > 0.0 ? 1.0 : -1.0;
+
+       /* For notes on the "wrong" side of the stem, we will only need
+        * to draw the outermost leger. */
+       if ( (gs_p->stemdir == DOWN && noteinfo_p->c[AE] < gs_p->c[AX]) ||
+                       (gs_p->stemdir == UP && noteinfo_p->c[AW] > gs_p->c[AX])) {
+               on_other_side = YES;
+       }
+       else {
+               on_other_side = NO;
+       }
+
+       /* Draw the legers */
+       do_linetype(L_NORMAL);
+       is_intermediate = NO;
+       for (    ; lines2draw > 0; lines2draw--) {
+
+               /* Find the y location for the leger line.
+                * They are 2 Stepsizes apart,
+                * beginning at the edge of the staff  */
+               y = Staffs_y[staffno]
+                               + (sign * (2 + lines2draw) * (2 * Stepsize));
+
+               /* If things are packed really close together, leger lines
+                * could bleed into leger lines of the neighboring chord.
+                * We need to see if there are any potentially
+                * troublesome leger lines on either side, and shorten
+                * this leger if necessary to avoid them.
+                */
+               left_leger = leger_length(noteinfo_p, gs_p->prev, lines2draw,
+                               YES, is_intermediate);
+               right_leger = leger_length(noteinfo_p, gs_p->next, lines2draw,
+                               NO, is_intermediate);
+
+               draw_line( noteinfo_p->c[AW] - left_leger, y,
+                       noteinfo_p->c[AE] + right_leger, y);
+               is_intermediate = YES;
+
+               /* For notes on the "wrong" side of the stem, we only need
+                * to draw the outermost leger */
+               if (on_other_side == YES) {
+                       break;
+               }
+       }
+}
+\f
+
+/* How many legers to draw is absolute value of stepsup divided
+ * by 2 minus the 2 lines that are already in the staff.
+ * Note that we only do legers on normal 5-line staffs. */
+
+static int
+numlegers(noteinfo_p)
+
+struct NOTE *noteinfo_p;
+
+{
+       return (abs(noteinfo_p->stepsup) / 2) - 2;
+}
+\f
+
+/* If things are packed really close together, leger lines
+ * could bleed into leger lines of the neighboring chord.
+ * This function will detect that and shorten them if necessary.
+ * To be completely correct, it should check all the voices on the
+ * staff, but that would be quite a bit more work, and chances of colliding
+ * with another voice's notes is not very high, so we just check
+ * the voice of the note in question.
+ */
+
+static double
+leger_length(noteinfo_p, othergs_p, lines, other_is_prev, is_intermediate)
+
+struct NOTE *noteinfo_p;       /* we are finding leger length for this note */
+struct GRPSYL *othergs_p;      /* check this group for a too close note */
+int lines;                     /* how many leger lines to draw */
+int other_is_prev;             /* YES if othergs_p is ->prev, NO if ->next */
+int is_intermediate;           /* YES if interior, NO is outermost leger */
+
+{
+       int n;                          /* note index */
+       double distance;                /* between 2 notes */
+       double length = 2.2 * Stdpad;   /* length of leger. Init to default */
+       double adjust;                  /* inners can be shortened extra */
+
+
+       if (othergs_p == 0) {
+               /* No group to collide with */
+               return(length);
+       }
+       if (othergs_p->grpcont != GC_NOTES) {
+               /* Can't have leger lines */
+               return(length);
+       }
+
+       /* Legers that are not through or right next to the note
+        * can be shortened a bit more to make their gap show up better.
+        */
+       adjust = (is_intermediate ? 0.5 * Stdpad : 0.0);
+
+       /* See if othergs_p has any notes that are too close */
+       for (n = 0; n < othergs_p->nnotes; n++) {
+               if (numlegers( &(othergs_p->notelist[n]) ) < lines) {
+                       /* Neighboring note has fewer legers; not relevant */
+                       continue;
+               }
+               if (noteinfo_p->stepsup > 0 &&
+                                       othergs_p->notelist[n].stepsup < 0) {
+                       /* Neighboring note's legers are below, ours above.
+                        * The remaining neighboring notes are irrelevant. */
+                       break;
+               }
+               if (noteinfo_p->stepsup < 0 &&
+                                       othergs_p->notelist[n].stepsup > 0) {
+                       /* Neighboring note's legers are above, ours below.
+                        * Haven't gotten to any potentially relevant
+                        * notes yet. */
+                       continue;
+               }
+
+               /* We have a pair of notes whose leger lines might collide.
+                * See how far apart they are. */
+               if (other_is_prev == YES) {
+                       distance = noteinfo_p->c[AW] - othergs_p->notelist[n].c[AE];
+               }
+               else {
+                       distance = othergs_p->notelist[n].c[AW] - noteinfo_p->c[AE];
+               }
+
+               /* Ideally, we try to make leger lines 2.2 Stdpads on each side,
+                * but if that leaves less than 2.0 Stdpads between them,
+                * we shorten them until they get down to 0.7 Stdpads.
+                * After that we let them join. That should only happen
+                * if things are really tightly packed.
+                * The 6.4 is from two legers of 2.2 each with 2.0 between.
+                */
+               if (distance < 6.4 * Stdpad) {
+                       /* Too close. Will have to shorten */
+                       length = (distance - (2.0 * Stdpad)) / 2.0 - adjust;
+                       if (length < 0.7 * Stdpad - adjust) {
+                               /* No shorter than minimum */
+                               length = 0.7 * Stdpad - adjust;
+                       }
+               }
+       }
+       return (length);
+}
+\f
+
+/* given the first group of a tuplet, return, via pointers, the x coords of
+ * the left and right boundaries of the tuplet number and its height.
+ * Return pointer to static string containing the tuplet number itself in
+ * internal string format */
+
+char *
+tupnumsize(gs_p, west_p, east_p, height_p, staff_p)
+
+struct GRPSYL *gs_p;
+float *west_p;         /* west coord returned here */
+float *east_p;         /* east coord returned here */
+float *height_p;       /* string height returned here */
+struct STAFF *staff_p; /* staff pointing at gs_p */
+
+{
+       char *numstr;                   /* tuplet number as internal string */
+       struct GRPSYL *last_gs_p;       /* last group in tuplet */
+       float num_x;                    /* x coord of number */
+       float halfnumwidth;             /* half the width of numstr */
+       int tupside;
+       int all_cue;
+
+
+       /* assume all cue till proven otherwise */
+       all_cue = YES;
+
+       /* find x of middle of tuplet number */
+       if (gs_p->tuploc == LONEITEM) {
+               if (gs_p->grpsize != GS_SMALL) {
+                       all_cue = NO;
+               }
+               num_x = gs_p->c[AX];
+       }
+       else {
+               for (last_gs_p = gs_p->next; last_gs_p != (struct GRPSYL *) 0;
+                                       last_gs_p = last_gs_p->next) {
+                       if (gs_p->grpsize != GS_SMALL) {
+                               all_cue = NO;
+                       }
+                       if (last_gs_p->tuploc == ENDITEM) {
+                               break;
+                       }
+               }
+               if (last_gs_p == (struct GRPSYL *) 0) {
+                       pfatal("missing end tuplet in tupnumsize");
+               }
+
+               /* Usually, the x location of tuplet number is average of
+                * beginning and end group x coords. But if there is a beam
+                * and the number is being printed on the beam side,
+                * and there is no bracket being printed,
+                * it generally looks better to center between the stems.
+                */
+               tupside = tupdir(gs_p, staff_p);
+               if (gs_p->beamloc == STARTITEM && last_gs_p->beamloc == ENDITEM
+                               && ((tupside == PL_ABOVE && gs_p->stemdir == UP)
+                               || (tupside == PL_BELOW && gs_p->stemdir == DOWN))
+                               && tupgetsbrack(gs_p) == NO) {
+                       num_x = (find_x_stem(last_gs_p) + find_x_stem(gs_p)) / 2.0;
+               }
+               else {
+                       num_x = (last_gs_p->c[AX]  + gs_p->c[AX]) / 2.0;
+               }
+       }
+
+       /* prepare the string to print */
+       numstr = num2str(gs_p->tupcont);
+       /* force to 11-point newcentury bold-italics, unless all cue,
+        * then smaller */
+       numstr[0] = FONT_NX;
+       numstr[1] = (char) adj_size((all_cue == YES ? 9 : 11), Staffscale,
+                               (char *) 0, -1);
+       halfnumwidth = strwidth(numstr) / 2.0;
+
+       /* return the values */
+       *west_p =  num_x - halfnumwidth - Stdpad;
+       *east_p = num_x + halfnumwidth + Stdpad;
+       *height_p = strheight(numstr);
+       return(numstr);
+}
+\f
+
+/* go through measure. If there are any tuplets, print a number by them,
+ * along with bracket if appropriate. */
+
+static void
+pr_tupnums(gs_p, staff_p)
+
+struct GRPSYL *gs_p;   /* start from here to walk through list of groups */
+struct STAFF *staff_p; /* staff pointing to gs_p */
+
+{
+       struct GRPSYL *first_gs_p = 0;  /* where to begin tuplet label.
+                                * Initialization is just to shut up bogus
+                                * compiler warning. */
+       struct GRPSYL *g_p;     /* to check for all spaces */
+       float x1, x2;           /* where tuplet bracket begins & ends */
+       float num_y;            /* y of tuplet number */
+       float y1, y2;           /* y coord of ends of bracket */
+       char *numstr;           /* ASCII version of tuplet number */
+       float numeast, numwest; /* boundaries of tuplet number */
+       float height;           /* of tuplet number */
+       float y_adjust;         /* adjustment for space taken by number */
+       float x_adjust;         /* from group x to where bracket goes */
+       int num_notes = 0;      /* how many notes in tuplet */
+       int need_brack = NO;    /* set to YES if the beaming of the notes
+                                * doesn't match the tuplet boundaries */
+       float brackdir;         /* how far in y direction to draw bracket ends
+                                * (positive or negative depending on the
+                                * direction that the bracket points) */
+       int size;
+
+
+       /* go through all the groups */
+       for (   ; gs_p != (struct GRPSYL *) 0; gs_p = gs_p->next) {
+
+               switch (gs_p->tuploc) {
+
+               case NOITEM:
+                       break;
+
+               case STARTITEM:
+                       /* remember beginning for later use */
+                       first_gs_p = gs_p;
+                       num_notes = 1;
+                       break;
+
+               case INITEM:
+                       num_notes++;
+                       break;
+
+               case LONEITEM:
+                       first_gs_p = gs_p;
+                       /*FALLTHRU*/
+
+               case ENDITEM:
+                       num_notes++;
+                       
+                       /* if not to be printed, nothing to do except reinit */
+                       if (gs_p->printtup == PT_NEITHER) {
+                               num_notes = 0;
+                               break;
+                       }
+
+                       /* we don't do tuplet numbers on cross-staff beams--
+                        * it's virtually impossible to know where to put them
+                        */
+                       if (gs_p->beamto != CS_SAME) {
+                               num_notes = 0;
+                               break;
+                       }
+
+                       /* If the tuplet is all spaces,
+                        * there is nothing to draw a bracket over,
+                        * and trying to do so causes problems,
+                        * so don't try. */
+                       for (g_p = first_gs_p; g_p->tuploc != NOITEM;
+                                                       g_p = g_p->next) {
+                               if (g_p->grpcont != GC_SPACE) {
+                                       /* good--it has something
+                                        * other than spaces */
+                                       break;
+                               }
+
+                               if (g_p->tuploc == ENDITEM
+                                               || g_p->tuploc == LONEITEM) {
+                                       /* reached end of all-space tuplet */
+                                       break;
+                               }
+                       }
+                       if (g_p->grpcont == GC_SPACE) {
+                               /* must have been all spaces */
+                               num_notes = 0;
+                               break;
+                       }
+
+                       /* if tuplet doesn't match beaming, need bracket */
+                       need_brack = tupgetsbrack(first_gs_p);
+
+                       if (num_notes == 0) {
+                               pfatal("no notes in tuplet");
+                       }
+
+                       numstr = tupnumsize(first_gs_p, &numwest, &numeast,
+                                                       &height, staff_p);
+
+                       if (tupdir(first_gs_p, staff_p) == PL_ABOVE) {
+                               y_adjust = strascent(numstr);
+                               y1 = first_gs_p->c[AN] - y_adjust;
+                               y2 = gs_p->c[AN] - y_adjust;
+                               brackdir = -3.0 * Stdpad;
+                       }
+                       else {
+                               /* print below */
+                               y1 = first_gs_p->c[AS];
+                               y2 = gs_p->c[AS];
+                               brackdir = 3.0 * Stdpad;
+                       }
+
+                       /* print tuplet number at correct place */
+                       y1 += first_gs_p->tupextend;
+                       y2 += gs_p->tupextend;
+                       num_y = (y1 + y2) / 2.0;
+                       pr_string(numwest + Stdpad, num_y, numstr, J_LEFT,
+                                       gs_p->inputfile, gs_p->inputlineno);
+
+                       /* add tuplet bracket if necessary */
+                       if (need_brack == YES) {
+                               do_linetype(L_NORMAL);
+                               
+                               /* adjust to reach edge of note head */
+                               size = (first_gs_p->grpsize == GS_NORMAL ?
+                                               DFLT_SIZE : SMALLSIZE)
+                                               * Staffscale;
+                               if (first_gs_p->grpcont == GC_NOTES) {
+                                       x_adjust = widest_head(first_gs_p)
+                                               * Staffscale / 2.0;
+                               }
+                               else if (first_gs_p->grpcont == GC_REST) {
+                                       x_adjust = width(FONT_MUSIC, size,
+
+                                               restchar(first_gs_p->basictime))
+                                               / 2.0;
+                               }
+                               else {
+                                       x_adjust = 0.0;
+                               }
+                               x1 = first_gs_p->c[AX] - x_adjust;
+
+                               size = (gs_p->grpsize == GS_NORMAL ?
+                                               DFLT_SIZE : SMALLSIZE)
+                                               * Staffscale;
+                               if (gs_p->grpcont == GC_NOTES) {
+                                       x_adjust = widest_head(gs_p)
+                                               * Staffscale / 2.0;
+                               }
+                               else if (gs_p->grpcont == GC_REST) {
+                                       x_adjust = width(FONT_MUSIC, size,
+                                               restchar(gs_p->basictime))
+                                               / 2.0;
+                               }
+                               else {
+                                       x_adjust = 0.0;
+                               }
+                               x2 = gs_p->c[AX] + x_adjust;
+
+                               /* move the bracket line up from the baseline
+                                * of the number */
+                               y1 += (4.0 * Stdpad);
+                               y2 += (4.0 * Stdpad);
+                               num_y += (4.0 * Stdpad);
+
+                               /* figure out how much to adjust y from num_y
+                                * to account for the space taken up by the
+                                * number. Use ratio of similar triangles. */
+                               if (numwest - x1 == 0.0) {
+                                       /* avoid any chance of divide by 0 */
+                                       y_adjust = 0.0;
+                               }
+                               else {
+                                       y_adjust = (((numeast - numwest
+                                               + (Stdpad * 2.0)) *
+                                               (num_y - y1)) / (numeast - x1))
+                                               / 2.0;
+                               }
+
+                               draw_line(x1, y1, numwest - Stdpad,
+                                               num_y - y_adjust); 
+                               draw_line(numeast + Stdpad,
+                                               num_y + y_adjust, x2, y2); 
+                               draw_line(x1, y1, x1, y1 + brackdir);
+                               draw_line(x2, y2, x2, y2 + brackdir); 
+                       }
+
+                       /* re-init in case other tuplets in same measure */
+                       num_notes = 0;
+
+                       break;
+
+               default:
+                       pfatal("bad tuplet type");
+                       break;
+               }
+       }
+}
+\f
+
+/* utility function. Given the first group in a tuplet, return YES if it
+ * is to have a bracket printed. It does if the tuplet itself is to be printed,
+ * and if not a LONEITEM and if any of the beamlocs do not match the tuploc */
+
+int
+tupgetsbrack(gs_p)
+
+struct GRPSYL *gs_p;   /* first group of tuplet */
+
+{
+       /* If nothing is to be printed or number only, no bracket */
+       if (gs_p->printtup == PT_NEITHER || gs_p->printtup == PT_NUMBER) {
+               return(NO);
+       }
+
+       /* single chord tuplets never get a bracket -- not enough room
+        * to draw one */
+       if (gs_p->tuploc == LONEITEM) {
+               return(NO);
+       }
+
+       /* if user insists on a bracket, we oblige */
+       if (gs_p->printtup == PT_BOTH) {
+               return(YES);
+       }
+
+       /* check for mismatches between beamloc and tuploc. */
+       for (  ; gs_p != (struct GRPSYL *) 0; gs_p = gs_p->next) {
+               /* grace notes don't count */
+               if (gs_p->grpvalue == GV_ZERO) {
+                       continue;
+               }
+
+               if (gs_p->tuploc != gs_p->beamloc) {
+                       return(YES);
+               }
+               if (gs_p->tuploc == ENDITEM) {
+                       /* matched beam everywhere, so no bracket needed */
+                       return(NO);
+               }
+       }
+       pfatal("missing end tuplet");
+
+       /*NOTREACHED*/
+       return(NO);
+}
+\f
+
+/* utility function to return PL_ABOVE  or PL_BELOW
+ * depending on whether the number for
+ * the given tuplet should get printed above or below the groups */
+/* Can be passed any group in the tuplet. If not the first, it will find the
+ * first and go from there */
+
+int
+tupdir(gs_p, staff_p)
+
+struct GRPSYL *gs_p;   /* group in tuplet */
+struct STAFF *staff_p; /* staff pointing to gs_p */
+
+{
+       RATIONAL starttime, endtime;    /* begin & end time of tuplet */
+       struct GRPSYL *save_gs_p;       /* temporarily save value of gs_p */
+       int othervoice;                 /* array subscript in staff_p->groups_p
+                                        * of the other voice on this staff */
+       int vscheme;                    /* V_* value */
+       RATIONAL smalltime;
+
+
+       smalltime.n = 1;
+       smalltime.d = 2 * MAXBASICTIME;
+
+
+       switch (gs_p->tuploc) {
+
+       case LONEITEM:
+       case STARTITEM:
+               /* this is the one we want */
+               break;
+
+       case NOITEM:
+               pfatal("arg of tupdir is not in a tuplet");
+               /*NOTREACHED*/
+               break;
+       default:
+               /* have to back up to beginning of tuplet first */
+               for (    ; gs_p != (struct GRPSYL *) 0; gs_p = gs_p->prev) {
+                       if (gs_p->tuploc == STARTITEM) {
+                               break;
+                       }
+               }
+               if (gs_p == (struct GRPSYL *) 0) {
+                       pfatal("can't find beginning of tuplet");
+               }
+               break;
+       }
+
+       /* figure out which side. First determine vscheme */
+
+       /* there is a circumstance where we're looking at an entire score,
+        * (in relvert), and if some of the score has V_1 and some of it
+        * doesn't, it's possible for us to get confused and think something
+        * isn't V_1 when it is. We would then try to look at the other
+        * voice, which is null, and would blow up. To avoid this, if one
+        * voice is null, treat measure as V_1 regardless of what vscheme
+        * might lead us to believe.
+        */
+       if (staff_p->groups_p[1] == (struct GRPSYL *) 0) {
+               return(tupdir1voice(gs_p));
+       }
+
+       /* voice 3 pays no attention to any other voices. */
+       if (gs_p->vno == 3) {
+               return(tupdir1voice(gs_p));
+       }
+
+       if ((vscheme = svpath(staff_p->staffno, VSCHEME)->vscheme) == V_1) {
+               return(tupdir1voice(gs_p));
+       }
+       else if (vscheme == V_2OPSTEM) {
+               /* 2 opposing stem voices, always put tuplet above voice 1 and
+                * below voice 2 */
+               if (gs_p->tupside != PL_UNKNOWN) {
+                       l_warning(gs_p->inputfile, gs_p->inputlineno,
+                                       "tuplet side specification not valid when vscheme=2o");
+                       /* fix so we don't print error again if called
+                        * again on this tuplet */
+                       gs_p->tupside = PL_UNKNOWN;
+               }
+               return(gs_p->vno == 1 ? PL_ABOVE : PL_BELOW);
+       }
+       else {
+               /* find the time period taken by tuplet */
+               save_gs_p = gs_p;
+               starttime = Zero;
+               /* find time to where tuplet begins */
+               for (gs_p = gs_p->prev; gs_p != (struct GRPSYL *) 0;
+                                                       gs_p = gs_p->prev) {
+                       starttime = radd(starttime, gs_p->fulltime);
+               }
+               /* find time up to last note of tuplet */
+               endtime = starttime;
+               for (gs_p = save_gs_p; gs_p->tuploc != ENDITEM
+                                       && gs_p->tuploc != LONEITEM;
+                                       gs_p = gs_p->next) {
+                       endtime = radd(endtime, gs_p->fulltime);
+               }
+               /* add on a little bit for the final group of the tuplet */
+               endtime = radd(endtime, smalltime);
+
+               /* now check if other voice has space or not */
+               othervoice = (gs_p->vno == 1 ? 1 : 0);
+               if (hasspace(staff_p->groups_p [othervoice], starttime, endtime)
+                                               == YES) {
+                       /* other voice is space: treat like V_1 */
+                       return(tupdir1voice(save_gs_p));
+               }
+               else {
+                       /* other voice not space: treat like V_2OPSTEM */
+                       if (gs_p->tupside != PL_UNKNOWN) {
+                               l_warning(gs_p->inputfile, gs_p->inputlineno,
+                                       "tuplet side specification not valid when there are two voices");
+                               /* fix so we don't print error again if called
+                                * again on this tuplet */
+                               gs_p->tupside = PL_UNKNOWN;
+                       }
+                       return(gs_p->vno == 1 ? PL_ABOVE : PL_BELOW);
+               }
+       }
+}
+\f
+
+/* return PL_ABOVE or PL_BELOW for tup location assuming a single voice */
+
+static int
+tupdir1voice(gs_p)
+
+struct GRPSYL *gs_p;   /* first group of tuplet */
+
+{
+       int stemdirsum; /* sum of stem directions to see if mostly up or down */
+
+
+       /* if user specified a direction, the answer is easy */
+       if (gs_p->tupside != PL_UNKNOWN) {
+               return(gs_p->tupside);
+       }
+
+        /* Count up stem directions. Whichever side
+         * has more stems, put it on that side. In case of tie,
+         * arbitrarily choose above. */
+       stemdirsum = 0;
+       for (   ; gs_p != (struct GRPSYL *) 0; gs_p = gs_p->next) {
+               if (gs_p->grpcont == GC_NOTES && gs_p->grpvalue != GV_ZERO) {
+                       stemdirsum += (gs_p->stemdir == UP ? 1 : -1);
+               }
+               if (gs_p->tuploc == LONEITEM || gs_p->tuploc == ENDITEM) {
+                       break;
+               }
+       }
+
+       return(stemdirsum >= 0 ? PL_ABOVE : PL_BELOW);
+}
+\f
+
+/* go through measure, printing any beams. Gets called once for normal sized
+ * notes, once for cue notes, and once for grace note. */
+
+static void
+pr_beams(gs_p, grpvalue, grpsize)
+
+struct GRPSYL *gs_p;   /* list of grpsyls for current measure
+                        * of current voice */
+int grpvalue;          /* GV_NORMAL, GV_ZERO */
+int grpsize;           /* GS_NORMAL, GS_SMALL */
+
+{
+       struct GRPSYL *startbeam_p;     /* first in beam group */
+       int t;                          /* 8, 16, etc for basictimes */
+
+
+       /* go through all the grpsyls in measure */
+       for (   ; gs_p != (struct GRPSYL *) 0; gs_p = gs_p->next) {
+
+               /* skip until we find a STARTITEM
+                * on the relevant kind of group */
+               if (gs_p->beamloc != STARTITEM || gs_p->grpvalue != grpvalue
+                                       || gs_p->grpsize != grpsize) {
+                       continue;
+               }
+
+               /* when there are cross-staff beams, we will find the beam
+                * on both staffs, but only need to draw it once. So skip
+                * it the second time */
+               if (gs_p->beamto == CS_ABOVE) {
+                       continue;
+               }
+
+               /* find the matching ENDITEM */
+               for (startbeam_p = gs_p; gs_p != 0 && (gs_p->beamloc != ENDITEM
+                                       || gs_p->grpvalue != grpvalue
+                                       || gs_p->grpsize != grpsize);
+                                       gs_p = gs_p->next) {
+
+               }
+               if (gs_p == 0) {
+                       pfatal("pr_beams couldn't find end of beam group");
+               }
+
+               /* now go through beam group drawing beams for 8th notes,
+                * then 16th, etc */
+               for (t = 8; t <= MAXBASICTIME; t <<= 1) {
+                       if (draw_beams(startbeam_p, gs_p, t, grpsize, grpvalue)
+                                                               <= 0) {
+                               break;
+                       }
+               }
+       }
+}
+\f
+
+/* In the case of cross-staff beams with the above staff's stems down,
+ * and the below staff's stems up, we need to do extra work.
+ * This function builds up a mesh of structs that represent the beams,
+ * with a row of CSBINFO structs linked horizontally for each beam,
+ * and vertical links at each stem. end_bm_offset() then uses this information
+ * to figure out where along the stem a beam ends.
+ * This function returns a pointer to the beginning of the 8th note beam.
+ *
+ * As an example, consider this input:
+ *   1: 8.c; 64f beam with staff below; 32.s; 16e; 8s; 8e; 16s; 32.f; 64s ebm;
+ *   2: 8.e; 64s beam with staff above; 32.a; 16s; 8g; 8s; 16g; 32.s; 64a ebm;
+ * The resulting mesh will look like this:
+ *                    .           .           .           .
+ *                    .           .           .           .
+ *       (64th)       X           .           .           .
+ *                    |           .           .           .
+ *       (32nd)       X --> X     .           .           .
+ *                    |     |     .           .           .
+ *       (16th)       X --> X --> X           .           X --> X   (32nd)
+ *                    |     |     |           .           |     |
+ *   return_value --> X --> X --> X --> X --> X --> X --> X --> X   (8th)
+ *                          .           .           |     |     |
+ *                          .           .           X --> X --> X   (16th)
+ *                          .           .           .           |
+ *                          .           .           .           X   (64th)
+ *                          .           .           .           .
+ *                          .           .           .           .
+ *
+ * Each X in the diagram represents a CSBINFO struct.
+ * Each row represents a beam. The --> is the "next" field.
+ * Each column represents a stem. It is a doubly-linked list,
+ * using above_p and below_p fields.
+ * The dots show the stem direction.
+ */
+
+static struct CSBINFO *
+mkcsbmesh(begin_p, end_p)
+
+struct GRPSYL *begin_p;        /* first group of cross-staff beam on upper staff */
+struct GRPSYL *end_p;  /* 8th note beam goes from begin_p to end_p.
+                        * There may be zero or more additional beams
+                        * for shorter durations that span part or all
+                        * of this list.
+                        */
+
+{
+       struct CSBINFO *csbi_list_p;    /* this points to the 8th note beam
+                                        * list, which is what will
+                                        * ultimately be returned */
+       struct CSBINFO *csbi_p;         /* the current information */
+       struct CSBINFO *csbi8_p;        /* to walk through 8th list */
+       struct CSBINFO *prevcsbi_p;     /* previous in horizontal list */
+       struct CSBINFO *c_p;            /* for walking vertical lists */
+       struct GRPSYL *gs_p;            /* to walk through beamed groups */
+       int basictime;                  /* 8, 16, 32, etc */
+       int stemdir;                    /* stem direction where beam starts */
+       int shortest;                   /* shortest basictime (8, 16, ...) */
+
+
+       /* There is always at least an 8th note beam that goes the
+        * entire length, so make a list for that. */
+       csbi_list_p = prevcsbi_p = 0;
+       shortest = 8;
+       for (gs_p = begin_p; gs_p != end_p->next; gs_p = nxtbmgrp(gs_p,
+                                               begin_p, end_p->next)) {
+               MALLOC(CSBINFO, csbi_p, 1);
+
+               /* set horizontal list links */
+               if (csbi_list_p == 0) {
+                       /* first item on the horizontal list */
+                       csbi_list_p = csbi_p;
+               }
+               else {
+                       /* link from previous horizontally */
+                       prevcsbi_p->next = csbi_p;
+               }
+               prevcsbi_p = csbi_p;
+               csbi_p->next = 0;
+
+               /* init vertical list links */
+               csbi_p->above_p = csbi_p->below_p = 0;
+
+               /* this is for the 8th note beam */
+               csbi_p->basictime = 8;
+               /* save what group this is for, for later convenience */
+               csbi_p->gs_p = gs_p;
+
+               /* remember the shortest basictime anywhere in the beam */
+               if (gs_p->basictime > shortest) {
+                       shortest = gs_p->basictime;
+               }
+       }
+
+       /* For each additional beam, build up a row of structs representing
+        * that beam, and link it vertically to the row below or above it,
+        * depending on whether the first group of the beam is on the staff
+        * above or below the 8th beam.
+        */
+       for (basictime = 16; basictime <= shortest; basictime <<= 1) {
+               stemdir = UNKNOWN;      /* Init to keep lint happy;
+                                        * this will get set to appropriate
+                                        * value before it is actually used. */
+               prevcsbi_p = 0;         /* No run of groups found yet */
+
+               /* Walk through list, finding any runs of groups that are
+                * at least as short in duration as the current basictime
+                * we are looking for. Note this could be as little as a single
+                * group in the case of a partial beam.
+                * We walk through the GRPSYLs and their
+                * corresponding CSBINFO structs in parallel.
+                */
+               for (gs_p = begin_p, csbi8_p = csbi_list_p;
+                               gs_p != end_p->next;
+                               gs_p = nxtbmgrp(gs_p, begin_p, end_p->next),
+                               csbi8_p = csbi8_p->next) {
+
+                       if (gs_p->basictime >= basictime) {
+                               /* this group is part of a beam of at least
+                                * as short as the basictime of interest. */
+                               MALLOC(CSBINFO, csbi_p, 1);
+                               csbi_p->next = 0;
+                               csbi_p->basictime = basictime;
+
+                               /* If not first group in this beam,
+                                * link from previous. If is first,
+                                * save its stem direction. That determines
+                                * which side of the 8th beam it goes on. */
+                               if (prevcsbi_p != 0) {
+                                       prevcsbi_p->next = csbi_p;
+                               }
+                               else {
+                                       stemdir = gs_p->stemdir;
+                               }
+                               /* Prepare to link more on horizonally,
+                                * if beam goes further. */
+                               prevcsbi_p = csbi_p;
+
+                               /* set vertical links */
+                               if (stemdir == DOWN) {
+                                       /* Must be from staff above.
+                                        * Find current top, and add
+                                        * above there */
+                                       for (c_p = csbi8_p; c_p->above_p != 0;
+                                                       c_p = c_p->above_p) {
+                                               ;
+                                       }
+                                       c_p->above_p = csbi_p;
+                                       csbi_p->below_p = c_p;
+                                       csbi_p->above_p = 0;
+                               }
+                               else {
+                                       /* similar for from staff below */
+                                       for (c_p = csbi8_p; c_p->below_p != 0;
+                                                       c_p = c_p->below_p) {
+                                               ;
+                                       }
+                                       c_p->below_p = csbi_p;
+                                       csbi_p->above_p = c_p;
+                                       csbi_p->below_p = 0;
+                               }
+                       }
+                       else {
+                               /* If we were doing a beam before,
+                                * it's done now */
+                               prevcsbi_p = 0;
+                       }
+               }
+       }
+       return(csbi_list_p);
+}
+\f
+
+/* draw beams in a beam group for a particular time value, 8th, 16th, etc */
+/* this gets called repeatedly, first for 8th, then 16ths, etc, until
+ * there are no more shorter notes.
+ * It returns the number of beams drawn (including partials) */
+
+
+static int
+draw_beams(gs_p, endbeam_p, basictime, grpsize, grpvalue)
+
+struct GRPSYL *gs_p;                   /* start of beam group */
+struct GRPSYL *endbeam_p;              /* end of beam group */
+int basictime;                         /* draw beam for this basic time:
+                                        * 8, 16, 32, 64, etc */
+int grpsize;                           /* GS_NORMAL, GS_SMALL */
+int grpvalue;                          /* GV_NORMAL, GV_ZERO */
+
+{
+       int found = 0;          /* how many beams found to be drawn */
+       int ngrps;              /* how many groups to beam together */
+       struct GRPSYL *first_p = 0;/* first group in beam (the one on the
+                                * above staff while doing cross-staff beams) */
+       struct GRPSYL *begin_p = 0, *end_p;     /* the initialization is
+                                * to shut up bogus compiler warning */
+       struct GRPSYL *other_p; /* other note that must be used to calculate
+                                * slope of partial beam */
+       float y_offset;         /* from end of stem to draw beam */
+       int side;               /* left or right for partial beam */
+       float x_begin, y_begin, x_other, y_other;       /* partial beam
+                                                        * coordinates */
+       double halfwidth;       /* half width of a beam */
+       double end_y_offset;    /* to deal with cross staff beams */
+       double slope;           /* of partial beam */
+       double halfstem;
+       double stemdist;        /* distance between stems */
+       double pbeam_len;       /* length of partial beam */
+
+
+       /* get relevant group, accounting for cross-staff beams */
+       first_p = gs_p;
+       gs_p = neighboring_note_beam_group(gs_p, first_p, NO);
+
+       /* go through the list */
+       while ( gs_p != endbeam_p->next) {
+
+               /* find however many in a row deserve to get another beam */
+               for (end_p = (struct GRPSYL *) 0, ngrps = 0;
+                                               (gs_p != endbeam_p->next);
+                                               gs_p = nxtbmgrp(gs_p,
+                                               first_p, endbeam_p->next)) {
+
+                       /* if wrong type (e.g a grace inside of
+                        * a set of normal notes), skip over */
+                       if (gs_p->grpsize != grpsize
+                                       || gs_p->grpvalue != grpvalue ) {
+                               continue;
+                       }
+
+                       /* if not beamed, skip */
+                       if ( gs_p->beamloc == NOITEM) {
+                               pfatal("non-beam inside beam group\n");
+                       }
+
+                       /* if this one deserves another beam,
+                        * keep track of that. If not, break out */
+                       if (gs_p->basictime >= basictime) {
+                               end_p = gs_p;
+                               found++;
+                               if (ngrps == 0) {
+                                       begin_p = gs_p;
+                               }
+                               ngrps++;
+                               if (gs_p->breakbeam == YES && basictime > 8) {
+                                       break;
+                               }
+                       }
+                       else {
+                               break;
+                       }
+               }
+
+               /* prepare to do next one */
+               if (gs_p != endbeam_p->next) {
+                       gs_p = nxtbmgrp(gs_p, first_p, endbeam_p->next);
+               }
+
+               /* if none we looked at deserved a beam, keep looking */
+               if (end_p == (struct GRPSYL *) 0) {
+                       continue;
+               }
+
+               /* calculate where on stem the beam should start */
+               y_offset = beam_offset(numbeams(basictime),
+                                       begin_p->grpsize, begin_p->stemdir);
+
+               if (end_p->grpsize == GS_NORMAL) {
+                       halfwidth = W_WIDE * Staffscale / PPI / 2.0;
+                       halfstem = W_NORMAL * Staffscale / PPI / 2.0;
+               }
+               else {
+                       halfwidth = W_WIDE * Staffscale * SM_FACTOR / PPI / 2.0;
+                       halfstem = W_NORMAL * Staffscale * SM_FACTOR / PPI / 2.0;
+               }
+
+               /* check if single group.
+                * If so, need to do a partial beam, otherwise full beam */
+               if (ngrps == 1) {
+                       /* rests and spaces don't get beams,
+                        * so don't get partial ones */
+                       if (end_p->grpcont != GC_NOTES) {
+                               continue;
+                       }
+
+                       side = pbeamside(end_p, first_p);
+
+                       /* Now that we decided where the
+                        * partial beam goes, we can draw it */
+
+                       /* in order to figure out the end point of the partial
+                        * beam, we have to calculate the slope of the beam as
+                        * if it were a full beam and derive from that where
+                        * the partial beam will end. */
+                       /* determine whether to use prev or next note, and
+                        * skip any notes of the wrong type! */
+                       if (side == PB_LEFT) {
+                               other_p = prevbmgrp(end_p, first_p);
+                       }
+                       else {
+                               other_p = nxtbmgrp(end_p, first_p, end_p->next);
+                       }
+
+                       /* the line then goes from the stem (at y_offset) to
+                        * a notehead width east or west of the stem,
+                        * with the y coordinate calculated from the slope
+                        * of what a full length beam would have been, unless
+                        * stems are too close, in which case shorten it
+                        * somewhat. */
+                       x_begin = find_x_stem(end_p);
+                       y_begin = find_y_stem(end_p);
+                       x_other = find_x_stem(other_p);
+                       y_other = find_y_stem(other_p);
+
+                       /* if cross-staff and the two stems are in opposite
+                        * directions, have to compensate for that */
+                       if (end_p->stemdir == other_p->stemdir) {
+                               /* in same direction */
+                               slope = (y_other - y_begin)
+                                                       / (x_other - x_begin);
+                       }
+                       else {
+                               double opp_adj;
+
+                               opp_adj = beam_offset(
+                                       numbeams(other_p->basictime),
+                                       other_p->grpsize, other_p->stemdir);
+                               slope = (y_other - (y_begin - opp_adj))
+                                                       / (x_other - x_begin);
+                       }
+
+                       /* adjust to overlap stem */
+                       x_begin += halfstem * side;
+
+                       /* determine partial beam length */
+                       /* find distance between stems */
+                       if (x_begin < x_other) {
+                               stemdist = x_other - x_begin;
+                       }
+                       else {
+                               stemdist = x_begin - x_other;
+                       }
+                       /* if wide enough, use note head width, else less */
+                       if (stemdist < 5.0 * Stepsize) {
+                               pbeam_len = 0.4 * stemdist;
+                       }
+                       else {
+                               pbeam_len = widest_head(end_p) * Staffscale;
+                       }
+
+                       /* draw the partial beam */
+                       do_beam(x_begin, y_begin + y_offset,
+                               x_begin + pbeam_len * side,
+                               y_begin + y_offset + side *
+                               pbeam_len * slope, halfwidth);
+               }
+
+               else {
+                       /* draw a normal beam */
+
+                       /* For regular beams, can use y_offset directly,
+                        * but with cross-staff beam, stems may be in opposite
+                        * directions, so have to call a function to get
+                        * appropriate offset.
+                        */
+                       if (begin_p->beamto == CS_SAME) {
+                               end_y_offset = y_offset;
+                       }
+                       else {
+                               end_y_offset = end_bm_offset(first_p, end_p,
+                                                               basictime);
+                       }
+
+                       /* If the stems on both ends of the beam
+                        * are zero length, don't draw any beams.
+                        * If user really wants the beams,
+                        * they can make one of the ends
+                        * barely longer than zero.
+                        */
+                       if (begin_p->stemlen <= 0.0 && end_p->stemlen <= 0.0) {
+                               continue;       
+                       }
+
+                       /* find end of first stem and last stem and draw
+                        * the beam at proper offset from there */
+                       do_beam(find_x_stem(begin_p) - halfstem,
+                                       find_y_stem(begin_p) + y_offset,
+                                       find_x_stem(end_p) + halfstem,
+                                       find_y_stem(end_p) + end_y_offset,
+                                       halfwidth);
+               }
+       }
+       return(found);
+}
+\f
+
+/* Figure out how far from the end of a stem a beam should be in the
+ * case of a cross-staff beam with opposite-direction stems at its ends.
+ * Will return some multiple (possibly 0) of the distance between beams,
+ * with the proper sign to account for stem direction.
+ * Should only be called if the beam in question is a cross-staff beam.
+ */
+
+double
+end_bm_offset(top_first_p, end_p, basictime)
+
+struct GRPSYL *top_first_p;    /* the group that has "bm with staff below" */
+struct GRPSYL *end_p;          /* the group where a beam ends. This could be
+                                * either a group with ebm or some intermediate
+                                * group that happens to end a beam segment
+                                * that is shorter. */
+int basictime;                 /* the basictime of the beam currently under
+                                * consideration. The first beam drawn will
+                                * be 8, the next 16, then 32, etc. */
+
+{
+       static struct CSBINFO *csbi_list_p = 0;/* Info about the cross beams */
+       static struct GRPSYL *cached_gs_p = 0;  /* Each time we get a different
+                                * top_first_p, we calculate its csbi_list
+                                * and cache it for future calls. This lets
+                                * us know if we can re-use the cached value. */
+       struct CSBINFO *csbi_p; /* for walking 8th note beam ->next links */
+       struct CSBINFO *c_p;    /* for walking vertical links of mesh */
+       int nbeams;             /* how many beams from the stem end */
+
+       if (cached_gs_p != top_first_p) {
+               /* Cached one is no good; need to recalculate */
+               if (csbi_list_p != 0) {
+                       /* We had a list before; need to clean it up */
+                       struct CSBINFO *nextvert_p;     /* to free vert list */
+                       struct CSBINFO *nexthor_p;      /* to free hor list */
+                       /* walk horizontal list */
+                       for (csbi_p = csbi_list_p; csbi_p != 0;
+                                                       csbi_p = nexthor_p) {
+                               /* clean up vert list, both directions */
+                               for (c_p = csbi_p->above_p; c_p != 0;
+                                                       c_p = nextvert_p) {
+                                       nextvert_p = c_p->above_p;
+                                       FREE(c_p);
+                               }
+                               for (c_p = csbi_p->below_p; c_p != 0;
+                                                       c_p = nextvert_p) {
+                                       nextvert_p = c_p->below_p;
+                                       FREE(c_p);
+                               }
+                               nexthor_p = csbi_p->next;
+                               FREE(csbi_p);
+                       }
+               }
+               /* Calculate everything for current beam */
+               csbi_list_p = mkcsbmesh(top_first_p, end_p);
+               cached_gs_p = top_first_p;
+       }
+
+       /* First follow the 8th note CSBINFO list across till we find
+        * the one matching the end group. */
+       for (csbi_p = csbi_list_p; csbi_p != 0 && csbi_p->gs_p != end_p;
+                                                       csbi_p = csbi_p->next) {
+               ;
+       }
+       if (csbi_p == 0) {
+               pfatal("couldn't find beam end group in end_bm_offset()");
+       }
+
+       /* Now follow the vertical links until we find the right basic time.
+        * It could be on either side of the 8th beam.
+        * First we find the end of the stem, then count the number of
+        * links we have to follow to get to the one with the right basictime.
+        */
+       if (end_p->stemdir == DOWN) {
+               /* Must be from staff above, so end of stem is all the way                       * down the below_p list.
+                */
+               for (c_p = csbi_p; c_p->below_p != 0; c_p = c_p->below_p) {
+                       ;
+               }
+               /* Now count the number of beams till the one we want */
+               for (nbeams = 1; c_p->basictime != basictime;
+                                                       c_p = c_p->above_p) {
+                       nbeams++;
+               }
+               if (c_p == 0) {
+                       pfatal("failed to find cross staff beam info go up");
+               }
+       }
+       else {
+               /* similar for staff below groups */
+               for (c_p = csbi_p; c_p->above_p != 0; c_p = c_p->above_p) {
+                       ;
+               }
+               /* Now count the number of beams till the one we want */
+               for (nbeams = 1; c_p->basictime != basictime;
+                                                       c_p = c_p->below_p) {
+                       nbeams++;
+               }
+               if (c_p == 0) {
+                       pfatal("failed to find cross staff beam info go up");
+               }
+       }
+       return (beam_offset(nbeams, end_p->grpsize, end_p->stemdir));
+}
+\f
+
+/* find y offset on stem based on number of beams, whether normal or small
+ * notes, and stem direction */
+
+static double
+beam_offset(nbeams, gsize, stemdir)
+
+int nbeams;    /* how many beams */
+int gsize;     /* GS_NORMAL or GS_SMALL */
+int stemdir;   /* UP or DOWN */
+
+{
+       /* for consistency, it would be nice to use FLAGSEP and SMFLAGSEP
+        * for beam separation too, but when we tried that, beams looked too
+        * close together, especially on certain low-resolution devices,
+        * so that's why we're using 5 and 4 stepsizes. */
+       return ( (nbeams - 1) * (gsize == GS_NORMAL ? 5.0 : 4.0)
+                                       * Staffscale
+                                       * (stemdir == UP ? -POINT : POINT) );
+}
+\f
+
+/* Given a group inside a beam, return the next group. Usually this will
+ * be gs_p->next, but in the case of a cross-staff beam, it might be a
+ * group on the other staff */
+
+struct GRPSYL *
+nxtbmgrp(gs_p, first_p, endnext_p)
+
+struct GRPSYL *gs_p;           /* find the beam group after this one */
+struct GRPSYL *first_p;                /* The first group in the top staff of the
+                                * beam */
+struct GRPSYL *endnext_p;      /* what to return upon reaching the end of
+                                * the beam. This will be the ->next field of
+                                * the last group in the beam on the top staff
+                                * of a cross-staff beam. Returning this lets
+                                * legacy code (code before we supported
+                                * cross-staff beams) keep working with minimal
+                                * changes. */
+
+{
+       int grpsize, grpvalue;
+
+       /* If we are passed the first group, it could be a space,
+        * in which case we need to use the below staff's group instead.
+        */
+       if (gs_p->grpcont == GC_SPACE && gs_p->beamto != CS_SAME) {
+               /* Need to hop to below staff. Go down the chord to find
+                * the matching cross-staff beam group. */
+               do {
+                       if ((gs_p = gs_p->gs_p) == (struct GRPSYL *) 0) {
+                               pfatal("can't find matching beam chord");
+                       }
+
+               /* skip any lyrics and such till we find the beamed-to group */
+               } while (gs_p->beamto != CS_ABOVE);
+       }
+
+       /* need to skip past any groups of the wrong kind */
+       grpsize = first_p->grpsize;
+       grpvalue = first_p->grpvalue;
+       do {
+               /* Move to next group. If that gets us to the end
+                * of the measure, report that we're done. */
+               if ((gs_p = gs_p->next) == (struct GRPSYL *) 0) {
+                       return(endnext_p);
+               }
+       } while (gs_p->grpsize != grpsize || gs_p->grpvalue != grpvalue);
+
+       /* if past end of beam group, report that we're done */
+       if (gs_p->beamloc != INITEM && gs_p->beamloc != ENDITEM) {
+               return(endnext_p);
+       }
+
+       return(neighboring_note_beam_group(gs_p, first_p, NO));
+}
+\f
+
+/* Given a group inside a beam (not the first),
+ * return the previous group. Usually this will
+ * be gs_p->prev, but in the case of a cross-staff beam, it might be a
+ * group on the other staff */
+
+struct GRPSYL *
+prevbmgrp(gs_p, first_p)
+
+struct GRPSYL *gs_p;           /* find the beam group after this one */
+struct GRPSYL *first_p;                /* The first group in the top staff of the
+                                * beam */
+
+{
+       int grpsize, grpvalue;
+       int staffno;
+
+       staffno = gs_p->staffno;
+
+       /* need to skip past any groups of the wrong kind */
+       grpsize = first_p->grpsize;
+       grpvalue = first_p->grpvalue;
+       do {
+               /* Move to prev group. */
+               if ((gs_p = gs_p->prev) == (struct GRPSYL *) 0) {
+                       pfatal("prevbmgrp couldn't find prev group");
+               }
+       } while (gs_p->grpsize != grpsize || gs_p->grpvalue != grpvalue);
+
+       gs_p = neighboring_note_beam_group(gs_p, first_p, YES);
+
+       /* if we hopped staffs, then the space on the original staff might
+        * have been a long note, in which case the group we have isn't
+        * really the one we want. So we have to go forward on this new staff
+        * until we find the space that corresponds to the groups we started
+        * with, then back up one group from there. That's the one we want */
+       if (staffno != gs_p->staffno) {
+               /* we hopped staffs. Go forward to the next space */
+               for (gs_p = gs_p->next; gs_p->grpcont != GC_SPACE;
+                                                       gs_p = gs_p->next) {
+                       ;
+               }
+               /* now take the group right before the space */
+               gs_p = gs_p->prev;
+       }
+       return(gs_p);
+}
+\f
+
+/* Given a group in a beam, skip over any embedded rests.
+ * Then if the group is not a space, return it as it is.
+ * If it is a space, return the corresponding group on the staff
+ * that this group is beamed to */
+
+static struct GRPSYL *
+neighboring_note_beam_group(gs_p, first_p, backwards)
+
+struct GRPSYL *gs_p;           /* find the beam group neighboring this one */
+struct GRPSYL *first_p;                /* The first group in the top staff of the
+                                * beam */
+int backwards;                 /* if YES, go backwards (find the previous
+                                * group rather than the following) */
+
+{
+       struct GRPSYL *tgs_p;   /* as we walk down a chord to try to find
+                                * the group we're looking for, this keeps
+                                * track of where we are */
+
+
+       /* skip over any embedded rests--they are not notes. */
+       while (gs_p->grpcont == GC_REST) {
+               if (backwards == YES) {
+                       gs_p = gs_p->prev;
+               }
+               else {
+                       gs_p = gs_p->next;
+               }
+       }
+       if (gs_p == 0) {
+               pfatal("neighboring_note_beam_group didn't find note group");
+       }
+
+       /* If this is a cross-staff beam, we may need to hop from
+        * staff to staff sometimes. If this group is a space
+        * group, then we have to hop now. */
+       if (gs_p->grpcont == GC_SPACE) {
+               if (gs_p->beamto == CS_SAME) {
+                       do {
+                               if (backwards == YES) {
+                                       gs_p = gs_p->prev;
+                               } else {
+                                       gs_p = gs_p->next;
+                               }
+                       } while (gs_p != 0 && gs_p->grpcont != GC_NOTES);
+               }
+
+               else if (gs_p->staffno == first_p->staffno) {
+                       /* Need to hop to below staff.
+                        * Go down the chord to find
+                        * the matching cross-staff beam group */
+                       do {
+                               if ((gs_p = gs_p->gs_p) ==
+                                               (struct GRPSYL *) 0) {
+                                       pfatal("can't find matching beam chord");
+                               }
+
+                       /* skip any lyrics and such till we find the
+                        * group beamed to us */
+                       } while (gs_p->beamto != CS_ABOVE);
+               }
+               else {
+                       /* Need to jump back to staff above.
+                        * Since the chord linked list is only one way (down)
+                        * and we need to look up the chord, this is a
+                        * little harder. Start at the first_p group, which
+                        * is the first group in the beam on the above staff.
+                        * Keep going down that staff until we find a chord
+                        * linked down to gs_p. */
+                       for (   ; first_p != (struct GRPSYL *) 0;
+                                               first_p = first_p->next) {
+
+                               /* walk down the chord */
+                               for (tgs_p = first_p->gs_p;
+                                               tgs_p != (struct GRPSYL *) 0;
+                                               tgs_p = tgs_p->gs_p) {
+
+                                       if (tgs_p == gs_p) {
+                                               /* Aha! We found it! */
+                                               return(first_p);
+                                       }
+
+                                       if (tgs_p->staffno > gs_p->staffno) {
+                                               /* we're past the staff we care
+                                                * about, so this chord can't
+                                                * be the right one. */
+                                               break;
+                                       }
+                               }
+                       }
+
+                       pfatal("failed to find group when jumping back to above staff");
+               }
+       }
+
+       return(gs_p);
+}
+\f
+
+/* given a GRPSYL that deserves a partial beam, return PB_LEFT if the beam
+ * goes on the left or PB_RIGHT if is goes on the right.  */
+
+int
+pbeamside(gs_p, first_p)
+
+struct GRPSYL *gs_p;
+struct GRPSYL *first_p;
+
+{
+       int side;
+       int beams2left, beams2right;    /* how many beams or dots for notes on
+                                        * either side of current group */
+       struct GRPSYL *prevgs_p, *nextgs_p;
+
+
+       /* need to figure out which side of stem to draw the
+        * partial beam. First the easy cases: if is STARTITEM,
+        * then it has to go on the right, if ENDITEM, it
+        * has to go on the left */
+       switch (gs_p->beamloc) {
+       case STARTITEM:
+               side = PB_RIGHT;
+               break;
+
+       case ENDITEM:
+               side = PB_LEFT;
+               break;
+
+       case INITEM:
+               /* Hmmm. Will have to be more clever. Check the
+                * note on either side. If we're at a breakbeam,
+                * it's easy to know. Otherwise, if one should have more
+                * beams than the other, put the partial on that
+                * side */
+               prevgs_p = prevbmgrp(gs_p, first_p);
+               nextgs_p = nxtbmgrp(gs_p, first_p, gs_p->next);
+               beams2left = numbeams(prevgs_p->basictime);
+               beams2right = numbeams(nextgs_p->basictime);
+               if (gs_p->breakbeam == YES) {
+                       side = PB_LEFT;
+               }
+               else if (prevgs_p != 0 && prevgs_p->breakbeam == YES) {
+                       side = PB_RIGHT;
+               }
+               else if (beams2left > beams2right) {
+                       side = PB_LEFT;
+               }
+               else if (beams2right > beams2left) {
+                       side = PB_RIGHT;
+               }
+
+               /* That was inconclusive.  So now we're going to try to decide
+                * based on logical groupings of notes; that is, notes grouped
+                * according to what the accents should be. */
+               else if (chkgroupings(&side, gs_p) == YES) {
+                       /* it found an answer and set "side" for us */
+                       ;
+               }
+               else {
+                       /* ok. that didn't help.
+                        * See if the notes on either side
+                        * have more dots than the other.
+                        * If so, put the partial towards
+                        * that one. If they are the same, then
+                        * throw in the towel and just stick it
+                        * on the left */
+                       beams2left = prevgs_p->dots;
+                       beams2right = nextgs_p->dots;
+                       if (beams2right > beams2left) {
+                               side = PB_RIGHT;
+                       }
+                       else {
+                               side = PB_LEFT;
+                       }
+               }
+               break;
+
+       default:
+               pfatal("invalid beamloc passed to pbeamside");
+               /*NOTREACHED*/
+               return(PB_LEFT);        /* to shut up bogus compiler warning */
+       }
+
+       return(side);
+}
+\f
+/*
+ * Name:        chkgroupings()
+ *
+ * Abstract:    Decide partial beam side based on groupings of notes.
+ *
+ * Returns:     YES if it found an answer (stored in *side_p), NO if not
+ *
+ * Description: This function breaks the measure down into successively
+ *             smaller pieces based on where the accents should be, trying to
+ *             find a piece where the current GRPSYL falls at the beginning or
+ *             end of the piece.  If the GRPSYL falls at the start of a piece,
+ *             its partial beam should point right; if end, left.  If we get
+ *             to the point where the pieces are shorter than the GRPSYL
+ *             itself, we have failed.
+ */
+
+static int
+chkgroupings(side_p, thisgs_p)
+
+int *side_p;                   /* where to put the answer, if found */
+struct GRPSYL *thisgs_p;       /* the GRPSYL we are working on */
+
+{
+       struct GRPSYL *gs_p;    /* point along GRPSYL list */
+       short *factors;         /* array to be malloc'ed */
+       int n;                  /* loop variable */
+       RATIONAL thisstart;     /* time offset in measure of thisgs_p */
+       RATIONAL nextstart;     /* time offset in measure of next GRPSYL */
+       RATIONAL quotient;      /* temp variable for dividing */
+       RATIONAL grouplen;      /* time length of a grouping */
+       RATIONAL tupstart;      /* time offset where tuplet starts */
+       RATIONAL tupdur;        /* time length of a tuplet */
+       int counts;             /* count in the current grouplen */
+       int fraction;           /* is grouplen a fraction of a count? */
+       int fact;               /* a factor */
+
+
+       /*
+        * If we're doing grace beams, skip this whole thing, since we're
+        * dealing with time values, and they are all zero.
+        */
+       if (thisgs_p->grpvalue == GV_ZERO) {
+               return (NO);
+       }
+
+       /* find the time offset of thisgs_p by adding up all previous GRPSYLs*/
+       thisstart = Zero;
+       for (gs_p = thisgs_p->prev; gs_p != 0; gs_p = gs_p->prev) {
+               thisstart = radd(thisstart, gs_p->fulltime);
+       }
+
+       /* find offset of GRPSYL following thisgs_p */
+       nextstart = radd(thisstart, thisgs_p->fulltime);
+
+       /*
+        * Interior notes of tuplets are dealt with in a special way.
+        */
+       if (thisgs_p->tuploc == INITEM) {
+               /*
+                * Find the duration of the tuplet by adding up all the
+                * previous GRPSYLs in the tuplet and this GRPSYL and all the
+                * later GRPSYLs.  (The loops stop when they hit a NOITEM
+                * that's not grace.)
+                */
+               tupdur = Zero;
+               for (gs_p = thisgs_p->prev; gs_p != 0 &&
+                               (gs_p->grpvalue == GV_ZERO ||
+                               gs_p->tuploc != NOITEM); gs_p = gs_p->prev) {
+                       tupdur = radd(tupdur, gs_p->fulltime);
+               }
+               /* remember where tuplet starts */
+               tupstart = rsub(thisstart, tupdur);
+               for (gs_p = thisgs_p; gs_p != 0 &&
+                               (gs_p->grpvalue == GV_ZERO ||
+                               gs_p->tuploc != NOITEM); gs_p = gs_p->next) {
+                       tupdur = radd(tupdur, gs_p->fulltime);
+               }
+
+               /*
+                * If the starting point of this tuplet is not at a multiple of
+                * its duration, we consider the tuplet synchopated.  This is
+                * pretty bizarre and not worth trying to deal with.
+                */
+               quotient = rdiv(tupstart, tupdur);
+               if (quotient.d != 1) {
+                       return (NO);
+               }
+
+               /* the first group length to consider is tupdur/tupcont */
+               grouplen = tupdur;
+               grouplen.d *= thisgs_p->tupcont;
+               rred(&grouplen);
+
+               /* loop until an answer is found, or we give up */
+               for (;;) {
+                       /*
+                        * If the group length is not longer than our note, it
+                        * makes no sense to try to see if our note is at the
+                        * start or end of such a group.  Maybe we never hit a
+                        * match because our note is syncopated.  Whatever the
+                        * reason, we have to give up.
+                        */
+                       if (LE(grouplen, thisgs_p->fulltime)) {
+                               return (NO);
+                       }
+
+                       /*
+                        * If thisstart/grouplen is an integer, it means
+                        * thisgs_p is on a grouping boundary; that is, it is
+                        * the first GRPSYL in a grouping.  So point right.
+                        */
+                       quotient = rdiv(thisstart, grouplen);
+                       if (quotient.d == 1) {
+                               *side_p = PB_RIGHT;
+                               return (YES);
+                       }
+
+                       /*
+                        * If nextstart/grouplen is an integer, it means the
+                        * GRPSYL after thisgs_p is on a grouping boundary,
+                        * which means that thisgs_p is the last GRPSYL in a
+                        * grouping.  So point left.
+                        */
+                       quotient = rdiv(nextstart, grouplen);
+                       if (quotient.d == 1) {
+                               *side_p = PB_LEFT;
+                               return (YES);
+                       }
+
+                       /* divide grouplen by 2 and try again */
+                       grouplen = rdiv(grouplen, Two);
+               }
+       }
+
+       /*
+        * This is the normal case, not the interior of a tuplet.
+        */
+
+       /* get all the prime factors of the time sig's numerator */
+       factors = factor(Score.timenum);
+
+       grouplen = Score.time;          /* first group is the whole measure */
+       counts = Score.timenum;         /* number of counts in measure */
+
+       /*
+        * Loop until we find an answer, or until we have to give up.  Each
+        * time through the loop, we reduce the grouping length.  At first, we
+        * divide out prime factors from the number of counts in the measure.
+        * Once we get down to one count, we start dividing by 2 all the time.
+        */
+       for (;;) {
+               fraction = YES;         /* default to "fraction of a count" */
+
+               /* if there are still multiple counts, divide out a prime */
+               if (counts > 1) {
+                       /*
+                        * See if there are any prime factors greater than 4.
+                        * This only happens with funny timesigs like 10/8 or
+                        * 7/4.  We work down from the top, because the
+                        * likelyhood is that the highest level grouping is by
+                        * the biggest factor, when these funny numbers are
+                        * involved.  At least 10/8, for example, is normally
+                        * 5 groups of 2, not 2 groups of 5.
+                        */
+                       for (n = Score.timenum; n > 4 && factors[n] == 0; n--)
+                               ;
+                       /* if we found a 5 or greater, use it */
+                       if (n > 4) {
+                               factors[n]--;
+                               fact = n;
+                       /*
+                        * There are no funny factors (5 or more) left.  Next,
+                        * we need to look for 2s, not 3s yet, because, for
+                        * example, 6/8 is 2 groups of 3, not 3 groups of 2.
+                        */
+                       } else if (counts % 2 == 0) {
+                               factors[2]--;
+                               fact = 2;
+                       /* no 2s either, so look for 3s */
+                       } else if (counts % 3 == 0) {
+                               factors[3]--;
+                               fact = 3;
+                       } else {
+                       /* no factors left, so flag it by setting fact to 1 */
+                               fact = 1;
+                       }
+
+                       /*
+                        * If a factor was found, divide it out, and remember
+                        * that we are not yet dealing with fractions of a
+                        * single count.
+                        */
+                       if (fact > 1) {
+                               counts /= fact;
+                               fraction = NO;
+                       }
+               }
+
+               if (fraction == YES) {
+                       /*
+                        * We are dealing with fractions of a count, so divide
+                        * by 2 from now on.
+                        */
+                       grouplen = rdiv(grouplen, Two);
+               } else {
+                       /*
+                        * Using the number of counts remaining, form the
+                        * length in lowest terms.
+                        */
+                       grouplen.n = counts;
+                       grouplen.d = Score.timeden;
+                       rred(&grouplen);
+               }
+
+               /*
+                * If the group length is not longer than our note, it makes no
+                * sense to try to see if our note is at the start or end of
+                * such a group.  Maybe we never hit a match because our note
+                * is syncopated.  Whatever the reason, we have to give up.
+                */
+               if (LE(grouplen, thisgs_p->fulltime)) {
+                       return (NO);
+               }
+
+               /*
+                * If thisstart/grouplen is an integer, it means thisgs_p is on
+                * a grouping boundary; that is, it is the first GRPSYL in a
+                * grouping.  So point right.
+                */
+               quotient = rdiv(thisstart, grouplen);
+               if (quotient.d == 1) {
+                       *side_p = PB_RIGHT;
+                       return (YES);
+               }
+
+               /*
+                * If nextstart/grouplen is an integer, it means the GRPSYL
+                * after thisgs_p is on a grouping boundary, which means that
+                * thisgs_p is the last GRPSYL in a grouping.  So point left.
+                */
+               quotient = rdiv(nextstart, grouplen);
+               if (quotient.d == 1) {
+                       *side_p = PB_LEFT;
+                       return (YES);
+               }
+       }
+
+       return (NO);            /* we can never get here; this is for lint */
+}
+\f
+
+/* actually draw a beam */
+
+static void
+do_beam(x1, y1, x2, y2, halfwidth)
+
+double x1, y1;         /* start beam here */
+double x2, y2;         /* end beam here */
+double halfwidth;      /* go this far up and down from y1 and y2 to get
+                        * corners of parallelogram that makes up the beam */
+
+{
+       do_newpath();
+       do_moveto(x1, y1 + halfwidth);
+       do_line(x2, y2 + halfwidth);
+       do_line(x2, y2 - halfwidth);
+       do_line(x1, y1 - halfwidth);
+       do_closepath();
+       do_fill();
+}
+\f
+
+/* print a multirest */
+
+void
+pr_multirest(gs_p, staff_p)
+
+struct GRPSYL *gs_p;   /* info about the multirest */
+struct STAFF *staff_p;
+
+{
+       double x;               /* horizontal position of number string */
+       double y, y_offset;     /* vertical location */
+       double height, width;   /* of meas num string */
+       float east, west;       /* edges of the multirest */
+       char *numstr;           /* ASCII version of numbers of measures */
+
+
+       /* avoid core dumps */
+       if (Score_location_p == (float *) 0) {
+               pfatal("can't do multirest: no feed");
+               return;
+       }
+
+       /* determine where to place the multirest */
+       y = mr_y_loc(gs_p->staffno);
+       east = gs_p->c[AE];
+       west = gs_p->c[AW];
+
+       /* If user wants us to use the alternative multirest style of using
+        * rest symbols (often used in orchestral music), and the length of
+        * the multirest is 8 or less, we use that alternate style,
+        * otherwise draw horizontal line along middle staff and two
+        * vertical lines near the bar lines. Note that the basictime is
+        * the negative of the number of measures of multirest.  
+        * We have seen rare examples of using the alternate style for all the
+        * way up to 11 measures, but consider normal usage only up to 8.
+        */
+       if (svpath(staff_p->staffno, RESTSYMMULT)->restsymmult == YES &&
+                                               gs_p->basictime > -9) {
+               double center;  /* can't use AX, must use avg of AE and AW */
+               int size;       /* actually will always be normal size,
+                                * but may as well make code be able to handle
+                                * small size just in case... */
+
+               center = (gs_p->c[AE] + gs_p->c[AW]) / 2.0;
+               size = (gs_p->grpsize == GS_NORMAL ? DFLT_SIZE : SMALLSIZE);
+
+               switch (gs_p->basictime) {
+               case -2:
+                       pr_muschar(center, gs_p->c[AY], C_DWHREST, size, FONT_MUSIC);
+                       break;
+               case -3:
+                       pr_muschar(gs_p->c[AW], gs_p->c[AY], C_DWHREST, size, FONT_MUSIC);
+                       pr_muschar(gs_p->c[AE], gs_p->c[AY], C_1REST, size, FONT_MUSIC);
+                       break;
+               case -4:
+                       pr_muschar(center, gs_p->c[AY], C_QWHREST, size, FONT_MUSIC);
+                       break;
+               case -5:
+                       pr_muschar(gs_p->c[AW], gs_p->c[AY], C_QWHREST, size, FONT_MUSIC);
+                       pr_muschar(gs_p->c[AE], gs_p->c[AY], C_1REST, size, FONT_MUSIC);
+                       break;
+               case -6:
+                       pr_muschar(gs_p->c[AW], gs_p->c[AY], C_QWHREST, size, FONT_MUSIC);
+                       pr_muschar(gs_p->c[AE], gs_p->c[AY], C_DWHREST, size, FONT_MUSIC);
+                       break;
+               case -7:
+                       pr_muschar(gs_p->c[AW], gs_p->c[AY], C_QWHREST, size, FONT_MUSIC);
+                       pr_muschar(center, gs_p->c[AY], C_DWHREST, size, FONT_MUSIC);
+                       pr_muschar(gs_p->c[AE], gs_p->c[AY], C_1REST, size, FONT_MUSIC);
+                       break;
+               case -8:
+                       pr_muschar(gs_p->c[AW], gs_p->c[AY], C_QWHREST, size, FONT_MUSIC);
+                       pr_muschar(gs_p->c[AE], gs_p->c[AY], C_QWHREST, size, FONT_MUSIC);
+                       break;
+               default:
+                       pfatal("restsymmult with illegal number of measures (%d)",
+                                       -(gs_p->basictime) );
+                       break;
+               }
+       }
+       else {
+               /* draw vertical lines at each end */
+               do_linetype(L_MEDIUM);
+
+               draw_line(west, y - (2.0 * Stepsize), west, y + (2.0 * Stepsize));
+               draw_line(east, y - (2.0 * Stepsize), east, y + (2.0 * Stepsize));
+
+               /* draw heavy horizontal */
+               do_linetype(L_WIDE);
+               draw_line(west, y, east, y);
+       }
+
+       if (svpath(staff_p->staffno, PRINTMULTNUM)->printmultnum == YES) {
+               /* print number of measures */
+               numstr = mrnum(staff_p, &x, &y_offset, &height, &width);
+               pr_string(x, y + y_offset, numstr, J_LEFT, (char *) 0, -1);
+       }
+}
+\f
+
+/* Given a STAFF pointing to a multirest or measure repeat GRPSYL,
+ * return a string for its number of measures,
+ * and return via pointers its x, relative y, height, and width */
+
+char *
+mrnum(staff_p, x_p, y_offset_p, height_p, width_p)
+
+struct STAFF *staff_p;
+double *x_p;           /* return where number starts horizontally */
+double *y_offset_p;    /* return y relative to staff */
+double *height_p;      /* return height of string */
+double *width_p;       /* return width of string */
+
+{
+       struct GRPSYL *gs_p = 0;/* initialize to avoid compiler warning */
+       char *numstr;           /* ASCII version of number of measures */
+       int v;                  /* voice index */
+
+       /* skip over invisible voices */
+       for (v = 0; v < MAXVOICES; v++) {
+               if (vvpath(staff_p->staffno, v + 1, VISIBLE)->visible == YES) {
+                       gs_p = staff_p->groups_p[v];
+                       break;
+               }
+       }
+       if (v == MAXVOICES) {
+               pfatal("no visible voice found by mrnum");
+       }
+       if (gs_p->grpcont == GC_NOTES) {
+               /* this is a measure repeat */
+               numstr = num2str(staff_p->mrptnum);
+               numstr[0] = FONT_TR;
+               numstr[1] = 11;
+       }
+       else if (gs_p->grpcont == GC_REST) {
+               /* this is a multi-rest */
+               numstr = num2str( -(gs_p->basictime) );
+               /* want these in bigger size */
+               /* Essential Dictionary of Music Notation says this
+                * should be in the same size and font as time signature. */
+               numstr[0] = FONT_NB;
+               numstr[1] = 16;
+       }
+       else {
+               pfatal("wrong group type (%d) passed to mrnum, line %d, staff %d, voice %d", gs_p->grpcont, gs_p->inputlineno, gs_p->staffno, gs_p->vno);
+               /*NOTREACHED*/
+               return (char *) 0;      /* to shut up bogus compiler warning */
+       }
+       numstr[1] = (char) adj_size((int) numstr[1], Staffscale, (char *) 0, -1);
+
+       *width_p = strwidth(numstr);
+       *height_p = strheight(numstr);
+
+       /* x is middle of measure minus 1/2 of number string width */
+       /* y offset is just above staff */
+       *x_p = ((gs_p->c[AE] + gs_p->c[AW]) / 2.0) - (*width_p / 2.0);
+       *y_offset_p = halfstaffhi(gs_p->staffno) + Stepsize;
+       return(numstr);
+}
+\f
+
+/* given a number, return pointer to string version (with font/size in first
+ * 2 bytes. Points to static area overwritten on each call, so if you need a
+ * unique copy of it, use copy_string(). Always makes a string in Roman in
+ * the default size. */
+
+char *
+num2str(num)
+
+int num;       /* the number to convert */
+
+{
+       static char numstr[12];
+
+       /* get ASCII version of number */
+       (void) sprintf(numstr, "%c%c%d", FONT_TR,
+                       adj_size(DFLT_SIZE, Staffscale, (char *) 0, -1), num);
+       return(numstr);
+}
+\f
+
+/* print cresc or decresc */
+
+static void
+pr_cresc(stuff_p)
+
+struct STUFF *stuff_p; /* info about what to print and where */
+
+{
+       float x1, x2;                   /* x coords of west and east points */
+       float line1y1, line2y1;         /* y coords of west points */
+       float line1y2, line2y2;         /* y coords of east points */
+
+
+       do_linetype(L_NORMAL);
+
+       /* get coords for point and midpoint of open end */
+       x1 = stuff_p->c[AW];
+       x2 = stuff_p->c[AE];
+       if (stuff_p->stuff_type == ST_CRESC) {
+               line1y1 = line2y1 = (stuff_p->c[AN] + stuff_p->c[AS]) / 2.0;
+               /* adjust by 1 point to allow some vertical padding */
+               line1y2 = stuff_p->c[AN] - (1.0 * Stdpad);
+               line2y2 = stuff_p->c[AS] + (1.0 * Stdpad);
+       }
+       else if (stuff_p->stuff_type == ST_DECRESC) {
+               line1y2 = line2y2 = (stuff_p->c[AN] + stuff_p->c[AS]) / 2.0;
+               line1y1 = stuff_p->c[AN] - (1.0 * Stdpad);
+               line2y1 = stuff_p->c[AS] + (1.0 * Stdpad);
+       }
+       else {
+               pfatal("pr_cres called for something other than cresc/decresc");
+               /*NOTREACHED*/
+               return; /* to shut up bogus compiler warning about unused variables */
+       }
+
+       /* draw the two sides of the hairpin */
+       draw_line(x1, line1y1, x2, line1y2);
+       draw_line(x1, line2y1, x2, line2y2);
+}
+\f
+
+/* if a STUFF has a til clause, may need to extend out. If a trill, extend
+ * with wavy line. If octave, use dashed line.  If strings ends with a ~,
+ * use a wavy line. If ends with an underscore or is figbass, use
+ * underline. For everything else, put out periodic dashed. */
+
+static void
+extend(stuff_p)
+
+struct STUFF *stuff_p; /* a stuff which may have a til clause */
+
+{
+       float extlen;           /* length of extension */
+       float y;                /* vertical position */
+       float x;                /* horizontal position */
+       float segment;          /* length of dash + white space */
+       char *dash;             /* dash in proper font/size */
+       char lch;               /* last character of string */
+
+
+       if (stuff_p->end.bars <= 0 && stuff_p->end.count <= 0.0) {
+               /* no til clause, so nothing to do */
+               return;
+       }
+
+       /* figure out how much to extend */
+       extlen = stuff_p->c[AE] - stuff_p->c[AW] - strwidth(stuff_p->string);
+       y = stuff_p->c[AY];
+
+       if (string_is_sym(stuff_p->string, C_TR, FONT_MUSIC) == YES) {
+               /* special case of a trill */
+               if ( extlen < Stepsize) {
+                       /* too short to bother */
+                       return;
+               }
+
+               y += (2.0 * Stepsize);
+               draw_wavy(stuff_p->c[AE] - extlen, y, stuff_p->c[AE], y);
+               return;
+       }
+
+       else if ((lch = last_char(stuff_p->string)) == '~') {
+               y += strascent(stuff_p->string) / 2.0;
+               draw_wavy(stuff_p->c[AE] - extlen, y, stuff_p->c[AE], y);
+               return;
+       }
+
+       else if (lch == '_' || stuff_p->modifier == TM_FIGBASS) {
+               do_linetype(L_NORMAL);
+               draw_line(stuff_p->c[AE] - extlen, y, stuff_p->c[AE], y);
+               return;
+       }
+
+       else if (stuff_p->stuff_type == ST_OCTAVE) {
+
+               if ( extlen < (4.0 * Stepsize)) {
+                       /* too short to bother */
+                       return;
+               }
+
+               y += (1.5 * Stepsize);
+               do_linetype(L_DASHED);
+               draw_line(stuff_p->c[AE] - extlen + (2.0 * Stepsize), y,
+                                               stuff_p->c[AE], y);
+
+               /* vertical line at end unless carried to next score */
+               if (stuff_p->carryout == NO) {
+                       draw_line(stuff_p->c[AE], y, stuff_p->c[AE],
+                               y + (3.0 * Stepsize *
+                               (stuff_p->place == PL_ABOVE ? -1.0 : 1.0)));
+               }
+
+               /* put linetype back to solid so some other music character that
+                * uses a line won't get messed up */
+               do_linetype(L_NORMAL);
+               return;
+       }
+
+       dash = dashstr(stuff_p->string);
+       segment = (3.0 * strwidth(dash));
+       for ( x = stuff_p->c[AE] - extlen + (2.0 * segment) / 3.0;
+                                       x < stuff_p->c[AE]; x += segment) {
+               pr_string(x, y, dash, J_LEFT, (char *) 0, -1);
+       }
+       FREE(dash);
+}
+\f
+
+/* Some characters have upside-down versions that are used
+ * if stem is down. This table maps such characters to their flips versions.
+ */
+
+static struct MIRRCHAR {
+       int     font;           /* Which music font. Note that both the
+                                * normal and inverted characters must be
+                                * in the same font. (We could relax
+                                * this constraint by storing a font for each,
+                                * and returning both character and font,
+                                * but there's no hardship in this simple way.)
+                                */
+       char    norm;
+       char    inverted;
+} mirrtbl[] = {
+       { FONT_MUSIC, C_FERM, C_UFERM },
+       { FONT_MUSIC, C_ACC_HAT, C_ACC_UHAT },
+       { FONT_MUSIC, C_WEDGE, C_UWEDGE },
+       { 0, 0 }
+};
+
+
+/* Given a string and the first character in it, if it is a music symbol
+ * that has a mirrored version, return that, otherwise, return
+ * it as it was.
+ */
+
+static int
+mirror(str, ch, font)
+
+char *str;     /* the string to check */
+int ch;                /* the first character (which better be a music character) */
+int font;      /* FONT_MUSIC or some other music font */
+
+{
+       int i;
+
+       for (i = 0; mirrtbl[i].norm != '\0'; i++) {
+               if (string_is_sym(str, mirrtbl[i].norm, mirrtbl[i].font) == YES) {
+                       return((int) mirrtbl[i].inverted);
+               }
+       }
+       return(ch);
+}