char version[12] = "2015-01-26";

/*  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.
 */

/*  fixprt - corrects note spacing in a single-staff 
 *           musixtex part
 */

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

# define LINE_LEN 1024

# define NNOTES "\\vnotes1.7\\elemskip"

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

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

FILE *infile, *outfile;

int debug = 0;
char line[LINE_LEN];    /* line of input                                        */
int staff = 1;          /* staff number                                    */
char *notes;            /* notes or other material for staff part               */
int spacing = 0;        /* 0 = not determined; 8 = eighth, 4 = quarter,         */
                        /* 2 = half, 1 = whole, -1 = breve                      */
int beaming = 0;        /* beam-note spacing                                    */
int new_beaming = 0;    /* spacing to be used *after* the next beam note        */
int restbars = 0;       /* number of whole-bar rests not yet output             */

void
warning (char msg[])    /* output warning message msg to stderr                 */
{
  fprintf (stderr, "Warning: %s\n", msg);
  return;
}

void
error (char msg[])      /* abort with stderr message msg                        */
{
  fprintf (stderr, "Error: %s\n", msg);
  exit (EXIT_FAILURE);
}

bool
prefix (const char *cs, const char *ct)
/*
   is string cs[] a prefix of ct[]?  
*/ 
{
  char *pcs = (char *) cs;
  char *pct = (char *) ct;
  while (*pcs != '\0')
    {
      if (*pcs != *pct) return false;
      pcs++; pct++;
    }
  return true;
}

bool
suffix (const char *cs, const char *ct)
/* is string cs[] a suffix of ct[]?  */
{
  char *pcs = (char *) cs;
  char *pct = (char *) ct + strlen (ct) - strlen (cs);
  while (*pcs != '\0')
    {
      if (*pct != *pcs)
        return false;
      pcs++;
      pct++;
    }
  return true;
}

size_t
append (char *dst, char **offset, const char *src, size_t n)
/*  Copies src to *offset and updates *offset accordingly (if possible). 
 *  Assumes *offset is dst if offset == NULL.
 *  Execution aborts if **offset != '\0'. The src string must be null-terminated. 
 *  Returns (original offset - dst) + strlen(src);  if >= n, the string was truncated.
 */
{
  const char *s = src;
  char *d;
  char *off_orig = ( offset != NULL ? *offset : dst );
  d = off_orig;
  if (*d != '\0')
  {
    char msg[LINE_LEN + 24] = {'\0'};
    char *msg_n = msg;
    append (msg, &msg_n, "\nNull character expected at offset specified for string ", sizeof (msg));
    append (msg, &msg_n, src, sizeof (msg));
    error (msg);
  }
  n = n - 1 - (off_orig - dst); /* number of available slots (leaving room for \0)  */
  while (n > 0 && *s != '\0')
    {
      *d = *s;                  /* copy non-null character */
      d++; s++; n--;
    }
  *d = '\0';                    /* null-terminate */
  if (offset != NULL)
    *offset = d;                /* update *offset (if possible) */
  while (*s != '\0')            /* traverse rest of src */
    s++;                        
  return (off_orig - dst) + (s - src);  /* length of the untruncated string */
}

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


char 
*analyze_notes (char **ln) {
/*
   skip initial TeX command, which may be unreliable; 
   then skip staff-1 ampersands or vertical bars; 
   then truncate tail and return a pointer to the 
   beginning of the staff commands.
   ln is moved to the token after \en
*/
  int i; char *s; char *t; char *tt;
  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);
    }
  }
  for (i=1; i<staff; i++)      /* skip staff-1 & or |       */
  { 
     s = strpbrk (s, "|&");
     if (s == NULL) error ("Can't find & or |.");
     s++;
  }
  t = strstr(s, "\\en"); 
  if (t == NULL) error ("Can't find \\en.");
  *ln = t+3;
  tt = strpbrk (s, "|&");
  if ( tt != NULL && tt < t) t = tt;
  *t = '\0';
  if (debug > 0)
    printf ("After analyze_notes notes=%s\n", s);
  return s;
}


void
output_notes (char *t) 
{ /* outputs text from *notes to *t (except for 
     commands \sk \hsk \noteskip= \multnoteskip);
     then updates notes.
  */
  char *s = notes;
  if (debug > 0)
    printf ("\nBefore output_notes: notes=%s\n", s);
  while (s < t) 
  {
    /* discard  commands \sk, \hsk, \noteskip= ..., \multnoteskip  */ 
    if ( prefix("\\sk", s)  ||
         prefix("\\hsk", s)  ||
         prefix("\\noteskip", s) ||
         prefix("\\multnoteskip", s ) )
    { 
      s = strchr (s+1, '\\');
      if (s == NULL) break;
    }
    else
    {
      char *tt;
      tt = strchr (s+1, '\\');
      if (tt == NULL) tt = t;
      while (s < tt)
      {  if (*s == '*')  /* equivalent to \sk */
         { s++; continue; }
         putc (*s, outfile); 
         s++; 
      }
    }
  }
  notes = t;
  if (debug > 0) 
    printf ("\nAfter output_notes, notes=%s\n", notes);
}

void
output_rests (bool output_bar)
{ /* outputs multi-bar rest and, possibly, a deferred \bar command */
  switch (restbars)
  { case 0: return; 
    case 1: 
      fprintf ( outfile, "\\NOTEs\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerpause\\en}%%\n" );
      break;
    case 2: 
      fprintf ( outfile, "\\NOTesp\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap2}}\\centerbar{\\PAuse}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno1%%\n" );
      break;
    case 3:
      fprintf ( outfile, "\\NOTes\\hqsk\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap3}}\\centerbar{\\PAuse\\off{2.5\\elemskip}\\pause}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno2%%\n" );
      break;
    case 4:
      fprintf ( outfile, "\\NOtesp\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap4}}\\centerbar{\\PAUSe}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno3%%\n" );
      break;
    case 5:
      fprintf ( outfile, "\\NOTes\\hqsk\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap5}}\\centerbar{\\PAUSe\\off{2.5\\elemskip}\\pause}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno4%%\n" );
      break;
    case 6:
      fprintf ( outfile, "\\NOTes\\hqsk\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap6}}\\centerbar{\\PAUSe\\off{2.5\\elemskip}\\PAuse}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno5%%\n" );
      break;
    case 7:
      fprintf ( outfile, "\\NOTEs\\hqsk\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap7}}\\centerbar{\\PAUSe\\off{2.5\\elemskip}\\PAuse\\off{2.5\\elemskip}\\pause}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno6%%\n" );
      break;
    case 8:
      fprintf ( outfile, "\\NOTes\\hqsk\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap8}}\\centerbar{\\PAUSe\\off{2.5\\elemskip}\\PAUSe}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno7%%\n" );
      break;
    case 9:
      fprintf ( outfile, "\\NOTEs\\hqsk\\sk\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap9}}\\centerbar{\\PAUSe\\off{2.5\\elemskip}\\PAUSe\\off{2.5\\elemskip}\\pause}\\en}%%\n" );
      fprintf ( outfile, "\\advance\\barno8%%\n" );
      break;
    default:  
      fprintf ( outfile, "\\NOTEs\\hqsk\\Hpause4{0.83}\\en\n" );
      fprintf ( outfile, "\\def\\atnextbar{\\znotes\\centerbar{\\zcharnote{9}{\\meterfont\\lrlap{%d}}}\\en}%%\n", restbars );
      fprintf ( outfile, "\\advance\\barno%d%%\n", restbars-1 );
  }
  if (output_bar) 
  { 
    fprintf ( outfile, "\\bar\n"); 
  }
  restbars = 0; 
}

char *spacing_note (char *s, int new_spacing)
{ /* outputs from notes* to just after note at *s,
     if necessary, preceded by a new notes command;
     then updates notes.
  */
  char *t;
  if (debug > 0)
    printf ("\nspacing_note: spacing=%d new_spacing=%d s=%s\n", spacing, new_spacing, s);
  if (spacing != 0 && spacing != new_spacing) 
  /* terminate current notes command  */
    fprintf (outfile,"\\en\n");
  if (spacing != new_spacing)
  {
    output_rests (true);
    switch (new_spacing)
    { /* start a new notes command  */
      case 64: fprintf (outfile,"%s", NNOTES); break;
      case 32: fprintf (outfile,"%s", NNOTES); break;
      case 16: fprintf (outfile,"\\notes"); break;
      case 8:  fprintf (outfile,"\\Notes"); break;
      case 4:  fprintf (outfile,"\\NOtes"); break;
      case 2:  fprintf (outfile,"\\NOTes"); break;
      case 1:  fprintf (outfile,"\\NOTEs"); break;
      case -1: fprintf (outfile,"\\NOTES"); break;
      default: error ("Spacing value not recognized");
    }
    spacing = new_spacing;
  }
  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen(s);
  output_notes (t);
  if (debug > 0)
    printf ("\nAfter spacing_note: spacing=%d notes=%s\n", spacing, notes);
  return notes;
}

char *semiautomatic_beam (char *s)
{
  char *t;
  if ( prefix ("\\Dqbb", s) 
    || prefix ("\\Tqbb", s)
    || prefix ("\\Qqbb", s) )
    beaming = 16; 
  else 
  if ( prefix ("\\Dqb", s)
    || prefix ("\\Tqb", s)
    || prefix ("\\Qqb", s) )
    beaming = 8; 
  if (beaming > spacing && spacing > 0)
    fprintf (outfile, "\\en\n");
  if (beaming > spacing)
  {
    output_rests (true);
    switch (beaming)
    {
      case 16: fprintf (outfile,"\\notes"); break;
      case 8:  fprintf (outfile,"\\Notes"); break;
    }
    spacing = beaming;  
  }
  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen(s);
  output_notes (t);
  return t;
}

char *beam_initiation (char *s) 
{ /* set beaming, but don't output anything */
  char *t;
  if ( prefix ("\\ibbbb", s)
    || prefix ("\\Ibbbb", s)
    || prefix ("\\nbbbb", s) )
    beaming = 64; 
  else if ( prefix ("\\ibbb", s) 
    || prefix ("\\Ibbb", s)
    || prefix ("\\nbbb", s) ) 
    beaming = 32; 
  else 
  if ( prefix ("\\ibb", s) 
    || prefix ("\\Ibb", s)
    || prefix ("\\nbb", s) )
    beaming = 16; 
  else 
  if ( prefix ("\\ib", s) 
    || prefix ("\\Ib", s) )
    beaming = 8; 
  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen(s);
  return t;
}

char *beam_note (char *s)  /* \qb... */
{ char *t;
  if (debug > 0) 
    printf ("\nIn beam_note: spacing=%d beaming=%d new_beaming=%d\n",  
                             spacing,   beaming,   new_beaming);
  if (beaming > spacing && spacing > 0)
    fprintf (outfile, "\\en\n");
  if (beaming > spacing)
  {
    output_rests (true);
    switch (beaming)
    {
      case 64: fprintf (outfile,"%s", NNOTES); break;
      case 32: fprintf (outfile,"%s", NNOTES); break;
      case 16: fprintf (outfile,"\\notes"); break;
      case 8:  fprintf (outfile,"\\Notes"); break;
    }
    spacing = beaming;  
  }
  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen (s);
  output_notes (t);
  if (new_beaming != 0)  /* set by preceding \tb... */
  {
    beaming = new_beaming; new_beaming = 0;
    if (*notes != '\0' && beaming != spacing)
    { fprintf (outfile,"\\en\n");
      switch (beaming)
      {
        case 64: fprintf (outfile,"%s", NNOTES); break;
        case 32: fprintf (outfile,"%s", NNOTES); break;
        case 16: fprintf (outfile,"\\notes"); break;
        case 8:  fprintf (outfile,"\\Notes"); break;
      }
      spacing = beaming;
   }
  }
  return t;
}

char *beam_termination (char *s)  /* \tb... */
{ char *t;
  if (debug > 0)
    printf ("\nIn beam_termination: spacing=%d beaming=%d new_beaming=%d\n",  
                                    spacing,   beaming,   new_beaming);
  if (spacing == 0)
  {
    output_rests (true);
    switch (beaming)
    {
      case 64: fprintf (outfile,"%s", NNOTES); break;
      case 32: fprintf (outfile,"%s", NNOTES); break;
      case 16: fprintf (outfile,"\\notes"); break;
      case 8:  fprintf (outfile,"\\Notes"); break;
    }
    spacing = beaming;  
  }
  if ( prefix ("\\tbbbb", s) )
    new_beaming = 32;
  else if ( prefix ("\\tbbb", s) )
    new_beaming = 16;
  else if ( prefix ("\\tbb", s) ) 
    new_beaming = 8;
  else if ( prefix ("\\tb", s) )
  { new_beaming = 0; }

  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen(s);
  return t;
}

char *beam_completion (char *s)  /* \tq... */
{ char *t;
  if (debug > 0)
    printf ("\nIn beam_completion: spacing=%d beaming=%d new_beaming=%d\n",  
                                   spacing,   beaming,   new_beaming);
  if (spacing == 0)
  {
    output_rests (true);
    switch (beaming)
    {
      case 64: fprintf (outfile,"%s", NNOTES); break;
      case 32: fprintf (outfile,"%s", NNOTES); break;
      case 16: fprintf (outfile,"\\notes"); break;
      case 8:  fprintf (outfile,"\\Notes"); break;
    }
    spacing = beaming;  
  }
  if (beaming > spacing && spacing > 0)
    fprintf (outfile, "\\en\n");
  if (beaming > spacing)
  {
    output_rests (true);
    switch (beaming)
    {
      case 64: fprintf (outfile,"%s", NNOTES); break;
      case 32: fprintf (outfile,"%s", NNOTES); break;
      case 16: fprintf (outfile,"\\notes"); break;
      case 8:  fprintf (outfile,"\\Notes"); break;
    }
    spacing = beaming;  
  }
  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen(s);
  output_notes (t);
  new_beaming = 0; spacing = 8;
  return t;
}

char *nonspacing_beam_termination (char *s)   /*  \ztq...  */
{ char *t;
  if (spacing == 0)
  {
    output_rests (true);
    fprintf (outfile,"\\znotes"); 
    spacing = beaming;
  }
  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen(s);
  output_notes (t);
  new_beaming = 0;
  return t;
}

char  *tiny (char *s) 
/* \tinynotesize  */
{
  char *t;
  if (spacing != 0 && spacing != 32 ) fprintf (outfile,"\\en\n");
  if (spacing != 32) 
  {
    fprintf (outfile,"\\vnotes1.4\\elemskip");
  }
  spacing = 32;
  t = s + strlen(s);
  output_notes (t);
  return t;
}

char *simple (char *s)
/* detect and correct simple slurs and ties over > 1 note  */
{ char *t;
  t = strchr (s+1, '\\');
  if (t == NULL) t = s + strlen(s);
  if (*(t-1) != '1')
  {
    char msg[LINE_LEN];
    sprintf (msg, "simple slur or tie with n == %c changed to 1.", *(t-1));
    warning (msg);
    *(t-1) = '1';
  }
  return t;
}

void
synthesize_notes (void){
/*
   generate well-spaced notes
*/
  char *s; 
  s = notes;
  while (true) 
  { 
    if (debug > 0)
      printf ("\nIn synthesize s=%s\n", s);
    if (*s == '\0') 
    {      
      if (spacing == 0) 
      {  if (*notes == '\0') return; /* to avoid \znotes\en */
         fprintf (outfile,"\\znotes");
      }
      output_notes (s);
      fprintf (outfile,"\\en"); 
      spacing = 0; 
 
      return; 
    }

    if ( prefix ("\\hs", s) 
       && !prefix ("\\hsk", s) ) 
       s = spacing_note (s, 32);

    else if ( prefix ("\\h", s) 
       && !prefix ("\\hqsk", s) 
       && !prefix ("\\hroff", s) 
       && !prefix ("\\hloff", s) )
       s = spacing_note (s, 2); 

    else if ( prefix ("\\qs", s)
       && !prefix ("\\qsk", s) ) 
       s = spacing_note (s, 16); 

    else if ( prefix ("\\q", s)
       && !prefix ("\\qb", s) 
       && !prefix ("\\qsk", s) 
       && !prefix ("\\qqsk", s) ) 
       s = spacing_note (s, 4);

    else if ( prefix ("\\ccc", s) )
       s = spacing_note (s, 32);

    else if ( prefix ("\\cc", s) 
       && !prefix ("\\ccharnote", s) ) 
       s = spacing_note (s, 16); 

    else if ( prefix ("\\ds", s ) 
       && !prefix ("\\dsh", s) ) 
       s = spacing_note (s, 8);

    else if ( prefix ("\\c", s) 
       && !prefix ("\\cna", s)  
       && !prefix ("\\csh", s)  
       && !prefix ("\\cfl", s) 
       && !prefix ("\\curve", s) 
       && !prefix ("\\ccharnote", s) ) 
       s = spacing_note (s, 8);

    else if ( prefix ("\\wh", s)
      || prefix ("\\pa", s) ) 
      s = spacing_note (s, 1);

    else if ( prefix ("\\Hpause", s) ) 
       s = spacing_note (s, 2);

    else if ( prefix("\\ib",s) 
      || prefix("\\Ib",s) 
      || prefix("\\nb",s) ) 
      s = beam_initiation (s); 

    else if ( prefix("\\Dq",s) 
      || prefix("\\Tq",s) 
      || prefix("\\Qq",s) )
      s = semiautomatic_beam (s); 

    else if ( prefix("\\qb", s) ) 
      s = beam_note (s); 

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

    else if ( prefix("\\tq", s) ) 
      s = beam_completion (s);

    else if ( prefix("\\ztq", s) ) 
       s = nonspacing_beam_termination (s); 

    else if ( prefix("\\tinynotesize", s)) 
      s = tiny (s);

    else if ( prefix("\\slur", s)
      || prefix("\\tie", s)
      || prefix("\\sslur", s)
      || prefix("\\bslur", s) )
      s = simple (s);
    
    else if ( prefix("\\zchar", s) 
           || prefix("\\lchar", s)
           || prefix("\\cchar", s) )
    { char *t;
      s = strchr (s+1, '}'); /* first }  */
      s = strchr (s+1, '}'); /* second } */
      t = strchr(s+1,'\\');
      if (t == NULL) t = s + strlen(s); 
      s = t;
    }
      
    else /* non-spacing; skip to next command */
    { char *t; 
      t = strchr(s+1,'\\'); 
      if (t == NULL) t = s + strlen(s); 
      s = t; 
    }
  }
}

void
process_command (char **ln)
{ char *s, *t;

  if ( prefix("\\def\\vnotes#1\\elemskip", *ln) ) 
  { /* determine staff */
    s = strstr (*ln, "@vnotes") + 7;
    t = strchr(s, '#'); /* may have \transpose... before # */
    if (sscanf (t, "#%d", &staff) != 1) error("sscanf for staff fails");
    staff--; 
    fprintf (outfile,"\\def\\vnotes#1\\elemskip#2\\en{\\noteskip#1\\@l@mskip\\@vnotes");
    while (s != t)  
    { putc(*s, outfile); s++; }
    fprintf (outfile,"#2\\enotes}");
    *ln = strchr (*ln, '}') ;
    (*ln)++;
  }

  else if ( prefix("\\TransformNotes", *ln) ) 
  { /* determine staff */
    s = strchr (*ln, '}');
    if (s == NULL) error ("Can't parse \\TransformNotes.");
    s = strchr (s+1, '{');
    if (s == NULL) error ("Can't parse \\TransformNotes.");
    s++;
    t = strchr(s, '#'); /* may have \transpose... before # */
    if (sscanf (t, "#%d", &staff) != 1) error("sscanf for staff fails");
    staff--; 
    fprintf (outfile,"\\TransformNotes{#2}{");
    while (s != t)
    { putc(*s, outfile); s++; }
    fprintf (outfile,"#2}");
    *ln = strchr (t, '}') ;
    if (*ln == NULL) error ("Can't find '}' after \\TransformNotes.\n");
    (*ln)++;
  }

  else if ( prefix("\\nnotes", *ln) ||
            prefix("\\notes", *ln) || 
            prefix("\\Notes", *ln) ||
            prefix("\\NOtes", *ln) ||
            prefix("\\NOTes", *ln) ||
            prefix("\\NOTEs", *ln) )
  { /*  spaced notes */
    char *save_notes;
    notes = analyze_notes (ln);
    save_notes = notes;
    synthesize_notes ();
    t = strpbrk (*ln, "%\\\n");
    *ln = t;
    if (notes == save_notes) 
      (*ln)++;  /* avoids blank line if no notes  */
  }

  else if ( prefix("\\vnotes", *ln) )
  { 
    char *s; char *t;
    s = *ln+7;
    t = strchr ( s, '\\');
    *ln = t; /* skip to \elemskip */
    notes = analyze_notes (ln);
    if ( strstr (notes, "\\tinynotesize") != NULL )
    { /* appoggiatura note  */
      output_rests (true);
      fprintf (outfile,"\\vnotes");
      while (s!=t) 
      { putc(*s, outfile); s++; } 
      fprintf (outfile,"\\elemskip%s\\en", notes);
      t = strpbrk (*ln, "%\\\n");
      if (t == NULL) t = *ln + strlen (*ln);
      *ln = t;
    }
    else
    { char *save_notes;
      save_notes = notes;
      synthesize_notes ();
      t = strpbrk (*ln, "%\\\n");
      *ln = t;
      if (notes == save_notes) 
        (*ln)++;  /* avoids blank line if no notes  */
    }
  }

  else if ( prefix("\\znotes", *ln) )
  { /*  non-spacing material */
    notes = analyze_notes (ln);
    fprintf (outfile,"\\znotes%s\\en", notes);
    t = strpbrk (*ln, "%\\\n");
    if (t == NULL) t = *ln + strlen (*ln);
    *ln = t;
  }

  else if ( prefix("\\def\\atnextbar{\\znotes", *ln))
  { /*  whole-bar or multi-bar rest? */
    *ln = *ln + 15;
    notes = analyze_notes(ln);
    if (strstr (notes, "\\centerpause") != NULL )
    { restbars++;   /* defer output until after a   */
                    /* possible multi-bar rest      */
    }
    else
    {
      if (*notes != '\0') 
      { fprintf (outfile,"\\def\\atnextbar{\\znotes%s\\en}", notes);}
    }
    t = strchr (*ln, '}');
    if (t == NULL) error ("Can't find }.");
    *ln = t+1;
    t = strchr (*ln, '\\');
    if (t == NULL ) t = *ln + strlen(*ln);
    *ln = t;
  }

  else if ( (prefix("\\bar", *ln) && !prefix("\\barno", *ln) ) 
         || prefix("\\doublebar", *ln)
         || prefix("\\ala", *ln) )
       { 
         if ( restbars > 0 ) 
         { /* defer output till next \Notes bar */ 
           t = strpbrk (*ln+1, "%\\");
           if (t == NULL) t = *ln + strlen (*ln); 
         }
         else 
         {
           if ( prefix("\\doublebar", *ln) )
             fprintf (outfile, "\\doublebar");
           else
             fprintf (outfile, "\\bar");
           t = strpbrk (*ln+1, "%\\\n");
           if (t == NULL) t = *ln + strlen (*ln); 
         }
         *ln = t;
       }

  else if ( prefix("\\Endpiece", *ln) 
        ||  prefix("\\endpiece", *ln) )
  {  
    output_rests (false);
    fprintf (outfile,"%s", *ln);
    *ln = *ln + strlen (*ln);
  }

  else if ( prefix("\\instrumentnumber", *ln) )
  { fprintf (outfile, "\\instrumentnumber1\n");
    t = strpbrk (*ln+1, "%\\");
    if (t == NULL) 
    { *ln = *ln + strlen (*ln); fprintf (outfile, "\n"); return; }
    *ln = t;
  }

  else if ( prefix("\\startpiece", *ln) )
  { fprintf (outfile, "%s", "\\setstaffs11\n\\nostartrule\n\\startpiece");
    t = strpbrk (*ln+1, "%\\\n");
    if (t == NULL) t = *ln + strlen (*ln);
    *ln = t;
  }

  else if ( prefix("\\mulooseness", *ln) 
         || prefix("\\eject", *ln)
         || prefix("\\linegoal", *ln)  
         || prefix("\\song", *ln)
         || prefix("\\group", *ln) 
         || prefix("\\akkoladen", *ln) )
  { fputc ('%', outfile);
    t = strpbrk (*ln+1, "%\\\n");
    if (t == NULL) 
    { fprintf (outfile, "%s", *ln); return; }
    while (*ln < t) { fputc (**ln, outfile); (*ln)++; } 
    fputc ('\n', outfile);
  }

  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);
  }     /* *ln == '\0'  */
}

void
process_score ()
/* process .tex file */
{
  int c;
  c = getc (infile);
  while ( c != EOF )
  {
    ungetc (c, infile);
    fgets(line, LINE_LEN, infile); 
    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 fixprt, 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) 2014-15  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 fixprt (%s).\n", today, version);
  process_score ();

  return 0;
}
