/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: April 2020 */ /* This file contains part VI of the code for reading a PMW score file - routines for setting stem flags on notes and chords, and sorting chords. */ #include "pmwhdr.h" #include "readhdr.h" typedef struct { uschar pitch; uschar inverted; uschar acc; short int accleft; short int orig_accleft; } accstr; static uschar tuckoffset[] = { 100, 6, 6, 6, 8, 100, 100, 6, 6, 6, 6, 100 }; /* when bottom is a flat */ /************************************************* * Sort the notes in a chord * *************************************************/ /* For a chord, quite a lot of work must be done once the stem direction is known. We must sort the notes into the correct order, so that the first one is the one that gets the stems, we must arrange for certain notes to be printed on the "wrong" side of the stem, and we must arrange the positioning of any accidentals. If we are sorting the last chord that has been read, we must sort stave_tiedata along with it. We must NOT do this when sorting other chords (those that were stacked up). Arguments: w point to the first note of the chord upflag TRUE for stem up Returns: nothing */ void read_sortchord(b_notestr *w, int upflag) { usint dynamics = 0; int SecondsExist = FALSE; int fuq = 0; int acc_count = 0; int acc_explicit = FALSE; b_notestr *ww; b_notestr *www = NULL; b_notestr *sorttop; b_notestr sortvec[MAX_CHORDSIZE]; tiedata *tt; tiedata sorttievec[MAX_CHORDSIZE]; sorttop = sortvec; /* End of list pointer */ ww = w; /* Working pointer */ ww->type = b_chord; /* Ensure all are flagged chord pro tem */ tt = stave_tiedata; /* Working tiedata pointer */ /* Get the notes of the chord into sortvec, in ascending order, by using a simple insertion (there won't be many of them). Collect the dynamics flags as we go, and set the stem direction flag on each note. */ while (ww->type == b_chord) { b_notestr *insertptr = sortvec; tiedata *tieinsertptr = sorttievec; int pitch = ww->spitch; int flags = ww->flags; usint acflags = ww->acflags; if (ww->acc) acc_count++; if ((flags & nf_accleft) != 0) acc_explicit = TRUE; dynamics |= acflags & (af_dynamics | af_opposite); fuq |= flags & nf_fuq; ww->flags = upflag | (flags & ~(nf_dotright | nf_invert | nf_stemup)); ww->acflags = acflags & ~(af_dynamics | af_opposite); while (insertptr < sorttop) { if (pitch < insertptr->spitch) break; insertptr++; tieinsertptr++; } memmove(tieinsertptr+1, tieinsertptr, (sorttop-insertptr)*sizeof(tiedata)); *tieinsertptr = *tt++; memmove(insertptr+1, insertptr, (sorttop-insertptr)*sizeof(b_notestr)); *insertptr = *ww; sorttop++; mac_advancechord(ww); } /*********************/ #ifdef SORTCHORD ww = w; debug_printf("\n"); { b_notestr *p; for (p = sortvec; p < sorttop; p++) { debug_printf("%d %d %d %d\n", ww->acc, ww->spitch, p->acc, p->spitch); mac_advancechord(ww); } } #endif /*********************/ /* Now we can scan the sorted notes to see if any of them need to be printed with their heads on the "wrong" side of their stems. (The same logic works for stemless notes.) At the same time, force the flags for augmentation dots for notes forming intervals of a second. */ ww = sortvec; while (ww < sorttop - 1) { b_notestr *wwA = ww + 1; /* Check for an interval of a second. It doesn't count if one note is coupled and the other isn't. We can also cope with two notes at the same horizontal level (usually these will have different accidentals). */ if (wwA->spitch - ww->spitch <= P_1S - P_1L && (wwA->flags & nf_couple) == (ww->flags & nf_couple)) { BOOL samelevel = wwA->spitch == ww->spitch; int count = 1; int increment, i; b_notestr *wwB = wwA + 1; b_notestr *wwL; /* Find the number of successive seconds; if the one pair are actually at the same level, we can't handle any more. */ while (wwB < sorttop && wwB->spitch - wwA->spitch <= P_1S - P_1L && (wwB->flags & nf_couple) == (wwA->flags & nf_couple)) { if (wwB->spitch == wwA->spitch) samelevel = TRUE; count++; wwA = wwB++; } if (count > 1 && samelevel) error_moan(ERR80); count = (count + 1)/2; /* number of pairs to consider */ /* Now process all the intervals, working up if the stem is up, and down if the stem is down. This ensures that the note at the end of the stem is on the normal side of the stem. */ if (upflag) { increment = 2; wwL = ww; } else { increment = -2; wwL = wwA - 1; } /* Loop through the pairs */ for (i = 0; i < count; i++) { b_notestr *wwH = wwL + 1; /* Flag higher note of a 2nd for inverting if stem up. Note that the note may not exist if there were an even number of intervals. */ if (upflag) { if (wwH < sorttop) wwH->flags |= nf_invert; } /* Flag lower note of a 2nd for inverting if stem down. Count this as an accidental, so as to cause accidental positioning to happen if there is at least one accidental on the chord. */ else if (wwL >= sortvec) { wwL->flags |= nf_invert; acc_count++; } /* Flag bottom note for dot lowering and top for not so, provided the notes exist. (The forcible removal is for the case of re-processing after failure to print on two sides of a beam.) However, if the upper one is already flagged for dot raising, don't do this. */ if ((wwH->flags & nf_highdot) == 0) { if (wwL >= sortvec) wwL->flags |= nf_lowdot; if (wwH < sorttop) wwH->flags &= ~nf_lowdot; } /* Advance to the next pair of notes */ wwL += increment; } /* Advance to check the rest of the chord */ ww = wwB; /* Note that intervals of a 2nd exist in this chord */ SecondsExist = TRUE; } /* This interval is not a second */ else ww = wwA; } /* If found any seconds, flag all the notes in the chord to print with any dots moved right if the stem is up. */ if (SecondsExist && upflag) { ww = sortvec; while (ww < sorttop) { ww->flags |= nf_dotright; ww++; } } /* Now we scan the chord to arrange the positioning of the accidentals. This is done by using a matrix of positions which are filled in as the chord is scanned from top to bottom. We do this only if there were no explicitly positioned accidentals anywhere in the chord and there is more than one accidental (or at least one accidental and one inverted note). */ if (!acc_explicit && acc_count > 1) { int state = 0; accstr a_matrix[MAX_CHORDSIZE]; accstr *row = a_matrix; accstr *a_end; /* First initialize the matrix, in descending order. Copy only those notes that have accidentals or inverted noteheads. */ for (ww = sorttop-1; ww >= sortvec; ww--) { int flags = ww->flags; if (ww->acc == 0 && (flags & nf_invert) == 0) continue; row->pitch = ww->spitch; if ((flags & nf_couple) != 0) row->pitch += ((flags & nf_coupleU) != 0)? 16 : -16; row->inverted = !upflag && ((flags & nf_invert) != 0); row->acc = ww->acc; row->accleft = row->orig_accleft = ww->accleft; row++; } a_end = row; /*********************/ #ifdef SORTCHORD debug_printf("Initialized matrix\n"); row = a_matrix; while (row < a_end) { debug_printf("%d %d %d %f %f\n", row->pitch, row->inverted, row->acc, row->accleft, row->orig_accleft); row++; } #endif /*********************/ /* Now scan from top to bottom and determine offset. This algorithm works in two states. In state 0, there is clear space above, while in state 1 there may be clashes. */ row = a_matrix; while (row < a_end) { accstr *nrow = row + 1; /* pointer to next row */ #ifdef SORTCHORD debug_printf("STATE=%d row->acc=%d\n", state, row->acc); #endif /* Deal with the case when all is clear above. If there is no accidental we just have an inverted note. */ /* ---- STATE = 0 ---- */ if (state == 0) { if (row->acc != 0) /* 0 => no accidental */ { /* If note is inverted, just position the accidental to clear it. Otherwise, search down for the next inversion and see if it is clear. */ if (row->inverted) row->accleft += 6000; else /* add for invert */ { accstr *nnrow = nrow; while (nnrow < a_end) { if (nnrow->inverted) { if ((row->pitch - nnrow->pitch) < ((row->acc <= ac_dflat)? 6:8)) { row->accleft += ((row->pitch - nnrow->pitch) <= 4)? ((row->acc <= ac_dflat)? 4500 : 6000) : 4500; } break; } nnrow++; } } } /* Change to state 1 if the next note is close enough */ if (nrow < a_end && (row->pitch - nrow->pitch) < ((row->acc <= ac_dflat)? 10:12)) state = 1; } /* ---- STATE = 1 ---- */ /* Deal with the case when not clear above. If there is no accidental we are at an inverted note. Accidentals above should have been positioned clear of it. We merely need to change state if we can. */ else if (row->acc == 0) { if (nrow < a_end && (row->pitch - nrow->pitch) >= 10) state = 0; } /* There is an accidental -- we have to scan up and move it clear of previous accidentals where necessary. There will always be at least one previous row, as we can't get into state 1 when row is pointing to a_matrix. */ else { int OK = FALSE; int offset = row->accleft; /* basic offset */ if (row->inverted) offset += 6000; /* plus extra if inverted note */ while (!OK) { accstr *prow = row - 1; /* previous row */ /* Loop, checking previous accidental positions for any overlap with the current accidental. */ for(;;) /* inner loop */ { int thistop = row->pitch + ((row->acc < ac_flat)? 3 : (row->acc > ac_dflat)? 6 : 7); int thatbot = prow->pitch - ((prow->acc < ac_natural)? 3 : 6); int thisleft = offset; int thisright = offset - row->orig_accleft; int thatleft = prow->accleft; int thatright = thatleft - prow->orig_accleft; #ifdef SORTCHORD debug_printf("thistop=%d thatbot=%d\n", thistop, thatbot); debug_printf("thisleft=%f thisright=%f\n", thisleft, thisright); debug_printf("thatleft=%f thatright=%f\n", thatleft, thatright); #endif if (thistop > thatbot && ((thatleft >= thisleft && thisleft > thatright) || (thatleft > thisright && thisright >= thatright))) /* There is an overlap. Adjust the offset and break from the inner loop with OK still set FALSE. This will cause a repeat of the outer loop to check the new position. Note we insert an extra quarter point over and above the specified width. */ { offset = thatleft + row->orig_accleft + 250; break; } /* We are clear of the accidental on the previous note, but need to check if we are clear of an inverted notehead. */ if (prow->inverted) { thatbot = prow->pitch - 2; thatleft = 4500; /* extra for notehead */ thatright = 0; if (thistop > thatbot && ((thatleft >= thisleft && thisleft > thatright) || (thatleft > thisright && thisright >= thatright))) { offset = thatleft + row->orig_accleft; break; } } /* Go back one more row; if no more, or if we have gone far enough, all is well, so break the inner loop with OK set TRUE. */ if (--prow < a_matrix || prow->pitch - row->pitch > ((prow->acc <= ac_dflat)? 10 : 12)) { OK = TRUE; break; } } /* If we have come out with OK set, we are clear above, but this ain't enough. If the offset is small, we must check that the accidental will clear any subsequent inverted notehead. */ if (OK && offset < row->orig_accleft + 4500) { accstr *nnrow = nrow; #ifdef SORTCHORD debug_printf("check invert below: offset=%f row->orig_accleft=%f\n", offset, row->orig_accleft); #endif while (nnrow < a_end) { if (nnrow->inverted) { if ((row->pitch - nnrow->pitch) < ((row->acc <= ac_dflat)? 6:10)) { offset = row->orig_accleft + ((row->pitch - nnrow->pitch <= 4)? ((row->acc <= ac_dflat)? 4500 : 6000) : 4500); OK = FALSE; /* unset OK so that the outer loops once more */ } break; } nnrow++; } } } /* End of while NOT OK loop */ /* We have now positioned the accidental successfully. Check to see whether the next note is far down, and if so, reset the state. */ row->accleft = offset; if (nrow < a_end && (row->pitch - nrow->pitch) >= ((row->acc <= ac_dflat)? 10 : 12)) state = 0; } /* Move on to next (accidentalized or inverted) note */ row++; } /* We now have the basic positioning, but there is still a little optimization that can be helpful. If a natural or a (double) flat is to the left of another natural or (double) flat that is a bit above, and there is nothing in the way to the right below, we can move the accidental (and everything below it) a bit right, to "tuck it in". This code does not cope with all cases, but it catches the most common. */ row = a_matrix; while (row < a_end) { if (row->accleft > row->orig_accleft + 250 && (row->acc == ac_flat || row->acc == ac_natural || row->acc == ac_dflat)) /* Check no inverted notes or rightwards accidentals here or below */ { int OK = TRUE; accstr *nrow = row; while (nrow < a_end) { if (nrow->inverted || nrow->accleft < row->accleft) { OK = FALSE; break; } nrow++; } /* If clear below, find the rightwards accidental above */ if (OK) { accstr *prow = row - 1; while (prow >= a_matrix) { int x; if (prow->pitch - row->pitch > 10) break; x = row->accleft - prow->accleft; if ( /* Check for the nearest rightwards accidental above */ (prow->acc != 0 && x > 0 && x < 10000) || /* Check if it's an inverted note just above */ (prow->inverted && row->accleft < 9500)) { int flatbottom = (row->acc == ac_flat)? 6 : 0; if (prow->pitch - row->pitch >= tuckoffset[prow->acc + flatbottom]) { accstr *xrow = row; while (xrow < a_end) { xrow->accleft = xrow->accleft - 2000; xrow++; } } break; } prow--; } } } /* Advance to check next accidental */ row++; } /*********************/ #ifdef SORTCHORD debug_printf("Modified matrix\n"); row = a_matrix; while (row < a_end) { debug_printf("%d %d %d %f %f\n", row->pitch, row->inverted, row->acc, row->accleft, row->orig_accleft); row++; } #endif /*********************/ /* Now set the information in the accleft byte */ row = a_matrix; for (ww = sorttop-1; ww >= sortvec; ww--) { int flags = ww->flags; if (ww->acc == 0 && (flags & nf_invert) == 0) continue; ww->accleft = (row++)->accleft; } } /* Now restore the data in the correct order -- ascending for stem down, and descending for stem up. Adjust stave_tiedata if we are dealing with the last-read chord. */ ww = w; tt = stave_tiedata; if (upflag) { b_notestr *wwP; tiedata *ttP = sorttievec + (sorttop - sortvec - 1); for (wwP = sorttop-1; wwP >= sortvec; wwP--) { *ww = *wwP; www = ww; /* save last */ mac_advancechord(ww); if (w == stave_firstnoteptr) *tt++ = *ttP--; } } else { b_notestr *wwP; tiedata *ttP = sorttievec; for (wwP = sortvec; wwP < sorttop; wwP++) { *ww = *wwP; www = ww; /* save last */ mac_advancechord(ww); if (w == stave_firstnoteptr) *tt++ = *ttP++; } } /* Dynamics to non-stem end in normal case; to stem end if flagged */ if ((dynamics & af_opposite) == 0) www->acflags |= dynamics; else w->acflags |= dynamics; /* First note is "true" note; ensure it has the fuq bit if any of the notes in the chord had it. */ w->type = b_note; w->flags |= fuq; } /************************************************* * Reset stem direction for note or chord * *************************************************/ /* This reset procedure is called when PMW discovers that it cannot print a beam with notes on both sides of it, in order to reset the stem direction of some of the notes before trying again. That is why we clear out the stemup flag before resetting. Before we reset the flag for a chord, we must reset the offsets of any accidentals, unless there is an explicit setting, because when the stem of a chord is set, the accidental positions are calculated, assuming that what is there already is the basic width of the accidental. As this is an error situation, we don't have to get it perfect. Arguments: noteptr pointer flag TRUE for stem up Returns: nothing */ void read_resetstemflag(b_notestr *noteptr, int flag) { b_notestr *p = noteptr; do { if (p->acc != 0) { p->accleft += curmovt->accspacing[p->acc] - curmovt->accadjusts[p->notetype]; if ((p->flags & (nf_accrbra+nf_accsbra)) != 0) p->accleft += (p->acc == ac_dflat)? 6800 : 5300; } p->flags &= ~nf_stemup; mac_advancechord(p); } while (p->type == b_chord); mac_setstemflag(noteptr, flag); } /************************************************* * Set stem directions for unforced beam * *************************************************/ /* This function is called at the end of a beam in all cases. For beams whose stem direction is forced, there is nothing on the beam stack. This procedure is even called for single notes that might have been the start of a beam, so we use the call to set the fuq flag when the stem direction is known. If the option for the stem swap level is "right", we can't take a decision here, so the notes are transferred on to the ordinary note pending stack. Arguments: none Returns: nothing */ void read_setbeamstems(void) { if (stave_beamstackptr > 0) { int i; int flag = 0; if (stave_maxaway == stave_stemswaplevel[curstave]) { switch (curmovt->stemswaptype) { case stemswap_default: case stemswap_left: if (stave_laststemup) flag = nf_stemup; break; case stemswap_up: flag = nf_stemup; break; case stemswap_down: break; case stemswap_right: for (i = 0; i < stave_beamstackptr; i++) stave_stemstack[stave_stemstackptr++] = stave_beamstack[i]; stave_beamstackptr = 0; return; } } else if (stave_maxaway < stave_stemswaplevel[curstave]) flag = nf_stemup; for (i = 0; i < stave_beamstackptr; i++) { /* NB */ mac_setstemflag(stave_beamstack[i], flag); } stave_beamstackptr = 0; stave_laststemup = flag != 0; mac_setstackedstems(flag); } stave_beaming = FALSE; if (stave_beamcount == 1 && (stave_beamfirstnote->flags & nf_stemup) != 0) stave_beamfirstnote->flags |= nf_fuq; } /* End of read6.c */