chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / tie.c
diff --git a/mup/mup/tie.c b/mup/mup/tie.c
new file mode 100644 (file)
index 0000000..41849e0
--- /dev/null
@@ -0,0 +1,1109 @@
+
+/* Copyright (c) 1995, 1997, 1998, 1999, 2001, 2002 by Arkkra Enterprises */
+/* All rights reserved */
+
+/* This file contains functions for handling ties and slurs.
+ * This includes doing error checking to make sure
+ * there is a note to tie/slur to.
+ * There is also code to add padding to make space for ties/slurs
+ * that are carried into second endings. */
+
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+
+
+/* lengths of line segments of carried in tie marks */
+#define HALFTIEPAD     (3 * STEPSIZE)
+
+/* infomation about a tied or slurred note that gets carried into an ending */
+struct TIECARRY {
+       short   letter;         /* pitch to be tied or slurred to */
+       short   octave;         /* octave of the pitch */
+       short   curveno;        /* slurto index or -1 for tie */
+       short   is_bend;        /* if slurto is actually a bend */
+       struct MAINLL *mll_p;   /* points to first group */
+       struct GRPSYL *gs_p;    /* group of first note */
+       struct TIECARRY *next;  /* linked list */
+};
+
+/* linked list of tie carry info for each staff/voice */
+struct TIECARRY *Tiecarryinfolist_p [MAXSTAFFS + 1] [MAXVOICES];
+
+/* flag to mark if there are any carry ins */
+static short Have_carry_ins = NO;
+
+
+/* static functions */
+static void do_tie P((struct MAINLL *mll_p));
+static void do_group_ties P((struct GRPSYL *gs_p, struct MAINLL *mll_p));
+static void chk_slides P((struct NOTE *note_p, struct GRPSYL *gs_p,
+               struct MAINLL *mll_p));
+static void chk_following4slide P((struct NOTE *note_p, struct GRPSYL *gs_p,
+               struct MAINLL *mll_p));
+static struct MAINLL *do_carry_ties P((struct MAINLL *staff_mll_p,
+               struct MAINLL *bar_mll_p));
+static void savetieinfo P((struct MAINLL *mll_p, struct GRPSYL *gs_p));
+static void do_save_tieinfo P((int staffno, int vno, int letter, int octave,
+               int is_tie, struct MAINLL *mainll_p, struct GRPSYL *gs_p,
+               int is_bend));
+static void carryin_ties P((struct MAINLL *mll_p));
+static void add_carryin P((struct STAFF *staff_p));
+static void free_carryin_info P((void));
+static void free_cinfo P((struct TIECARRY *carryinfo_p));
+static void chk4xpose P((struct MAINLL *mll_p));
+static void chkxpstaff P((struct MAINLL *mll_p, int s));
+static void chkxpgrp P((struct GRPSYL *gs_p, char *inputfile, int lineno));
+static void set_inhibitprint_if_appropriate P((struct GRPSYL *gs_p,
+               struct MAINLL *mll_p));
+\f
+
+
+/* go through main list, checking each STAFF struct for ties and slurs.
+ * For each, do appropriate error checking */
+
+void
+tie()
+
+{
+       struct MAINLL * mll_p;  /* walk through main list */
+
+
+       debug(2, "tie");
+
+       /* first check for any ties across transpositions, and delete them.
+        * Can't do inside the next loop, because by then a less informative
+        * message could be generated for the error */
+       initstructs();
+       for (mll_p = Mainllhc_p; mll_p != (struct MAINLL *) 0;
+                               mll_p = mll_p->next) {
+               if (mll_p->str == S_SSV) {
+                       chk4xpose(mll_p);
+                       asgnssv(mll_p->u.ssv_p);
+               }
+       }
+
+       /* go through the main list again for other checks */
+       initstructs();
+       for (mll_p = Mainllhc_p; mll_p != (struct MAINLL *) 0;
+                               mll_p = mll_p->next) {
+       
+               /* process any GRPSYL lists with note info */
+               if (mll_p->str == S_STAFF) {
+                       if ( (svpath(mll_p->u.staff_p->staffno, VISIBLE))
+                                               ->visible == YES) {
+                               do_tie(mll_p);
+                       }
+               }
+               else if (mll_p->str == S_SSV) {
+                       asgnssv(mll_p->u.ssv_p);
+               }
+       }
+}
+\f
+
+/* do the ties and slurs on all groups off a STAFF struct */
+
+static void
+do_tie(mll_p)
+
+struct MAINLL *mll_p;  /* the STAFF struct with list of GRPSYLs */
+
+{
+       struct GRPSYL *gs_p;            /* walk through GRPSYL list */
+       int v;                          /* walk through voices per staff */
+       int is_tab;                     /* YES if tablature staff */
+
+
+       is_tab = is_tab_staff(mll_p->u.staff_p->staffno);
+
+       for (v = 0; v < MAXVOICES; v++) {
+               /* go through entire list of GRPSYLs */
+               for (gs_p = mll_p->u.staff_p->groups_p[v];
+                                       gs_p != (struct GRPSYL *) 0;
+                                       gs_p = gs_p->next) {
+
+                       /* error check */
+                       if (gs_p->grpcont != GC_NOTES) {
+                               if (gs_p->tie == YES) {
+                                       l_warning(mll_p->inputfile,
+                                               mll_p->inputlineno,
+                                               "tie can only apply to notes");
+                                       gs_p->tie = NO;
+                               }
+
+                               /* if rest or space, nothing more to do */
+                               continue;
+                       }
+
+                       do_group_ties(gs_p, mll_p);
+                       if (is_tab == YES) {
+                               set_inhibitprint_if_appropriate(gs_p, mll_p);
+                       }
+               }
+       }
+}
+\f
+
+/* do ties on all notes in a group that have ties. While we're at it, also
+ * make sure any staffs with clefs not printed, don't have accidentals on any
+ * notes  */
+
+static void
+do_group_ties(gs_p, mll_p)
+
+struct GRPSYL *gs_p;           /* do ties from this group */
+struct MAINLL *mll_p;          /* points to gs_p */
+
+{
+       struct GRPSYL *gs2_p;           /* group to tie to */
+       register int n;                 /* walk through note list */
+       struct NOTE *note1_p;           /* info about note */
+       struct NOTE *note2_p;           /* note slurred to */
+       int slur;                       /* index into slurtolist */
+       int d;                          /* index for deleting illegal slurs */
+       short err = NO;                 /* error flag */
+       short inhibitprint;             /* if to set inhibitprint flag on the
+                                        * tied-to group (tab staff only) */
+
+
+       /* if all notes in a group are tied on a tablature staff,
+        * then all the corresponding notes on the tied-to group
+        * should have their inhibitprint flag set. So we need to
+        * find out if that is a possibility */
+       if (is_tab_staff(gs_p->staffno) == YES) {
+               if (gs_p->tie == YES) {
+                       inhibitprint = YES;
+               }
+               else {
+                       /* first assume that all notes will be tied, then
+                        * check them all. If any are found that are not
+                        * tied, then turn the inhibitprint flag back off */
+                       inhibitprint = YES;
+                       for (n = 0; n < gs_p->nnotes; n++) {
+                               if (gs_p->notelist[n].tie == NO) {
+                                       inhibitprint = NO;
+                                       break;
+                               }
+                       }
+               }
+       }
+       else {
+               /* inhibitprint flag is only used on tablature staffs */
+               inhibitprint = NO;
+       }
+
+       /* go through all notes in group, looking for ties */
+       gs2_p = (struct GRPSYL *) 0;
+       for (n = 0; n < gs_p->nnotes; n++) {
+
+               /* For staffs without clef, make sure there are no accidentals.
+                * But tab staffs keep accidentals, even with no clef.
+                * Philosophically, this isn't really a good place to
+                * do this, but since we're going through the list of notes
+                * anyway here, and time-wise in the scheme of the program
+                * this is the right time, rather than make yet another trip
+                * through the list later we do it here. But for MIDI
+                * purposes, we need to keep the accidental, or the wrong
+                * note will play! */
+               if (svpath(gs_p->staffno, STAFFLINES)->printclef != SS_NORMAL
+                               && is_tab_staff(gs_p->staffno) == NO
+                               && Doing_MIDI == NO) {
+                        gs_p->notelist[n].accidental = '\0';
+               }
+
+               note1_p = &(gs_p->notelist[n]);
+               /* if this note's tie flag is set, check the tie */
+               if ( note1_p->tie == YES) {
+
+                       /* if haven't yet found the group to tie to, do that.
+                        * (If this is not the first note to be tied
+                        * in this group, we would have already found
+                        * the other group) */
+                       if (gs2_p == (struct GRPSYL *) 0) {
+                               gs2_p = find_next_group(mll_p, gs_p, "tie");
+                       }
+
+                       if (gs2_p == (struct GRPSYL *) 0) {
+                               /* if nothing to tie to, cancel the tie. We
+                                * will have already printed an error msg. */
+                               note1_p->tie = NO;
+                               gs_p->tie = NO;
+                       }
+                       else {
+                               /* if the inhibitprint flag is set, set it
+                                * in the tied-to group. However,
+                                * if the groups have different numbers of
+                                * notes and the inhibitprint flag is set,
+                                * we have to cancel it, because that only
+                                * applies when the tied-from and tied-to
+                                * groups have identical notes in them */
+                               if (inhibitprint == YES) {
+                                       if (gs_p->nnotes == gs2_p->nnotes) {
+                                               gs2_p->inhibitprint = YES;
+                                       }
+                                       /* turn flag off, so we don't
+                                        * waste time checking it
+                                        * again for each of the remaining
+                                        * notes in the group */
+                                       inhibitprint = NO;
+                               }
+
+                               if (find_matching_note(gs2_p, note1_p->letter,
+                                               note1_p->octave, "tie")
+                                               == (struct NOTE *) 0) {
+                                       note1_p->tie = NO;
+                                       gs_p->tie = NO;
+                               }
+                       }
+               }
+
+
+               /* handle all slurs from current note */
+               for (slur = 0; slur < note1_p->nslurto; slur++) {
+
+                       /* slides to/from nowhere don't get processed here */
+                       if (IS_NOWHERE(note1_p->slurtolist[slur].octave)) {
+                               continue;
+                       }
+
+                       /* if haven't yet found the group to slur to, do that.
+                        * (We may have already found it earlier) */
+                       if (gs2_p == (struct GRPSYL *) 0) {
+                               gs2_p = find_next_group(mll_p, gs_p, "slur");
+                       }
+
+                       if (gs2_p == (struct GRPSYL *) 0) {
+                               /* if nothing to slur to, cancel all slurs */
+                               note1_p->nslurto = 0;
+                               continue;
+                       }
+
+                       /* special case of 'M' when a group 'slur'
+                        * has been specified. Find matching note
+                        * in the second chord */
+                       if (note1_p->slurtolist[slur].letter == 'M') {
+                               if (gs_p->nnotes != gs2_p->nnotes) {
+                                       /* only print message first time */
+                                       if (err == NO) {
+                                               l_warning(gs_p->inputfile,
+                                                       gs_p->inputlineno,
+                                                       "'slur' requires equal number of notes in each chord");
+                                       }
+                                       note2_p = (struct NOTE *) 0;
+
+                                       /* don't do any more on this
+                                        * chord, to avoid multiple
+                                        * error messages */
+                                       err = YES;
+                               }
+                               else {
+                                       note2_p = & (gs2_p->notelist[n]);
+                               }
+                       }
+                       else {
+                               note2_p = find_matching_note(gs2_p,
+                                       note1_p->slurtolist[slur].letter,
+                                       note1_p->slurtolist[slur].octave,
+                                       note1_p->is_bend ? "bend" : "slur");
+                       }
+
+                       if (note2_p != (struct NOTE *) 0) {
+                               /* fill in the letter/octave if they had to
+                                * be derived */
+                               if ((note1_p->slurtolist[slur].letter == 'U')
+                                        || (note1_p->slurtolist[slur].letter == 'M')
+                                        || is_tab_staff(gs_p->staffno) == YES) {
+                                   note1_p->slurtolist[slur].letter =
+                                                       note2_p->letter;
+                                   note1_p->slurtolist[slur].octave =
+                                                       note2_p->octave;
+                               }
+                       }
+                       else {
+                               /* discard this slur--
+                                * nothing to slur to */
+                               for (d = slur + 1; d < note1_p->nslurto; d++) {
+                                       note1_p->slurtolist[d-1] =
+                                               note1_p->slurtolist[d];
+                               }
+                               (note1_p->nslurto)--;
+                       }
+               }
+
+               /* do additional slide checks for tab and tabnote */
+               if (is_tab_staff(gs_p->staffno) == YES
+                               || (gs_p->staffno < MAXSTAFFS
+                               && is_tab_staff(gs_p->staffno + 1) == YES)) {
+                       chk_slides(note1_p, gs_p, mll_p);
+               }
+       }
+}
+
+/* do extra checks for slide. There can be no more than one incoming and one
+ * outgoing slide for any given note */
+
+static void
+chk_slides(note_p, gs_p, mll_p)
+
+struct NOTE *note_p;           /* this note might have slides */
+struct GRPSYL *gs_p;           /* note is in this group */
+struct MAINLL *mll_p;          /* group is tied to this main list struct */
+
+{
+       int s;          /* index through slurtolist */
+       int incoming = 0, outgoing = 0; /* how many slides of each type */
+
+
+       /* go through list counting up incoming and outgoing slides */
+       for (s = 0; s < note_p->nslurto; s++) {
+               switch(note_p->slurtolist[s].octave) {
+
+               case OUT_UPWARD:
+               case OUT_DOWNWARD:
+                       outgoing++;
+                       break;
+
+               case IN_UPWARD:
+               case IN_DOWNWARD:
+                       incoming++;
+                       break;
+
+               default:
+                       outgoing++;
+                       /* make sure following group doesn't have any
+                        * incoming nowhere slides */
+                       chk_following4slide(note_p, gs_p, mll_p);
+                       break;
+               }
+       }
+
+       if (incoming > 1) {
+               l_yyerror(gs_p->inputfile, gs_p->inputlineno,
+                       "can't have more than one slide into a note");
+       }
+       if (outgoing > 1) {
+               l_yyerror(gs_p->inputfile, gs_p->inputlineno,
+                       "can't have more than one slide from a note");
+
+       }
+}
+\f
+
+/* Given a note with a slide to a specific fret,
+ * if there is a following group, see if it has a matching note,
+ * and if so, check that note's slurtolist to see if it
+ * contains any incoming nowhere slides. If so, there is a
+ * problem, because we already have a slide to that note */
+
+static void
+chk_following4slide(note_p, gs_p, mll_p)
+
+struct NOTE *note_p;   /* this note has a slide to a specific fret */
+struct GRPSYL *gs_p;   /* note is in this group */
+struct MAINLL *mll_p;  /* group is attached to this main list struct */
+
+{
+       struct GRPSYL *ngs_p;   /* next group */
+       int n;                  /* index through notes */
+       int ns;                 /* index through slides on next group note */
+
+
+       if ((ngs_p = nextgrpsyl(gs_p, &mll_p)) == (struct GRPSYL *) 0) {
+               /* no next group, so no problem */
+               return;
+       }
+
+       /* check each note in next group */
+       for (n = 0; n < ngs_p->nnotes; n++) {
+               /* is this the matching note?  If the letter matches and
+                * either it's a tab staff or the octave also matches,
+                * then it is the matching note. */
+               if (ngs_p->notelist[n].letter == note_p->letter &&
+                               (is_tab_staff(gs_p->staffno) ||
+                               ngs_p->notelist[n].octave == note_p->octave)) {
+
+                       /* found the matching note. Check its slurtolist */
+                       for (ns = 0; ns < ngs_p->notelist[n].nslurto; ns++) {
+                               switch (ngs_p->notelist[n].slurtolist[ns].octave) {
+                               case IN_UPWARD:
+                               case IN_DOWNWARD:
+                                       l_yyerror(gs_p->inputfile,
+                                               gs_p->inputlineno,
+                                               "can't slide to note that has </n> or <\\n>");
+                                       break;
+                               default:
+                                       break;
+                               }
+                       }
+               }
+       }
+}
+\f
+
+/* find note in following chord having specified pitch/octave.
+ * If the note is found, return pointer to it, otherwise 0
+ */
+
+struct NOTE *
+find_matching_note(gs_p, letter, octave, type)
+
+struct GRPSYL *gs_p;   /* which GRPSYL we're tying to */
+int letter;            /* find note with this pitch, 'a' to 'g' */
+int octave;            /* find note with this octave */
+char *type;            /* "tie", "slur", "slide", or "bend",
+                        * or null if not to print any error messages */
+
+{
+       struct NOTE *note2_p;   /* note to tie to */
+       register int n2;        /* index through notelist of 2nd group */
+
+
+       if (gs_p == (struct GRPSYL *) 0) {
+               return( (struct NOTE *) 0);
+       }
+
+       /* we don't allow tie/slur into a measure repeat. */
+       if (is_mrpt(gs_p) == YES) {
+               l_warning(gs_p->inputfile, gs_p->inputlineno,
+                       "tie/slur/bend not allowed into measure rpt");
+               return (struct NOTE *) 0;
+       }
+
+       /* special case. On slurto, if second group has only a single
+        * note, user doesn't have to specify it. We will have marked the
+        * pitch as 'U'. If second group has only one note in it, use that
+        * one. If not, error */
+       if ( letter == 'U') {
+               if ( gs_p->nnotes != 1) {
+                       if (type != (char *) 0) {
+                               l_warning(gs_p->inputfile, gs_p->inputlineno,
+                                       "note to %s to not specified", type);
+                       }
+                       return(struct NOTE *) 0;
+               }
+               else {
+                       return( &(gs_p->notelist[0]) );
+               }
+       }
+
+       /* try to find matching note in second note group */
+       /* If first note has an accidental and the corresponding one in
+        * the next group doesn't, that's a
+        * match, 'cause we only print the accidental once.
+        */
+       for (n2 = 0; n2 < gs_p->nnotes; n2++) {
+
+               note2_p = &(gs_p->notelist[n2]);
+
+               if (is_tab_staff(gs_p->staffno) == YES) {
+                       /* on tab staff, we just have to match
+                        * the string number */
+                       if (note2_p->letter == letter) {
+                               return(note2_p);
+                       }
+                       else {
+                               continue;
+                       }
+               }
+
+               if ( (note2_p->letter == letter)
+                               && (note2_p->octave == octave) ) {
+
+                       if (type != (char *) 0 && (strcmp(type, "tie") == 0)
+                                       && (note2_p->accidental != '\0')
+                                       && is_tab_staff(gs_p->staffno) == NO) {
+                               l_warning(gs_p->inputfile, gs_p->inputlineno,
+                                       "second note of tie not allowed to have an accidental");
+                               /* fix it so in case we're called again on the
+                                * same note (which is possible), we'll only
+                                * print one error message */
+                               note2_p->accidental = '\0';
+                       }
+
+                       /* found it! */
+                       return(note2_p);
+               }
+       }
+
+       /* oh-oh. User goofed */
+       if (is_tab_staff(gs_p->staffno) == YES) {
+               if (type != (char *) 0) {
+                       l_yyerror(gs_p->inputfile, gs_p->inputlineno,
+                               "can't do %s: %s string not in chord%s",
+                               type, stringname(letter, gs_p->staffno),
+                               gs_p->nnotes == 0 ?
+                               " (in fact no strings at all)" : "");
+               }
+       }
+       else {
+               if (type != (char *) 0) {
+                       if (letter < 'a' || letter > 'g' || octave < MINOCTAVE
+                                               || octave > MAXOCTAVE) {
+                               l_warning(gs_p->inputfile, gs_p->inputlineno,
+                                       "can't do %s: note not in chord",
+                                       type);
+                       }
+                       else {
+                               l_warning(gs_p->inputfile, gs_p->inputlineno,
+                                       "can't do %s: %c%d not in chord%s",
+                                       type, letter, octave,
+                                       gs_p->nnotes == 0 ?
+                                       " (in fact no notes at all)" : "");
+                       }
+               }
+       }
+       return( (struct NOTE *) 0 );
+}
+\f
+
+/* given one GRPSYL, find the next one in the same staff and voice,
+ * which might be in the next measure */
+
+struct GRPSYL *
+find_next_group(mll_p, gs_p, type)
+
+struct MAINLL *mll_p;          /* current place in main list */
+struct GRPSYL *gs_p;           /* group to tie from */
+char *type;                    /* "tie" or "slur" */
+
+{
+       struct MAINLL *ml_p;
+
+       ml_p = mll_p;
+       if ((gs_p = nextgrpsyl(gs_p, &ml_p)) == (struct GRPSYL *) 0) {
+               l_warning(mll_p->inputfile, mll_p->inputlineno,
+                                               "no chord to %s to", type);
+       }
+
+       return(gs_p);
+}
+\f
+
+/* go through main list. If we hit a bar that begins an ending, back up
+ * and go through the previous measure. If the final group of any voice
+ * has any tied or slurred notes, save information about them. Then for
+ * each additional beginning of an ending up until an endending, add
+ * user padding to allow for carried in tie mark. At the endending, free
+ * the information and continue through the rest of the main list */
+
+void
+tie_carry()
+
+{
+       struct MAINLL *mll_p;                   /* walk through main list */
+       struct MAINLL *first_staff_mll_p;       /* points to first STAFF
+                                                * struct of measure */
+
+
+       debug(2, "tie_carry");
+
+       initstructs();
+       first_staff_mll_p = (struct MAINLL *) 0;
+       for (mll_p = Mainllhc_p; mll_p != (struct MAINLL *) 0;
+                                               mll_p = mll_p->next) {
+               switch (mll_p->str) {
+
+               case S_STAFF:
+                       /* remember where list of staffs begins and skip
+                        * the rest of the STAFFs */
+                       first_staff_mll_p = mll_p;
+                       for (   ; mll_p->next->str == S_STAFF;
+                                               mll_p = mll_p->next) {
+                               ;
+                       }
+                       break;
+
+               case S_BAR:
+                       if (mll_p->u.bar_p->endingloc == STARTITEM) {
+                               mll_p = do_carry_ties(first_staff_mll_p, mll_p);
+                       }
+                       break;
+
+               case S_CLEFSIG:
+                       /* actually, it should be impossible to hit this case,
+                        * because clefsigs with pseudo-bar haven't been
+                        * created yet at the time this is called, but if things
+                        * are changed some day so things get done in a different
+                        * order, this should then work. */
+                       if (mll_p->u.clefsig_p->bar_p != (struct BAR *) 0 &&
+                                       mll_p->u.clefsig_p->bar_p->endingloc
+                                       == STARTITEM) {
+                               mll_p = do_carry_ties(first_staff_mll_p, mll_p);
+                       }
+                       break;
+
+               case S_SSV:
+                       asgnssv(mll_p->u.ssv_p);
+                       break;
+               default:
+                       break;
+               }
+       }
+}
+\f
+
+/* Save info about any ties and slurs on the last chords before the beginning
+ * of the ending. Then search forward in main list. If there are any more
+ * beginnings of endings, add padding to the appropriate groups.
+ * Return MAINLL at the end of the last ending processed. */
+
+static struct MAINLL *
+do_carry_ties(staff_mll_p, bar_mll_p)
+
+struct MAINLL *staff_mll_p;    /* first staff in measure which ends on
+                                * bar that begins an ending */
+struct MAINLL *bar_mll_p;      /* the bar that begins an ending */
+
+{
+       struct MAINLL *mll_p;   /* walk through list of staffs */
+       int v;                  /* voice number */
+
+
+       /* save all the tie / slur info */
+       for (mll_p = staff_mll_p; mll_p->str == S_STAFF; mll_p = mll_p->next) {
+
+               for (v = 0; v < MAXVOICES; v++) {
+                       savetieinfo(mll_p, mll_p->u.staff_p->groups_p[v]);
+               }
+       }
+
+       /* now search ahead for other endings */
+       for (mll_p = bar_mll_p->next; mll_p != (struct MAINLL *) 0;
+                                               mll_p = mll_p->next) {
+               if (mll_p->str != S_BAR) {
+                       continue;
+               }
+
+               switch (mll_p->u.bar_p->endingloc) {
+               case NOITEM:
+               case ENDITEM:
+                       free_carryin_info();
+                       return(mll_p);
+               case STARTITEM:
+                       carryin_ties(mll_p->next);
+                       break;
+               default:
+                       break;
+               }
+       }
+       pfatal("fell off end of list while doing tie carries");
+       /*NOTREACHED*/
+       return( (struct MAINLL *) 0);
+}
+\f
+
+/* given a GRPSYL, save info about any notes in it that have ties or slurs */
+
+static void
+savetieinfo(mll_p, gs_p)
+
+struct MAINLL *mll_p;          /* main list struct that gs_p is connected to */
+struct GRPSYL *gs_p;           /* save info about ties/slurs on last group
+                                * in this list */
+
+{
+       int n;          /* note index */
+       int s;          /* slurto index */
+
+
+       if (gs_p == (struct GRPSYL *) 0) {
+               return;
+       }
+
+       /* find last group in list */
+       for (  ; gs_p->next != (struct GRPSYL *) 0; gs_p = gs_p->next) {
+               ;
+       }
+
+       for (n = 0; n < gs_p->nnotes; n++) {
+
+               /* save tie info */
+               if (gs_p->notelist[n].tie == YES) {
+                       do_save_tieinfo(gs_p->staffno, gs_p->vno,
+                                       gs_p->notelist[n].letter,
+                                       gs_p->notelist[n].octave, -1,
+                                       mll_p, gs_p, NO);
+               }
+
+               /* save slurto info */
+               for (s = 0; s < gs_p->notelist[n].nslurto; s++) {
+                       do_save_tieinfo(gs_p->staffno, gs_p->vno,
+                                       gs_p->notelist[n].slurtolist[s].letter,
+                                       gs_p->notelist[n].slurtolist[s].octave,
+                                       s, mll_p, gs_p,
+                                       gs_p->notelist[n].is_bend);
+               }
+       }
+}
+\f
+
+/* save info about one tie or slur mark that will need to be carried into
+ * subsequent endings. Malloc space for info, fill it in, and put into table */
+
+static void
+do_save_tieinfo(staffno, vno, letter, octave, curveno, mll_p, gs_p, is_bend)
+
+int staffno;
+int vno;
+int letter;    /* a to g */
+int octave;
+int curveno;
+struct MAINLL *mll_p;  /* points to first group */
+struct GRPSYL *gs_p;   /* group of first note */
+int is_bend;           /* YES if is actually a bend rather than slur */
+
+{
+       struct TIECARRY *new_p;
+
+       MALLOC(TIECARRY, new_p, 1);
+       new_p->letter = (short) letter;
+       new_p->octave = (short) octave;
+       new_p->curveno = (short) curveno;
+       new_p->is_bend = is_bend;
+       new_p->mll_p = mll_p;
+       new_p->gs_p = gs_p;
+       new_p->next = Tiecarryinfolist_p [staffno] [vno - 1];
+       Tiecarryinfolist_p [staffno] [vno - 1] = new_p;
+
+       Have_carry_ins = YES;
+}
+\f
+
+/* Once an ending has been found that may have ties/slurs carried in, use
+ * the saved information to add padding. */
+
+static void
+carryin_ties(mll_p)
+
+struct MAINLL *mll_p;  /* look for staffs from here for chords that may have
+                        * things tied or slurred in */
+
+{
+       if (Have_carry_ins == NO) {
+               /* nothing to do */
+               return;
+       }
+
+       /* skip everything up to STAFFS */
+       for (  ; mll_p != (struct MAINLL *) 0; mll_p = mll_p->next) {
+               if (mll_p->str == S_STAFF) {
+                       add_carryin(mll_p->u.staff_p);
+               }
+               else if (mll_p->str == S_BAR) {
+                       break;
+               }
+       }
+}
+\f
+
+/* given a STAFF which is at the beginning of an ending that may have ties/slurs
+ * carried in, go through each voice. If there is anything to carry in, 
+ * add appropriate padding, then generate curve */
+
+static void
+add_carryin(staff_p)
+
+struct STAFF *staff_p;         /* which staff to do ties/slur carry in on */
+
+{
+       int staffno;
+       int v;                          /* voice number */
+       int n;                          /* index into notelist */
+       struct GRPSYL *gs_p;            /* first chord in measure */
+       struct TIECARRY *info_p;        /* info about things carried in */
+       int found;                      /* if matching note found in chord */
+       double padding;                 /* how much padding to add */
+
+
+       staffno = staff_p->staffno;
+       /* do each carried in item on each voice */
+       for (v = 0; v < MAXVOICES; v++) {
+
+               padding = HALFTIEPAD;
+
+               for (info_p = Tiecarryinfolist_p [staffno] [v];
+                                       info_p != (struct TIECARRY *) 0;
+                                       info_p = info_p->next) {
+
+                       gs_p = staff_p->groups_p[v];
+
+                       /* add padding to allow for carried-in mark */
+                       gs_p->padding += padding;
+                       /* only add padding once per chord! */
+                       padding = 0.0;
+
+                       /* mark any notes that will get carried-in mark */
+                       for (found = NO, n = 0; n <  gs_p->nnotes; n++) {
+                               if (gs_p->notelist[n].letter
+                                               == info_p->letter &&
+                                               gs_p->notelist[n].octave
+                                               == info_p->octave) {
+
+                                       /* A carried-in tie on a tablature
+                                        * staff isn't printed, but the fret
+                                        * is put in parentheses. */
+                                       if (is_tab_staff(staff_p->staffno) == YES
+                                                       && info_p->curveno == -1) {
+                                               gs_p->notelist[n].FRET_HAS_PAREN = YES;
+                                       }
+                                       found = YES;
+                                       break;
+                               }
+                       }
+
+                       if (found == NO) {
+                               l_warning(gs_p->inputfile, gs_p->inputlineno,
+                                       "can't carry tie/slur/bend into ending: %c%d not in chord",
+                                       info_p->letter, info_p->octave);
+                       }
+               }
+       }
+}
+\f
+
+/* free all the tie carry in info */
+
+static void
+free_carryin_info()
+
+{
+       int s;
+       int v;
+
+
+       for (s = 1; s <= MAXSTAFFS; s++) {
+               for (v = 0; v < MAXVOICES; v++) {
+                       free_cinfo(Tiecarryinfolist_p [s] [v]);
+                       Tiecarryinfolist_p [s] [v] = (struct TIECARRY *) 0;
+               }
+       }
+
+       Have_carry_ins = NO;
+}
+
+
+/* recursively free list of tie carry information */
+
+static void
+free_cinfo(carryinfo_p)
+
+struct TIECARRY *carryinfo_p;
+
+{
+       if (carryinfo_p == (struct TIECARRY *) 0) {
+               return;
+       }
+
+       free_cinfo(carryinfo_p->next);
+       FREE(carryinfo_p);
+}
+\f
+
+/* check if a transposition occurred, and if so, see if there were any
+ * ties that would cross the bar. If so, print warning and discard the tie */
+
+static void
+chk4xpose(mll_p)
+
+struct MAINLL *mll_p;  /* containing SSV that might contain transpose */
+
+{
+       struct SSV *ssv_p;
+       int s;                  /* staff index */
+       int intnum;             /* transposition interval */
+       int inttype;            /* transposition interval type */
+
+
+       if (mll_p->str != S_SSV) {
+               return;
+       }
+
+       ssv_p = mll_p->u.ssv_p;
+       if (ssv_p->used[TRANSPOSITION] == YES ||
+                                       ssv_p->used[ADDTRANSPOSITION] == YES) {
+               /* this SSV changes transpose value, need to check further */
+               if (ssv_p->context == C_STAFF) {
+                       /* if staff now has a different transpose value than
+                        * before, need to see if any notes tied over the
+                        * previous bar */
+                       s = ssv_p->staffno;
+                       totaltrans(s, &inttype, & intnum);
+                       if (ssv_p->inttype != inttype
+                                       || ssv_p->intnum != intnum) {
+                               chkxpstaff(mll_p, s);
+                       }
+               }
+               else {
+                       /* must be score wide change. This is a little
+                        * trickier. Go through each staff. If its transpose
+                        * value is not set in staff context and it's
+                        * different than the new transpose value, then
+                        * we need to check for ties */
+                       for (s = 1; s <= Score.staffs; s++) {
+                               totaltrans(0, &inttype, & intnum);
+                               if (Staff[s].used[TRANSPOSITION] == NO &&
+                                       Staff[s].used[ADDTRANSPOSITION] == NO &&
+                                       (ssv_p->inttype != inttype
+                                       || ssv_p->intnum != intnum)) {
+                                   chkxpstaff(mll_p, s);
+                               }
+                       }
+               }
+       }
+}
+\f
+
+/* check a specific staff for possible ties across transposition */
+
+static void
+chkxpstaff(mll_p, s)
+
+struct MAINLL *mll_p;  /* look backward in main list from here */
+int s;                 /* which staff */
+
+{
+       int v;
+
+
+       /* back up to find appropriate staff */
+       for (mll_p = mll_p->prev; mll_p != (struct MAINLL *) 0;
+                                               mll_p = mll_p->prev) {
+               if (mll_p->str == S_STAFF) {
+                       if (mll_p->u.staff_p->staffno == s) {
+                               /* found the correct staff. check each voice */
+                               for (v = 0; v < MAXVOICES; v++) {
+                                       chkxpgrp(mll_p->u.staff_p->groups_p[v],
+                                               mll_p->inputfile,
+                                               mll_p->inputlineno);
+                               }
+                               return;
+                       }
+                       else if (mll_p->u.staff_p->staffno < s) {
+                               /* user must have increased the number of
+                                * staffs as well, so the staff in question
+                                * didn't exist in previous measure */
+                               return;
+                       }
+               }
+       }
+}
+\f
+
+/* find the last group in a list of grpsyls. If it has any ties on it,
+ * print warning message for trying to tie across a transposition, and discard
+ * the tie(s). */
+
+static void
+chkxpgrp(gs_p, inputfile, lineno)
+
+struct GRPSYL *gs_p;   /* check this grpsyl list */
+char *inputfile;       /* for error message */
+int lineno;
+
+{
+       register int n;         /* index through notelist */
+
+
+       /* find last group in list */
+       for (   ; gs_p != (struct GRPSYL *) 0; gs_p = gs_p->next) {
+
+               if (gs_p->next == (struct GRPSYL *) 0) {
+                       /* this is the last group in the measure. See if
+                        * it has any ties on it */
+                       for (n = 0; n < gs_p->nnotes; n++) {
+                               if (gs_p->notelist[n].tie == YES) {
+                                       /* Aha! User tried to do a tie over
+                                        * a transpose */
+                                       l_warning(inputfile, lineno,
+                                               "can't tie into transposition change (use slur)");
+                                       /* cancel any and all ties on this grp,
+                                        * and return, so we don't print more
+                                        * than one error per group */
+                                       for (n = 0; n < gs_p->nnotes; n++) {
+                                               gs_p->notelist[n].tie = NO;
+                                       }
+                                       gs_p->tie = NO;
+                                       return;
+                               }
+                       }
+               }
+       }
+}
+\f
+
+/* On tablature staffs, if two consecutive groups are tied together,
+ * normally the frets are not printed for the second group, so we need
+ * to set the inhibitprint flag on the group. However if there is any
+ * reason why inhibiting printing on a given group isn't a good idea,
+ * we won't set the inhibitprint flag. So this function checks all the
+ * possible reasons for not setting inhibitprint, and if none of them
+ * apply, then it is set.
+ */
+
+static void
+set_inhibitprint_if_appropriate(gs_p, mll_p)
+
+struct GRPSYL *gs_p;
+struct MAINLL *mll_p;
+
+{
+       struct GRPSYL *nextgs_p;        /* the group after gs_p */
+       struct GRPSYL *following_p;     /* the group after next_gs_p */
+       int n;                          /* index through notelist */
+       
+
+
+       if ((nextgs_p = nextgrpsyl(gs_p, &mll_p)) == (struct GRPSYL *) 0) {
+               /* no next group, so nothing to set */
+               return;
+       }
+
+       /* if this group and next group don't have same number of notes,
+        * then there won't be an inhibitprint on the next group */
+       if (gs_p->nnotes == 0 || gs_p->nnotes != nextgs_p->nnotes) {
+               return;
+       }
+
+       /* if next group has a "with" list, no inhibitprint */
+       if (nextgs_p->nwith != 0) {
+               return;
+       }
+
+       for (n = 0; n < gs_p->nnotes; n++) {
+               /* if any notes in this group are not tied, then there won't be
+                * an inhibitprint on the next group */
+               if (gs_p->notelist[n].tie == NO) {
+                       return;
+               }
+
+               /* if next group has any slides to/from nowhere, or slurs
+                * to the next group, it won't get inhibitprint set */
+               if (nextgs_p->notelist[n].nslurto != 0) {
+                       return;
+               }
+       }
+
+       /* next group has a bend of any sort, it doesn't get inhibitprint */
+       for (n = 0; n < nextgs_p->nnotes; n++) {
+               if (HASBEND(nextgs_p->notelist[n]) == YES) {
+                       return;
+               }
+       }
+       
+       /* if group following next group has a non-prebend bend, then the
+        * next group does not get inhibitprint */
+       if ((following_p = nextgrpsyl(nextgs_p, &mll_p)) != (struct GRPSYL *) 0) {
+               for (n = 0; n < following_p->nnotes; n++) {
+                       if (HASBEND(following_p->notelist[n]) == YES &&
+                                       following_p->notelist[n].FRETNO == NOFRET) {
+                               return;
+                       }
+               }
+       }
+
+       /* Whew! If we got here, the group passed all the tests to have its
+        * inhibitprint flag set, so set it */
+       nextgs_p->inhibitprint = YES;
+}