X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/mup/blobdiff_plain/cdb3c0882392596f814cf939cbfbd38adc6f2bfe..ddf6330b56bcfb657e0186b24b9b1422c51d3424:/mup/mup/undrscre.c?ds=sidebyside diff --git a/mup/mup/undrscre.c b/mup/mup/undrscre.c new file mode 100644 index 0000000..e45213c --- /dev/null +++ b/mup/mup/undrscre.c @@ -0,0 +1,1542 @@ +/* Copyright (c) 1995, 1996, 1997, 1999, 2000, 2001, 2002, 2003 by Arkkra Enterprises */ +/* All rights reserved */ + +/* This file contains functions that deal with + * extending underscores and dashes on lyric syllables. + */ + +#include "defines.h" +#include "structs.h" +#include "globals.h" + +static double end_dashes P((struct MAINLL *mll_p, struct GRPSYL *syl_p, + int verse, int place, int *carryover_p)); +static double end_underscore P((struct MAINLL *mll_p, struct GRPSYL *syl_p, + int verse, int place, int *carryover_p)); +static double endx P((struct GRPSYL *last_grp_p, double end)); +static int has_above_lyr P((struct MAINLL *mll_p, RATIONAL begin_time, + struct GRPSYL *group_p, int verse)); +static int voice_is_above P((int v1, int v2)); +static struct GRPSYL *find_verse_place P((struct STAFF *staff_p, + int verse, int place)); +static RATIONAL default_end P((struct MAINLL *mll_p, struct GRPSYL *syl_p, + RATIONAL start_time, double *end_p)); +static int bar_ends_extender P((struct BAR *bar_p, struct MAINLL *mll_p, + int staffno, int verse, int place, + struct GRPSYL **nextsyl_p_p)); +static void pr_extender P((int ch, double start, double end, double y, + int font, int size)); +static void insert_carryover_syllable P((struct MAINLL *mll_p, int staffno, + int sylplace, int verseno, char *dash_or_underscore, + int font, int size)); +static void add_syllable P((struct STAFF *staff_p, int sylplace, int verseno, + char *dash_or_underscore, int font, int size, + double begin_x, struct CHORD *chord_p)); +static void stitch_syl_into_chord P((struct CHORD *chord_p, + struct GRPSYL *syl_gs_p)); + + +/* This function is called on lyric syllables in two cases: + * 1) During placement phase to determine if an extender needs to + * be carried over to a following staff. In this case, really_print + * will be NO. (The easiest way to see if an extender needs to carry + * over is to pretend to print an extender). The return value will + * be YES if there should be a carryover. + * 2) When printing the syllable, to draw an extender after it, + * if appropriate. In this case, really_print will be YES, + * and the return value is meaningless. + * + * If a syllable ends with a dash, the dash should be placed halfway between + * where this syllable ends and the next begins. Or if there is a big space, + * multiple dashes should be spread out in that space. If it ends with an + * underscore, then an underline should be drawn from the end of this + * syllable to the east edge of the notes in the last chord before + * the next syllable for the same staff/verse/place. + * But if there is a carryover, this just does the extender up to the + * end of the current score; it will get called again on the next score + * to continue the extender. + * If an underscore is used on a single chord, rather than a mellisma + * (which might technically be considered an "incorrect" usage of underscore), + * we figure the underscore should be drawn to just before the next syllable, + * unless there is a rest earlier. Or if the next syllable begins a measure, + * the underscore ends before the bar line, to look better. + * If really printing, the dash or underscore is removed + * from the end of the string so it won't get printed + * with the syllable. + */ + +int +spread_extender(syl_p, mll_p, verse, sylplace, really_print) + +struct GRPSYL *syl_p; /* current syllable */ +struct MAINLL *mll_p; /* which MAINLL struct it's hanging off of */ +int verse; /* verse number */ +int sylplace; /* PL_ABOVE, etc */ +int really_print; /* if YES, actually print, otherwise just return + * whether needs to be carried over to next score */ + +{ + char *syl; /* to walk through characters of the syllable */ + int font; + int size; + double start; /* dash area or underscore starts here */ + double end; /* dash area or underscore ends here */ + int ch; /* current character in syllable */ + int last_ch = '\0'; /* final character in syllable */ + int extndr_font; /* in case user changes font/size after + * extender, keep track of what font/size the + * extender was */ + int extndr_size = -1; + char *ch_p; /* pointer to where - or _ is in string */ + int carryover; /* YES if will carry over to next staff */ + + + + /* See if there is a dash or underscore at the end of the syllable. + * If so, save pointer to it. Note that this may not be the last + * byte in the string, because there could be font/size changes + * after it. */ + font = syl_p->syl[0]; + size = syl_p->syl[1]; + syl = syl_p->syl + 2; + + /* These two assignments avoid "used without being set" warnings */ + extndr_font = font; + ch_p = syl; + + /* Find last character of syllable */ + while ( (ch = next_str_char( &syl, &font, &size)) != '\0') { +#ifdef EXTCHAR + if ( ( ch == '-' || ch == '_') && ! IS_MUSIC_FONT(font) + && (font < FONT_XTR) ) { +#else + if ( ( ch == '-' || ch == '_') && ! IS_MUSIC_FONT(font) ) { +#endif + ch_p = syl - 1; + extndr_font = font; + extndr_size = size; + last_ch = ch; + } + else { + last_ch = '\0'; + } + } + + /* If there is an extender, handle it */ + if (last_ch != '\0') { + if ( last_ch == '-') { + end = end_dashes(mll_p, syl_p, verse, sylplace, + &carryover); + } + else { + end = end_underscore(mll_p, syl_p, verse, sylplace, + &carryover); + } + + if (really_print == NO) { + return(carryover); + } + + /* Move the rest of the string + * over the dash or underscore, + * so it won't get printed with the syllable */ + do { + *ch_p = *(ch_p + 1); + } while ( *++ch_p != '\0'); + start = syl_p->c[AE]; + + /* procsyls() adjusted the east in certain cases for + * placement purposes. For printing we need to cancel out + * those adjustments. */ + if (syl_p->next != 0 && last_ch != '-') { + start -= width(extndr_font, extndr_size, ' '); + } + if (syl_p->next == 0 && last_ch == '-') { + start += width(extndr_font, extndr_size, ' '); + } + + /* actually print the extender */ + pr_extender(last_ch, start, end, syl_p->c[AY], + extndr_font, extndr_size); + } + return(NO); +} + + +/* Given a syllable ending with a dash, and some other info, + * return where to end the dash(es). If the dashes carry over + * to the following score, this will return a point near the east end of + * the current score, after setting *carryover_p to YES. + */ + +static double +end_dashes(mll_p, syl_p, verse, place, carryover_p) + +struct MAINLL *mll_p; /* points to STAFF containing the syl with dash */ +struct GRPSYL *syl_p; /* this is the syllable with dash */ +int verse; /* which verse the syl_p is for */ +int place; /* a PL_* value for where the lyric is */ +int *carryover_p; /* return value, set to YES if there was a carryover */ + +{ + int staffno; + struct BAR *lastbar_p; + + staffno = syl_p->staffno; + *carryover_p = NO; + lastbar_p = 0; /* will get set to something better before being used */ + syl_p = syl_p->next; + + do { + /* Go forward looking for another non-space syllable */ + if (syl_p != 0) { + for ( ; syl_p != 0; syl_p = syl_p->next) { + if (syl_p->grpcont != GC_SPACE) { + /* found it! */ + return(syl_p->c[AW] - Stepsize); + } + } + } + + /* No ending syl in current measure. Try the next. */ + for (mll_p = mll_p->next; mll_p != 0; mll_p = mll_p->next) { + if (mll_p->str == S_BAR) { + if (bar_ends_extender(mll_p->u.bar_p, + mll_p, staffno, verse, + place, 0) == YES) { + return(mll_p->u.bar_p->c[AW] - Stepsize); + } + lastbar_p = mll_p->u.bar_p; + } + + else if (mll_p->str == S_FEED) { + /* If this is a feed at the very end of the + * main list, or one or more blocks follow it, + * this is not the kind of feed + * we're looking for. */ + if (mll_p->next == 0 || + mll_p->next->str == S_BLOCKHEAD) { + continue; + } + /* There is a carryover unless the + * pseudo-bar is something that would end + * the extender. */ + mll_p = mll_p->next; + if (mll_p->str != S_CLEFSIG + || mll_p->u.clefsig_p->bar_p + == 0) { + if (mll_p->str == S_FEED) { + /* Being here means there is + * a bug somewhere else, + * because the main list rules + * are violated. But we can + * render such a bug harmless + * by continuing here. + */ + continue; + } + pfatal("end_dashes found unexpected main list contents after feed"); + } + if (bar_ends_extender(mll_p->u.clefsig_p->bar_p, + mll_p, staffno, verse, place, 0) + == NO) { + *carryover_p = YES; + } + return(lastbar_p->c[AW] - Stepsize); + } + + else if (mll_p->str == S_STAFF + && mll_p->u.staff_p->staffno + == staffno) { + syl_p = find_verse_place(mll_p->u.staff_p, + verse, place); + break; + } + } + } while (mll_p != 0); + + /* Fell off end of song. Use final bar */ + return(lastbar_p->c[AW] - Stepsize); +} + + +/* Given a syllable ending with an underscore, and some other info, + * return where to end the underscore. If the underscore carries over + * to the following score, this will return a point near the east end of + * the current score, after setting *carryover_p to YES. + */ + +static double +end_underscore(mll_p, syl_p, verse, place, carryover_p) + +struct MAINLL *mll_p; /* points to STAFF containing the syl with underscore */ +struct GRPSYL *syl_p; /* this is the syllable with underscore */ +int verse; /* which verse the syl_p is for */ +int place; /* a PL_* value for where the lyric is */ +int *carryover_p; /* return value, set to YES if there was a carryover */ + +{ + struct GRPSYL *current_grp_p[MAXVOICES];/* which group we are + * currently dealing with on + * each voice */ + RATIONAL group_time[MAXVOICES]; /* accumulated time value of + * groups up to the one we + * are currently dealing with */ + short had_rest[MAXVOICES]; /* YES or NO */ + RATIONAL current_time; /* how far we are in meas */ + RATIONAL end_time; /* where next non-space + * syllable is for this + * staff/place/verse, if + * there is one + * in the current measure, + * otherwise the end of the + * current measure. */ + struct GRPSYL *last_grp_p; /* if non-zero, this is the + * current candidate group + * with which we could + * potentially align the + * end of the underscore. */ + double end; /* this is how far we will + * draw the underscore if we + * don't find any reason to + * stop it sooner. */ + struct GRPSYL *grp_p; /* walk through GRPSYLs */ + struct STAFF *staff_p; /* next measure's STAFF */ + struct GRPSYL *nextsyl_p; /* syl list for same verse/place + * in the next measure */ + RATIONAL grp_end_time; /* where a current group ends */ + + int vindex; /* voice index */ + int v; /* voice index */ + short found_feed; /* YES or NO */ + int staffno; + + + *carryover_p = NO; /* assume no carryover for now */ + staffno = mll_p->u.staff_p->staffno; + + /* Back up from the syllable with underscore to count up time-wise how + * far into the measure it is */ + current_time = Zero; + for (grp_p = syl_p->prev; grp_p != 0; grp_p = grp_p->prev) { + current_time = radd(current_time, grp_p->fulltime); + } + + /* Set a default end time and place. + * Most likely, we will discover later we need to stop the underscore + * earlier than this, but if the user is using underscore in a strange + * way, like on a single long note rather than a melissma, + * we'll use this as the default place to end the underscore. */ + end_time = default_end(mll_p, syl_p, current_time, &end); + + /* We don't yet have any candidate group with which to align + * the ending of the underscore. */ + last_grp_p = 0; + + /* For each voice, if it exists, find the group that contains + * the time of the syllable with the underscore, and make that + * the "current group" for that voice. If the voice doesn't exist, + * set the current group pointer to zero. */ + staff_p = mll_p->u.staff_p; + for (vindex = 0; vindex < MAXVOICES; vindex++) { + group_time[vindex] = Zero; + had_rest[vindex] = NO; + if (staff_p->groups_p[vindex] != 0) { + for (current_grp_p[vindex] = staff_p->groups_p[vindex]; + current_grp_p[vindex] != 0; + current_grp_p[vindex] + = current_grp_p[vindex]->next) { + if (GE(current_time, group_time[vindex]) && + LT(current_time, + radd(group_time[vindex], + current_grp_p[vindex]->fulltime))) { + /* This group contains the syl's time */ + + if (current_grp_p[vindex]->grpcont == GC_REST) { + had_rest[vindex] = YES; + } + break; + } + group_time[vindex] = radd(group_time[vindex], + current_grp_p[vindex]->fulltime); + } + if (current_grp_p[vindex] == 0) { + pfatal("unable to find group containing syl's time"); + } + } + else { + /* voice doesn't exist in this measure */ + current_grp_p[vindex] = 0; + } + } + + for ( ; ; ) { + /* Most of the time, we use voice 1 to determine where to + * end the underscore. However, if voice 1 has spaces, + * we'll use voice 3 (the "middle" voice), and + * if that is non-existent or space, we use voice 2. + * If everything is space, we keep going and hope for the + * best. If all else fails, we would end up using the "end" + * value as the default. + * + * However, if this is a below or between lyric, + * and there exists an above lyric + * during the time we are dealing with, + * we assume voice 1 goes with the above lyric, and the + * below lyric probably goes with voice 2, or possibly voice 3. + * If both those voices exist, it's probably not possible + * to divine which the user wants the lyric associated with + * without reading their mind. But 3 voices on a vocal staff + * is quite unusual, especially with rests in different places, + * so we use voice 2 if it exists and is non-space. + * If that fails, we try 3, then 1, then punt. + * If it is a between lyric, there is a slight chance the user + * really wanted us to use the staff below, but we always + * associate "between" things with the staff above. + * They should use "above" on the next staff instead. + */ + vindex = 0; /* use voice 1 as default */ + if (place != PL_ABOVE) { + /* The lyric is below or between. + * Test voices 2, 3, and 1 (indexes 1, 2, 0) + * in that order till we find one that isn't a space, + * and see if there is an above lyric + * during its time. If so, that is the voice to use + * during this time to figure out + * where to end underscore. */ + if (current_grp_p[1] != 0 && + current_grp_p[1]->grpcont != GC_SPACE && + has_above_lyr(mll_p, current_time, + current_grp_p[1], verse) == YES) { + vindex = 1; + } + else if (current_grp_p[2] != 0 && + current_grp_p[2]->grpcont != GC_SPACE && + has_above_lyr(mll_p, current_time, + current_grp_p[2], verse) == YES) { + vindex = 2; + } + /* Otherwise we go with the default, voice 1. + * We know voice 1 will always exist. */ + } + + else { /* place is above */ + /* Note that voice 1 always exists, so + * so we don't need to check for null first + * on that voice. */ + if (current_grp_p[0]->grpcont != GC_SPACE) { + vindex = 0; + } + else if (current_grp_p[2] != 0 && + current_grp_p[2]->grpcont != GC_SPACE) { + vindex = 2; + } + else if (current_grp_p[1] != 0 && + current_grp_p[1]->grpcont != GC_SPACE) { + vindex = 1; + } + } + + /* At this point, we know which voice is most relevant for + * checking if it is time to end the underscore. + * See if the current group in that voice contains the + * time value of the ending syllable. */ + if ( GE(end_time, group_time[vindex]) && LT(end_time, + radd(group_time[vindex], + current_grp_p[vindex]->fulltime)) ) { + /* We need to end the underscore now. */ + return(endx(last_grp_p, end)); + } + + /* If the relevant group is a rest, need to stop here */ + if (current_grp_p[vindex]->grpcont == GC_REST) { + return(endx(last_grp_p, current_grp_p[vindex]->c[AW])); + } + else if (current_grp_p[vindex]->grpcont == GC_NOTES) { + /* Save as last known group so far at which we + * could potentially end the underscore. */ + last_grp_p = current_grp_p[vindex]; + } + + /* We're done with this group; move to next */ + current_time = radd(current_time, + current_grp_p[vindex]->fulltime); + + /* Catch up all the voices to the current time */ + for (v = 0; v < MAXVOICES; v++) { + if (current_grp_p[v] != 0) { + grp_end_time = radd(group_time[v], + current_grp_p[v]->fulltime); + + while ( LE(grp_end_time, current_time) ){ + /* Special case. Suppose, + * as an example, soprano and + * alto share a staff and the + * soprano has a long note + * while the alto has a + * melissma. The underscore + * should then go to the + * last note of the melissma, + * even though soprano is + * the reference voice. + * However, if the alto line + * had had rests, it's likely + * it's just accompaniment, + * not a vocal line, or at + * least they should have + * used separate above/below + * lyrics. + * So if this group is below + * the reference group and + * hasn't had any rests and + * is east of our candidate + * last group, make it the + * new candidate last group. */ + if (voice_is_above(vindex, v) + && place != PL_ABOVE + && had_rest[v] == NO + && last_grp_p != 0 + && current_grp_p[v]->grpcont + == GC_NOTES + && current_grp_p[v]->c[AX] + > last_grp_p->c[AX]) { + last_grp_p = current_grp_p[v]; + } + + /* move on to next group */ + current_grp_p[v] = + current_grp_p[v]->next; + if (current_grp_p[v] == 0) { + break; + } + + group_time[v] = grp_end_time; + grp_end_time = radd( + group_time[v], + current_grp_p[v]->fulltime); + if (current_grp_p[v]->grpcont + == GC_REST) { + had_rest[v] = YES; + } + } + } + } + + /* Are we now at the end of the current measure? */ + if (current_grp_p[vindex] == 0) { + /* If there is a feed after this bar, + * we need to see if a carryover is needed. + * If so, we will end this underscore just before + * the bar, and carry it over to the next score. + */ + found_feed = NO; + for (mll_p = mll_p->next; mll_p != 0; mll_p = mll_p->next) { + if (mll_p->str == S_BAR) { + if (bar_ends_extender(mll_p->u.bar_p, mll_p, + syl_p->staffno, verse, + place, &nextsyl_p) + == YES) { + /* It's not clear + * where we should stop if + * we can't deduce a following + * syllable. However, + * if we go to the bar line, + * the user can always use + * a <> syllable to force + * an earlier ending if needed, + * whereas if we go + * with the last group, + * there's probably no + * reasonable workaround + * if that's not what they want, + * so use the bar line. */ + if (nextsyl_p == 0) { + return(end); + } + else if (nextsyl_p->grpcont + == GC_SPACE) { + /* "carries over" */ + return(end); + } + else { + return(endx(last_grp_p, end)); + } + } + } + else if (mll_p->str == S_FEED) { + found_feed = YES; + } + else if (mll_p->str == S_STAFF && + mll_p->u.staff_p->staffno == + staffno) { + break; + } + else if (mll_p->str == S_SSV) { + /* if this staff becomes invisible, + * end the underscore at the last + * group before that. */ + struct SSV *ssv_p; + ssv_p = mll_p->u.ssv_p; + if (ssv_p->context == C_STAFF + && ssv_p->staffno == staffno + && ssv_p->used[VISIBLE] == YES + && ssv_p->visible == NO) { + return(endx(last_grp_p, end)); + } + } + } + if (mll_p == 0) { + /* fell off end of song */ + return(endx(last_grp_p, end)); + } + staff_p = mll_p->u.staff_p; + + /* See if there is a syllable at the same verse/place */ + if ((nextsyl_p = find_verse_place(staff_p, + verse, place)) != 0 && + nextsyl_p->grpcont != GC_SPACE) { + /* There is a syllable at the + * beginning of the next meas, + * so we end the underscore, + * unless it was just a carryover syllable + * that we added earlier. */ + if (nextsyl_p->syl[2] != '_' + || nextsyl_p->syl[3] != '\0') { + return(endx(last_grp_p, end)); + } + } + if (found_feed == YES) { + if (staff_p->groups_p[vindex] != 0 && + staff_p->groups_p[vindex]->grpcont == GC_REST) { + /* next meas begins with a rest, + * so no need to carry over */ + return(endx(last_grp_p, end)); + } + /* We need to end the underscore on the + * current score, and arrange to carry it + * over on the next score. */ + *carryover_p = YES; + return(end); + } + + + /* Move to next measure by initing each + * current_grp_p[vindex] to the first group + * in the next measure. */ + for (vindex = 0; vindex < MAXVOICES; vindex++) { + current_grp_p[vindex] = staff_p->groups_p[vindex]; + group_time[vindex] = Zero; + if (current_grp_p[vindex] != 0 && + current_grp_p[vindex]->grpcont + == GC_REST) { + had_rest[vindex] = YES; + } + } + end_time = default_end(mll_p, + find_verse_place(staff_p, verse, place), + Zero, &end); + current_time = Zero; + } + } +} + + +/* If we found a last group where we could end a underscore, + * return where the east edge of its notes are, + * otherwise return the "end" value as the default. + */ + +static double +endx(last_grp_p, end) + +struct GRPSYL *last_grp_p; /* if != 0, use east edge of notes of this */ +double end; /* if all else fails, use this */ + +{ + int n; /* note index */ + double edge; /* return value */ + + + if (last_grp_p == 0) { + return(end); + } + + if (last_grp_p->grpcont != GC_NOTES) { + /* This should actually never happen with the current code, + * but just in case, we use the east of the group */ + return(last_grp_p->c[AE]); + } + + /* find east edge of notes, not counting any dots or flags */ + edge = -1000000.0; /* init to impossible value */ + for (n = 0; n < last_grp_p->nnotes; n++) { + if (last_grp_p->notelist[n].c[AE] > edge) { + edge = last_grp_p->notelist[n].c[AE]; + } + } + /* If the edge we calculated is east of the default end, use + * the default end, because that is suppose to be the farthest + * possible east we can be. This could happen if the user used + * <^....> on a lyric to force part of the lyric to encroach + * into the previous groups' space. In that case we need to end + * the underscore where the encroaching lyric begins, not where + * the last note group ends. + */ + if (edge > end) { + return(end); + } + return(edge); +} + + +/* + * Return YES if there is an above lyrics during the specified time. + * We have to use some heuristics. + * + * If there is any non-space above lyric for the given verse + * at any point between the begin time + * and the begin time plus the fulltime of the group_p, + * then there is an above lyric. + * + * If there is a rest on voice 1, that implies a rest in an above lyric + * line. + * + * If there is lyric space for the duration in question, either + * explicit space, or just no above lyrics at all for the given verse + * in this measure, then we don't know for sure where there are no + * above lyrics, or there is an earlier above lyric for this verse + * that extends into the duration. + * If we find some earlier non-space above lyric + * and it ends with an extender (dash or underscore), + * we say there is an above lyric. + * If there is no such lyric, or the first non-space above lyric + * lyric we come to in backing up does not end with an extender, + * we say there isn't an above lyric. + */ + +static int +has_above_lyr(mll_p, begin_time, group_p, verse) + +struct MAINLL *mll_p; /* points to syl's STAFF */ +RATIONAL begin_time; +struct GRPSYL *group_p; /* see if there a lyric above this group */ +int verse; + +{ + struct STAFF *staff_p; + struct GRPSYL *grp_p; + RATIONAL cumm_time; /* current cummulative time */ + RATIONAL new_cumm_time; /* cumm_time + group's fulltime */ + RATIONAL end_time; /* begin_time + syl's fulltime */ + int n; /* syllist index */ + int prev_extends; /* YES/NO if prev syl has extender */ + + + if (mll_p->str != S_STAFF) { + pfatal("has_above_lyr passed wrong type of struct"); + } + + staff_p = mll_p->u.staff_p; + end_time = radd(begin_time, group_p->fulltime); + + /* Go through syllists for the staff */ + prev_extends = NO; + for (n = 0; n < staff_p->nsyllists; n++) { + if (staff_p->sylplace[n] == PL_ABOVE && + staff_p->syls_p[n]->vno == verse) { + cumm_time = Zero; + for (grp_p = staff_p->syls_p[n]; grp_p != 0; grp_p = grp_p->next) { + new_cumm_time = radd(cumm_time, grp_p->fulltime); + + if ( LT(new_cumm_time, begin_time) && + grp_p->grpcont != GC_SPACE) { + prev_extends = has_extender(grp_p->syl); + } + + /* See if this syllable overlaps the time + * of the group we are checking against. */ + else if ( (GE(begin_time, cumm_time) && + LT(begin_time, new_cumm_time)) || + (GE(end_time, cumm_time) && + LT(end_time, new_cumm_time)) ) { + + /* This is a relevant group. If it isn't + * a space, then we know there is + * indeed an above lyric. */ + if (grp_p->grpcont != GC_SPACE) { + return(YES); + } + if (prev_extends == YES) { + /* A syllable + * earlier in the measure + * is extending into the + * duration, so that counts. + */ + return(YES); + } + } + else if (GT(new_cumm_time, end_time)) { + /* we're past the relevant syl(s) */ + break; + } + cumm_time = new_cumm_time; + } + /* We've dealt with the only relevant syl list + * in this measure. */ + break; + } + } + + /* If there is a rest on voice 1, there is an implicit above + * lyric (albeit a pause in the above lyrics). Or at least it hardly + * makes sense to use voice 1 for below/between lyrics if voice 1 + * is a rest but there is another voice below it that isn't. + */ + cumm_time = Zero; + for (grp_p = mll_p->u.staff_p->groups_p[0]; grp_p != 0; grp_p = grp_p->next) { + new_cumm_time = radd(cumm_time, grp_p->fulltime); + if ( (GE(begin_time, cumm_time) && + LT(begin_time, new_cumm_time)) || + (GE(end_time, cumm_time) && + LT(end_time, new_cumm_time)) ) { + if (grp_p->grpcont == GC_REST) { + return(YES); + } + } + cumm_time = new_cumm_time; + if (GT(cumm_time, end_time)) { + /* past the relevant groups */ + break; + } + } + + /* If we got here, we weren't able to tell for + * sure if there is an above lyric, because there + * was either implicit or explicit space. + * Most likely there is no above lyric, + * but there is a slight possibility there is + * a lyric holding over into this time period + * via a melisma or tied notes from a previous measure. + * So we back up looking for such a lyric. If we find an above lyric + * that ends with an extender (underscore or dash), + * we declare that there is an above lyric. + * If we find one without an extender or back up + * all the way to the beginning of the song without + * finding any above lyric, there is no above lyric here. + * But we give up after 20 measures, figuring it's + * really unlikely for any melisma or tie to last + * that long, especially since any scorefeeds + * would cause a syllable to get added. The exact + * value of 20 is arbitrary; it just seems like plenty. + * + * prevgrpsyl doesn't work on syls, just groups, + * but by giving it staff_p->groups_p[0] (we + * know voice 1 will always exist), it will give + * us the mll_p for the staff we need. + */ + for (n = 0; n < 20; n++) { + struct GRPSYL *last_non_space_p; + + if (prevgrpsyl(mll_p->u.staff_p->groups_p[0], + &mll_p) == 0) { + /* Got to beginning of song */ + return(NO); + } + + grp_p = find_verse_place(mll_p->u.staff_p, verse, PL_ABOVE); + + if (grp_p == 0) { + /* No relevant lyrics in this meas */ + continue; + } + + last_non_space_p = 0; + for ( ; grp_p != 0; grp_p = grp_p->next) { + if (grp_p->grpcont != GC_SPACE) { + last_non_space_p = grp_p; + } + } + if (last_non_space_p != 0) { + /* Found a preceeding syllable */ + return(has_extender(last_non_space_p->syl)); + } + } + /* We've backed up far enough that the chances of there actually being + * an above lyrics are very, very slim. */ + return(NO); +} + + +/* Returns YES if voice with index v1 is "above" voice v2; else NO */ + +static int +voice_is_above(v1, v2) + +int v1; +int v2; + +{ + /* Voice number is one more than its index, so convert index to + * number so it's easier to think about */ + v1++; + v2++; + + /* Voice 1 is above voice 2 and 3 */ + if (v1 == 1) { + return(YES); + } + + /* Voice 3 is the "middle" voice and thus "above" voice 2 */ + if (v1 == 3 && v2 == 2) { + return(YES); + } + + return(NO); +} + + +/* Given a STAFF, return the first GRPSYL in the syllable list for the given + * verse and place, if one exists. Otherwise return 0. + */ + +static struct GRPSYL * +find_verse_place(staff_p, verse, place) + +struct STAFF *staff_p; +int verse; +int place; + +{ + int n; + + for (n = 0; n < staff_p->nsyllists; n++) { + if (staff_p->sylplace[n] == place && + staff_p->syls_p[n]->vno == verse) { + return(staff_p->syls_p[n]); + } + } + return(0); +} + + +/* Given a syl and related info, return the default time and place at which to + * end an underscore from that syl, for this measure. If there is a + * non-space syl later in the measure, this will be right before that syl, + * otherwise right before the bar line. + */ + +static RATIONAL +default_end(mll_p, syl_p, start_time, end_p) + +struct MAINLL *mll_p; /* the STAFF pointing to the syl */ +struct GRPSYL *syl_p; /* start looking from the syl */ +RATIONAL start_time; /* syl is already this far into measure */ +double *end_p; /* X value at which to end underscore is + * returned via this pointer */ + +{ + struct GRPSYL *grp_p; + RATIONAL end_time; /* return value */ + + if (syl_p == 0) { + /* No syllable for current verse/place in current measure. + * Time signature may not be up to date, so add up the + * time of voice 1, which we know exists. + */ + end_time = start_time; + for (grp_p = mll_p->u.staff_p->groups_p[0]; grp_p != 0; + grp_p = grp_p->next) { + end_time = radd(end_time, grp_p->fulltime); + } + } + else { + /* Go forward in the syl list, finding where the next non-space + * syllable is, if there is one in the current measure, + * otherwise find the end of the measure. + * Save the time and location of this. + */ + end_time = radd(start_time, syl_p->fulltime); + for (grp_p = syl_p->next; grp_p != 0; grp_p = grp_p->next) { + if (grp_p->grpcont == GC_SPACE) { + /* Underscore continues through "space" syls */ + end_time = radd(end_time, grp_p->fulltime); + continue; + } + else { + /* We have found the syllable + * at which the underscore + * from the previous syllable ends */ + break; + } + } + } + + /* If a next syl was found, set end to near its west. + * Most likely, we will discover later we need to stop the underscore + * earlier than this, but if the user is using underscore in a strange + * way, like on a single long note rather than a melissma, + * we'll use this as the default place to end the underscore.*/ + if (grp_p != 0) { + *end_p = grp_p->c[AW] - Stepsize; + } + else { + /* The ending syllable (if any) is not in the current measure. + * So for now we set the end to near the west of the bar line. + * Note that end_time will have added up to + * the full measure duration in this case. + */ + for ( ; mll_p != 0; mll_p = mll_p->next) { + if (mll_p->str == S_BAR) { + *end_p = mll_p->u.bar_p->c[AW] - Stepsize; + break; + } + } + if (mll_p == 0) { + pfatal("underscore: failed to find next bar"); + } + } + return(end_time); +} + + +/* Given a bar, see if it is a bar that might force stopping an extender, + * and return YES, if so. If nextsyl_p_p is non-null, it also attempts + * to fill that in with a pointer to the next "logical" syllable. + * (Usually the next syllable, but at the end of a repeat it would + * be the first syllable in the repeated section). + * If it can't figure out the correct syllable, it fills in null. + */ + +static int +bar_ends_extender(bar_p, mll_p, staffno, verse, place, nextsyl_p_p) + +struct BAR *bar_p; +struct MAINLL *mll_p; /* points to a BAR or CLEFSIG*/ +int staffno; +int verse; +int place; +struct GRPSYL **nextsyl_p_p; /* If this is non-zero, and we can deduce + * the next "logical" syl, the pointed to value + * will be updated to point to that next syl, + * else will be zero. */ + +{ + int bartype; + + bartype = bar_p->bartype; + if (bartype == RESTART) { + /* We shouldn't continue an extender over a restart. + * The "next" logical measure is probably the + * target of a D.S. or a D.C. + * But we don't attempt to parse + * STUFF strings to know such things. + * So we say the extender ends here, + * but we don't know what the "next" measure is. + */ + if (nextsyl_p_p != 0) { + *nextsyl_p_p = 0; + } + return(YES); + } + + if (bartype == REPEATEND || bartype == REPEATBOTH) { + if (nextsyl_p_p == 0) { + return(YES); + } + + /* This ends the extender. The next logical measure + * is at the beginning of the repeat. */ + for (mll_p = mll_p->prev; mll_p != 0; mll_p = mll_p->prev) { + if (mll_p->str == S_BAR && + (mll_p->u.bar_p->bartype + == REPEATSTART || + mll_p->u.bar_p->bartype + == REPEATBOTH)) { + mll_p = mll_p->next; + break; + } + } + if (mll_p == 0) { + /* repeatstart is implicit at beginning of song */ + mll_p = Mainllhc_p; + } + for ( ; mll_p != 0; mll_p = mll_p->next) { + if (mll_p->str == S_BAR) { + /* staff doesn't exist in this measure */ + *nextsyl_p_p = 0; + return(YES); + } + if (mll_p->str == S_STAFF && mll_p->u.staff_p->staffno + == staffno) { + *nextsyl_p_p = find_verse_place( + mll_p->u.staff_p, verse, place); + return(YES); + } + } + } + + if (mll_p->u.bar_p->endingloc == STARTITEM) { + /* If this is the start of a second or subsequent ending, + * this ends the extender. This is the case if the previous + * bar was a STARTITEM on INITEM. But apparently there + * isn't a repeat ending here, or we would have hit the + * bartype check for that. So it is too hard to try to deduce + * the next logical syllable. */ + for (mll_p = mll_p->prev; mll_p != 0; mll_p = mll_p->prev) { + if (mll_p->str == S_BAR) { + if (mll_p->u.bar_p->endingloc == STARTITEM || + mll_p->u.bar_p->endingloc + == INITEM) { + if (nextsyl_p_p != 0) { + *nextsyl_p_p = 0; + } + return(YES); + } + break; + } + } + } + + return(NO); +} + + +/* Actually print an extender (dash or underscore) */ + +static void +pr_extender(ch, start, end, y, font, size) + +int ch; /* dash or underscore */ +double start; /* where to start printing */ +double end; /* where to end printing */ +double y; /* y coordinate */ +int font; /* font to use for dash */ +int size; /* size to use for dash */ + +{ + if (ch == '-') { + double dashwidth; + char dashstring[4]; + + dashwidth = width(font, size, '-'); + + /* generate the internal string format of a dash */ + /* can't use dash_string function here since that also + * deals with ~ which is okay for stuff but not lyrics */ + dashstring[0] = (char) font; + dashstring[1] = (char) size; + dashstring[2] = '-'; + dashstring[3] = '\0'; + + if ( (end - start) < (15.0 * dashwidth) ) { + /* not much space, so find midpoint of + * available distance and put dash there */ + pr_string(start + ((end - start) / 2.0) + - (dashwidth / 2.0), + y, dashstring, J_LEFT, + (char *) 0, -1); + } + else { + int numdashes; /* how many dashes to print */ + double spacebetween; /* between dashes */ + + /* Lots of space, so will need to print multiple dashes. + * Figure out how to spread out */ + numdashes = (int) ((end - start) / (8.0 * dashwidth)); + spacebetween = ((end - start) - (dashwidth * numdashes)) + / numdashes; + + for ( ; numdashes > 0; numdashes--) { + pr_string(start + + (numdashes - 0.5) * spacebetween + + ((numdashes - 1.0) * dashwidth), + y, dashstring, J_LEFT, + (char *) 0, -1); + } + } + } + else { + /* if long enough to bother drawing underscore, draw it */ + if (end - start > Stepsize) { + /* Note: line width probably really ought to + * be scaled based on the lyric size, but unless + * somebody uses really huge or really tiny lyrics, + * a normal line width looks good enough, + * so we just go with that. + */ + do_linetype(L_NORMAL); + draw_line(start, y, end, y); + } + } +} + + +/* Return YES if last character of syllable is an underscore or dash, + * NO if it isn't. + */ + +int +has_extender(syl) + +char *syl; /* the syllable to check */ + +{ + switch (last_char(syl)) { + + case '_': + case '-': + return(YES); + + default: + return(NO); + } +} + + +/* Return last character in a string. + * If last character is a music character, + * or the string is null, return null. + */ + +int +last_char(str) + +char *str; /* return last character in this string */ + +{ + int font, size; + int ch; /* current character in string */ + int last_font = FONT_UNKNOWN; /* font of last character */ + int last_ch = '\0'; + + + if (str == (char *) 0) { + return('\0'); + } + + font = str[0]; + size = str[1]; + + /* keep track of each character. When we hit + * end of string, return the last character we saw */ + for ( str += 2; (ch = next_str_char(&str, &font, &size)) != 0; ) { + last_font = font; + last_ch = ch; + } + /* music characters don't count */ + if (IS_MUSIC_FONT(last_font)) { + return('\0'); + } + return (last_ch & 0xff); +} + + +/* See if an underscore or dash will need to be carried to the following score. + * If so, add an appropriate "syllable" at the beginning of that score */ + +void +cont_extender(mll_p, sylplace, verseno) + +struct MAINLL *mll_p; /* the syllable is hanging off of this STAFF */ +int sylplace; /* PL_ABOVE, etc */ +int verseno; /* verse number */ + +{ + struct GRPSYL *syl_p; /* walk through GRPSYL list */ + struct GRPSYL *last_non_space_p; + int last_ch; /* last character of syllable */ + int font; /* of syllable */ + int size; /* of syllable */ + + + if (mll_p->str != S_STAFF) { + pfatal("cont_extender called with wrong argument"); + } + + /* Find the actual syl grpsyl that is the last on the score */ + syl_p = find_verse_place(mll_p->u.staff_p, verseno, sylplace); + + if (syl_p == 0) { + pfatal("cont_extender called without any syllable"); + } + + /* Find the final non-space syllable in the list */ + last_non_space_p = 0; + for ( ; syl_p != 0; syl_p = syl_p->next) { + if (syl_p->grpcont != GC_SPACE) { + last_non_space_p = syl_p; + } + } + + if (last_non_space_p == 0) { + pfatal("cont_extender couldn't find non-space syllable"); + } + + last_ch = last_char(last_non_space_p->syl); + if (last_ch != '-' && last_ch != '_') { + pfatal("cont_extender called on syl without extender"); + } + + /* See if will carry over */ + if (spread_extender(last_non_space_p, mll_p, verseno, sylplace, NO) + == YES) { + + /* determine proper font/size of + * carried over dash/underscore + * based on font/size at end of syllable */ + end_fontsize(last_non_space_p->syl, &font, &size); + + /* insert the syllable on next score */ + insert_carryover_syllable(mll_p, + last_non_space_p->staffno, sylplace, + verseno, + (last_ch == '-' ? "-" : "_"), + font, size); + } +} + + +/* A dash or underscore needs to be carried over to the following score. + * Search forward for the appropriate STAFF. If there is already a lyric + * there for the sylplace and verseno, if its first syllable is a space, + * change it to a dash or underscore as appropriate. + * If there is no lyric in that measure for the sylplace and verseno, + * insert a measure long syllable of the appropriate type. + * If there is no STAFF of the proper number after a FEED, assume we are + * at the end of the piece or of visibility of the staff, and do nothing. + * If there is already a syllable, leave it as is. + */ + + +static void +insert_carryover_syllable(mll_p, staffno, sylplace, verseno, dash_or_underscore, + font, size) + +struct MAINLL *mll_p; /* points to staff info */ +int staffno; /* staff number */ +int sylplace; /* PL_ABOVE, etc */ +int verseno; /* verse number */ +char *dash_or_underscore; /* "-" or "_" */ +int font; /* font and size to use for dash or underscore */ +int size; + +{ + struct STAFF *staff_p; /* add syllable to this staff */ + struct CHORD *chord_p; /* chord syllables goes with */ + int v; /* verse index */ + float begin_x; /* where to start carryover syllable */ + + + /* search forward for FEED */ + for ( ; mll_p != (struct MAINLL *) 0; mll_p = mll_p->next) { + if (IS_CLEFSIG_FEED(mll_p)) { + break; + } + } + + if (mll_p == (struct MAINLL *) 0) { + return; + } + + /* The AE coordinates of syllable groups + * have already been set, but we need to have + * this one set for the underscore/dash syllable being added. So deduce + * where it should be using the pseudo-bar */ + if ((mll_p = mll_p->next) == (struct MAINLL *) 0) { + return; + } + if (mll_p->str == S_CLEFSIG) { + begin_x = mll_p->u.clefsig_p->bar_p->c[AE] + STDPAD; + } + else { + /* setting begin_x is just to shut up compilers that erroneously + * think it could be used without being set. */ + begin_x = 0.0; + pfatal("no clefsig after feed"); + } + + /* silence compilers that think chord_p might not be set */ + chord_p = (struct CHORD *) 0; + + /* search forward for STAFF of interest, and save CHORD info */ + for ( mll_p = mll_p->next; mll_p != (struct MAINLL *) 0; + mll_p = mll_p->next) { + + if (mll_p->str == S_CHHEAD) { + chord_p = mll_p->u.chhead_p->ch_p; + } + else if (mll_p->str == S_STAFF) { + if (mll_p->u.staff_p->staffno == staffno) { + break; + } + } + } + + /* see if has syllable of specified place and verse */ + if (mll_p != (struct MAINLL *) 0) { + + staff_p = mll_p->u.staff_p; + for (v = 0; v < staff_p->nsyllists; v++) { + + if (staff_p->sylplace[v] == sylplace && + staff_p->syls_p[v]->vno == verseno) { + + /* are lyrics in this measure. See if first + * syllable is a space. If so, replace with + * a dash. Otherwise we are done */ + if (staff_p->syls_p[v]->syl == (char *) 0) { + staff_p->syls_p[v]->syl = + copy_string(dash_or_underscore, + font, size); + /* no longer a "space" syllable */ + staff_p->syls_p[v]->grpcont = GC_NOTES; + } + return; + } + } + + /* no lyrics in first measure on next score for this + * verse/place. Need to insert one */ + add_syllable(staff_p, sylplace, verseno, + dash_or_underscore, font, size, + begin_x, chord_p); + } +} + + +/* Add a dash or underscore syllable to list of lyrics. Need to alloc new + * space for the sylplace and syls_p arrays, copy the existing data into + * them, adding the new syllable at the proper place (sorted by verseno), + * then free the old arrays */ + +static void +add_syllable(staff_p, sylplace, verseno, dash_or_underscore, font, size, + begin_x, chord_p) + +struct STAFF *staff_p; /* add syllable to this staff */ +int sylplace; /* PL_ABOVE, etc */ +int verseno; +char *dash_or_underscore; /* "-" or "_" */ +int font; +int size; +double begin_x; /* where syllable is to start */ +struct CHORD *chord_p; /* what chord to attach to */ + +{ + short *new_sylplace; /* new, expanded sylplace array */ + struct GRPSYL **new_syls_p; /* new, expanded syls_p array */ + int v; /* verse index */ + int insert_index; /* where to put in new arrays */ + int inserted; /* 0 if haven't found where to insert + * yet, 1 if we have. This is then the + * difference between the index of the + * original arrays and where the copy + * goes in the new arrays. Since it's + * used in array subscript calculation + * we can't use YES and NO here */ + + + /* alloc arrays that are one larger than the current arrays */ + MALLOCA(short, new_sylplace, staff_p->nsyllists + 1); + MALLOCA(struct GRPSYL *, new_syls_p, staff_p->nsyllists + 1); + + /* now copy and insert */ + insert_index = staff_p->nsyllists; + for (inserted = v = 0; v < staff_p->nsyllists; v++) { + if (insert_index > v && staff_p->syls_p[v]->vno > verseno) { + /* insert here */ + insert_index = v; + inserted = 1; + } + + new_sylplace[v + inserted] = staff_p->sylplace[v]; + new_syls_p[v + inserted] = staff_p->syls_p[v]; + } + + /* alloc and fill in the new GRPSYL */ + new_sylplace[insert_index] = (short) sylplace; + new_syls_p[insert_index] = newGRPSYL(GS_SYLLABLE); + new_syls_p[insert_index]->syl = copy_string(dash_or_underscore, + font, size); + new_syls_p[insert_index]->inputlineno = -1; + new_syls_p[insert_index]->basictime = -1; + new_syls_p[insert_index]->is_meas = YES; + new_syls_p[insert_index]->fulltime = Score.time; + new_syls_p[insert_index]->staffno = staff_p->staffno; + new_syls_p[insert_index]->vno = (short) verseno; + /* X coords of normal syllables already set, so have to set for + * this special syllable here */ + new_syls_p[insert_index]->c[AE] = begin_x + + strwidth(new_syls_p[insert_index]->syl); + new_syls_p[insert_index]->c[AW] = begin_x; + new_syls_p[insert_index]->c[AX] = begin_x; + + /* now have one one list of syllables */ + (staff_p->nsyllists)++; + + /* free old arrays if non-null */ + if (staff_p->sylplace != (short *) 0) { + FREE(staff_p->sylplace); + } + if (staff_p->syls_p != (struct GRPSYL **) 0) { + FREE(staff_p->syls_p); + } + + /* now link up the new arrays */ + staff_p->sylplace = new_sylplace; + staff_p->syls_p = new_syls_p; + + /* add to appropriate chord */ + stitch_syl_into_chord(chord_p, new_syls_p[insert_index]); +} + + +/* Given a syllable and chord to attach it to, attach it */ +/* Strictly speaking, this function probably isn't necessary, since I think + * all use of the CHORD struct has already been done before this function is + * called, but it's probably good to do anyway on general principles, in case + * some day the CHORDs are looked at later */ + +static void +stitch_syl_into_chord(chord_p, syl_gs_p) + +struct CHORD *chord_p; /* add to this chord */ +struct GRPSYL *syl_gs_p; /* add this syllable */ + +{ + struct GRPSYL *gs_p; /* walk through chord */ + + + /* go down chord list */ + for (gs_p = chord_p->gs_p; gs_p->gs_p != (struct GRPSYL *) 0; + gs_p = gs_p->gs_p) { + + /* if next grpsyl in chord has staffno < staffno of syl to add, + * keep going */ + if (gs_p->gs_p->staffno < syl_gs_p->staffno) { + continue; + } + + /* if next grpsyl in chord had staffno > staffno of syl to add, + * put it here */ + if (gs_p->gs_p->staffno > syl_gs_p->staffno) { + /* found where to insert */ + break; + } + + /* If here, must be same staffno. + * Keep going until find syllable with larger vno. */ + if (gs_p->gs_p->grpsyl == GS_GROUP) { + continue; + } + + if (gs_p->gs_p->vno > syl_gs_p->vno) { + /* found where to insert */ + break; + } + } + + /* insert syllable */ + syl_gs_p->gs_p = gs_p->gs_p; + gs_p->gs_p = syl_gs_p; +}