/* 
 * parseabc.c - code to parse an abc file. This file is used by the
 * following 3 programs :
 * abc2midi - program to convert abc files to MIDI files.
 * abc2abc  - program to manipulate abc files.
 * yaps     - program to convert abc to PostScript music files.
 * Copyright (C) 1999 James Allwright
 * e-mail: J.R.Allwright@westminster.ac.uk
 *
 * 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
 *
 */


/* Macintosh port 30th July 1996 */
/* DropShell integration   27th Jan  1997 */
/* Wil Macaulay (wil@syndesis.com) */


#define TAB 9
#include "abc.h"
#include "parseabc.h"
#include <stdio.h>
#include <stdlib.h>

#define SIZE_ABBREVIATIONS ('Z' - 'H' + 1)

#ifdef _MSC_VER
#define ANSILIBS
#define casecmp stricmp
#else
#define casecmp strcasecmp
#endif
#define	stringcmp	strcmp

#ifdef __MWERKS__
#define __MACINTOSH__ 1
#endif /* __MWERKS__ */

#ifdef __MACINTOSH__
#define main macabc2midi_main
#define STRCHR
#endif /* __MACINTOSH__ */

/* define USE_INDEX if your C libraries have index() instead of strchr() */
#ifdef USE_INDEX
#define strchr index
#endif

#ifdef ANSILIBS
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#else
extern char *malloc ();
extern char *strchr ();
#endif

int lineno;
int parsing_started = 0;
int parsing, slur;
int inhead, inbody;
int parserinchord;
int ingrace = 0;
int chorddecorators[DECSIZE];
char decorations[] = ".MLRH~Tuv";
char *abbreviation[SIZE_ABBREVIATIONS];

int voicecodes = 0;
char voicecode[16][30];		/*for interpreting V: string */

int decorators_passback[DECSIZE];
/* this global array is linked as an external to store.c and 
 * yaps.tree.c and is used to pass back decorator information
 * from event_instruction to parsenote.
*/

char inputline[512];		/* [SS] 2011-06-07 2012-11-22 */
char *linestart;		/* [SS] 2011-07-18 */
int lineposition;		/* [SS] 2011-07-18 */
char timesigstring[16];		/* [SS] 2011-08-19 links with stresspat.c */

int nokey = 0;			/* K: none was encountered */
int chord_n, chord_m;		/* for event_chordoff */
int fileline_number = 1;
int intune = 1;
int inchordflag;		/* [SS] 2012-03-30 */
struct fraction setmicrotone;	/* [SS] 2014-01-07 */
int microtone;			/* [SS] 2014-01-19 */


extern programname fileprogram;
int oldchordconvention = 0;

char *mode[10] = { "maj", "min", "m",
  "aeo", "loc", "ion", "dor", "phr", "lyd", "mix"
};

int modeshift[10] = { 0, -3, -3,
  -3, -5, 0, -2, -4, 1, -1
};

int modeminor[10] = { 0, 1, 1,
  1, 0, 0, 0, 0, 0, 0
};
int modekeyshift[10] = { 0, 5, 5, 5, 6, 0, 1, 2, 3, 4 };

int *
checkmalloc (bytes)
/* malloc with error checking */
     int bytes;
{
  int *p;

  p = (int *) malloc (bytes);
  if (p == NULL)
    {
      printf ("Out of memory error - malloc failed!\n");
      exit (0);
    };
  return (p);
}

char *
addstring (s)
/* create space for string and store it in memory */
     char *s;
{
  char *p;

  p = (char *) checkmalloc (strlen (s) + 1);
  strcpy (p, s);
  return (p);
}

void
initvstring (s)
     struct vstring *s;
/* initialize vstring (variable length string data structure) */
{
  s->len = 0;
  s->limit = 40;
  s->st = (char *) checkmalloc (s->limit + 1);
  *(s->st) = '\0';
}

void
extendvstring (s)
     struct vstring *s;
/* doubles character space available in string */
{
  char *p;

  if (s->limit > 0)
    {
      s->limit = s->limit * 2;
      p = (char *) checkmalloc (s->limit + 1);
      strcpy (p, s->st);
      free (s->st);
      s->st = p;
    }
  else
    {
      initvstring (s);
    };
}

void
addch (ch, s)
     char ch;
     struct vstring *s;
/* appends character to vstring structure */
{
  if (s->len >= s->limit)
    {
      extendvstring (s);
    };
  *(s->st + s->len) = ch;
  *(s->st + (s->len) + 1) = '\0';
  s->len = (s->len) + 1;
}

void
addtext (text, s)
     char *text;
     struct vstring *s;
/* appends a string to vstring data structure */
{
  int newlen;

  newlen = s->len + strlen (text);
  while (newlen >= s->limit)
    {
      extendvstring (s);
    };
  strcpy (s->st + s->len, text);
  s->len = newlen;
}

void
clearvstring (s)
     struct vstring *s;
/* set string to empty */
/* does not deallocate memory ! */
{
  *(s->st) = '\0';
  s->len = 0;
}

void
freevstring (s)
     struct vstring *s;
/* deallocates memory allocated for string */
{
  if (s->st != NULL)
    {
      free (s->st);
      s->st = NULL;
    };
  s->len = 0;
  s->limit = 0;
}

void
parseron ()
{
  parsing = 1;
  slur = 0;
  parsing_started = 1;
}

void
parseroff ()
{
  parsing = 0;
  slur = 0;
}

int
getarg (option, argc, argv)
/* look for argument 'option' in command line */
     char *option;
     char *argv[];
     int argc;
{
  int j, place;

  place = -1;
  for (j = 0; j < argc; j++)
    {
      if (strcmp (option, argv[j]) == 0)
	{
	  place = j + 1;
	};
    };
  return (place);
}

void
skipspace (p)
     char **p;
{
  /* skip space and tab */
  while (((int) **p == ' ') || ((int) **p == TAB))
    *p = *p + 1;
}

void
skiptospace (p)
     char **p;
{
  while (((int) **p != ' ') && ((int) **p != TAB) && (int) **p != '\0')
    *p = *p + 1;
}


int
isnumberp (p)
     char **p;
/* returns 1 if positive number, returns 0 if not a positive number */
/* ie -4 or 1A both return 0. This function is needed to get the    */
/* voice number.                                                    */
{
  char **c;
  c = p;
  while (((int) **c != ' ') && ((int) **c != TAB) && (int) **c != '\0')
    {
      if (((int) *c >= '0') && ((int) *c <= '9'))
	*c = *c + 1;
      else
	return 0;
    }
  return 1;
}



int
readnumf (num)
     char *num;
/* read integer from string without advancing character pointer */
{
  int t;
  char *p;

  p = num;
  if (!isdigit (*p))
    {
      event_error ("Missing Number");
    };
  t = 0;
  while (((int) *p >= '0') && ((int) *p <= '9'))
    {
      t = t * 10 + (int) *p - '0';
      p = p + 1;
    };
  return (t);
}

int
readsnumf (s)
     char *s;
/* reads signed integer from string without advancing character pointer */
{
  char *p;

  p = s;
  if (*p == '-')
    {
      p = p + 1;
      skipspace (&p);
      return (-readnumf (p));
    }
  else
    {
      return (readnumf (p));
    }
}

int
readnump (p)
     char **p;
/* read integer from string and advance character pointer */
{
  int t;

  t = 0;
  while (((int) **p >= '0') && ((int) **p <= '9'))
    {
      t = t * 10 + (int) **p - '0';
      *p = *p + 1;
    };
  return (t);
}

int
readsnump (p)
     char **p;
/* reads signed integer from string and advance character pointer */
{
  if (**p == '-')
    {
      *p = *p + 1;
      skipspace (p);
      return (-readnump (p));
    }
  else
    {
      return (readnump (p));
    }
}

void
readsig (a, b, sig)
     int *a, *b;
     char **sig;
/* read time signature (meter) from M: field */
{
  int t;

  /* [SS] 2012-08-08  cut time (C| or c|) is 2/2 not 4/4 */
  if ((*(*sig + 1) == '|') && ((**sig == 'C') || (**sig == 'c')))
    {
      *a = 2;
      *b = 2;
      return;
    }

  if ((**sig == 'C') || (**sig == 'c'))
    {
      *a = 4;
      *b = 4;
      return;
    };
  *a = readnump (sig);
  if ((int) **sig != '/')
    {
      event_error ("Missing / ");
    }
  else
    {
      *sig = *sig + 1;
    };
  *b = readnump (sig);
  if ((*a == 0) || (*b == 0))
    {
      event_error ("Expecting fraction in form A/B");
    }
  else
    {
      t = *b;
      while (t > 1)
	{
	  if (t % 2 != 0)
	    {
	      event_error ("divisor must be a power of 2");
	      t = 1;
	      *b = 0;
	    }
	  else
	    {
	      t = t / 2;
	    };
	};
    };
}

void
readlen (a, b, p)
     int *a, *b;
     char **p;
/* read length part of a note and advance character pointer */
{
  int t;

  *a = readnump (p);
  if (*a == 0)
    {
      *a = 1;
    };
  *b = 1;
  if (**p == '/')
    {
      *p = *p + 1;
      *b = readnump (p);
      if (*b == 0)
	{
	  *b = 2;
	  while (**p == '/')
	    {
	      *b = *b * 2;
	      *p = *p + 1;
	    };
	};
    };
  t = *b;
  while (t > 1)
    {
      if (t % 2 != 0)
	{
	  event_warning ("divisor not a power of 2");
	  t = 1;
	}
      else
	{
	  t = t / 2;
	};
    };
}

void
readlen_nocheck (a, b, p)
     int *a, *b;
     char **p;
/* read length part of a note and advance character pointer */
{
  int t;

  *a = readnump (p);
  if (*a == 0)
    {
      *a = 1;
    };
  *b = 1;
  if (**p == '/')
    {
      *p = *p + 1;
      *b = readnump (p);
      if (*b == 0)
	{
	  *b = 2;
	  while (**p == '/')
	    {
	      *b = *b * 2;
	      *p = *p + 1;
	    };
	};
    };
  t = *b;
  while (t > 1)
    {
      if (t % 2 != 0)
	{
	  /*event_warning("divisor not a power of 2"); */
	  t = 1;
	}
      else
	{
	  t = t / 2;
	};
    };
}

int
ismicrotone (p, dir)
     char **p;
     int dir;
{
  int a, b;
  readlen_nocheck (&a, &b, p);
  if (b != 1)
    {
      event_microtone (dir, a, b);
      return 1;
    }
  setmicrotone.num = 0;
  setmicrotone.denom = 0;
  return 0;
}




int
isclef (s, gotoctave, octave, strict)
     char *s;
     int *gotoctave, *octave;
     int strict;
/* part of K: parsing - looks for a clef in K: field                 */
/* format is K:string where string is treble, bass, baritone, tenor, */
/* alto, mezzo, soprano or K:clef=arbitrary                          */
{
  int gotclef;

  s = s;
  gotclef = 0;
  if (strncmp (s, "bass", 4) == 0)
    {
      gotclef = 1;
    };
  if (strncmp (s, "treble", 6) == 0)
    {
      gotclef = 1;
      *gotoctave = 1;		/* [SS] 2011-12-19 */
      *octave = 0;
      if (fileprogram == ABC2MIDI && *gotoctave != 1 && *octave != 1)
	event_warning ("clef= is overriding octave= setting");
    };
  if (strncmp (s, "treble+8", 8) == 0)
    {
      gotclef = 1;
      if (fileprogram == ABC2MIDI && *gotoctave != 1 && *octave != 1)
	event_warning ("clef= is overriding octave= setting");
      *gotoctave = 1;
      *octave = 1;
    };
  if (strncmp (s, "treble-8", 8) == 0)
    {
      gotclef = 1;
      if (fileprogram == ABC2MIDI && *gotoctave == 1 && *octave != -1)
	event_warning ("clef= is overriding octave= setting");
      *gotoctave = 1;
      *octave = -1;
    };
  if (strncmp (s, "baritone", 8) == 0)
    {
      gotclef = 1;
    };
  if (strncmp (s, "tenor", 5) == 0)
    {
      gotclef = 1;
    };
  if (strncmp (s, "tenor-8", 7) == 0)
    {
      gotclef = 1;
      if (fileprogram == ABC2MIDI && *gotoctave == 1 && *octave != -1)
	event_warning ("clef= is overriding octave= setting");
      *gotoctave = 1;
      *octave = -1;
    };
  if (strncmp (s, "alto", 4) == 0)
    {
      gotclef = 1;
    };
  if (strncmp (s, "mezzo", 5) == 0)
    {
      gotclef = 1;
    };
  if (strncmp (s, "soprano", 7) == 0)
    {
      gotclef = 1;
    };
/*
 * only clef=F or clef=f is allowed, or else
 * we get a conflict with the key signature
 * indication K:F
*/

  if (strncmp (s, "f", 1) == 0 && strict == 0)
    {
      gotclef = 1;
    }
  if (strncmp (s, "F", 1) == 0 && strict == 0)
    {
      gotclef = 1;
    }
  if (strncmp (s, "g", 1) == 0 && strict == 0)
    {
      gotclef = 1;
    }
  if (strncmp (s, "G", 1) == 0 && strict == 0)
    {
      gotclef = 1;
    }
  if (strncmp (s, "perc", 1) == 0 && strict == 0)
    {
      gotclef = 1;
    }				/* [SS] 2011-04-17 */

  if (!strict && !gotclef)
    {
      gotclef = 1;
      event_warning ("cannot recognize clef indication");
    }

  return (gotclef);
}

char *
readword (word, s)
/* part of parsekey, extracts word from input line */
/* besides the space, the symbols _, ^, and = are used */
/* as separators in order to handle key signature modifiers. */
/* [SS] 2010-05-24 */
     char word[];
     char *s;
{
  char *p;
  int i;

  p = s;
  i = 0;
  while ((*p != '\0') && (*p != ' ') && (*p != '\t') && ((i == 0) ||
							 ((*p != '=')
							  && (*p != '^')
							  && (*p != '_'))))
    {
      if (i < 29)
	{
	  word[i] = *p;
	  i = i + 1;
	};
      p = p + 1;
    };
  word[i] = '\0';
  return (p);
}

void
lcase (s)
/* convert word to lower case */
     char *s;
{
  char *p;

  p = s;
  while (*p != '\0')
    {
      if (isupper (*p))
	{
	  *p = *p + 'a' - 'A';
	};
      p = p + 1;
    };
}


void
init_voicecode ()
{
  int i;
  for (i = 0; i < 16; i++)
    voicecode[i][0] = 0;
  voicecodes = 0;
}

void
print_voicecodes ()
{
  int i;
  if (voicecodes == 0)
    return;
  printf ("voice mapping:\n");
  for (i = 0; i < voicecodes; i++)
    {
      if (i % 4 == 3)
	printf ("\n");
      printf ("%s  %d   ", voicecode[i], i + 1);
    }
  printf ("\n");
}

int
interpret_voicestring (char *s)
{
/* if V: is followed  by a string instead of a number
 * we check to see if we have encountered this string
 * before. We assign the number associated with this
 * string and add it to voicecode if it was not encountered
 * before. If more than 16 distinct strings were encountered
 * we report an error -1.
*/
  int i;
  char code[32];
  char msg[80];			/* [PHDM] 2012-11-22 */
  char *c;
  c = readword (code, s);

/* [PHDM] 2012-11-22 */
  if (*c != '\0' && *c != ' ' && *c != ']')
    {
      sprintf (msg, "invalid character `%c' in Voice ID", *c);
      event_error (msg);
    }
/* [PHDM] 2012-11-22 */

  if (code[0] == '\0')
    return 0;
  if (voicecodes == 0)
    {
      strcpy (voicecode[voicecodes], code);
      voicecodes++;
      return voicecodes;
    }
  for (i = 0; i < voicecodes; i++)
    if (stringcmp (code, voicecode[i]) == 0)
      return (i + 1);
  if ((voicecodes + 1) > 15)
    return -1;
  strcpy (voicecode[voicecodes], code);
  voicecodes++;
  return voicecodes;
}

/* The following three functions parseclefs, parsetranspose,
 * parseoctave are used to parse the K: field which not
 * only specifies the key signature but also other descriptors
 * used for producing a midi file or postscript file.
 *
 * The char* word contains the particular token that
 * is being interpreted. If the token can be understood,
 * other parameters are extracted from char ** s and
 * s is advanced to point to the next token.
 */

int
parseclef (s, word, gotclef, clefstr, gotoctave, octave)
     char **s;
     char *word;
     int *gotclef;
     char *clefstr;
     int *gotoctave, *octave;
/* extracts string clef= something */
{
  int successful;
  skipspace (s);
  *s = readword (word, *s);
  successful = 0;
  if (casecmp (word, "clef") == 0)
    {
      skipspace (s);
      if (**s != '=')
	{
	  event_error ("clef must be followed by '='");
	}
      else
	{
	  *s = *s + 1;
	  skipspace (s);
	  *s = readword (clefstr, *s);
	  if (isclef (clefstr, gotoctave, octave, 0))
	    {
	      *gotclef = 1;
	    };
	};
      successful = 1;
    }
  else if (isclef (word, gotoctave, octave, 1))
    {
      *gotclef = 1;
      strcpy (clefstr, word);
      successful = 1;
    };
  return successful;
}


int
parsetranspose (s, word, gottranspose, transpose)
/* parses string transpose= number */
     char **s;
     char *word;
     int *gottranspose;
     int *transpose;
{
  if (casecmp (word, "transpose") != 0)
    return 0;
  skipspace (s);
  if (**s != '=')
    {
      event_error ("transpose must be followed by '='");
    }
  else
    {
      *s = *s + 1;
      skipspace (s);
      *transpose = readsnump (s);
      *gottranspose = 1;
    };
  return 1;
};


int
parseoctave (s, word, gotoctave, octave)
/* parses string octave= number */
     char **s;
     char *word;
     int *gotoctave;
     int *octave;
{
  if (casecmp (word, "octave") != 0)
    return 0;
  skipspace (s);
  if (**s != '=')
    {
      event_error ("octave must be followed by '='");
    }
  else
    {
      *s = *s + 1;
      skipspace (s);
      *octave = readsnump (s);
      *gotoctave = 1;
    };
  return 1;
};


int
parsename (s, word, gotname, namestring, maxsize)
/* parses string name= "string" in V: command 
   for compatability of abc2abc with abcm2ps
*/
     char **s;
     char *word;
     int *gotname;
     char namestring[];
     int maxsize;
{
  int i;
  i = 0;
  if (casecmp (word, "name") != 0)
    return 0;
  skipspace (s);
  if (**s != '=')
    {
      event_error ("name must be followed by '='");
    }
  else
    {
      *s = *s + 1;
      skipspace (s);
      if (**s == '"')		/* string enclosed in double quotes */
	{
	  namestring[i] = (char) **s;
	  *s = *s + 1;
	  i++;
	  while (i < maxsize && **s != '"' && **s != '\0')
	    {
	      namestring[i] = (char) **s;
	      *s = *s + 1;
	      i++;
	    }
	  namestring[i] = (char) **s;	/* copy double quotes */
	  i++;
	  namestring[i] = '\0';
	}
      else			/* string not enclosed in double quotes */
	{
	  while (i < maxsize && **s != ' ' && **s != '\0')
	    {
	      namestring[i] = (char) **s;
	      *s = *s + 1;
	      i++;
	    }
	  namestring[i] = '\0';
	}
      *gotname = 1;
    }
  return 1;
};

int
parsesname (s, word, gotname, namestring, maxsize)
/* parses string name= "string" in V: command 
   for compatability of abc2abc with abcm2ps
*/
     char **s;
     char *word;
     int *gotname;
     char namestring[];
     int maxsize;
{
  int i;
  i = 0;
  if (casecmp (word, "sname") != 0)
    return 0;
  skipspace (s);
  if (**s != '=')
    {
      event_error ("name must be followed by '='");
    }
  else
    {
      *s = *s + 1;
      skipspace (s);
      if (**s == '"')		/* string enclosed in double quotes */
	{
	  namestring[i] = (char) **s;
	  *s = *s + 1;
	  i++;
	  while (i < maxsize && **s != '"' && **s != '\0')
	    {
	      namestring[i] = (char) **s;
	      *s = *s + 1;
	      i++;
	    }
	  namestring[i] = (char) **s;	/* copy double quotes */
	  i++;
	  namestring[i] = '\0';
	}
      else			/* string not enclosed in double quotes */
	{
	  while (i < maxsize && **s != ' ' && **s != '\0')
	    {
	      namestring[i] = (char) **s;
	      *s = *s + 1;
	      i++;
	    }
	  namestring[i] = '\0';
	}
      *gotname = 1;
    }
  return 1;
};

int
parsemiddle (s, word, gotmiddle, middlestring, maxsize)
/* parse string middle=X in V: command
 for abcm2ps compatibility
*/
     char **s;
     char *word;
     int *gotmiddle;
     char middlestring[];
     int maxsize;
{
  int i;
  i = 0;
  if (casecmp (word, "middle") != 0)
    return 0;
  skipspace (s);
  if (**s != '=')
    {
      event_error ("middle must be followed by '='");
    }
  else
    {
      *s = *s + 1;
      skipspace (s);
/* we really ought to check the we have a proper note name; for now, just copy non-space
characters */
      while (i < maxsize && **s != ' ' && **s != '\0')
	{
	  middlestring[i] = (char) **s;
	  *s = *s + 1;
	  ++i;
	}
      middlestring[i] = '\0';
      *gotmiddle = 1;
    }
  return 1;
}

int
parseother (s, word, gotother, other, maxsize)	/* [SS] 2011-04-18 */
/* parses any left overs in V: command (eg. stafflines=1) */
     char **s;
     char *word;
     int *gotother;
     char other[];
{
  if (word[0] != '\0')
    {
      if (strlen (other) < maxsize)
	strncat (other, word, maxsize);
      if (**s == '=')
	{			/* [SS] 2011-04-19 */
	  *s = readword (word, *s);
	  if (strlen (other) < maxsize)
	    strncat (other, word, maxsize);
	}
      strncat (other, " ", maxsize);	/* in case other codes follow */
      *gotother = 1;
      return 1;
    }
  return 0;
}

int
parsekey (str)
/* parse contents of K: field */
/* this works by picking up a strings and trying to parse them */
/* returns 1 if valid key signature found, 0 otherwise */
     char *str;
{
  char *s;
  char word[30];
  int parsed;
  int gotclef, gotkey, gotoctave, gottranspose;
  int explict;			/* [SS] 2010-05-08 */
  int modnotes;			/* [SS] 2010-07-29 */
  int foundmode;
  int transpose, octave;
  char clefstr[30];
  char modestr[30];
  char msg[80];
  char *moveon;
  int sf = -1, minor = -1;
  char modmap[7];
  int modmul[7];
  struct fraction modmicrotone[7];
  int i, j;
  int cgotoctave, coctave;
  char *key = "FCGDAEB";
  int modeindex;
  int a, b;			/* for microtones [SS] 2014-01-06 */
  int success;
  char c;

  clefstr[0] = (char) 0;
  modestr[0] = (char) 0;
  s = str;
  transpose = 0;
  gottranspose = 0;
  octave = 0;
  gotkey = 0;
  gotoctave = 0;
  gotclef = 0;
  cgotoctave = 0;
  coctave = 0;
  modeindex = 0;
  explict = 0;
  modnotes = 0;
  for (i = 0; i < 7; i++)
    {
      modmap[i] = ' ';
      modmul[i] = 1;
      modmicrotone[i].num = 0;	/* [SS] 2014-01-06 */
      modmicrotone[i].denom = 0;
    };
  while (*s != '\0')
    {
      parsed = parseclef (&s, word, &gotclef, clefstr, &cgotoctave, &coctave);
      /* parseclef also scans the s string using readword(), placing */
      /* the next token  into the char array word[].                   */
      if (!parsed)
	parsed = parsetranspose (&s, word, &gottranspose, &transpose);

      if (!parsed)
	parsed = parseoctave (&s, word, &gotoctave, &octave);

      if ((parsed == 0) && (casecmp (word, "Hp") == 0))
	{
	  sf = 2;
	  minor = 0;
	  gotkey = 1;
	  parsed = 1;
	};

      if ((parsed == 0) && (casecmp (word, "none") == 0))
	{
	  gotkey = 1;
	  parsed = 1;
	  nokey = 1;
	  minor = 0;
	  sf = 0;
	}

      if (casecmp (word, "exp") == 0)
	{
	  explict = 1;
	  parsed = 1;
	}

      if ((parsed == 0) && ((word[0] >= 'A') && (word[0] <= 'G')))
	{
	  gotkey = 1;
	  parsed = 1;
	  /* parse key itself */
	  sf = strchr (key, word[0]) - key - 1;
	  j = 1;
	  /* deal with sharp/flat */
	  if (word[1] == '#')
	    {
	      sf += 7;
	      j = 2;
	    }
	  else
	    {
	      if (word[1] == 'b')
		{
		  sf -= 7;
		  j = 2;
		};
	    }
	  minor = 0;
	  foundmode = 0;
	  if ((int) strlen (word) == j)
	    {
	      /* look at next word for mode */
	      skipspace (&s);
	      moveon = readword (modestr, s);
	      lcase (modestr);
	      for (i = 0; i < 10; i++)
		{
		  if (strncmp (modestr, mode[i], 3) == 0)
		    {
		      foundmode = 1;
		      sf = sf + modeshift[i];
		      minor = modeminor[i];
		      modeindex = i;
		    };
		};
	      if (foundmode)
		{
		  s = moveon;
		};
	    }
	  else
	    {
	      strcpy (modestr, &word[j]);
	      lcase (modestr);
	      for (i = 0; i < 10; i++)
		{
		  if (strncmp (modestr, mode[i], 3) == 0)
		    {
		      foundmode = 1;
		      sf = sf + modeshift[i];
		      minor = modeminor[i];
		      modeindex = i;
		    };
		};
	      if (!foundmode)
		{
		  sprintf (msg, "Unknown mode '%s'", &word[j]);
		  event_error (msg);
		  modeindex = 0;
		};
	    };
	};
      if (gotkey)
	{
	  if (sf > 7)
	    {
	      event_warning ("Unusual key representation");
	      sf = sf - 12;
	    };
	  if (sf < -7)
	    {
	      event_warning ("Unusual key representation");
	      sf = sf + 12;
	    };
	};
      if ((word[0] == '^') || (word[0] == '_') || (word[0] == '='))
	{
	  modnotes = 1;
	  if ((strlen (word) == 2) && (word[1] >= 'a') && (word[1] <= 'g'))
	    {
	      j = (int) word[1] - 'a';
	      modmap[j] = word[0];
	      modmul[j] = 1;
	      parsed = 1;
	    }
	  else
	    {			/*double sharp or double flat */
	      if ((strlen (word) == 3) && (word[0] != '=')
		  && (word[0] == word[1]) && (word[2] >= 'a')
		  && (word[2] <= 'g'))
		{
		  j = (int) word[2] - 'a';
		  modmap[j] = word[0];
		  modmul[j] = 2;
		  parsed = 1;
		};
	    };
	};

/*   if (explict)  for compatibility with abcm2ps 2010-05-08  2010-05-20 */
      if ((word[0] == '^') || (word[0] == '_') || (word[0] == '='))
	{
	  modnotes = 1;
	  if ((strlen (word) == 2) && (word[1] >= 'A') && (word[1] <= 'G'))
	    {
	      j = (int) word[1] - 'A';
	      modmap[j] = word[0];
	      modmul[j] = 1;
	      parsed = 1;
	    }
	  else if		/*double sharp or double flat */
	    ((strlen (word) == 3) && (word[0] != '=') && (word[0] == word[1])
	     && (word[2] >= 'A') && (word[2] <= 'G'))
	    {
	      j = (int) word[2] - 'A';
	      modmap[j] = word[0];
	      modmul[j] = 2;
	      parsed = 1;
	    };
	  /* microtone? */
	  success = sscanf (&word[1], "/%d%c", &b, &c);
	  if (success > 0)
	    a = 1;
	  else
	    success = sscanf (&word[1], "%d/%d%c", &a, &b, &c);
	  if (success > 0)
	    {
	      parsed = 1;
	      j = (int) c - 'A';
              if (j > 7) j = (int) c - 'a';
              if (j > 7 || j < 0) {printf("invalid j = %d\n"); exit(-1);}
	      if (word[0] == '_')
		a = -a;
	      /*printf("a/b = %d/%d for %c\n",a,b,c); */
	      modmap[j] = word[0];
	      modmicrotone[j].num = a;
	      modmicrotone[j].denom = b;
	    }
	}
    }
  if ((parsed == 0) && (strlen (word) > 0))
    {
      sprintf (msg, "Ignoring string '%s' in K: field", word);
      event_warning (msg);
    };
  if (cgotoctave)
    {
      gotoctave = 1;
      octave = coctave;
    }
  if (modnotes & !gotkey)
    {				/*[SS] 2010-07-29 for explicit key signature */
      sf = 0;
      /*gotkey = 1; [SS] 2010-07-29 */
      explict = 1;		/* [SS] 2010-07-29 */
    }
  event_key (sf, str, modeindex, modmap, modmul, modmicrotone, gotkey,
	     gotclef, clefstr, octave, transpose, gotoctave, gottranspose,
	     explict);
  return (gotkey);
}


void
parsevoice (s)
     char *s;
{
  int num;			/* voice number */
  struct voice_params vparams;
  char word[30];
  int parsed;
  int coctave, cgotoctave;

  vparams.transpose = 0;
  vparams.gottranspose = 0;
  vparams.octave = 0;
  vparams.gotoctave = 0;
  vparams.gotclef = 0;
  cgotoctave = 0;
  coctave = 0;
  vparams.gotname = 0;
  vparams.gotsname = 0;
  vparams.gotmiddle = 0;
  vparams.gotother = 0;		/* [SS] 2011-04-18 */
  vparams.other[0] = '\0';	/* [SS] 2011-04-18 */

  skipspace (&s);
  if (isnumberp (&s) == 1)
    {
      num = readnump (&s);
    }
  else
    {
      num = interpret_voicestring (s);
      if (num == 0)
	event_error ("No voice number or string in V: field");
      if (num == -1)
	{
	  event_error ("More than 16 voices encountered in V: fields");
	  num = 0;
	}
      skiptospace (&s);
    };
  skipspace (&s);
  while (*s != '\0')
    {
      parsed =
	parseclef (&s, word, &vparams.gotclef, vparams.clefname, &cgotoctave,
		   &coctave);
      if (!parsed)
	parsed =
	  parsetranspose (&s, word, &vparams.gottranspose,
			  &vparams.transpose);
      if (!parsed)
	parsed = parseoctave (&s, word, &vparams.gotoctave, &vparams.octave);
      if (!parsed)
	parsed =
	  parsename (&s, word, &vparams.gotname, vparams.namestring,
		     V_STRLEN);
      if (!parsed)
	parsed =
	  parsesname (&s, word, &vparams.gotsname, vparams.snamestring,
		      V_STRLEN);
      if (!parsed)
	parsed =
	  parsemiddle (&s, word, &vparams.gotmiddle, vparams.middlestring,
		       V_STRLEN);
      if (!parsed)
	parsed = parseother (&s, word, &vparams.gotother, vparams.other, V_STRLEN);	/* [SS] 2011-04-18 */
    }
  if (cgotoctave)
    {
      vparams.gotoctave = 1;
      vparams.octave = coctave;
    }
  event_voice (num, s, &vparams);

/*
if (gottranspose) printf("transpose = %d\n", vparams.transpose);
 if (gotoctave) printf("octave= %d\n", vparams.octave);
 if (gotclef) printf("clef= %s\n", vparams.clefstr);
if (gotname) printf("parsevoice: name= %s\n", vparams.namestring);
if(gotmiddle) printf("parsevoice: middle= %s\n", vparams.middlestring);
*/
}


void
parsenote (s)
     char **s;
/* parse abc note and advance character pointer */
{
  int decorators[DECSIZE];
  int i, t;
  int mult;
  char accidental, note;
  int octave, n, m;
  char msg[80];

  mult = 1;
  microtone = 0;
  accidental = ' ';
  note = ' ';
  for (i = 0; i < DECSIZE; i++)
    {
      decorators[i] = decorators_passback[i];
      if (!inchordflag)
	decorators_passback[i] = 0;	/* [SS] 2012-03-30 */
    }
  while (strchr (decorations, **s) != NULL)
    {
      t = strchr (decorations, **s) - decorations;
      decorators[t] = 1;
      *s = *s + 1;
    };
  /*check for decorated chord */
  if (**s == '[')
    {
      lineposition = *s - linestart;	/* [SS] 2011-07-18 */
      if (fileprogram == YAPS)
	event_warning ("decorations applied to chord");
      for (i = 0; i < DECSIZE; i++)
	chorddecorators[i] = decorators[i];
      event_chordon (chorddecorators);
      if (fileprogram == ABC2ABC)
	for (i = 0; i < DECSIZE; i++)
	  decorators[i] = 0;
      parserinchord = 1;
      *s = *s + 1;
      skipspace (s);
    };
  if (parserinchord)
    {
      /* inherit decorators */
      if (fileprogram != ABC2ABC)
	for (i = 0; i < DECSIZE; i++)
	  {
	    decorators[i] = decorators[i] | chorddecorators[i];
	  };
    };

/* [SS] 2011-12-08 to catch fermata H followed by a rest */

  if (**s == 'z')
    {
      *s = *s + 1;
      readlen (&n, &m, s);
      event_rest (decorators, n, m, 0);
      return;
    }
  if (**s == 'x')
    {
      *s = *s + 1;
      readlen (&n, &m, s);
      event_rest (decorators, n, m, 1);
      return;
    }

  /* read accidental */
  switch (**s)
    {
    case '_':
      accidental = **s;
      *s = *s + 1;
      if (**s == '_')
	{
	  *s = *s + 1;
	  mult = 2;
	};
      microtone = ismicrotone (s, -1);
      if (microtone)
	{
	  if (mult == 2)
	    mult = 1;
	  else
	    accidental = ' ';
	}
      break;
    case '^':
      accidental = **s;
      *s = *s + 1;
      if (**s == '^')
	{
	  *s = *s + 1;
	  mult = 2;
	};
      microtone = ismicrotone (s, 1);
      if (microtone)
	{
	  if (mult == 2)
	    mult = 1;
	  else
	    accidental = ' ';
	}

      break;
    case '=':
      accidental = **s;
      *s = *s + 1;
      /* if ((**s == '^') || (**s == '_')) {
         accidental = **s;
         }; */
      if (**s == '^')
	{
	  accidental = **s;
	  *s = *s + 1;
	  microtone = ismicrotone (s, 1);
	  if (microtone == 0)
	    accidental = '^';
	}
      else if (**s == '_')
	{
	  accidental = **s;
	  *s = *s + 1;
	  microtone = ismicrotone (s, -1);
	  if (microtone == 0)
	    accidental = '_';
	}
      break;
    default:
      microtone = ismicrotone (s, 1);		/* [SS] 2014-01-19 */
      break;
    };
  if ((**s >= 'a') && (**s <= 'g'))
    {
      note = **s;
      octave = 1;
      *s = *s + 1;
      while ((**s == '\'') || (**s == ','))
	{
	  if (**s == '\'')
	    {
	      octave = octave + 1;
	      *s = *s + 1;
	    };
	  if (**s == ',')
	    {
	      sprintf (msg, "Bad pitch specifier , after note %c", note);
	      event_error (msg);
	      octave = octave - 1;
	      *s = *s + 1;
	    };
	};
    }
  else
    {
      octave = 0;
      if ((**s >= 'A') && (**s <= 'G'))
	{
	  note = **s + 'a' - 'A';
	  *s = *s + 1;
	  while ((**s == '\'') || (**s == ','))
	    {
	      if (**s == ',')
		{
		  octave = octave - 1;
		  *s = *s + 1;
		};
	      if (**s == '\'')
		{
		  sprintf (msg, "Bad pitch specifier ' after note %c",
			   note + 'A' - 'a');
		  event_error (msg);
		  octave = octave + 1;
		  *s = *s + 1;
		};
	    };
	};
    };
  if (note == ' ')
    {
      event_error ("Malformed note : expecting a-g or A-G");
    }
  else
    {
      readlen (&n, &m, s);
      event_note (decorators, accidental, mult, note, octave, n, m);
      if (!microtone)
	event_normal_tone ();	/* [SS] 2014-01-09 */
    };
}

char *
getrep (p, out)
     char *p;
     char *out;
/* look for number or list following [ | or :| */
{
  char *q;
  int digits;
  int done;
  int count;

  q = p;
  count = 0;
  done = 0;
  digits = 0;
  while (!done)
    {
      if (isdigit (*q))
	{
	  out[count] = *q;
	  count = count + 1;
	  q = q + 1;
	  digits = digits + 1;
	  /* [SS] 2013-04-21 */
	  if (count > 50)
	    {
	      event_error ("malformed repeat");
	      break;
	    }
	}
      else
	{
	  if (((*q == '-') || (*q == ',')) && (digits > 0)
	      && (isdigit (*(q + 1))))
	    {
	      out[count] = *q;
	      count = count + 1;
	      q = q + 1;
	      digits = 0;
	      /* [SS] 2013-04-21 */
	      if (count > 50)
		{
		  event_error ("malformed repeat");
		  break;
		}
	    }
	  else
	    {
	      done = 1;
	    };
	};
    };
  out[count] = '\0';
  return (q);
}

int
checkend (s)
     char *s;
/* returns 1 if we are at the end of the line 0 otherwise */
/* used when we encounter '\' '*' or other special line end characters */
{
  char *p;
  int atend;

  p = s;
  skipspace (&p);
  if (*p == '\0')
    {
      atend = 1;
    }
  else
    {
      atend = 0;
    };
  return (atend);
}

void
readstr (out, in, limit)
     char out[];
     char **in;
     int limit;
/* copy across alphanumeric string */
{
  int i;

  i = 0;
  while ((isalpha (**in)) && (i < limit - 1))
    {
      out[i] = **in;
      i = i + 1;
      *in = *in + 1;
    };
  out[i] = '\0';
}

void
parse_precomment (s)
     char *s;
/* handles a comment field */
{
  char package[40];
  char *p;

  if (*s == '%')
    {
      p = s + 1;
      readstr (package, &p, 40);
      event_specific (package, p);
    }
  else
    {
      event_comment (s);
    };
}

void
parse_tempo (place)
     char *place;
/* parse tempo descriptor i.e. Q: field */
{
  char *p;
  int a, b;
  int n;
  int relative;
  char *pre_string;
  char *post_string;

  relative = 0;
  p = place;
  pre_string = NULL;
  if (*p == '"')
    {
      p = p + 1;
      pre_string = p;
      while ((*p != '"') && (*p != '\0'))
	{
	  p = p + 1;
	};
      if (*p == '\0')
	{
	  event_error ("Missing closing double quote");
	}
      else
	{
	  *p = '\0';
	  p = p + 1;
	  place = p;
	};
    };
  while ((*p != '\0') && (*p != '='))
    p = p + 1;
  if (*p == '=')
    {
      p = place;
      skipspace (&p);
      if (((*p >= 'A') && (*p <= 'G')) || ((*p >= 'a') && (*p <= 'g')))
	{
	  relative = 1;
	  p = p + 1;
	};
      readlen (&a, &b, &p);
      skipspace (&p);
      if (*p != '=')
	{
	  event_error ("Expecting = in tempo");
	};
      p = p + 1;
    }
  else
    {
      a = 1;			/* [SS] 2013-01-27 */
      /*a = 0;  [SS] 2013-01-27 */
      b = 4;
      p = place;
    };
  skipspace (&p);
  n = readnump (&p);
  post_string = NULL;
  if (*p == '"')
    {
      p = p + 1;
      post_string = p;
      while ((*p != '"') && (*p != '\0'))
	{
	  p = p + 1;
	};
      if (*p == '\0')
	{
	  event_error ("Missing closing double quote");
	}
      else
	{
	  *p = '\0';
	  p = p + 1;
	};
    };
  event_tempo (n, a, b, relative, pre_string, post_string);
}

void
preparse_words (s)
     char *s;
/* takes a line of lyrics (w: field) and strips off */
/* any continuation character */
{
  int continuation;
  int l;

  /* printf("Parsing %s\n", s); */
  /* strip off any trailing spaces */
  l = strlen (s) - 1;
  while ((l >= 0) && (*(s + l) == ' '))
    {
      *(s + l) = '\0';
      l = l - 1;
    };
  if (*(s + l) != '\\')
    {
      continuation = 0;
    }
  else
    {
      continuation = 1;
      /* remove continuation character */
      *(s + l) = '\0';
      l = l - 1;
      while ((l >= 0) && (*(s + l) == ' '))
	{
	  *(s + l) = '\0';
	  l = l - 1;
	};
    };
  event_words (s, continuation);
}

void
init_abbreviations ()
/* initialize mapping of H-Z to strings */
{
  int i;

  for (i = 0; i < 'Z' - 'H'; i++)
    {
      abbreviation[i] = NULL;
    };
}

void
record_abbreviation (char symbol, char *string)
/* update record of abbreviations when a U: field is encountered */
{
  int index;

  if ((symbol < 'H') || (symbol > 'Z'))
    {
      return;
    };
  index = symbol - 'H';
  if (abbreviation[index] != NULL)
    {
      free (abbreviation[index]);
    };
  abbreviation[index] = addstring (string);
}

char *
lookup_abbreviation (char symbol)
/* return string which s abbreviates */
{
  if ((symbol < 'H') || (symbol > 'Z'))
    {
      return (NULL);
    }
  else
    {
      return (abbreviation[symbol - 'H']);
    };
}

void
free_abbreviations ()
/* free up any space taken by abbreviations */
{
  int i;

  for (i = 0; i < SIZE_ABBREVIATIONS; i++)
    {
      if (abbreviation[i] != NULL)
	{
	  free (abbreviation[i]);
	};
    };
}

void
parsefield (key, field)
     char key;
     char *field;
/* top-level routine handling all lines containing a field */
{
  char *comment;
  char *place;
  char *xplace;
  int iscomment;
  int foundkey;

  if (key == 'X')
    {
      int x;

      xplace = field;
      skipspace (&xplace);
      x = readnumf (xplace);
      if (inhead)
	{
	  event_error ("second X: field in header");
	};
      event_refno (x);
      init_voicecode ();	/* [SS] 2011-01-01 */
      inhead = 1;
      inbody = 0;
      parserinchord = 0;
      return;
    };

  if (parsing == 0)
    return;

  if ((inbody) && (strchr ("EIKLMPQTVdswW", key) == NULL))
    {
      event_error ("Field not allowed in tune body");
    };
  comment = field;
  iscomment = 0;
  while ((*comment != '\0') && (*comment != '%'))
    {
      comment = comment + 1;
    };
  if (*comment == '%')
    {
      iscomment = 1;
      *comment = '\0';
      comment = comment + 1;
    };
  place = field;
  skipspace (&place);
  switch (key)
    {
    case 'K':
      foundkey = parsekey (place);
      if (inhead || inbody)
	{
	  if (foundkey)
	    {
	      inbody = 1;
	      inhead = 0;
	    }
	  else
	    {
	      if (inhead)
		{
		  event_error ("First K: field must specify key signature");
		};
	    };
	}
      else
	{
	  event_error ("No X: field preceding K:");
	};
      break;
    case 'M':
      {
	int num, denom;

	strncpy (timesigstring, place, 16);	/* [SS] 2011-08-19 */
	if (strncmp (place, "none", 4) == 0)
	  {
	    event_timesig (4, 4, 0);
	  }
	else
	  {
	    readsig (&num, &denom, &place);
	    if ((*place == 's') || (*place == 'l'))
	      {
		event_error ("s and l in M: field not supported");
	      };
	    if ((num != 0) && (denom != 0))
	      {
		event_timesig (num, denom, 1);
	      };
	  };
	break;
      };
    case 'L':
      {
	int num, denom;

	readsig (&num, &denom, &place);
	if (num != 1)
	  {
	    event_error ("Default length must be 1/X");
	  }
	else
	  {
	    if (denom > 0)
	      {
		event_length (denom);
	      }
	    else
	      {
		event_error ("invalid denominator");
	      };
	  };
	break;
      };
    case 'P':
      event_part (place);
      break;
    case 'I':
      event_info (place);
      break;
    case 'V':
      parsevoice (place);
      break;
    case 'Q':
      parse_tempo (place);
      break;
    case 'U':
      {
	char symbol;
	char container;
	char *expansion;

	skipspace (&place);
	if ((*place >= 'H') && (*place <= 'Z'))
	  {
	    symbol = *place;
	    place = place + 1;
	    skipspace (&place);
	    if (*place == '=')
	      {
		place = place + 1;
		skipspace (&place);
		if (*place == '!')
		  {
		    place = place + 1;
		    container = '!';
		    expansion = place;
		    while ((!iscntrl (*place)) && (*place != '!'))
		      {
			place = place + 1;
		      };
		    if (*place != '!')
		      {
			event_error ("No closing ! in U: field");
		      };
		    *place = '\0';
		  }
		else
		  {
		    container = ' ';
		    expansion = place;
		    while (isalnum (*place))
		      {
			place = place + 1;
		      };
		    *place = '\0';
		  };
		if (strlen (expansion) > 0)
		  {
		    record_abbreviation (symbol, expansion);
		    event_abbreviation (symbol, expansion, container);
		  }
		else
		  {
		    event_error ("Missing term in U: field");
		  };
	      }
	    else
	      {
		event_error ("Missing '=' U: field ignored");
	      };
	  }
	else
	  {
	    event_warning ("only 'H' - 'Z' supported in U: field");
	  };
      };
      break;
    case 'w':
      preparse_words (place);
      break;
    case 'd':
      /* decoration line in abcm2ps */
      event_field (key, place);	/* [SS] 2010-02-23 */
      break;
    case 's':
      event_field (key, place);	/* [SS] 2010-02-23 */
      break;
    default:
      event_field (key, place);
    };
  if (iscomment)
    {
      parse_precomment (comment);
    };
}

char *
parseinlinefield (p)
     char *p;
/* parse field within abc line e.g. [K:G] */
{
  char *q;

  event_startinline ();
  q = p;
  while ((*q != ']') && (*q != '\0'))
    {
      q = q + 1;
    };
  if (*q == ']')
    {
      *q = '\0';
      parsefield (*p, p + 2);
      q = q + 1;
    }
  else
    {
      event_error ("missing closing ]");
      parsefield (*p, p + 2);
    };
  event_closeinline ();
  return (q);
}

/* this function is used by toabc.c [SS] 2011-06-10 */
void
print_inputline_nolinefeed ()
{
  if (inputline[sizeof inputline - 1] != '\0')
    {
      /*
       * We are called exclusively by toabc.c,
       * and when we are called, event_error is muted,
       * so, event_error("input line truncated") does nothing.
       * Simulate it with a plain printf. [PHDM 2012-12-01]
       */
      printf ("%%Error : input line truncated\n");
    }
  printf ("%s", inputline);
}

/* this function is used by toabc.c [SS] 2011-06-07 */
void
print_inputline ()
{
  print_inputline_nolinefeed ();
  printf ("\n");
}

void
parsemusic (field)
     char *field;
/* parse a line of abc notes */
{
  char *p;
  char *comment;
  char endchar;
  int iscomment;
  int starcount;
  int i;
  char playonrep_list[80];
  int decorators[DECSIZE];

  for (i = 0; i < DECSIZE; i++)
    decorators[i] = 0;		/* [SS] 2012-03-30 */

  event_startmusicline ();
  endchar = ' ';
  comment = field;
  iscomment = 0;
  while ((*comment != '\0') && (*comment != '%'))
    {
      comment = comment + 1;
    };
  if (*comment == '%')
    {
      iscomment = 1;
      *comment = '\0';
      comment = comment + 1;
    };

  p = field;
  skipspace (&p);
  while (*p != '\0')
    {
      lineposition = p - linestart;	/* [SS] 2011-07-18 */
      if (((*p >= 'a') && (*p <= 'g')) || ((*p >= 'A') && (*p <= 'G')) ||
	  (strchr ("_^=", *p) != NULL) || (strchr (decorations, *p) != NULL))
	{
	  parsenote (&p);
	}
      else
	{
	  switch (*p)
	    {
	    case '"':
	      {
		struct vstring gchord;

		p = p + 1;
		initvstring (&gchord);
		while ((*p != '"') && (*p != '\0'))
		  {
		    addch (*p, &gchord);
		    p = p + 1;
		  };
		if (*p == '\0')
		  {
		    event_error ("Guitar chord name not properly closed");
		  }
		else
		  {
		    p = p + 1;
		  };
		event_gchord (gchord.st);
		freevstring (&gchord);
		break;
	      };
	    case '|':
	      p = p + 1;
	      switch (*p)
		{
		case ':':
		  event_bar (BAR_REP, "");
		  p = p + 1;
		  break;
		case '|':
		  event_bar (DOUBLE_BAR, "");
		  p = p + 1;
		  break;
		case ']':
		  event_bar (THIN_THICK, "");
		  p = p + 1;
		  break;
		default:
		  p = getrep (p, playonrep_list);
		  event_bar (SINGLE_BAR, playonrep_list);
		};
	      break;
	    case ':':
	      p = p + 1;
	      switch (*p)
		{
		case ':':
		  event_bar (DOUBLE_REP, "");
		  p = p + 1;
		  break;
		case '|':
		  p = p + 1;
		  p = getrep (p, playonrep_list);
		  event_bar (REP_BAR, playonrep_list);
		  if (*p == ']')
		    p = p + 1;	/* [SS] 2013-10-31 */
		  break;
		default:
		  event_error ("Single colon in bar");
		};
	      break;
	    case ' ':
	      event_space ();
	      skipspace (&p);
	      break;
	    case TAB:
	      event_space ();
	      skipspace (&p);
	      break;
	    case '(':
	      p = p + 1;
	      {
		int t, q, r;

		t = 0;
		q = 0;
		r = 0;
		t = readnump (&p);
		if ((t != 0) && (*p == ':'))
		  {
		    p = p + 1;
		    q = readnump (&p);
		    if (*p == ':')
		      {
			p = p + 1;
			r = readnump (&p);
		      };
		  };
		if (t == 0)
		  {
		    event_sluron (1);
		  }
		else
		  {
		    event_tuple (t, q, r);
		  };
	      };
	      break;
	    case ')':
	      p = p + 1;
	      event_sluroff (0);
	      break;
	    case '{':
	      p = p + 1;
	      event_graceon ();
	      ingrace = 1;
	      break;
	    case '}':
	      p = p + 1;
	      event_graceoff ();
	      ingrace = 0;
	      break;
	    case '[':
	      p = p + 1;
	      switch (*p)
		{
/* following lines are now redundant */
/*
        case '1':
          p = p + 1;
          event_rep1();
          break;
        case '2':
          p = p + 1;
          event_rep2();
          break;
*/
		case '|':
		  p = p + 1;
		  event_bar (THICK_THIN, "");
		  break;
		default:
		  if (isdigit (*p))
		    {
		      p = getrep (p, playonrep_list);
		      event_playonrep (playonrep_list);
		    }
		  else
		    {
		      if (isalpha (*p) && (*(p + 1) == ':'))
			{
			  p = parseinlinefield (p);
			}
		      else
			{
			  lineposition = p - linestart;	/* [SS] 2011-07-18 */
			  /* [SS] 2012-03-30 */
			  for (i = 0; i < DECSIZE; i++)
			    chorddecorators[i] =
			      decorators[i] | decorators_passback[i];
			  event_chordon (chorddecorators);
			  parserinchord = 1;
			};
		    };
		  break;
		};
	      break;
	    case ']':
	      p = p + 1;
	      readlen (&chord_n, &chord_m, &p);
	      event_chordoff (chord_n, chord_m);
	      parserinchord = 0;
	      for (i = 0; i < DECSIZE; i++)
		{
		  chorddecorators[i] = 0;
		  decorators_passback[i] = 0;	/* [SS] 2012-03-30 */
		}
	      break;
/*  hidden rest  */
	    case 'x':
	      {
		int n, m;

		p = p + 1;
		readlen (&n, &m, &p);
/* in order to handle a fermata applied to a rest we must
 * pass decorators to event_rest.
 */
		for (i = 0; i < DECSIZE; i++)
		  {
		    decorators[i] = decorators_passback[i];
		    decorators_passback[i] = 0;
		  }
		event_rest (decorators, n, m, 1);
		break;
	      };
/*  regular rest */
	    case 'z':
	      {
		int n, m;

		p = p + 1;
		readlen (&n, &m, &p);
/* in order to handle a fermata applied to a rest we must
 * pass decorators to event_rest.
 */
		for (i = 0; i < DECSIZE; i++)
		  {
		    decorators[i] = decorators_passback[i];
		    decorators_passback[i] = 0;
		  }
		event_rest (decorators, n, m, 0);
		break;
	      };
	    case 'y':		/* used by Barfly and abcm2ps to put space */
/* I'm sure I've seen somewhere that /something/ allows a length
 * specifier with y to enlarge the space length. Allow it anyway; it's
 * harmless.
 */
	      {
		int n, m;

		p = p + 1;
		readlen (&n, &m, &p);
		event_spacing (n, m);
		break;
	      };
/* full bar rest */
	    case 'Z':
	    case 'X':		/* [SS] 2012-11-15 */

	      {
		int n, m;

		p = p + 1;
		readlen (&n, &m, &p);
		if (m != 1)
		  {
		    event_error
		      ("X or Z must be followed by a whole integer");
		  };
		event_mrest (n, m);
		break;
	      };
	    case '>':
	      {
		int n;

		n = 0;
		while (*p == '>')
		  {
		    n = n + 1;
		    p = p + 1;
		  };
		if (n > 3)
		  {
		    event_error ("Too many >'s");
		  }
		else
		  {
		    event_broken (GT, n);
		  };
		break;
	      };
	    case '<':
	      {
		int n;

		n = 0;
		while (*p == '<')
		  {
		    n = n + 1;
		    p = p + 1;
		  };
		if (n > 3)
		  {
		    event_error ("Too many <'s");
		  }
		else
		  {
		    event_broken (LT, n);
		  };
		break;
	      };
	    case 's':
	      if (slur == 0)
		{
		  slur = 1;
		}
	      else
		{
		  slur = slur - 1;
		};
	      event_slur (slur);
	      p = p + 1;
	      break;
	    case '-':
	      event_tie ();
	      p = p + 1;
	      break;
	    case '\\':
	      p = p + 1;
	      if (checkend (p))
		{
		  event_lineend ('\\', 1);
		  endchar = '\\';
		}
	      else
		{
		  event_error ("'\\' in middle of line ignored");
		};
	      break;
	    case '+':
	      if (oldchordconvention)
		{
		  lineposition = p - linestart;	/* [SS] 2011-07-18 */
		  event_chord ();
		  parserinchord = 1 - parserinchord;
		  if (parserinchord == 0)
		    {
		      for (i = 0; i < DECSIZE; i++)
			chorddecorators[i] = 0;
		    };
		  p = p + 1;
		  break;
		}
	      /* otherwise we fall through into the next case statement */
	    case '!':
	      {
		struct vstring instruction;
		char *s;
		char endcode;

		endcode = *p;
		p = p + 1;
		s = p;
		initvstring (&instruction);
		while ((*p != endcode) && (*p != '\0'))
		  {
		    addch (*p, &instruction);
		    p = p + 1;
		  };
		if (*p != endcode)
		  {
		    p = s;
		    if (checkend (s))
		      {
			event_lineend ('!', 1);
			endchar = '!';
		      }
		    else
		      {
			event_error ("'!' or '+' in middle of line ignored");
		      };
		  }
		else
		  {
		    event_instruction (instruction.st);
		    p = p + 1;
		  };
		freevstring (&instruction);
	      }
	      break;
	    case '*':
	      p = p + 1;
	      starcount = 1;
	      while (*p == '*')
		{
		  p = p + 1;
		  starcount = starcount + 1;
		};
	      if (checkend (p))
		{
		  event_lineend ('*', starcount);
		  endchar = '*';
		}
	      else
		{
		  event_error ("*'s in middle of line ignored");
		};
	      break;
	    case '/':
	      p = p + 1;
	      if (ingrace)
		event_acciaccatura ();
	      else
		event_error ("stray / not in grace sequence");
	      break;
	    case '&':
	      p = p + 1;
	      event_split_voice ();
	      break;
	    default:
	      {
		char msg[40];

		if ((*p >= 'H') && (*p <= 'Z'))
		  {
		    event_reserved (*p);
		  }
		else
		  {
		    sprintf (msg, "Unrecognized character: %c", *p);
		    event_error (msg);
		  };
	      };
	      p = p + 1;
	    };
	};
    };
  event_endmusicline (endchar);
  if (iscomment)
    {
      parse_precomment (comment);
    };
}

void
parseline (line)
     char *line;
/* top-level routine for handling a line in abc file */
{
  char *p, *q;

  /*printf("%d parsing : %s\n", lineno, line); */
  strncpy (inputline, line, sizeof inputline);	/* [SS] 2011-06-07 [PHDM] 2012-11-27 */

  p = line;
  linestart = p;		/* [SS] 2011-07-18 */
  ingrace = 0;
  skipspace (&p);
  if (strlen (p) == 0)
    {
      event_blankline ();
      inhead = 0;
      inbody = 0;
      return;
    };
  if ((int) *p == '\\')
    {
      if (parsing)
	{
	  event_tex (p);
	};
      return;
    };
  if ((int) *p == '%')
    {
      parse_precomment (p + 1);
      if (!parsing)
	event_linebreak ();
      return;
    };
  if (strchr ("ABCDEFGHIKLMNOPQRSTUVdwsWXZ", *p) != NULL)
    {
      q = p + 1;
      skipspace (&q);
      if ((int) *q == ':')
	{
	  if (*(line + 1) != ':')
	    {
	      event_warning ("whitespace in field declaration");
	    };
	  if ((*(q + 1) == ':') || (*(q + 1) == '|'))
	    {
	      event_warning ("Potentially ambiguous line - either a :| repeat or a field command -- cannot distinguish.");
/*    [SS] 2013-03-20 */
/*     };             */
/*      parsefield(*p,q+1); */

/*    [SS} 2013-03-20 start */
/*    malformed field command try processing it as a music line */
	      if (inbody)
		{
		  if (parsing)
		    parsemusic (p);
		}
	      else
		{
		  if (parsing)
		    event_text (p);
		};
	    }
	  else
	    parsefield (*p, q + 1);	/* not field command malformed */
/*    [SS] 2013-03-20  end */

	}
      else
	{
	  if (inbody)
	    {
	      if (parsing)
		parsemusic (p);
	    }
	  else
	    {
	      if (parsing)
		event_text (p);
	    };
	};
    }
  else
    {
      if (inbody)
	{
	  if (parsing)
	    parsemusic (p);
	}
      else
	{
	  if (parsing)
	    event_text (p);
	};
    };
}

void
parsefile (name)
     char *name;
/* top-level routine for parsing file */
{
  FILE *fp;
  int reading;
  int fileline;
  struct vstring line;
  /* char line[MAXLINE]; */
  int t;
  int lastch, done_eol;

  /* printf("parsefile called %s\n", name); */
  /* The following code permits abc2midi to read abc from stdin */
  if ((strcmp (name, "stdin") == 0) || (strcmp (name, "-") == 0))
    {
      fp = stdin;
    }
  else
    {
      fp = fopen (name, "r");
    };
  if (fp == NULL)
    {
      printf ("Failed to open file %s\n", name);
      exit (1);
    };
  inhead = 0;
  inbody = 0;
  parseroff ();
  reading = 1;
  line.limit = 4;
  initvstring (&line);
  fileline = 1;
  done_eol = 0;
  lastch = '\0';
  while (reading)
    {
      t = getc (fp);
      if (t == EOF)
	{
	  reading = 0;
	  if (line.len > 0)
	    {
	      parseline (line.st);
	      fileline = fileline + 1;
	      lineno = fileline;
	      if (parsing)
		event_linebreak ();
	    };
	}
      else
	{
	  /* recognize  \n  or  \r  or  \r\n  or  \n\r  as end of line */
	  /* should work for DOS, unix and Mac files */
	  if ((t != '\n') && (t != '\r'))
	    {
	      addch ((char) t, &line);
	      done_eol = 0;
	    }
	  else
	    {
	      if ((done_eol) && (((t == '\n') && (lastch == '\r')) ||
				 ((t == '\r') && (lastch == '\n'))))
		{
		  done_eol = 0;
		  /* skip this character */
		}
	      else
		{
		  /* reached end of line */
		  parseline (line.st);
		  clearvstring (&line);
		  fileline = fileline + 1;
		  lineno = fileline;
		  if (parsing)
		    event_linebreak ();
		  done_eol = 1;
		};
	    };
	  lastch = t;
	};
    };
  fclose (fp);
  event_eof ();
  freevstring (&line);
  if (parsing_started == 0)
    event_error ("No tune processed. Possible missing X: field");
}


int
parsetune (FILE * fp)
/* top-level routine for parsing file */
{
  struct vstring line;
  /* char line[MAXLINE]; */
  int t;
  int lastch, done_eol;

  inhead = 0;
  inbody = 0;
  parseroff ();
  intune = 1;
  line.limit = 4;
  initvstring (&line);
  done_eol = 0;
  lastch = '\0';
  do
    {
      t = getc (fp);
      if (t == EOF)
	{
	  if (line.len > 0)
	    {
	      printf ("%s\n", line.st);
	      parseline (line.st);
	      fileline_number = fileline_number + 1;
	      lineno = fileline_number;
	      event_linebreak ();
	    };
	  break;
	}
      else
	{
	  /* recognize  \n  or  \r  or  \r\n  or  \n\r  as end of line */
	  /* should work for DOS, unix and Mac files */
	  if ((t != '\n') && (t != '\r'))
	    {
	      addch ((char) t, &line);
	      done_eol = 0;
	    }
	  else
	    {
	      if ((done_eol) && (((t == '\n') && (lastch == '\r')) ||
				 ((t == '\r') && (lastch == '\n'))))
		{
		  done_eol = 0;
		  /* skip this character */
		}
	      else
		{
		  /* reached end of line */
		  parseline (line.st);
		  clearvstring (&line);
		  fileline_number = fileline_number + 1;
		  lineno = fileline_number;
		  event_linebreak ();
		  done_eol = 1;
		};
	    };
	  lastch = t;
	};
    }
  while (intune);
  freevstring (&line);
  return t;
}

/*
int getline ()
{
  return (lineno);
}
*/
