char version[12] = "2015-04-16";

/*  Copyright (C) 2014-15 R. D. Tennent School of Computing,
 *  Queen's University, rdt@cs.queensu.ca
 *
 *  This program is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU General
 *  Public License as published by the Free Software
 *  Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *
 *  This program is distributed in the hope that it will
 *  be useful, but WITHOUT ANY WARRANTY; without even the
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A
 *  PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General
 *  Public License along with this program; if not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street,
 *  Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*  autosp - automatic spacing of notes commands in musixtex scores */

/*  Usage: autosp [-v | --version | -h | --help]
 *         autosp [-d | --debug] infile[.tex] [outfile[.tex]]
 */

# include <stdlib.h>
# include <string.h>
# include <stdio.h>
# include <getopt.h>
# include <time.h>
# include <ctype.h>
# include <limits.h>

# include "utils.h"

# define LINE_LEN 1024
# define SHORT_LEN 256
# define MAX_STAFFS 9

# define bool    _Bool
# define true    1
# define false   0

# define SMALL_NOTE 256
# define SP(note) (SMALL_NOTE/note)  /* note = { 256 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 }  */
# define MAX_SPACING 2*SP(1)

# define DEBUGP if (debug > 0) printf
int debug = 0;

char infilename[SHORT_LEN];
char *infilename_n = infilename;
char outfilename[SHORT_LEN];
char *outfilename_n = outfilename;

FILE *infile, *outfile;

char line[LINE_LEN];            /* line of input                                */
int lineno;                     /* line number      */

int ninstr = 1;                 /* number of instruments                        */
int staffs[MAX_STAFFS] = {0,1}; /* number of staffs for ith instrument          */
int nstaffs = 1;                /* number of staffs                             */
char terminator[MAX_STAFFS];    /* one of '&' "|', '$'                          */
bool active[MAX_STAFFS];        /* is staff i active?                           */

char *notes[MAX_STAFFS];        /* note segment for ith staff                   */
char *current[MAX_STAFFS];

int spacings[MAX_STAFFS];       /* spacing for ith staff                        */
int spacing = 0;                /* spacing for current notes                    */

int vspacing[MAX_STAFFS];       /* virtual-note (skip) spacing                  */

int cspacing[MAX_STAFFS];       /* nominal collective-note spacing              */
char collective[MAX_STAFFS][SHORT_LEN];
                               /* prefixes for collective note sequences        */
bool first_collective[MAX_STAFFS];

int beaming[MAX_STAFFS];       /* spacing for beamed notes                      */
int new_beaming;

int xtuplets[MAX_STAFFS];      /* # of xtuplets in ith staff                    */
int xtuplet;                   /* count of the # of xtuplets processed          */

char outstrings[MAX_STAFFS][LINE_LEN];  
                               /* accumulate commands to be output              */
char *n_outstrings[MAX_STAFFS];

void
usage (void)
{
  puts ("Usage: autosp [-v | --version | -h | --help]");
  puts ("       autosp infile[.tex] [outfile[.tex]] ");
}

char *ps(int spacing)
/* convert spacing value to note value(s)  */
{ char *s = malloc(SHORT_LEN);
  char *s_n = s; 
  *s = '\0';
  if (spacing == MAX_SPACING || spacing == 0)
    sprintf(s, "SP(0)");
  else 
  { int n = 1;
    while (true)
    { if (spacing >= SMALL_NOTE/n)
      { s_n = s_n + sprintf (s_n, "SP(%d)", n);
        spacing = spacing - SMALL_NOTE/n;
        if (spacing > 0) 
          s_n = s_n + sprintf (s_n, "+");
        else break;
      }
      n = 2*n;
    }
  }
  return s;
}

void analyze_notes (char **ln) 
/* divide material from *ln to \en into notes[i] segments */
{
  int i; char *s; char *t; 
  int newlines = 0;
  s = strpbrk (*ln+1, "|&\\\n"); /* skip initial command      */
  while (true)
  { /* look for \en */
    t = strstr(s, "\\en");
    if (t != NULL) break;
    /* replace EOL by a blank and append another line of input */
    { t = strchr (s, '\n');
      *t = ' ';
      fgets (t+1, LINE_LEN - (t - line), infile);
      newlines++;
    }
  }
  /* t -> "\en"  */
  if ( prefix("\\enotes", t) ) *ln = t+7;
  else *ln = t+3;
  *t = '$'; /* replace \en[otes] by "$" */
  *(t+1) = '\0';
  for (i=1; i <= nstaffs; i++)      
  { 
    notes[i] = s; current[i] = s;
    s = strpbrk (s, "|&$");
    terminator[i] = *s;
    s++;
  }
  lineno = lineno + newlines;
}

void output_notes (int i)
{ char *s;
  if (vspacing[i] > 0)
  { if (xtuplet != 1)
    {
      DEBUGP ("\noutputing \\sk with vspacing[%d]=%s\n", i, ps(vspacing[i]));
      append (outstrings[i], &(n_outstrings[i]), "\\sk", LINE_LEN); 
    }
    vspacing[i] = vspacing[i] - spacing;
    DEBUGP ("\nvspacing[%d] reduced by %s\n", i, ps(spacing));
  }
  else if (collective[i][0])
  { 
    if (!first_collective[i])
      append (outstrings[i], &(n_outstrings[i]), collective[i], LINE_LEN);
    first_collective[i] = false;
    s = notes[i];
    while (s < current[i]) 
    { *n_outstrings[i] = *s; 
       n_outstrings[i]++; s++; 
    }
    *(n_outstrings[i]) = '\0';
    append (outstrings[i], &(n_outstrings[i]), "}", LINE_LEN);
    notes[i] = s;
    if (*notes[i] == '}')
    { collective[i][0] = '\0';
      notes[i]++;
    }
  }
  else
  { s = notes[i];
    while (s < current[i]) 
    { *n_outstrings[i] = *s; 
       n_outstrings[i]++; s++; 
    }
    *(n_outstrings[i]) = '\0';
    notes[i] = s;
    DEBUGP ("\nIn output_notes, outstrings[%d]=%s\n", i, outstrings[i]);
  }
}

void collective_note (int i)
{ char *s = current[i];
  spacings[i] = cspacing[i];
  while (true) /* search for alphabetic note */
  {
    if (*s == '.') 
    {
      spacings[i] = spacings[i] * 1.50; 
    }
    else if (isalpha (*s) || *s == '*')
    {  
      current[i] = s+1; 
      if (spacings[i] < spacing) 
        spacing = spacings[i];
      return; 
    }
    s++;
  }
}

void beam_initiation (char *s, int i)
{ 
  if ( prefix ("\\ibbbbbb", s)
    || prefix ("\\Ibbbbbb", s)
    || prefix ("\\nbbbbbb", s) )
    beaming[i] = SP(256); 
  else if ( prefix ("\\ibbbbb", s)
    || prefix ("\\Ibbbbb", s)
    || prefix ("\\nbbbbb", s) )
    beaming[i] = SP(128); 
  else if ( prefix ("\\ibbbb", s)
    || prefix ("\\Ibbbb", s)
    || prefix ("\\nbbbb", s) )
    beaming[i] = SP(64); 
  else if ( prefix ("\\ibbb", s) 
    || prefix ("\\Ibbb", s)
    || prefix ("\\nbbb", s) ) 
    beaming[i] = SP(32); 
  else if ( prefix ("\\ibb", s) 
    || prefix ("\\Ibb", s)
    || prefix ("\\nbb", s) )
    beaming[i] = SP(16); 
  else if ( prefix ("\\ib", s) 
    || prefix ("\\Ib", s) )
    beaming[i] = SP(8); 
  DEBUGP ("\nbeam initiation: beaming[%d]=%s\n", i, ps(beaming[i]));
}

void beam_termination (char *s, int i)
{
  
  
  DEBUGP ("\nbeam termination:\n");
  DEBUGP ("current s=%s\n", s);
  DEBUGP ("beaming[%d]=%s\n", i, ps(beaming[i]));
  if ( prefix ("\\tbbbbbb", s) )
  { 
    new_beaming = SP(128);
    if (beaming[i] > SP(256))
      beaming[i] = SP(256);
  }
  else if ( prefix ("\\tbbbbb", s) )
  { 
    new_beaming = SP(64);
    if (beaming[i] > SP(128))
      beaming[i] = SP(128);
  }
  else if ( prefix ("\\tbbbb", s) )
  { 
    new_beaming = SP(32);
    if (beaming[i] > SP(64))
      beaming[i] = SP(64);
  }
  else if ( prefix ("\\tbbb", s) )
  { 
    new_beaming = SP(16);
    if (beaming[i] > SP(32))
      beaming[i] = SP(32);
  }
  else if ( prefix ("\\tbb", s) ) 
  { 
    new_beaming = SP(8);
    if (beaming[i] > SP(16))
      beaming[i] = SP(16);
  }
  else if ( prefix ("\\tb", s) )
    new_beaming = 0; 
  DEBUGP ("\nafter beam termination:\n");
  DEBUGP ("beaming[%d]=%s", i, ps(beaming[i]));
  if (new_beaming > 0) 
  {
    DEBUGP ("  new_beaming=%s", ps(new_beaming));
    DEBUGP ("\n");
  }
}

void spacing_note (int i)
{ 
  char *s, *t;
  new_beaming = 0;
  DEBUGP ("\nIn spacing_note, current[%d]=%s\n", i, current[i]);
  if (!active[i]) 
    return;
  if (xtuplet == 1 && xtuplets[i] == 1) 
  { 
    DEBUGP ("\nReturning from spacing_note because xtuplet == 1\n");
    spacings[i] = spacing;
    return;
  }
  if (vspacing[i] > 0) 
  { spacings[i] = vspacing[i]; 
    if (spacings[i] < spacing) 
      spacing = spacings[i];
    return; 
  } 
  if (collective[i][0])
  { collective_note (i); 
    return; 
  }
  s = strpbrk (current[i], "\\&|$");
  if (*s != '\\') return;
  while (true)
  { 
    if ( prefix ("\\hs", s) 
       && !prefix ("\\hsk", s) ) 
    { spacings[i] = SP(32); break;}

    if ( prefix ("\\h", s) 
       && !prefix ("\\hqsk", s) ) 
    { spacings[i] = SP(2); break;}

    if ( prefix ("\\qs", s)
       && !prefix ("\\qsk", s) ) 
    { spacings[i] = SP(16); break; }

    if ( prefix ("\\q", s)
       && !prefix ("\\qb", s) 
       && !prefix ("\\qsk", s) 
       && !prefix ("\\qqsk", s) ) 
    { spacings[i] = SP(4); break; }

    if ( prefix ("\\qb", s))  /* beam note */
    { spacings[i] = beaming[i]; 
      DEBUGP ("\nbeam note: spacings[%d]=%s\n", i, ps(spacings[i]));
      if (new_beaming != 0) /* set by preceding \tb... */
      { 
        DEBUGP ("\nbeaming[%d] set to new_beaming=%s\n", i, ps(new_beaming));
        beaming[i] = new_beaming; 
        new_beaming = 0; 
      }
      break; 
    }
    
    if ( prefix ("\\cccc", s) ) 
    { spacings[i] = SP(64); break; }

    if ( prefix ("\\ccc", s) ) 
    { spacings[i] = SP(32); break; }

    if ( prefix ("\\cc", s) ) 
    { spacings[i] = SP(16); break; }

    if ( prefix ("\\ds", s ) 
       && !prefix ("\\dsh", s) ) 
    { spacings[i] = SP(8); break; }

    if ( prefix ("\\c", s) 
       && !prefix ("\\cna", s)  
       && !prefix ("\\csh", s)  
       && !prefix ("\\cfl", s)  
       && !prefix ("\\curve", s)  
       && !prefix ("\\ccharnote", s) )
    { spacings[i] = SP(8); break; }

    if ( prefix ("\\wh", s)
      || prefix ("\\pa", s) 
      || prefix ("\\breve", s) )
    { spacings[i] = SP(1); break; }

    if ( prefix ("\\Hpause", s) ) 
    { spacings[i] = SP(2); break; }

    if ( prefix("\\tq", s) )
    { 
       DEBUGP ("\nbeam completion: beaming[%d]=%s, new_beaming=%s\n", i, ps(beaming[i]), ps(new_beaming));
       spacings[i] = beaming[i]; 
       break; 
    }
    
    if ( prefix ("\\ib", s) 
      || prefix ("\\Ib", s)
      || prefix ("\\nb", s) )
      beam_initiation (s, i);

    else if ( prefix("\\tb", s) )
      beam_termination (s, i);

    else 
    if ( prefix("\\triolet", s)
      || prefix("\\uptrio", s)
      || prefix("\\downtrio", s)
      || prefix("\\uptuplet", s)
      || prefix("\\downtuplet", s) )
    { xtuplets[i] = 3;
      xtuplet = 3;
      DEBUGP ("\nxtuplet[%d] and xtuplet set to %d\n", i, 3);
    }
    else if ( prefix("\\xtuplet", s))
    { char *t = s+1;
      while (!isdigit(*t)) t++;
      xtuplets[i] = atoi(t);
      xtuplet = xtuplets[i];
      DEBUGP ("\nxtuplet[%d] and xtuplet set to %d\n", i, xtuplets[i]);
    }

    else if ( prefix("\\zchar", s) 
       || prefix("\\lchar", s)
       || prefix("\\cchar", s) 
       || prefix("\\zcn", s)
       || prefix("\\lcn", s)
       || prefix("\\ccn", s) )
    { /* need to skip two arguments, possibly with embedded commands */
      s = strchr (s+1, '}'); /* first }  */
      s = strchr (s+1, '}'); /* second } */
    }

    /* Command is non-spacing.*/;
    /* Skip ahead to the next command. */
    s = strpbrk (s+1, "\\&|$");
    if ( *s != '\\')
    { current[i] = s; return; }
  }

  if (prefix ("\\whpp", s)
   || prefix ("\\hupp", s)
   || prefix ("\\hlpp", s)
   || prefix ("\\qupp", s)
   || prefix ("\\qlpp", s)
   || prefix ("\\cupp", s)
   || prefix ("\\clpp", s)
   || prefix ("\\qbpp", s) )
  {
    /* double-dotted note */
    spacings[i] = spacings[i] * 1.75;
    DEBUGP ("\ndouble-dotted note; spacings[%d] increased to %s\n", i, ps(spacings[i]));
  }
  else 
  if (prefix ("\\whp", s)
   || prefix ("\\hup", s)
   || prefix ("\\hlp", s)
   || prefix ("\\qup", s)
   || prefix ("\\qlp", s)
   || prefix ("\\cup", s)
   || prefix ("\\clp", s)
   || prefix ("\\qbp", s) )
  {
    /* dotted note */
    spacings[i] = spacings[i] * 1.5;
    DEBUGP ("\ndotted note; spacings[%d] increased to %s\n", i, ps(spacings[i]));
  }

  t = strpbrk (s+1, "{\\&|$"); /* collective coding?  */
  if (*t == '{')  /*  {...}  */
  { char *ss = s; 
    char *tt = collective[i];
    while (ss <= t)
    { *tt = *ss; tt++, ss++; }
    *tt = '\0';
    current[i] = t+1;
    cspacing[i] = spacings[i];
    collective_note (i);
    first_collective[i] = true;
    return;
  } 
  if (spacings[i] < spacing) spacing = spacings[i];
  current[i] = strpbrk (s+1, "\\&|$");
}

void initialize_notes ()
{ int i;
  if (spacing == MAX_SPACING)   
    fprintf (outfile, "\\NOTEs"); 
  else if (spacing == SP(2)+SP(4))
    fprintf (outfile, "\\NOTesp"); 
  else if (spacing == SP(2))
    fprintf (outfile, "\\NOTes"); 
  else if (spacing == SP(4)+SP(8) || spacing == SP(4)+SP(8)+SP(16))
    fprintf (outfile, "\\NOtesp"); 
  else if (spacing == SP(4))
    fprintf (outfile, "\\NOtes"); 
  else if (spacing == SP(8)+SP(16) || spacing == SP(8)+SP(16)+SP(32))
    fprintf (outfile, "\\Notesp"); 
  else if (spacing == SP(8))
    fprintf (outfile, "\\Notes"); 
  else if (spacing == SP(16)+SP(32) || spacing == SP(16)+SP(32)+SP(64)) 
    fprintf (outfile, "\\notesp"); 
  else if (spacing == SP(16))
    fprintf (outfile, "\\notes"); 
  else if (spacing == SP(32))
    fprintf (outfile, "\\nnotes"); 
  else if (spacing == SP(64))
    fprintf (outfile, "\\nnnotes"); 
  else 
  { fprintf (stderr, "Error: spacing %d not recognized.\n", spacing);
    exit (EXIT_FAILURE);
  }
  for (i=1; i <= nstaffs; i++) 
  { n_outstrings[i] = outstrings[i];
    *n_outstrings[i] = '\0';
      if (xtuplet > 1 && xtuplet == xtuplets[i])
      { DEBUGP ("\nmultinoteskip with xtuplet=%d and xtuplets[%d]=%d\n", xtuplet, i, xtuplets[i]);
        append ( outstrings[i], &(n_outstrings[i]), "\\multnoteskip{", LINE_LEN);
        switch (xtuplets[i]) 
        {
          case 5: 
            append ( outstrings[i], &(n_outstrings[i]), "0.8}", LINE_LEN);
            break;
          case 3: 
            append ( outstrings[i], &(n_outstrings[i]), "0.66667}", LINE_LEN);
            break;
          case 2: 
            append ( outstrings[i], &(n_outstrings[i]), "1.5}", LINE_LEN);
            break;
          default: 
            fprintf (outfile,"Error: xtuplet for x=%d not supported.\n", xtuplets[i]);
            exit (EXIT_FAILURE);
        }
     }
  }
}

void terminate_notes ()
{ int i;
  DEBUGP ("\nIn terminate_notes:\n");
  for (i=1; i<=nstaffs; i++)
  { if (active[i]) DEBUGP ("notes[%d]=%s\n", i, notes[i]);
    if (active[i]) DEBUGP ("outstrings[%d]=%s\n", i, outstrings[i]);
  }
  for (i=1; i <= nstaffs; i++)
  { 
    fprintf (outfile, "%s", outstrings[i]);
    if (spacing == MAX_SPACING)
    /* output any non-spacing commands left in notes[i] */
    { char *t;
      char *s = notes[i];
      DEBUGP ("\nAppending notes[%d]=%s\n", i, notes[i]);
      t = strpbrk (s, "&|$");  
      while (s < t)
      { putc (*s, outfile); 
        s++; 
      }
    }
    if ( (terminator[i] == '&') || (terminator[i] == '|') ) 
      putc (terminator[i], outfile);
  }
  fprintf (outfile, "\\en");
  if (spacing < MAX_SPACING) 
    fprintf (outfile, "\n");
}

void generate_notes ()
{ int i;
  while (true)
  { int old_spacing = spacing;
    bool firstxtuplet = false;
    spacing = MAX_SPACING;
    for (i=1; i <= nstaffs; i++) 
    {
      if (active[i]) 
      {
        spacing_note (i);
        if (xtuplet > 1 && xtuplet == xtuplets[i]) 
        { firstxtuplet = true;
          DEBUGP ("firstxtuplet = true with xtuplet=%d == xtuplets[%d]\n", xtuplet, i);
        }
      }
    }
    if (spacing != old_spacing || firstxtuplet)  
    { if (old_spacing < MAX_SPACING) 
        terminate_notes ();
      if (spacing == MAX_SPACING) 
      {
        DEBUGP ("\nAfter generate_notes:\n");
        for (i=1; i <= nstaffs; i++)
          if (active[i]) DEBUGP ("outstrings[%d]=%s\n", i, outstrings[i]);
        for (i=1; i <= nstaffs; i++)
          if (active[i]) DEBUGP ("notes[%d]=%s\n", i, notes[i]);
        for (i=1; i <= nstaffs; i++)
          if (active[i]) DEBUGP ("vspacing[%d]=%s\n", i, ps(vspacing[i]));
        return;
      }
      initialize_notes ();
    }
    for (i=1; i <= nstaffs; i++)  /* append current notes/skips to outstrings */
      output_notes (i);
    for (i=1; i <= nstaffs; i++)
    {
      /* virtual notes needed?  */
      if (active[i] && spacings[i] != 0 && spacings[i] != spacing && vspacing[i] == 0 && xtuplet != 1)
      {
        DEBUGP ("\nvspacing[%d] set to spacings[%d]=%s\n", i, i, ps(spacings[i]));
        vspacing[i] = spacings[i];
        if (firstxtuplet && xtuplet != xtuplets[i]) 
        { DEBUGP ("\nvspacing[%d] not reduced\n", i);}
        else
        {
          vspacing[i] = vspacing[i] - spacing;
          DEBUGP ("\nvspacing[%d] reduced by %s\n", i, ps(spacing));
        }
      } 
      else 
        DEBUGP ("\nNo vspacing set: spacing=%s  spacings[%d]=%s  vspacing[%d]=%s  xtuplet=%d\n",  
                   ps(spacing), i, ps(spacings[i]), i, ps(vspacing[i]), xtuplet);
    }
    for (i=1; i <= nstaffs; i++)
      if (xtuplets[i] > 1 && xtuplet > 0) 
      { xtuplet--;
        DEBUGP ("\nxtuplet reduced to %d\n", xtuplet);
      }
    DEBUGP ("\nIn generate_notes:\n");
    for (i=1; i <= nstaffs; i++)
      if (active[i]) DEBUGP ("outstrings[%d]=%s\n", i, outstrings[i]);
    for (i=1; i <= nstaffs; i++)
      if (active[i]) DEBUGP ("vspacing[%d]=%s\n", i, ps(vspacing[i]));
  }
}

void
checkc (char *s, char c)
{ if (*s != c) 
  {
    printf ("Error: Expected %c but found %c in line %d:\n%s\n", c, *s, lineno, line);
    exit (EXIT_FAILURE);
  }
}

void
checkn (char *s)
{ if (strpbrk (s, "0123456789") != s) 
  {
    printf ("Error: Expected digit but found %c in line %d:\n%s\n", *s, lineno, line);
    exit (EXIT_FAILURE);
  }
}


void
process_command (char **ln)
{ char *s, *t;
  if ( prefix("\\instrumentnumber", *ln) )
  { 
    s = strpbrk (*ln, "123456789");
    if ( s == NULL ) error ("\\instrumentnumber command unreadable.");
    ninstr = atoi (s);
    while (*ln <= s) { putc (**ln, outfile); (*ln)++;}
  }

  if ( prefix("\\def\\nbinstrument", *ln) )
  { 
    s = strpbrk (*ln, "123456789");
    if ( s == NULL ) error ("\\def\\nbinstrument command unreadable.");
    ninstr = atoi (s);
    while (*ln <= s) { putc (**ln, outfile); (*ln)++;}
  }

  else if ( prefix("\\setstaffs", *ln) )
  { /* for now, just record in staffs[i]  */
    int n, p;
    s = strpbrk (*ln, "123456789");
    if ( s == NULL ) error ("\\setstaffs command unreadable.");
    n = (int)(*s) - (int)('0'); /* instrument number  */
    s = strpbrk (s+1, "123456789");
    if ( s == NULL ) error ("\\setstaffs command unreadable.");
    p = (int)(*s) - (int)('0'); /* number of staffs */
    staffs[n] = p;
    while (*ln <= s) { putc (**ln, outfile); (*ln)++;}
  }

  else if ( prefix("\\startpiece", *ln) )
  { int i, j;
    nstaffs = 0; 
    for (i=1; i <= ninstr; i++)
    { for (j=1; j <= staffs[i]; j++) 
      { nstaffs++; 
        active[nstaffs] = true;  
      }
    }
    if (nstaffs == 1) fprintf (outfile, "\\nostartrule\n");
    fprintf (outfile, "\\startpiece");
    t = strpbrk (*ln+1, "\\%\n");
    if (t == NULL) t = *ln + strlen (*ln);
    *ln = t;
  }

  else if ( prefix("\\TransformNotes", *ln) ) 
  { /* determine ninstr, nstaffs, staffs[i], and active[i] */
    int i = 0;
    int j;
    ninstr = 0;
    nstaffs = 0;
    s = *ln + 16;  /* first parameter  */
    while (true)
    { ninstr++; nstaffs++;
      staffs[ninstr] = 1;
      checkc (s, '#'); s++;
      checkn (s); s++;
      while (*s == '|')
      { staffs[ninstr]++; nstaffs++;
        s++;
        checkc (s, '#'); s++;
        checkn (s); s++; 
      }
      if (*s != '&') break;
      s++;
    }
    checkc (s, '}'); s++;
    for (j=1; j <= ninstr; j++) active[j] = false;
    t = strchr (s,'{');
    if (t == NULL) error ("Can't parse \\TransformNotes.");
    s = strpbrk (t, "#}"); /* may have \transpose etc. before # */
    while (*s == '#')
    { 
      i++; 
      if (sscanf (s, "#%d", &j) != 1) 
        error("sscanf for argument number fails");
      active[j-1] = true;  /* parameters start at 2 */
      s = strpbrk (s+1, "#}"); 
    }
    while (*ln <= s) 
    { putc (**ln, outfile); (*ln)++;}
  }

  else if ( prefix("\\def\\vnotes#1\\elemskip", *ln) ) 
  { /* determine ninstr, nstaffs, staffs[i], nactive and active_staff[i] */
    int i = 0;
    int j;
    ninstr = 0;
    nstaffs = 0;
    s = *ln + 22; /* first parameter */
    while (true)
    { ninstr++; nstaffs++;
      staffs[ninstr] = 1;
      checkc (s, '#'); s++;
      checkn (s); s++;
      while (*s == '|')
      { staffs[ninstr]++; nstaffs++;
        s++;
        checkc (s, '#'); s++;
        checkn (s); s++; 
      }
      if (*s != '&') break;
      s++;
    }
    for (j=1; j <= ninstr; j++) active[j] = false;
    t = strstr (s, "@vnotes") + 7;
    if (t == NULL) error ("Can't parse \\def\\vnotes.");
    s = strpbrk(s, "#}"); /* may have \transpose etc. before # */
    while (*s == '#')
    {
      i++; 
      if (sscanf (s, "#%d", &j) != 1) 
        error("sscanf for argument number fails");
      active[j-1] = true;  /* parameters start at 2 */
      s = strpbrk (s+1, "#}"); 
    }
    while (*ln <= s) 
    { putc (**ln, outfile); (*ln)++;}
  }

  else if ( prefix("\\anotes", *ln) )
  { int i;
    analyze_notes (ln);
    for (i=1; i <= nstaffs; i++)
    { n_outstrings[i] = outstrings[i];
      *n_outstrings[i] = '\0';
      vspacing[i] = 0;  
      collective[i][0] = '\0'; 
      cspacing[i] = MAX_SPACING; 
      xtuplets[i] = 1;
    }
    xtuplet = 0;
    generate_notes (); 
  }

  else if ( prefix("\\def", *ln) )
  { t = *ln + strlen(*ln);
    while (*ln < t)
    { fputc (**ln, outfile); 
      (*ln)++;
    }
  }

  else  /* everything else */
  { t = strpbrk (*ln+1, "%\\");
    if (t == NULL || *t == '%') t = *ln + strlen (*ln);
    while (*ln < t) 
    { fputc (**ln, outfile); 
      (*ln)++;
    }
  }
}

void
process_line ()
{
  char  *ln; 
  ln = &line[0];
  while ( *ln != '\0') 
  {
    while (*ln == ' ') { ln++; fputc (' ', outfile); }
    if (*ln == '%') {
      fprintf (outfile, "%s", ln);
      return;
    }
    process_command (&ln);
  }     
}

void
process_score ()
/* process .tex file */
{
  int c; int i;
  lineno = 0;
  for (i=1; i<MAX_STAFFS; i++) 
    staffs[i] = 1; 
  spacing = MAX_SPACING;
  c = getc (infile);
  while ( c != EOF )
  {
    ungetc (c, infile);
    fgets(line, LINE_LEN, infile); 
    lineno++;
    process_line ();
    c = getc (infile);
  }     /* c == EOF  */
}     


int 
main (int argc, char *argv[])
{
  int c;
  char today[12];
  time_t mytime; 
# define NOPTS 4
  struct option longopts[NOPTS] =
  {  { "help", 0, NULL, 'h'},
     { "version", 0, NULL, 'v'},
     { "debug", 0, NULL, 'd'},
     { NULL, 0, NULL, 0}
  };
  
  time (&mytime);
  strftime (today, 11, "%Y-%m-%d", localtime (&mytime) );
  printf ("This is autosp, version %s.\n", version);
  c = getopt_long (argc, argv, "hvd", longopts, NULL);
  while (c != -1)
    {
      switch (c)
        {
        case 'h':
          usage ();
          puts ( "Please report bugs to rdt@cs.queensu.ca." );
          exit (0);
        case 'v':
          exit (0);
        case 'd':
          debug++; break;
        case '?':
          exit (EXIT_FAILURE);
        default:
          fprintf (outfile,"Function getopt returned character code 0%o.\n",
                  (unsigned int) c);
          exit (EXIT_FAILURE);
        }
      c = getopt_long (argc, argv, "hvd", longopts, NULL);
    }
  puts ( "Copyright (C) 2015  R. D. Tennent" );
  puts ( "School of Computing, Queen's University, rdt@cs.queensu.ca" );
  puts ( "License GNU GPL version 2 or later <http://gnu.org/licences/gpl.html>." );
  puts ( "There is NO WARRANTY, to the extent permitted by law." );
  puts ( "" );

  infilename[0] = '\0';
  infilename_n = infilename;
  if (optind < argc)
    {
      append (infilename, &infilename_n, argv[optind], sizeof (infilename));
      if (!suffix (".tex", infilename))
      append (infilename, &infilename_n, ".tex", sizeof (infilename));
    }
  else 
  {  usage();
     exit (EXIT_FAILURE);
  }
  optind++;
  outfilename[0] = '\0';
  outfilename_n = outfilename;
  if (optind < argc)  /* user-provided outfilename */
    {
      append (outfilename, &outfilename_n, argv[optind], sizeof (outfilename));
      if (!suffix (".tex", outfilename))
        append (outfilename, &outfilename_n, ".tex", sizeof (outfilename));
    }
  infile = fopen (infilename, "r");
  if (infile == NULL)
    {
      fprintf (outfile,"Can't open %s\n", infilename);
      exit (EXIT_FAILURE);
    }
  printf ("Reading from %s.", infilename);
  if (*outfilename == '\0')
  {
    outfile = stdout;
    printf (" Writing to stdout.\n");
  }
  else
  {
    outfile = fopen (outfilename, "w");
    if (outfile == NULL)
    {
      fprintf (outfile,"Can't open %s\n", outfilename);
      exit (EXIT_FAILURE);
    }
    printf (" Writing to %s.\n", outfilename);
  }
  puts ("");

  fprintf (outfile, "%% Generated on %s by autosp (%s).\n", today, version);
  process_score ();

  return 0;
}
