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