chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / map.c
diff --git a/mup/mup/map.c b/mup/mup/map.c
new file mode 100644 (file)
index 0000000..2ec577a
--- /dev/null
@@ -0,0 +1,1242 @@
+
+/* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+/* This file contains functions for mapping input to individual voices.
+ * The user can give a single line of input
+ * that gets expanded into several voices.
+ * There are two flavors of this:
+ * voice-at-a-time input and chord-at-a-time input.
+ * For the former, the GRPSYL list just gets cloned and altered as needed.
+ * For the latter, brand new GRPSYL lists are created
+ * by distributing individual notes from the user's input.
+ * For any given staff/voice, only one type of input can be used per measure.
+ * For chord-at-a-time, a given staff/voice can appear more than once
+ * within a single input line, but not on multiple input lines per measure.
+ */
+
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+/* mark whether we mapped user data, only mapped implicit spaces, or nothing */
+#define MAPPED_NOTHING (0)
+#define MAPPED_IMPLICIT        (1)
+#define MAPPED_EXPLICIT        (2)
+
+/* This struct tells how to map one note for chord-at-a-time input,
+ * i.e., which staff/voice combinations it should be mapped to.
+ * Each map item in input (semicolon-separated)
+ * gets saved in one of these. */
+struct NOTEMAP {
+       struct SVRANGELIST *svlist_p;   /* staffs/voices to map this note to */
+       struct NOTEMAP *next;           /* linked list */
+};
+
+/* This struct gives the mapping for a particular number of notes.
+ * The set of things inside a pair of brackets gets saved in one of these. */
+struct MAP {
+       int num_entries;                /* how many item in list */
+       struct NOTEMAP *notemap_p;      /* one entry for each note in chord */
+       struct MAP *next;               /* linked list */
+};
+
+/* This points to the list of maps, or to 0 if doing voice-at-a-time input */
+static struct MAP *Map_p;
+/* This points to where to add to the map list */
+static struct MAP **End_map_p_p = &Map_p;
+
+/* This is where to insert the next NOTEMAP */
+static struct NOTEMAP **Note_p_p;
+
+/* It is handy to be able to treat both input styles identically
+ * as much as possible. So for when input is voice-at-a-time, it can be handy
+ * to have a MAP that just points to Svrangelist_p. These two structs are
+ * used for that purpose. Since the value of Svrangelist_p changes
+ * at runtime, we have to set Sv_notemap->svlist_p each time
+ * before these are used. */
+static struct NOTEMAP Sv_notemap = {
+       (struct SVRANGELIST *) 0, (struct NOTEMAP *) 0
+};
+static struct MAP Voice_at_a_time_map = {
+       1, &Sv_notemap, (struct MAP *) 0
+};
+
+/* Keep track of which input style was used */
+static short Input_style[MAXSTAFFS][MAXVOICES];
+
+static int map_groups P((void));
+static void clean_map_data P((void));
+static void free_maps P((struct MAP *map_p));
+static void free_notemaps P((struct NOTEMAP *notemap_p));
+static struct NOTEMAP *find_notemap P((int num_notes));
+static void map1note P((struct GRPSYL *from_gs_p, int n, int staff, int voice,
+       short allocated[MAXSTAFFS][MAXVOICES]));
+static void do_link_groups P((void));
+static void link_a_grouplist P((int staffno, int vno, int copies));
+static void convert_rest_space P((struct GRPSYL *grpsyl_p, int staffno, int vno));
+static void conv_grpsyl P((struct GRPSYL *grpsyl_p, int staffno, int vno));
+static void mult_def P((int staff, int voice));
+\f
+
+/* Initialize Input_style to default of IS_VOICE_INPUT */
+
+void
+reset_input_style()
+
+{
+       int staff;
+       int voice;
+
+       for (staff = 0; staff < MAXSTAFFS; staff++) {
+               for (voice = 0; voice < MAXVOICES; voice++) {
+                       Input_style[staff][voice] = IS_VOICE_INPUT;
+               }
+       }
+}
+\f
+
+/* Return the current input style for a given staff/voice */
+
+int
+input_style(staff, voice)
+
+int staff;
+int voice;
+
+{
+       return(Input_style[staff-1][voice-1]);
+}
+\f
+
+/* This is called when a '[' is encountered in input, starting a new map */
+
+void
+begin_map()
+
+{
+       struct MAP *new_p;
+
+
+       /* allocate space for a new map */
+       CALLOC(MAP, new_p, 1);
+
+       /* Keep track of where to link on the first NOTEMAP */
+       Note_p_p = &(new_p->notemap_p);
+
+       /* Add to MAP list */
+       *End_map_p_p = new_p;
+
+       begin_sv_list();
+}
+\f
+
+/* Save one item of a map. Items are the semicolon-separated specifications. */
+
+void
+map_item()
+{
+       CALLOC(NOTEMAP, *Note_p_p, 1);
+
+       /* Save the current range */
+       (*Note_p_p)->svlist_p = Svrangelist_p;
+       ((*End_map_p_p)->num_entries)++;
+
+       /* prepare for another, if any */
+       Note_p_p = &( (*Note_p_p)->next);
+
+       begin_sv_list();
+       begin_range(PL_UNKNOWN);
+}
+\f
+
+/* At the end of a map specification, this function is called to save the
+ * info about the map for later use, and prepare for another map, if any.
+ */
+
+void
+end_map()
+
+{
+       /* prepare for another map, if any */
+       End_map_p_p = &( (*End_map_p_p)->next);
+
+       begin_range(Place);
+}
+\f
+
+/* Map chord-at-a-time input so it looks just like voice-at-a-time input.
+ * Return YES if current measure is chord-at-a-time, and thus
+ * mapping was done, NO if current measure is voice-at-a-time.
+ */
+
+static int
+map_groups()
+
+{
+       struct MAP *map_p;
+       /* Multiple notes in a chord might get mapped to a single staff/voice.
+        * In that case, after the first note for that staff/voice,
+        * we need to just add a note to the existing GRPSYL rather than
+        * allocating a new one. This keeps track of whether we have already
+        * allocated a GRPSYL for the current chord of a given staff/voice. */
+       short allocated[MAXSTAFFS][MAXVOICES];
+       /* This array tells us which staffs/voices we are mapping things to */
+       short used[MAXSTAFFS][MAXVOICES];
+       /* This array will have MAPPED_EXPLICIT
+        * in entries where we mapped actual user data.
+        * If we only mapped implicit spaces (MAPPED_IMPLICIT),
+        * we can treat things as if user didn't use the voice
+        * on this input line. */
+       short mapped_something[MAXSTAFFS][MAXVOICES];
+       /* This says if we've printed an error yet for multiply defined voice,
+        * to make sure we only print it once. */
+       short printed_mult_err[MAXSTAFFS][MAXVOICES];
+       /* This tells which numbers of notes we have maps for. */
+       short have_map[MAXHAND];
+       int s;                  /* staff number */
+       int v;                  /* voice number */
+       int n;                  /* note index */
+       struct NOTEMAP *notemap_p;      /* how to map notes to voices */
+       struct SVRANGELIST *svr_p;
+       struct RANGELIST *sr_p; /* range of staffs being defined */
+       struct RANGELIST *vr_p; /* range of vno's being defined */
+       struct GRPSYL *gs_p;
+       struct GRPSYL *g_p;
+       int errors;
+
+
+       if (Map_p == (struct MAP *) 0) {
+               /* not chord-at-a-time mapping, so nothing to do here */
+               return(NO);
+       }
+
+       /* remember current error count */
+       errors = Errorcount;
+
+       /* Initialize arrays. These will later tell us
+        * which GRPSYL lists we are mapping to, and whether we mapped
+        * any actual user input, or just implicit spaces. */
+       for (s = 0; s < Score.staffs; s++) {
+               for (v = 0; v < MAXVOICES; v++) {
+                       used[s][v] = NO;
+                       mapped_something[s][v] = MAPPED_NOTHING;
+                       printed_mult_err[s][v] = NO;
+               }
+       }
+       /* This tells for which numbers of notes we have maps */
+       for (n = 0; n < MAXHAND; n++) {
+               have_map[n] = NO;
+       }
+
+       /* Do some error checking on the MAP list */
+       for (map_p = Map_p; map_p != (struct MAP *) 0; map_p = map_p->next) {
+
+               if (have_map[map_p->num_entries] == YES) {
+                       l_yyerror(Curr_filename, yylineno,
+                               "more than one map for chords with %d notes",
+                               map_p->num_entries);
+                       continue;
+               }
+               else {
+                       have_map[map_p->num_entries] = YES;
+               }
+
+               for (notemap_p = map_p->notemap_p;
+                               notemap_p != (struct NOTEMAP *) 0;
+                               notemap_p = notemap_p->next) {
+                       for (svr_p = notemap_p->svlist_p;
+                                       svr_p != (struct SVRANGELIST *) 0;
+                                       svr_p = svr_p->next) {
+                           for (sr_p = svr_p->stafflist_p; sr_p != 0;
+                                                       sr_p = sr_p->next) {
+                               for (s = sr_p->begin; s <= sr_p->end; s++) {
+
+                                       if (s > Score.staffs) {
+                                               l_yyerror(Curr_filename,
+                                                       yylineno,
+                                                       "staff %d does not exist",
+                                                       s);
+                                               continue;
+                                       }
+
+                                       for (vr_p = svr_p->vnolist_p; vr_p != 0;
+                                                       vr_p = vr_p->next) {
+                                           for (v = vr_p->begin;
+                                                       v <= vr_p->end; v++) {
+
+                                               /* make sure voice exists */
+                                               if (v > 1 && svpath(s, VSCHEME)
+                                                       ->vscheme == V_1) {
+                                                  l_yyerror(Curr_filename,
+                                                       yylineno,
+                                                       "there is no voice %d on staff %d",
+                                                       v, s);
+                                               }
+                                               used[s-1][v-1] = YES;
+                                               Input_style[s-1][v-1]
+                                                       = IS_CHORD_INPUT;
+                                           }
+                                       }
+                               }
+                           }
+                       }
+               }
+       }
+
+       if (Errorcount > errors) {
+               clean_map_data();
+               return(YES);
+       }
+
+       /* process each chord in the GRPSYL list */
+       for (gs_p = Curr_gs_list_p; gs_p != (struct GRPSYL *) 0;
+                                                       gs_p = gs_p->next) {
+               /* initialize the allocation array for current chord */
+               for (s = 0; s < Score.staffs; s++) {
+                       for (v = 0; v < MAXVOICES; v++) {
+                               allocated[s][v] = NO;
+                       }
+               }
+
+               /* With voice-at-a-time input, we allow the first group
+                * to have no pitch specified iff it is on a 1-line staff.
+                * For chord-at-a-time, to allow that
+                * we would have to allow a mapping of zero notes,
+                * which doesn't make sense, or map an implicit note,
+                * which seems questionable at best.
+                * If there is a mixture of 1-line and not-1-line
+                * staffs being mapped, things get even more confusing.
+                * So we disallow implicit pitch on chord-at-at-time. */
+               if (gs_p->nnotes == 1 && gs_p->notelist[0].letter == PP_NO_PITCH) {
+                       l_yyerror(Curr_filename, yylineno, "no notes specified");
+                       notemap_p = (struct NOTEMAP *) 0;
+               }
+               else {
+                       /* Find the pattern matching the number of notes.
+                        * If none is found, this will return 0, and the 'for'
+                        * below will get skipped, and we'll add spaces */
+                       notemap_p = find_notemap(gs_p->nnotes);
+               }
+
+               /* Go through each note in the chord, and copy it
+                * to the appropriate staffs/voices */
+               for (n = 0; notemap_p != (struct NOTEMAP *) 0;
+                                       n++, notemap_p = notemap_p->next) {
+                       for (svr_p = notemap_p->svlist_p;
+                                       svr_p != (struct SVRANGELIST *) 0;
+                                       svr_p = svr_p->next) {
+                           for (sr_p = svr_p->stafflist_p; sr_p != 0;
+                                                       sr_p = sr_p->next) {
+                               for (s = sr_p->begin; s <= sr_p->end; s++) {
+                                   for (vr_p = svr_p->vnolist_p; vr_p != 0;
+                                                       vr_p = vr_p->next) {
+                                       for (v = vr_p->begin;
+                                                       v <= vr_p->end; v++) {
+                                               /* If we have not yet mapped
+                                                * anything from the current
+                                                * input line, yet there is
+                                                * something in the grpsyl
+                                                * list for this staff/voice,
+                                                * that means user must have
+                                                * already defined data for
+                                                * this staff/voice on some
+                                                * other input line, and thus
+                                                * is not allowed to map
+                                                * anything from the current
+                                                * line. */
+                                               if (mapped_something[s-1][v-1]
+                                                       == MAPPED_NOTHING
+                                                       && Staffmap_p[s]->u.staff_p->groups_p[v-1] != 0
+                                                       && printed_mult_err[s-1][v-1] == NO) {
+                                                   mult_def(s, v);
+                                                   printed_mult_err[s-1][v-1] = YES;
+                                                   continue;
+                                               }
+                                               map1note(gs_p, n, s, v,
+                                                               allocated);
+                                               mapped_something[s-1][v-1]
+                                                       = MAPPED_EXPLICIT;
+                                       }
+                                   }
+                               }
+                           }
+                       }
+               }
+
+               /* For any staff/voice that is being mapped to, but which
+                * didn't get anything mapped for this particular chord,
+                * add a space group. This could happen either because
+                * user specified several patterns and some patterns don't
+                * contain all the staffs/voices, which implies they want
+                * us to fill in spaces, or because there was an error in
+                * input. If there was an error, it's still nice to add the
+                * space, because it prevents extra error messages */
+               for (s = 1; s <= Score.staffs; s++) {
+                       for (v = 1; v <= MAXVOICES; v++) {
+                               /* If we haven't mapped anything to this
+                                * voice, but there is something there,
+                                * user must have defined it on an earlier
+                                * input line. In that case we should leave
+                                * it be, because either (1) user didn't
+                                * actually use any pattern that uses this
+                                * voice, or (2) they multiply defined the
+                                * voice, in which case the error is caught
+                                * elsewhere. In either case, their
+                                * earlier input should stand. */
+                               if (mapped_something[s-1][v-1] == MAPPED_NOTHING
+                                               && Staffmap_p[s]->u.staff_p->groups_p[v-1] != 0) {
+                                       continue;
+                               }
+
+                               if (used[s-1][v-1] == YES &&
+                                               allocated[s-1][v-1] == NO) {
+                                       map1note(gs_p, -1, s, v, allocated);
+                                       if (mapped_something[s-1][v-1] !=
+                                                       MAPPED_EXPLICIT) {
+                                               mapped_something[s-1][v-1]
+                                                       = MAPPED_IMPLICIT;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /* If this particular input line didn't actually use some of the
+        * patterns, some voices might not *really* have been used--we
+        * merely filled in implicit spaces for it. So we can undo that
+        * so user can specify the voice via voice-at-a-time if they want to.
+        * If they don't, the regular filling in of missing voices with
+        * implicit spaces will happen later. */
+       for (s = 0; s < Score.staffs; s++) {
+               for (v = 0; v < MAXVOICES; v++) {
+                       if (used[s][v] == YES &&
+                                       mapped_something[s][v] != MAPPED_EXPLICIT) {
+                               used[s][v] = NO;
+                               Input_style[s][v] = IS_VOICE_INPUT;
+
+                               /* If only implict, we free that up */
+                               if (mapped_something[s][v] == MAPPED_IMPLICIT) {
+                                       free_grpsyls(Staffmap_p[s+1]->u.staff_p->groups_p[v]);
+                                       Staffmap_p[s+1]->u.staff_p->groups_p[v] = 0;
+                               }
+                       }
+               }
+       }
+
+       /* Now we can go through and free up any wasted space */
+       for (s = 0; s < Score.staffs; s++) {
+               for (v = 0; v < MAXVOICES; v++) {
+                       if (used[s][v] == YES) {
+                               /* Rests and spaces get moved from
+                                * NOTE pseudo-pitches to GRPSYL */
+                               convert_rest_space(Staffmap_p[s+1]->u.
+                                               staff_p->groups_p[v], s+1, v+1);
+                               for (g_p = Staffmap_p[s+1]->u.staff_p->groups_p[v];
+                                               g_p != (struct GRPSYL *) 0;
+                                               g_p = g_p->next) {
+                                       resize_notelist(g_p);
+                               }
+                       }
+               }
+       }
+
+       clean_map_data();
+
+       /* Everything in the original GRPSYL list
+        * has been copied to other lists, so original can be freed */
+       free_grpsyls(gs_p);
+
+       return(YES);
+}
+\f
+
+/* map one note to one staff/voice */
+
+static void
+map1note(from_gs_p, n, staff, voice, allocated)
+
+struct GRPSYL *from_gs_p;      /* copy from here */
+int n;                         /* copy the nth note in from_gs_p, or if -1,
+                                * create a space group */
+int staff;
+int voice;
+short allocated[MAXSTAFFS][MAXVOICES]; /* tracks whether to allocate a new
+                                * GRPSYL; may be updated */
+
+{
+       struct GRPSYL *to_gs_p;         /* where to map note to */
+       struct GRPSYL **add_p_p;        /* where to add to_gs_p */
+       struct NOTE *from_note_p;
+       struct NOTE *to_note_p;
+       struct GRPSYL *prev;            /* value to set to_gs_p->prev to */
+
+
+       /* If original group is a grace group, we don't need to add a
+        * space group, since grace already take no time */
+       if (n == -1 && from_gs_p->grpvalue == GV_ZERO) {
+               return;
+       }
+
+       /* If this is the first note allocated to this staff/voice for
+        * current chord, have to allocate a GRPSYL for it. */
+       if (allocated [staff - 1] [voice - 1] == NO) {
+               to_gs_p = newGRPSYL(GS_GROUP);
+               copy_attributes(to_gs_p, from_gs_p);
+               /* by the time we get here, we've already gone past the
+                * newline, so the input line number is one too much */
+               to_gs_p->inputlineno--;
+
+               allocated [staff - 1] [voice - 1] = YES;
+
+               /* Add to end of list */
+               prev = (struct GRPSYL *) 0;
+               for (add_p_p = &(Staffmap_p[staff]->u.staff_p->groups_p[voice-1]);
+                                       *add_p_p != (struct GRPSYL *) 0;
+                                       add_p_p = &((*add_p_p)->next) ) {
+                       prev = *add_p_p;
+               }
+               to_gs_p->prev = prev;
+               *add_p_p = to_gs_p;
+
+               /* copy the other attributes */
+               to_gs_p->staffno = staff;
+               to_gs_p->vno = voice;
+               to_gs_p->basictime = from_gs_p->basictime;
+               to_gs_p->fulltime = from_gs_p->fulltime;
+               to_gs_p->dots = from_gs_p->dots;
+               to_gs_p->is_meas = from_gs_p->is_meas;
+               to_gs_p->tuploc = from_gs_p->tuploc;
+               to_gs_p->tupcont = from_gs_p->tupcont;
+               to_gs_p->tupside = from_gs_p->tupside;
+               to_gs_p->beamloc = from_gs_p->beamloc;
+               to_gs_p->breakbeam = from_gs_p->breakbeam;
+               to_gs_p->beamto = from_gs_p->beamto;
+               to_gs_p->printtup = from_gs_p->printtup;
+               to_gs_p->tie = from_gs_p->tie;
+               to_gs_p->inhibitprint = from_gs_p->inhibitprint;
+               to_gs_p->ho_usage = from_gs_p->ho_usage;
+               to_gs_p->ho_value = from_gs_p->ho_value;
+       }
+       else {
+               /* find the last group for this staff/voice */
+               for (to_gs_p = Staffmap_p[staff]->u.staff_p->groups_p[voice-1];
+                                       to_gs_p->next != (struct GRPSYL *) 0;
+                                       to_gs_p = to_gs_p->next) {
+                       ;
+               }
+       }
+
+       /* Special case: If n == -1, make this a space group */
+       if (n == -1) {
+               to_gs_p->grpcont = GC_SPACE;
+               /* some things don't make sense with space,
+                * so nullify the things that just apply to notes */
+               to_gs_p->grpvalue = GV_NORMAL;
+               to_gs_p->headshape = HS_UNKNOWN;
+               to_gs_p->grpsize = GS_NORMAL;
+               to_gs_p->stemdir = UNKNOWN;
+               to_gs_p->stemlen = STEMLEN_UNKNOWN;
+               to_gs_p->roll = NOITEM;
+               to_gs_p->beamloc = NOITEM;
+               to_gs_p->breakbeam = NO;
+               to_gs_p->beamto = CS_SAME;
+               to_gs_p->stemto = CS_SAME;
+               to_gs_p->slash_alt = 0;
+               return;
+       }
+
+       from_note_p = &(from_gs_p->notelist[n]);
+       if (Doing_tab_staff == YES) {
+               /* fret, nticks, and bendstring exist in from_note_p
+                * in an internal format, whereas add_note() needs them
+                * in something close to user input format,
+                * so we have to reconstruct
+                * what the user input must have been. */
+               add_note(to_gs_p, from_gs_p->notelist[n].letter,
+                       from_note_p->accidental,
+                       TMP_FRET(from_note_p),
+                       TMP_NTICKS(from_note_p),
+                       from_note_p->acc_has_paren,
+                       (HASBEND(from_gs_p->notelist[n])
+                         ? bend_string(from_note_p)
+                         : (char *) 0) );
+       }
+       else {
+               add_note(to_gs_p, from_gs_p->notelist[n].letter,
+                       from_note_p->accidental,
+                       from_note_p->octave,
+                       0,
+                       from_note_p->acc_has_paren,
+                       (char *) 0);
+       }
+
+       /* copy remaining note attributes: tie, slur, etc */
+       to_note_p = &(to_gs_p->notelist[to_gs_p->nnotes - 1]);
+       to_note_p->tie = from_note_p->tie;
+       to_note_p->tiestyle = from_note_p->tiestyle;
+       to_note_p->tiedir = from_note_p->tiedir;
+       to_note_p->nslurto = from_note_p->nslurto;
+       if (from_note_p->nslurto > 0) {
+               /* slurto lists cannot be safely shared, so make copy */
+               MALLOC(SLURTO, to_note_p->slurtolist, 
+                                       from_note_p->nslurto);
+               (void) memcpy(to_note_p->slurtolist,
+                                       from_note_p->slurtolist,
+                                       sizeof(struct SLURTO) *
+                                       from_note_p->nslurto);
+       }
+       else {
+               to_note_p->slurtolist = (struct SLURTO *) 0;
+       }
+       to_note_p->notesize = from_note_p->notesize;
+       to_note_p->note_has_paren = from_note_p->note_has_paren;
+       to_note_p->is_bend = from_note_p->is_bend;
+       to_note_p->smallbend = from_note_p->smallbend;
+}
+\f
+
+/* When done with temporary map data, clean everything up, to prepare
+ * for potentially getting another set of data */
+
+static void
+clean_map_data()
+
+{
+       /* free up the lists */
+       free_maps(Map_p);
+
+       /* reset pointers to be ready for more data */
+       Map_p = (struct MAP *) 0;
+       End_map_p_p = &Map_p;
+}
+\f
+
+/* free up the MAP list and everything hanging off of it */
+
+static void
+free_maps(map_p)
+
+struct MAP *map_p;
+
+{
+       if (map_p == (struct MAP *) 0) {
+               /* end recursion */
+               return;
+       }
+
+       /* free the list hanging off of this struct */
+       free_notemaps(map_p->notemap_p);
+
+       /* recurse */
+       free_maps(map_p->next);
+
+       /* free the passed-in struct */
+       FREE(map_p);
+}
+
+/* free up a NOTEMAP list and everything hanging off of it */
+
+static void
+free_notemaps(notemap_p)
+
+struct NOTEMAP *notemap_p;
+
+{
+       if (notemap_p == (struct NOTEMAP *) 0) {
+               return;
+       }
+
+       free_sv_list(notemap_p->svlist_p);
+       free_notemaps(notemap_p->next);
+       FREE(notemap_p);
+}
+\f
+
+/* Given a number of notes, find the NOTEMAP list for that many and return it.
+ * If not found, return 0. */
+
+static struct NOTEMAP *
+find_notemap(num_notes)
+
+int num_notes;
+
+{
+       struct MAP *m_p;
+
+       for (m_p = Map_p; m_p != (struct MAP *) 0; m_p = m_p->next) {
+               if (m_p->num_entries == num_notes) {
+                       return(m_p->notemap_p);
+               }
+       }
+       
+       l_yyerror(Curr_filename, yylineno,
+                       "there is no bracketed mapping for chords containing %d note%s", num_notes, num_notes == 1 ? "" : "s");
+       return ((struct NOTEMAP *) 0);
+}
+\f
+
+/* Once a measure-worth of data is gathered for one or more staffs/voices,
+ * link copies onto the appropriate STAFF structs */
+
+void
+link_groups()
+
+{
+       /* if haven't yet set up the STAFFs for this measure, do so now */
+       create_staffs();
+
+       /* if we are in this function, user specified some music data */
+       Got_some_data = YES;
+       Got_group = YES;
+
+       /* do error check--can't have notes and multirest in same measure */
+       if (Got_multirest == 1) {
+               report_mix_error();
+               return;
+       }
+
+       /* Do either chord-to-voice-mapping or standard voice mapping,
+        * as appropriate. */
+       if (map_groups() == NO) {
+               do_link_groups();
+       }
+
+       /* re-initialize for next measure */
+       Curr_gs_list_p = (struct GRPSYL *) 0;
+       free_rlists();
+}
+\f
+
+/* Go through Svrangelist, creating copies of the GRPSYL lists and
+ * linking them to the appropriate STAFFs. */
+
+static void
+do_link_groups()
+
+{
+       struct SVRANGELIST *svr_p;      /* list of ranges of staffs and vnos */
+       register int s;         /* staff index */
+       register int v;         /* voice index */
+       struct RANGELIST *sr_p; /* range of staffs being defined */
+       struct RANGELIST *vr_p; /* range of vno's being defined */
+       int copies = 0;         /* how many copies of grpsyl list made so far */
+
+
+       for (svr_p = Svrangelist_p; svr_p != (struct SVRANGELIST *) 0;
+                                               svr_p = svr_p->next) {
+
+               for (sr_p = svr_p->stafflist_p; sr_p != (struct RANGELIST *) 0;
+                                                       sr_p = sr_p->next) {
+
+                       for (s = sr_p->begin; s <= sr_p->end; s++) {
+
+                               for (vr_p = svr_p->vnolist_p;
+                                               vr_p != (struct RANGELIST *) 0;
+                                               vr_p = vr_p->next) {
+
+                                       for (v = vr_p->begin;
+                                                       v <= vr_p->end; v++) {
+                                               link_a_grouplist(s, v, copies++);
+                                       }
+                               }
+                       }
+               }
+       }
+}
+\f
+
+/* connect list of GRPSYLs to a staff. If copies == 0, use the current
+ * grpsyl list, otherwise make a copy of it and use the copy */
+
+static void
+link_a_grouplist(staffno, vno, copies)
+
+int staffno;
+int vno;
+int copies;    /* if non-zero, need to make a copy */
+
+{
+       if (rangecheck(staffno, MINSTAFFS, Score.staffs, "staff number")
+                                       == NO) {
+               return;
+       }
+
+       if (rangecheck(vno, MINVOICES, MAXVOICES, "voice number") == NO) {
+               return;
+       }
+
+       if (Staffmap_p[staffno] == (struct MAINLL *) 0) {
+               return;
+       }
+
+       if (Staffmap_p[staffno]->u.staff_p == (struct STAFF *) 0) {
+               pfatal("null staff pointer while linking group list");
+       }
+
+       if (Staffmap_p[staffno]->u.staff_p->groups_p[vno-1]
+                       != (struct GRPSYL *) 0) {
+               mult_def(staffno, vno);
+               return;
+       }
+
+       /* the first time through, we can use the
+        * existing list. After that we need to
+        * make a clone of the list */
+       if (copies == 0) {
+               (Staffmap_p[staffno])->u.staff_p->groups_p[vno-1]
+                                       = Curr_gs_list_p;
+               convert_rest_space(Staffmap_p[staffno]->u.
+                                       staff_p->groups_p[vno-1], staffno, vno);
+       }
+       else {
+               (Staffmap_p[staffno])->u.staff_p->groups_p[vno-1]
+                                       = clone_gs_list(Curr_gs_list_p, YES);
+       }
+}
+\f
+
+/* With chord-at-a-time input style, it is legal to have
+ * a mixture of pitches, spaces, and rests. However, once
+ * everything has been distributed to individual voices, we need to check
+ * that there aren't still any mixtures, and convert the rest and
+ * space pseudo-notes into rest and space groups. Some error checks
+ * also get done that couldn't be done till after this conversion. */
+
+static void
+convert_rest_space(grpsyl_p, staffno, vno)
+
+struct GRPSYL *grpsyl_p;
+int staffno;
+int vno;
+
+{
+       for (   ; grpsyl_p != (struct GRPSYL *) 0; grpsyl_p = grpsyl_p->next) {
+               conv_grpsyl(grpsyl_p, staffno, vno);
+       }
+}
+\f
+
+/* Given a GRPSYL, convert all the rest and space
+ * pseudo notes to groups and do related error checking */
+
+static void
+conv_grpsyl(grpsyl_p, staffno, vno)
+
+struct GRPSYL *grpsyl_p;
+int staffno;
+int vno;
+
+{
+       int notes, rests, spaces, rpts;/* count how many of each in chord */
+       int n;                          /* index through notes */
+
+       /* Count how many notes, rests, and spaces in the group */
+       rests = spaces = notes = rpts = 0;
+       for (n = 0; n < grpsyl_p->nnotes; n++) {
+               if (grpsyl_p->notelist[n].letter == PP_REST) {
+                       rests++;
+               }
+               else if (grpsyl_p->notelist[n].letter == PP_SPACE) {
+                       spaces++;
+               }
+               else if (grpsyl_p->notelist[n].letter == PP_RPT) {
+                       rpts++;
+               }
+               else {
+                       notes++;
+               }
+       }
+
+       /* Group may not mix space, rest, rpt, and notes */
+       if (spaces > 0 && spaces != grpsyl_p->nnotes) {
+               l_yyerror(grpsyl_p->inputfile, grpsyl_p->inputlineno,
+                       "staff %d voice %d: mixture of space and non-space",
+                       staffno, vno);
+               return;
+       }
+       if (rests > 0 && rests != grpsyl_p->nnotes) {
+               l_yyerror(grpsyl_p->inputfile, grpsyl_p->inputlineno,
+                       "staff %d voice %d: mixture of rest and non-rest",
+                       staffno, vno);
+               return;
+       }
+       if (rpts > 0 && rpts != grpsyl_p->nnotes) {
+               l_yyerror(grpsyl_p->inputfile, grpsyl_p->inputlineno,
+                       "staff %d voice %d: mixture of rpt and non-rpt",
+                       staffno, vno);
+               return;
+       }
+
+       /* convert rest, space, and rpt pseudo-notes to groups */
+       if (notes < grpsyl_p->nnotes) {
+               if (rests > 0) {
+                       /* This is actually a rest group */
+                       grpsyl_p->grpcont = GC_REST;
+                       grpsyl_p->tie = NO;
+                       /* If entire group was marked cue, leave it that way.
+                        * Otherwise, if multiple rests map to this group,
+                        * grpsize should be the biggest of them.
+                        * So initialize to small size,
+                        * and if we find any normal size,
+                        * set to normal and jump out of the loop. */
+                       if (grpsyl_p->grpsize != GS_SMALL) {
+                               grpsyl_p->grpsize = GS_SMALL;
+                               for (n = 0; n < grpsyl_p->nnotes; n++) {
+                                       if (grpsyl_p->notelist[n].notesize == GS_NORMAL) {
+       
+                                               grpsyl_p->grpsize = GS_NORMAL;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               else if (spaces > 0) {
+                       /* This is actually a space group */
+                       grpsyl_p->grpcont = GC_SPACE;
+                       grpsyl_p->tie = NO;
+                       /* Uncompressibility was temporarily saved
+                        * in octave, so move it now. If multiple spaces
+                        * mapped to this group, if any of them are
+                        * uncompressible, make the group uncompressible. */
+                       for (n = 0; n < grpsyl_p->nnotes; n++) {
+                               if (grpsyl_p->notelist[n].octave == YES) {
+                                       grpsyl_p->uncompressible = YES;
+                                       break;
+                               }
+                       }
+               }
+               else if (rpts > 0) {
+                       /* This is actually a rpt. Internally, that is stored
+                        * as a note group with no notes (nnotes gets zeroed
+                        * a few lines down from here). This should 
+                        * already be notes but doesn't hurt to set again. */
+                       grpsyl_p->grpcont = GC_NOTES;
+               }
+               if (grpsyl_p->notelist[0].slurtolist != (struct SLURTO *) 0) {
+                       l_yyerror(grpsyl_p->inputfile, grpsyl_p->inputlineno,
+                                       "can't have slur on rest, space, or rpt");
+               }
+               free_notelist(grpsyl_p);
+               grpsyl_p->notelist = 0;
+               grpsyl_p->nnotes = 0;
+       }
+
+       if (grpsyl_p->grpcont == GC_NOTES) {
+               if (grpsyl_p->is_meas == YES && grpsyl_p->nnotes > 0) {
+                       l_yyerror(grpsyl_p->inputfile, grpsyl_p->inputlineno,
+                               "'m' can only be used with rest, space, or rpt, not notes");
+                       return;
+               }
+       }
+
+       /* grace can only apply to notes */
+       if (grpsyl_p->grpvalue == GV_ZERO && (grpsyl_p->grpcont != GC_NOTES ||
+                                       grpsyl_p->nnotes == 0)) {
+               l_yyerror(grpsyl_p->inputfile, grpsyl_p->inputlineno,
+                               "grace can only be used with notes");
+       }
+}
+\f
+
+/* If first group of a measure has no time value specified, we have to use
+ * the default. This is complicated by the fact that the user could be 
+ * defining mulitples staffs/voices at once. If they are, we need to make
+ * sure that all of them have the same default. */
+
+struct SSV *
+get_dflt_timeunit_ssv()
+
+{
+       struct MAP *map_p;              /* list of maps */
+       struct NOTEMAP *notemap_p;      /* list of notemaps per map */
+       struct SVRANGELIST *svr_p;      /* list of staff/voice ranges */
+       struct RANGELIST *sr_p;         /* list of staffs being defined */
+       struct RANGELIST *vr_p;         /* list of voices being defined */
+       int s;                          /* staff number */
+       int v;                          /* voice */
+       int got_one = NO;               /* YES if have found a dflt value */
+       RATIONAL this_timeunit;         /* value for current staff/voice */
+       RATIONAL dflt_timeunit;         /* the default time unit to use */
+       struct SSV *tu_ssv_p;           /* SSV containing relevent timeunit */
+       struct SSV *ref_ssv_p;          /* SSV we had checked on prev
+                                        * staff/voice being defined together */
+
+
+       /* If doing voice-at-a-time input, use the special MAP for that case,
+        * otherwise use the Map_p */
+       if (Map_p == (struct MAP *) 0) {
+               map_p = &Voice_at_a_time_map;
+               map_p->notemap_p->svlist_p = Svrangelist_p;
+       }
+       else {
+               map_p = Map_p;
+       }
+
+       /* score value is the ultimate default */
+       dflt_timeunit = Score.timeunit;
+       tu_ssv_p = ref_ssv_p = &Score;
+
+       /* check each map/notemap/svrangelist/svrange/staff/voice combination */
+       for (   ; map_p != (struct MAP *) 0; map_p = map_p->next) {
+           for (notemap_p = map_p->notemap_p;
+                               notemap_p != (struct NOTEMAP *) 0;
+                               notemap_p = notemap_p->next) {
+               for (svr_p = notemap_p->svlist_p;
+                                       svr_p != (struct SVRANGELIST *) 0;
+                                       svr_p = svr_p->next) {
+                   for (sr_p = svr_p->stafflist_p;
+                                               sr_p != (struct RANGELIST *) 0;
+                                               sr_p = sr_p->next) {
+                       for (s = sr_p->begin; s <= sr_p->end; s++) {
+                           for (vr_p = svr_p->vnolist_p;
+                                               vr_p != (struct RANGELIST *) 0;
+                                               vr_p = vr_p->next) {
+                               for (v = vr_p->begin; v <= vr_p->end; v++) {
+
+                                   /* find default timeunit for
+                                    * this staff/voice */
+                                   tu_ssv_p = vvpath(s, v, TIMEUNIT);
+                                   this_timeunit = tu_ssv_p->timeunit;
+
+                                   if (got_one == NO) {
+                                       /* now we have one to
+                                        * compare against */
+                                       dflt_timeunit = this_timeunit;
+                                       ref_ssv_p = tu_ssv_p;
+                                       got_one = YES;
+                                   }
+                                   else if ( NE(this_timeunit, dflt_timeunit)
+                                               || timelists_equal(
+                                               tu_ssv_p->timelist_p,
+                                               ref_ssv_p->timelist_p)
+                                               == NO) {
+                                       yyerror("timeunit value must be the same for all staffs being defined on the same input line");
+                                   }
+                               }
+                           }
+                       }
+                   }
+               }
+           }
+       }
+
+       return(tu_ssv_p);
+}
+\f
+
+/* Return YES if the given lists are equivalent, NO if they aren't. */
+
+int
+timelists_equal(list1_p, list2_p)
+
+struct TIMELIST *list1_p;
+struct TIMELIST *list2_p;
+
+{
+       for (   ; list1_p != 0 && list2_p != 0;
+                       list1_p = list1_p->next, list2_p = list2_p->next) {
+               if ( NE(list1_p->fulltime, list2_p->fulltime) ) {
+                       return(NO);
+               }
+       }
+       return((list1_p == 0 && list2_p == 0) ? YES : NO);
+}
+\f
+
+/* Return YES if the current staff range is for all tablature staffs.
+ * Return NO if not. If there are a mixture, this is an error, so print
+ * a message. It still returns NO in this case, so if the user wanted tab,
+ * they may get a lot of errors.  Oh well. After all, they did make an error.
+ */ 
+
+int
+is_tab_range()
+
+{
+       struct MAP *map_p;
+       struct NOTEMAP *notemap_p;
+       struct SVRANGELIST *svr_p;
+       struct RANGELIST *sr_p;
+       int s;                          /* staff number */
+       int found_tab_staff = NO;
+       int found_non_tab_staff = NO;
+
+       /* If doing voice-at-a-time input, use the special MAP for that case,
+        * otherwise use the Map_p */
+       if (Map_p == (struct MAP *) 0) {
+               map_p = &Voice_at_a_time_map;
+               map_p->notemap_p->svlist_p = Svrangelist_p;
+       }
+       else {
+               map_p = Map_p;
+       }
+
+       /* check each map/notemap/svrangelist/svrange/staff/voice combination */
+       for (   ; map_p != (struct MAP *) 0; map_p = map_p->next) {
+               for (notemap_p = map_p->notemap_p;
+                               notemap_p != (struct NOTEMAP *) 0;
+                               notemap_p = notemap_p->next) {
+                       for (svr_p = notemap_p->svlist_p;
+                                       svr_p != (struct SVRANGELIST *) 0;
+                                       svr_p = svr_p->next) {
+                               for (sr_p = svr_p->stafflist_p;
+                                               sr_p != (struct RANGELIST *) 0;
+                                               sr_p = sr_p->next) {
+                                       for (s = sr_p->begin; s <= sr_p->end; s++) {
+                                               if (is_tab_staff(s) == YES) {
+                                                       found_tab_staff = YES;
+                                               }
+                                               else {
+                                                       found_non_tab_staff = YES;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if (found_tab_staff == YES && found_non_tab_staff == YES) {
+               yyerror("mixture of tab and non-tab staffs not allowed");
+       }
+
+       return(found_tab_staff);
+}
+\f
+
+/* When two notes in a chord are duplicates from chord-at-a-time input,
+ * we want to merge the notes, and get rid of the extra. This function will
+ * change the value of gs_p->nnotes and the size of the notelist array. */
+
+void
+merge_dup_notes(gs_p, n)
+
+struct GRPSYL *gs_p;   /* remove duplicate from here */
+int n;                 /* merge note n and n+1 into slot n, then remove
+                        * the note in slot n+1 by moving any remaining
+                        * notes down. */
+
+{
+       int i;
+       int sn, sn1;            /* slurto index */
+       struct NOTE *note_p, *extra_p;
+
+
+       /* get shorter names for what we will use a lot */
+       note_p = &(gs_p->notelist[n]);
+       extra_p = &(gs_p->notelist[n+1]);
+       /* Merge the data between the two as best we can.
+        * In general, if one has a "stronger" version of some attribute,
+        * go with that one. We check the second; if it is "stronger",
+        * force the result to that--if it was already set the same, fine--
+        * probably faster to just assign than check and maybe assign.
+        * If second wasn't stronger, go with whatever the first was. */
+
+       /* If one is normal, one small, override the small */
+       if (extra_p->notesize == GS_NORMAL) {
+               note_p->notesize = GS_NORMAL;
+       }
+
+       /* If either has a tie, do a tie */
+       if (extra_p->tie == YES) {
+               note_p->tie = YES;
+       }
+       /* Consider normal tie the strongest, then dashed, then dotted */
+       if (extra_p->tiestyle == L_NORMAL) {
+               note_p->tiestyle = L_NORMAL;
+       }
+       else if (extra_p->tiestyle == L_DASHED &&
+                       note_p->tiestyle != L_NORMAL) {
+               note_p->tiestyle = L_DASHED;
+       }
+       if ( (extra_p->tiedir == UP && note_p->tiedir == DOWN) ||
+                       (extra_p->tiedir == DOWN && note_p->tiedir == UP)) {
+               /* It would be nice to allow both up and down tie,
+                * especially since we can do that for slurs,
+                * but we only support one tie per note. */
+               l_yyerror(gs_p->inputfile, gs_p->inputlineno,
+                       "duplicate notes with opposite tie direction not allowed");
+       }
+       else if (extra_p->tiedir != UNKNOWN) {
+               note_p->tiedir = extra_p->tiedir;
+       }
+
+
+       /* Parentheses around an accidental means "just in case you forgot..."
+        * which is weaker than no parentheses, so only use parentheses if
+        * both have it. */
+       if (extra_p->acc_has_paren == NO) {
+               note_p->acc_has_paren = NO;
+       }
+       /* Parentheses around a note generally means the note is optional.
+        * An optional merged with a non-optional, is non-optional. */
+       if (extra_p->note_has_paren == NO) {
+               note_p->note_has_paren = NO;
+       }
+
+       /* Sorry, we don't deal with incompatible bends */
+       if (note_p->is_bend != extra_p->is_bend ||
+                       extra_p->smallbend != note_p->smallbend) {
+               l_yyerror(gs_p->inputfile, gs_p->inputlineno,
+                       "duplicate notes with bend mismatch not allowed");
+       }
+
+       /* Slurs... If duplicate between the two slurto lists, just leave
+        * the one, but use strongest slurstyle. If there is one in the
+        * NOTE to be deleted but not in the one to keep, move it. */
+       for (sn1 = 0; sn1 < extra_p->nslurto; sn1++) {
+               for (sn = 0; sn < note_p->nslurto; sn++) {
+                       if (note_p->slurtolist[sn].letter ==
+                                       extra_p->slurtolist[sn1].letter &&
+                                       note_p->slurtolist[sn].slurdir ==
+                                       extra_p->slurtolist[sn1].slurdir &&
+                                       note_p->slurtolist[sn].octave ==
+                                       extra_p->slurtolist[sn1].octave) {
+                               /* duplicate; just fix style if necessary */
+                               if (extra_p->slurtolist[sn1].slurstyle
+                                                               == L_NORMAL) {
+                                       note_p->slurtolist[sn].slurstyle
+                                                       = L_NORMAL;
+                               }
+                               else if (extra_p->slurtolist[sn1].slurstyle
+                                               == L_DASHED &&
+                                               note_p->slurtolist[sn].slurstyle
+                                               != L_NORMAL) {
+                                       note_p->slurtolist[sn].slurstyle
+                                                       = L_DASHED;
+                               }
+                               break;
+                       }
+               }
+               if (sn == note_p->nslurto) {
+                       /* wasn't on the list to keep, so add it */
+                       add_slurto(gs_p, extra_p->slurtolist[sn1].letter,
+                                       extra_p->slurtolist[sn1].octave,
+                                       sn,
+                                       extra_p->slurtolist[sn1].slurstyle);
+               }
+       }
+
+       /* Move things down in the notelist to remove the extra */
+       for (i = n + 1; i < gs_p->nnotes - 1; i++) {
+               gs_p->notelist[i] = gs_p->notelist[i+1];
+       }
+       (gs_p->nnotes)--;
+       REALLOC(NOTE, gs_p->notelist, gs_p->nnotes);
+}
+\f
+
+/* print error message for multiply defined voice */
+
+static void
+mult_def(staff, voice)
+
+int staff;
+int voice;
+
+{
+       l_yyerror(Curr_filename, yylineno,
+               "staff %d voice %d multiply defined (first defined on line %d)",
+               staff, voice,
+               Staffmap_p[staff]->u.staff_p->groups_p[voice-1]->inputlineno);
+}