--- /dev/null
+
+/* 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);
+}