chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / trnspose.c
diff --git a/mup/mup/trnspose.c b/mup/mup/trnspose.c
new file mode 100644 (file)
index 0000000..6dd5dab
--- /dev/null
@@ -0,0 +1,819 @@
+/* Copyright (c) 1995, 1997, 1998, 1999, 2000, 2002, 2004, 2005 by Arkkra Enterprises */
+/* All rights reserved */
+/*
+ * Name:       trnspose.c
+ *
+ * Description:        This file contains functions for transposing to different keys.
+ */
+
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+#define BAD    (99)            /* a bad interval */
+
+/*
+ * For each possible transposition, this table shows the change in the number
+ * of sharps in whatever key we are in.  Invalid intervals are marked as BAD.
+ */
+static short Delshtab[5][8] = {
+       /*      1       2       3       4       5       6       7 */
+
+      { 0,     -7,     -12,    -10,    -8,     -6,     -11,    -9  },  /* d */
+      { 0,     BAD,    -5,     -3,     BAD,    BAD,    -4,     -2  },  /* m */
+      { 0,     0,      BAD,    BAD,    -1,     1,      BAD,    BAD },  /* P */
+      { 0,     BAD,    2,      4,      BAD,    BAD,    3,      5   },  /* M */
+      { 0,     7,      9,      11,     6,      8,      10,     12  },  /* A */
+};
+
+/* index this by an interval type to get a string naming it */
+static char *Inttab[] =
+               { "diminished", "minor", "perfect", "major", "augmented" };
+
+/*
+ * The following hold the transposition information for the score and all the
+ * staffs.  After every bar line it is updated if the transposition changed.
+ * The score's info is stored in inttype[0], intnum[0], and octint[0].
+ */
+static int inttype[MAXSTAFFS+1];       /* interval type of simple interval */
+static int intnum[MAXSTAFFS+1];                /* simple interval (>0, octs removed)*/
+static int octint[MAXSTAFFS+1];                /* number of octaves in interval */
+
+
+static void transnote P((struct GRPSYL *g_p, struct NOTE *n_p, int inttype,
+               int intnum, int octint));
+static void translurto P((struct GRPSYL *g_p, struct NOTE *n_p, int tnum,
+               int toct));
+static void simptrans P((int origtype, int orignum, int *inttype_p,
+               int *intnum_p, int *octint_p));
+static void fixslurto P((int s, struct MAINLL *mainll_p, int nintnum,
+               int noctint));
+\f
+/*
+ * Name:        transgroups()
+ *
+ * Abstract:    Transpose all GRPSYLs by the requested intervals.
+ *
+ * Returns:     void
+ *
+ * Description: This function loops through the main linked list, applying
+ *             SSVs to keep the transpositions of the score and each staff
+ *             up to date.  Whenever it hits a STAFF, it loops through all
+ *             the GRPSYLs in the linked list(s) for the voice(s), changing
+ *             all the affected information.  It also loops through all the
+ *             chords, transposing them.
+ */
+
+void
+transgroups()
+
+{
+       struct MAINLL *mainll_p;        /* point along main LL */
+       struct GRPSYL *g_p;             /* point along LL of groups */
+       struct STUFF *stuff_p;          /* point at stuff, looking for chords*/
+       int tinttype, tintnum;          /* current total transposition */
+       int ninttype, nintnum, noctint; /* new transposition in standard form*/
+       int v;                          /* voice number */
+       int s;                          /* staff number */
+       int n;                          /* loop variable */
+       int gotssv;                     /* seen an SSV since the last STAFF? */
+       int rtran;                      /* rest "transposition" */
+
+
+       debug(16, "transgroups");
+       initstructs();                  /* clean out old SSV info */
+
+       /*
+        * Loop through the rest of the main linked list, applying SSVs to keep
+        * the tranposition info up to date, and processing linked lists of
+        * groups and STUFF.
+        */
+       gotssv = YES;   /* force init of arrays at start even if no SSVs */
+       for (mainll_p = Mainllhc_p; mainll_p != 0; mainll_p = mainll_p->next) {
+
+               switch (mainll_p->str) {
+               case S_SSV:
+                       asgnssv(mainll_p->u.ssv_p);
+                       gotssv = YES;
+                       break;
+
+               case S_STAFF:
+                       if (gotssv == YES) {
+                               /*
+                                * This is the first staff encountered after
+                                * hitting SSV(s).  If the transposition has
+                                * changed on any staff, update any slurto
+                                * lists in the previous measure that go across
+                                * this bar line, and reset the transposition
+                                * tables in preparation to processing this
+                                * staff and the rest of the staffs in this
+                                * measure.
+                                */
+                               for (s = 1; s <= Score.staffs; s++) {
+                                       /*
+                                        * Convert this staff's new transpostion
+                                        * to standard form, storing it in
+                                        * local variables.
+                                        */
+                                       totaltrans(s, &tinttype, &tintnum);
+                                       simptrans(tinttype, tintnum,
+                                            &ninttype, &nintnum, &noctint);
+
+                                       /*
+                                        * If num or oct changed since last
+                                        * measure, we have to fix up slurto
+                                        * lists.  Interval type is irrelevant.
+                                        */
+                                       if (nintnum != intnum[s] ||
+                                           noctint != octint[s])
+                                               fixslurto(s, mainll_p,
+                                                       nintnum, noctint);
+
+                                       /* store whether changed or not */
+                                       inttype[s] = ninttype;
+                                       intnum[s] = nintnum;
+                                       octint[s] = noctint;
+                               }
+
+                               /* do the score; no slurtos to worry about */
+                               totaltrans(0, &tinttype, &tintnum);
+                               simptrans(tinttype, tintnum,
+                                       &inttype[0], &intnum[0], &octint[0]);
+
+                               gotssv = NO;
+                       }
+
+                       /* the staff we're supposed to work on */
+                       s = mainll_p->u.staff_p->staffno;
+
+                       /* loop through stuff list, transposing chords */
+                       for (stuff_p = mainll_p->u.staff_p->stuff_p;
+                                       stuff_p != 0; stuff_p = stuff_p->next) {
+                               if (stuff_p->string != 0 &&
+                                               stuff_p->modifier == TM_CHORD)
+                                       stuff_p->string = tranchstr(stuff_p->
+                                               string, stuff_p->all ? 0 : s);
+                       }
+
+                       /* never transpose tablature staff */
+                       if (is_tab_staff(s))
+                               continue;
+
+                       /* don't transpose notes if a normal clef is not to */
+                       /*  be printed */
+                       if (svpath(s, STAFFLINES)->printclef != SS_NORMAL)
+                               continue;
+
+                       /* loop through all voices that can exist */
+                       for (v = 0; v < MAXVOICES; v++) {
+                               /*
+                                * Loop through the voice's list of GRPSYLs.
+                                * If the voice doesn't exist, the loop will
+                                * execute 0 times.
+                                */
+                               for (g_p = mainll_p->u.staff_p->groups_p[v];
+                                               g_p != 0; g_p = g_p->next) {
+
+                                       if (g_p->grpcont == GC_NOTES) {
+                                               for (n = 0; n < g_p->nnotes;
+                                                                       n++) {
+                                                       transnote(g_p, &g_p->
+                                                               notelist[n],
+                                                               inttype[s],
+                                                               intnum[s],
+                                                               octint[s]);
+                                               }
+                                       } else if (g_p->grpcont == GC_REST &&
+                                         g_p->restdist != NORESTDIST) {
+                                               /*
+                                                * The user hardcoded a rest's
+                                                * position, so "transpose" it.
+                                                * This is complicated by the
+                                                * fact that we normally want
+                                                * to force an even result so
+                                                * that it will look good.
+                                                */
+                                               /* vertical stepsize shift */
+                                               rtran = 7 * octint[s] +
+                                                               intnum[s] - 1;
+
+                                               if (EVEN(g_p->restdist)) {
+                                                       g_p->restdist += rtran;
+                                                       /*
+                                                        * Force even result,
+                                                        * rounded away from
+                                                        * center line.
+                                                        */
+                                                       if (ODD(g_p->restdist)){
+                                                               g_p->restdist =
+                                                               g_p->restdist > 0 ?
+                                                               g_p->restdist + 1 :
+                                                               g_p->restdist - 1;
+                                                       }
+                                               } else {
+                                                       /* no rounding */
+                                                       g_p->restdist += rtran;
+                                                       
+                                                       /* warn if odd result*/
+                                                       if (ODD(g_p->restdist)){
+                                                               l_warning(
+                                                               g_p->inputfile,
+                                                               g_p->inputlineno,
+                                                               "'dist' on rest is an odd number, which may look bad");
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       break;
+               }
+       }
+}
+\f
+/*
+ * Name:        transnote()
+ *
+ * Abstract:    transpose a note
+ *
+ * Returns:    void
+ *
+ * Description: This function alters a NOTE structure according to the given
+ *             transposition.  This involves changing the note itself (letter,
+ *             accidental, and octave) and any notes in its slurred-to list
+ *             (letter and octave).
+ */
+
+static void
+transnote(g_p, n_p, ttype, tnum, toct)
+
+struct GRPSYL *g_p;    /* ptr to note's group, used only in error messages */
+register struct NOTE *n_p; /* pointer to the note structure */
+int ttype;             /* interval type (DIMINISHED, MINOR, . . .) */
+int tnum;              /* simple interval (positive, with octaves removed) */
+int toct;              /* number of octaves in interval */
+
+{
+       int oldaccnum;          /* old accidental number (&&=0, &=1, ...) */
+       int newaccnum;          /* new accidental number (&&=0, &=1, ...) */
+       int oldcircnum;         /* position of old note in circle of 5ths */
+       int newcircnum;         /* position of new note in circle of 5ths */
+       char newlet;            /* new note letter */
+       char newacc;            /* new accidental letter */
+       int newoct;             /* new octave number */
+
+
+       /*
+        * First do the note itself:  letter, accidental, octave.
+        */
+       /* calculate new note letter from old */
+       newlet = (n_p->letter - 'a' + tnum - 1) % 7 + 'a';
+
+       if (n_p->accidental == '\0') {
+               newacc = '\0';  /* no acc before, so no acc now */
+               /* set as if natural, for benefit of error messages later */
+               oldaccnum = strchr(Acclets, 'n') - Acclets;
+       } else {
+               /*
+                * There was an accidental, so we need to get the proper
+                * transposition of it.  Get position of the old note letter
+                * in the circle of 5ths, and the old accidental index.
+                * The index to the new note letter is shifted by delsh.
+                * If this falls outside the circle string, change the index
+                * and accidental until it lies within the string.
+                */
+               oldcircnum = strchr(Circle, n_p->letter) - Circle;
+               oldaccnum = strchr(Acclets, n_p->accidental) - Acclets;
+               newaccnum = oldaccnum;
+               newcircnum = oldcircnum + Delshtab [ ttype ] [ tnum ];
+               while (newcircnum < 0) {
+                       newaccnum--;            /* one more flat */
+                       newcircnum += 7;        /* 7 letters "sharper" */
+               }
+               while (newcircnum >= 7) {
+                       newaccnum++;            /* one more sharp */
+                       newcircnum -= 7;        /* 7 letters "flatter" */
+               }
+
+               /* test for accidental overflow */
+               if (newaccnum < 0 || newaccnum > 4) {
+                       l_ufatal(g_p->inputfile, g_p->inputlineno,
+                               "note %c%s%d is transposed to have triple sharp or flat",
+                               n_p->letter, Acctostr[oldaccnum], n_p->octave);
+               }
+
+               newacc = Acclets[newaccnum];
+       }
+
+       /*
+        * Calculate the new octave.  Add toct (number of octaves to
+        * transpose) to the old octave.  Then, add tnum to the old note
+        * number.  If it exceeds a 7th, wrap into the next octave.
+        */
+       newoct = n_p->octave + toct;
+       if (Letshift[n_p->letter - 'a'] + tnum - 1 >= 7)
+               newoct++;
+
+       /* check for octave overflow, and exit if so */
+       if (newoct < MINOCTAVE || newoct > MAXOCTAVE) {
+               l_ufatal(g_p->inputfile, g_p->inputlineno,
+                               "note %c%s%d octave is transposed out of range",
+                               n_p->letter, Acctostr[oldaccnum], n_p->octave);
+       }
+
+       /* store away the new values */
+       n_p->letter = newlet;
+       n_p->accidental = newacc;
+       n_p->octave = (short)newoct;
+
+
+       /*
+        * Now do any notes in the slurred-to list, notes this note is slurred
+        * to:  letter, octave.  (There is never an accidental here.)
+        */
+       translurto(g_p, n_p, tnum, toct);
+}
+\f
+/*
+ * Name:        translurto()
+ *
+ * Abstract:    transpose a note's slurred-to list
+ *
+ * Returns:    void
+ *
+ * Description: This function is given a pointer to a note and a transposition.
+ *             It transposes the note's slurto list.  Notice that the "type"
+ *             of transposition interval is not needed, since these lists
+ *             never contain accidentals.
+ */
+
+static void
+translurto(g_p, n_p, tnum, toct)
+
+struct GRPSYL *g_p;    /* note's group, used only in error messages */
+struct NOTE *n_p;      /* note whose slurto list is to be transposed */
+int tnum;              /* transposition interval number */
+int toct;              /* transposition interval octave */
+
+{
+       int s;                  /* index into slurto list */
+       char newlet;            /* new note letter */
+       int newoct;             /* new octave number */
+
+
+       /* loop through each note (if any) in the slurred-to list */
+       for (s = 0; s < n_p->nslurto; s++) {
+
+               /* if this is a slur to or from nowhere, don't change it */
+               if (IS_NOWHERE(n_p->slurtolist[s].octave))
+                       continue;
+
+               /* calculate new note letter from old */
+               newlet = (n_p->slurtolist[s].letter - 'a' + tnum - 1) % 7 + 'a';
+
+               newoct = n_p->slurtolist[s].octave + toct;
+               if (Letshift[n_p->slurtolist[s].letter - 'a'] + tnum - 1 >= 7)
+                       newoct++;
+
+               /* check for octave overflow, and exit if so */
+               if (newoct < MINOCTAVE || newoct > MAXOCTAVE) {
+                       l_ufatal(g_p->inputfile, g_p->inputlineno,
+                                       "note in slurred-to list transposed to out of range octave (%c%d)",
+                                       newlet, newoct);
+               }
+
+               /* store away the new values */
+               n_p->slurtolist[s].letter = newlet;
+               n_p->slurtolist[s].octave = (short)newoct;
+       }
+}
+\f
+/*
+ * Name:        tranchnote()
+ *
+ * Abstract:    transpose a note name that's inside a chord symbol
+ *
+ * Returns:    void
+ *
+ * Description: This function is given a letter and accidental that occur
+ *             inside a chord symbol.  It could be the main chord name itself,
+ *             or the name of a note, like the E in "C/E" or "DaddE".  It
+ *             returns a pointer to a static area containing the transposed
+ *             string.
+ */
+
+char *
+tranchnote(letter, acc, s)
+
+int letter;            /* A to G */
+int acc;               /* one of:  'x', '#', '\0', '&', 'B' */
+int s;                 /* staff number, needed to get tranposition interval */
+                       /*  0 means the score as a whole ("all") */
+
+{
+       static char circle[] = "FCGDAEB";       /* circle of 5ths */
+       static char newchord[3];        /* put transposed result here */
+
+       int oldaccnum;          /* old accidental number (&&=0, &=1, ...) */
+       int newaccnum;          /* new accidental number (&&=0, &=1, ...) */
+       int oldcircnum;         /* position of old note in circle of 5ths */
+       int newcircnum;         /* position of new note in circle of 5ths */
+       char newlet;            /* new note letter */
+       char newacc;            /* new accidental letter */
+
+
+       debug(32, "tranchnote letter=%c acc=%c s=%d", letter,
+                       acc==0 ? ' ' : acc, s);
+       /* need to translate naturals so that strchr can use Acclets[] */
+       if (acc == '\0')
+               acc = 'n';
+
+       /* calculate new note letter from old */
+       newlet = (letter - 'A' + intnum[s] - 1) % 7 + 'A';
+
+       /*
+        * Get the proper transposition of the accidental.  Get position of the
+        * old note letter in the circle of 5ths, and the old accidental index.
+        * The index to the new note letter is shifted by delsh.  If this falls
+        * outside the circle string, change the index and accidental until it
+        * lies within the string.
+        */
+       oldcircnum = strchr(circle, letter) - circle;
+       oldaccnum = strchr(Acclets, acc) - Acclets;
+       newaccnum = oldaccnum;
+       newcircnum = oldcircnum + Delshtab [ inttype[s] ] [ intnum[s] ];
+       while (newcircnum < 0) {
+               newaccnum--;            /* one more flat */
+               newcircnum += 7;        /* 7 letters "sharper" */
+       }
+       while (newcircnum >= 7) {
+               newaccnum++;            /* one more sharp */
+               newcircnum -= 7;        /* 7 letters "flatter" */
+       }
+
+       /* test for accidental overflow */
+       if (newaccnum < 0 || newaccnum > 4)
+               ufatal("chord note %c%s is transposed to have triple sharp or flat",
+                       letter, Acctostr[oldaccnum]);
+
+       newacc = Acclets[newaccnum];
+
+       /* store away the new values */
+       newchord[0] = newlet;
+       newchord[1] = (newacc == 'n' ? '\0' : newacc);
+       newchord[2] = '\0';
+
+       return (newchord);
+}
+\f
+/*
+ * Name:        eff_key()
+ *
+ * Abstract:    Return the "effective key" (the key after any transposition).
+ *
+ * Returns:     the number of sharps in the effective key (flats are negative)
+ *
+ * Description: This function, given a staff number, returns the number of
+ *             sharps currently in effect, considering any transpostion that
+ *             may have been requested.  If given 0 for the staff number, it
+ *             does this for the score's key signature.  It assumes the SSVs
+ *             are up to date.
+ */
+
+int
+eff_key(staff)
+
+int staff;                     /* staff number to do it for (0 = score) */
+
+{
+       int sharps;             /* sharps in old key (flats count negative) */
+       int origtype;           /* original transposition interval type */
+       int orignum;            /* original transposition interval number */
+       int inttype;            /* interval type of simple interval */
+       int intnum;             /* simple interval (positive, octs removed) */
+       int octint;             /* number of octaves in interval */
+       int newsharps;          /* sharps in key after transposition */
+
+
+       /*
+        * If no normal clef is to be printed, always treat it like there is
+        * no key signature.
+        */
+       if (staff == 0) {
+               if (Score.printclef != SS_NORMAL)
+                       return (0);
+       } else {
+               if (svpath(staff, STAFFLINES)->printclef != SS_NORMAL)
+                       return (0);
+       }
+
+       /* viewpath to get this staff's current key and transposition */
+       if (staff == 0) {
+               sharps = Score.sharps;
+       } else {
+               sharps = svpath(staff, SHARPS)->sharps;
+       }
+       totaltrans(staff, &origtype, &orignum);
+
+       simptrans(origtype, orignum, &inttype, &intnum, &octint);
+
+       /*
+        * Change number of sharps by the appropriate delta.  We assume the
+        * interval isn't BAD, because the parser wouldn't have allowed it.
+        */
+       newsharps = sharps + Delshtab [ inttype ] [ intnum ];
+
+       /* make sure the resulting key is valid */
+       if (newsharps < -7 || newsharps > 7) {
+               /*
+                * Normally we take the final "else" here.  But for the "1"
+                * interval there is an ambiguity.  If transpose + addtranspose
+                * add up to an aug or dim 1, there are two ways to state the
+                * result.  (Also for per 1, but in that case, we'd never have
+                * an invalid key.)  So we state both ways of looking at it.
+                */
+               if (orignum == 1  && origtype == AUGMENTED ||
+                   orignum == -1 && origtype == DIMINISHED) {
+                       ufatal("staff %d: key of %d %s transposed up by augmented 1 or down diminished 1 results in %d %s",
+                               staff,
+                               abs(sharps),
+                               (sharps >= 0 ? "sharps" : "flats"),
+                               abs(newsharps),
+                               (newsharps >= 0 ? "sharps" : "flats"));
+               } else if (orignum == -1 && origtype == AUGMENTED ||
+                          orignum == 1  && origtype == DIMINISHED) {
+                       ufatal("staff %d: key of %d %s transposed down by augmented 1 or up diminished 1 results in %d %s",
+                               staff,
+                               abs(sharps),
+                               (sharps >= 0 ? "sharps" : "flats"),
+                               abs(newsharps),
+                               (newsharps >= 0 ? "sharps" : "flats"));
+               } else {
+                       ufatal("staff %d: key of %d %s transposed %s by %s %d results in %d %s",
+                               staff,
+                               abs(sharps),
+                               (sharps >= 0 ? "sharps" : "flats"),
+                               (orignum > 0 ? "up" : "down"),
+                               Inttab[origtype],
+                               abs(orignum),
+                               abs(newsharps),
+                               (newsharps >= 0 ? "sharps" : "flats"));
+               }
+       }
+       return (newsharps);
+}
+\f
+/*
+ * Name:        simptrans()
+ *
+ * Abstract:    Simplify a transpostion into standard form.
+ *
+ * Returns:     void
+ *
+ * Description: This function, given a transposition, converts it into
+ *             standard form (a "simple" upwards interval and an octave).
+ */
+
+static void
+simptrans(origtype, orignum, inttype_p, intnum_p, octint_p)
+
+int origtype;          /* original transposition interval type */
+int orignum;           /* original transposition interval number */
+int *inttype_p;                /* interval type (DIMINISHED, MINOR, . . .) */
+int *intnum_p;         /* simple interval (positive, with octaves removed) */
+int *octint_p;         /* number of octaves in interval */
+
+{
+       int direction;          /* UP or DOWN */
+
+
+       *inttype_p = origtype;
+       *intnum_p = orignum;
+
+       /* set direction; if down, make intnum positive */
+       if (*intnum_p > 0) {
+               direction = UP;
+       } else {
+               direction = DOWN;
+               *intnum_p = -*intnum_p;
+       }
+
+       /* break interval into octaves plus a simple interval */
+       *octint_p = (*intnum_p - 1) / 7;
+       *intnum_p -= 7 * *octint_p;
+
+       /* if downwards, adjust so that *intnum_p is upwards */
+       if (direction == DOWN) {
+               if (*intnum_p == 1) {
+                       /* for unison, negate octaves and reverse intvl type */
+                       *octint_p = -*octint_p;
+                       *inttype_p = 4 - *inttype_p;
+               } else {
+                       /* for other intervals, octave becomes one less than */
+                       /* negation, and *intnum_p flips as does its type */
+                       *octint_p = -1 - *octint_p;
+                       *intnum_p = 9 - *intnum_p;
+                       *inttype_p = 4 - *inttype_p;
+               }
+       }
+}
+\f
+/*
+ * Name:        fixslurto()
+ *
+ * Abstract:    Fix transposition of notes in a slurred-to list.
+ *
+ * Returns:     void
+ *
+ * Description: Notes in a slurred-to list are initially transposed the same as
+ *             the regular notes in that measure.  But if they occur in a
+ *             group immediately before a bar line where the transposition
+ *             changes, they should have been transposed according to the new
+ *             transposition.  This function is called when entering the new
+ *             measure.  It searches back and finds any such slurred-to lists
+ *             in the previous measure and fixes their transposition.
+ */
+
+static void
+fixslurto(s, mainll_p, nintnum, noctint)
+
+int s;                         /* staff number */
+struct MAINLL *mainll_p;       /* initially points at current staff */
+int nintnum;                   /* interval number of new transposition */
+int noctint;                   /* octaves in new transposition */
+
+{
+       struct GRPSYL *g_p;     /* point to a group */
+       int deltanum, deltaoct; /* change in transposition */
+       int v;                  /* voice number */
+       int n;                  /* loop index */
+
+
+       /* search back to the last staff of the preceding measure, if any */
+       for (mainll_p = mainll_p->prev; mainll_p != 0 &&
+                       mainll_p->str != S_STAFF; mainll_p = mainll_p->prev)
+               ;
+       if (mainll_p == 0)
+               return; /* no preceding measure; nothing to do */
+
+       /* search back to the matching staff in that measure, if any */
+       while (mainll_p != 0 && mainll_p->str == S_STAFF &&
+                               mainll_p->u.staff_p->staffno != s)
+               mainll_p = mainll_p->prev;
+       if (mainll_p == 0 || mainll_p->str != S_STAFF)
+               return;         /* no matching staff (no. of staffs changed) */
+       /* we found a matching staff in the preceding measure */
+
+       /*
+        * "Subtract" the old transposition from the new one, to find the
+        * "delta" transposition.  This is what we need to apply to the
+        * slurred-to notes to change them from the old to new transposition.
+        */
+       deltanum = nintnum - intnum[s] + 1;
+       deltaoct = noctint - octint[s];
+       if (deltanum < 1) {
+               deltanum += 7;
+               deltaoct--;
+       }
+
+       /*
+        * mainll_p now points to the matching staff in the preceding measure.
+        * Loop through all voices that can exist.
+        */
+       for (v = 0; v < MAXVOICES; v++) {
+               g_p = mainll_p->u.staff_p->groups_p[v];
+               if (g_p == 0)
+                       continue;
+
+               /* find the last grpsyl in this voice */
+               while (g_p->next != 0)
+                       g_p = g_p->next;
+
+               /* if it doesn't contain notes, there is nothing to do */
+               if (g_p->grpcont != GC_NOTES)
+                       continue;
+
+               /* found a group with notes at end of measure; process it */
+               for (n = 0; n < g_p->nnotes; n++) {
+                       translurto(g_p, &g_p->notelist[n], deltanum, deltaoct);
+               }
+       }
+}
+\f
+/*
+ * Name:        totaltrans()
+ *
+ * Abstract:    Find the current total transposition of a staff or the score.
+ *
+ * Returns:     void
+ *
+ * Description: This function is given a staff number, or zero for the score.
+ *             It assumes that the SSVs are up to date.  It gets the two
+ *             tranposition parameters, and adds them to get the current
+ *             total transpostion.  If it's invalid, it does a ufatal.
+ */
+
+void
+totaltrans(s, type_p, num_p)
+
+int s;                         /* staff number, or 0 for score */
+int *type_p;                   /* return type of resulting transposition */
+int *num_p;                    /* return number of resulting transposition */
+
+{
+       /*
+        * inths is to be indexed by interval type and number (1 through 7).
+        * It gives the number of half steps in the interval.
+        */
+       static short inths[5][8] = {
+            /* 1       2       3       4       5       6       7 */
+
+          { 0, -1,     0,      2,      4,      6,      7,      9    }, /* d */
+          { 0, BAD,    1,      3,      BAD,    BAD,    8,      10   }, /* m */
+          { 0, 0,      BAD,    BAD,    5,      7,      BAD,    BAD  }, /* P */
+          { 0, BAD,    2,      4,      BAD,    BAD,    9,      11   }, /* M */
+          { 0, 1,      3,      5,      6,      8,      10,     12   }, /* A */
+       };
+
+       struct SSV *ssv_p;      /* point at the SSV hold a transposition */
+       int type[2];            /* interval types (DIMINISHED, MINOR, . . .) */
+       int num[2];             /* interval numbers (down is negative) */
+       int totalhs;            /* total half steps in resulting interval */
+       char place[15];         /* temp storage for error message use */
+       int offset;             /* like interval no. but counting from 0 */
+       int n;                  /* loop variable */
+
+
+       /* get the type and num of each transpostion interval */
+       ssv_p = svpath(s, TRANSPOSITION);
+       type[0] = ssv_p->inttype;
+       num[0] = ssv_p->intnum;
+       ssv_p = svpath(s, ADDTRANSPOSITION);
+       type[1] = ssv_p->addinttype;
+       num[1] = ssv_p->addintnum;
+
+       /*
+        * To get the interval number of the num of the transpostions, we
+        * basically add the two.  But musicians unfortunately start counting
+        * intervals from 1 instead of 0, so we have to play some games.
+        */
+       offset = (num[0] > 0 ? num[0] - 1 : num[0] + 1) + /* add true offsets*/
+                (num[1] > 0 ? num[1] - 1 : num[1] + 1);
+       if (offset >= 0) {              /* get interval number from offset */
+               *num_p = offset + 1;
+       } else {
+               *num_p = offset - 1;
+       }
+
+       /* accumulate total half steps in both transpositions */
+       totalhs = 0;
+       for (n = 0; n < NUMELEM(num); n++) {
+               if (num[n] > 0) {               /* interval is up */
+                       while (num[n] > 7) {
+                               num[n] -= 7;    /* subtract an octave */
+                               totalhs += 12;  /* add 12 half steps */
+                       }
+                       /* account for this simple interval */
+                       totalhs += inths[type[n]][num[n]];
+               } else {                        /* interval is down */
+                       while (num[n] < -7) {
+                               num[n] += 7;    /* add an octave */
+                               totalhs -= 12;  /* subtract 12 half steps */
+                       }
+                       /* account for this simple interval */
+                       totalhs -= inths[type[n]][abs(num[n])];
+               }
+       }
+
+       /* if interval is down, find the up version of offset and halfsteps */
+       if (offset < 0) {
+               offset = -offset;
+               totalhs = -totalhs;
+       }
+       /* bring it into the range of a simple interval */
+       totalhs -= (offset / 7) * 12;
+       offset %= 7;
+
+       /* make sure this simple interval is valid */
+       if (totalhs < inths[DIMINISHED][offset + 1] ||
+           totalhs > inths[AUGMENTED ][offset + 1]) {
+
+               if (s == 0) {
+                       (void)sprintf(place, "score");
+               } else {
+                       (void)sprintf(place, "staff %d", s);
+               }
+               ufatal("on %s, 'transpose' %s %s %d and 'addtranspose' %s %s %d add up to an invalid interval",
+               place,
+               num[0] > 0 ? "up" : "down", Inttab[type[0]], abs(num[0]),
+               num[1] > 0 ? "up" : "down", Inttab[type[1]], abs(num[1]));
+       }
+
+       /* search table for the type of interval this is; it will be found */
+       for (n = DIMINISHED; n <= AUGMENTED; n++) {
+               if (totalhs == inths[n][offset + 1]) {
+                       break;
+               }
+       }
+
+       *type_p = n;
+}