/*
 *  This file is part of abc2ps, Copyright (C) 1996,1997  Michael Methfessel
 *  See file abc2ps.c for details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include "abc2ps.h" 

struct STAFF staff_tb[MAXSTAFF];	/* staff table */
int nstaff;			/* (0..MAXSTAFF-1) */
tWhistleTab WhistleTab_tb[MAXWHISTLETAB]; /* whistle tablature table */
int nWhistleTab = 0;
tStringTab StringTab_tb[MAXSTRINGTAB];
int nStringTab;		/* (0..MAXSTRINGTAB-1) */
struct VOICE_S voice_tb[MAXVOICE];	/* voice table */
int nvoice;			/* (0..MAXVOICE-1) */
int VoiceMute_tb[MAXVOICE];

int current_voice;		/* current voice while parsing */
BOOL	VoiceSet;

static int nwline;		/* current number of vocals */

static char meter_top[81];	 /* top part of meter, as string */

static int word,slur;		/* variables used for parsing... */
static int last_note,last_real_note;
static int pplet,qplet,rplet;
static int carryover;		/* for interpreting > and < chars */
static int end_slur_next_note;	/* for patched in a-b slurs */
static int ntinext,tinext[MAXHD]; /* for chord ties */
	char *p;		/* global pointers for parsing music line */
	char *p0;		/* beginning of line */
static int nsym0;		/* nsym at start of parsing a line */
static int insert_meter;	/* flag to insert time signature */
static char escseq[81];		/* escape sequence string */
static int dlen;		/* default length */
		int meter1,meter2;	/* for meter */
		int	NoMeter;		/* for M:none */
static int mflag,lflag;		/* flags for C, l or s in meter */
static char gch[201];		/* guitar chord string */
int sharps_flats,add_pitch;

static struct SYMBOL zsym;	/* symbol containing zeros */
static char empty_str[] = "";	/* empty string */

/* The original file was implemented with an oddball scheme */
/* combining slurs and ties because they are drawn the same.*/
/* For tablature, we need to distinguish between slurs and  */
/* ties so we use this mechanism.                           */
static int PendingTies[MAXPITCHVAL];

static int get_voice(char *p);
static void decomment_line(char *p);
static int parse_basic_note(int *pitch, int *length, int *accidental);
static char IdentifyDeco(char **t);

/* This table is used to adjust the "absolute" pitch for the key in use. */
/* Second index is note C, D, E, F, G, A, B.                             */
short key_convert[15][7] = {
	{ -1, -1, -1, -1, -1, -1, -1 },		/* seven flats  */
	{ -1, -1, -1, 0, -1, -1, -1 },		/* six flats    */
	{ 0, -1, -1, 0, -1, -1, -1 },		/* five flats   */
	{ 0, -1, -1, 0, 0, -1, -1 },		/* four flats   */
	{ 0, 0, -1, 0, 0, -1, -1 },			/* three flats  */
	{ 0, 0, -1, 0, 0, 0, -1 },			/* two flats    */
	{ 0, 0, 0, 0, 0, 0, -1 },			/* one flat     */
	{ 0, 0, 0, 0, 0, 0, 0 },			/* all natural  */
	{ 0, 0, 0, 1, 0, 0, 0 },			/* one sharp    */
	{ 1, 0, 0, 1, 0, 0, 0 },			/* two sharps   */
	{ 1, 0, 0, 1, 1, 0, 0 },			/* three sharps */
	{ 1, 1, 0, 1, 1, 0, 0 },			/* four sharps  */
	{ 1, 1, 0, 1, 1, 1, 0 },			/* five sharps  */
	{ 1, 1, 1, 1, 1, 1, 0 },			/* six sharps   */
	{ 1, 1, 1, 1, 1, 1, 1 }				/* seven sharps */
};

/* For U: substitution [JSA] 05/00		*/
/* ~ @ 0, H-Z @ 1-19, h-w @ 20-35		*/
char G_DecoTbl[36] = {
	D_GRACE,
	D_HOLD, 0, 0, 0, D_ACCENT, D_LOWM, 0, 0, D_UPM, 0, 0, 0, D_TRILL, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, D_UPBOW, D_DOWNBOW, 0
};

/*  subroutines connected with parsing the input file  */

/* ----- sytax: print message for syntax errror -------- */
static void syntax(char msg[],
		   char *q)
{
	int i,n,len,m1,m2,pp,qq,maxcol=65;

	if (verbose<=2)
		printf("\n");
	qq=q-p0+1;
	if (qq<0)
		qq=0;
	printf("++++ %s in line %d.%d \n", msg, linenum, qq);
	m1=0;
	m2=len=strlen(p0);
	n=q-p0;
	if (m2>maxcol) {
		if (n<maxcol)
			m2=maxcol;
		else {
			m1=n-10;
			m2=m1+maxcol;
			if (m2>len)
				m2=len;
		}
	}

	printf("%4d ", linenum);
	pp=5;
	if (m1>0) {
		printf("...");
		pp+=3;
	}
	for (i=m1;i<m2;i++)
		printf("%c", p0[i]);
	if (m2<len)
		printf("...");
	printf("\n");

	if (n>=0 && n<200) {
		for (i=0;i<n+pp-m1;i++)
			printf(" ");
		printf("^\n");
	}
}

/* ----- isnote: checks char for valid note symbol ----- */
int isnote(char c)
{
	if (c == '\0')
		return 0;
	if (strchr("CDEFGABcdefgab^=_",c))
		return 1;
	return 0;
}

/* ----- zero_sym: init global zero SYMBOL struct ----- */
void zero_sym()
{
	memset(&zsym, 0, sizeof zsym);
	zsym.ylo = 12;
	zsym.yhi = 12;
}

/* ----- returns index for new symbol at end of list ---- */
int add_sym(int type)
{
	int k, i;

	if (nsym>=MAXSYMS) bug("Internal dimension MAXSYMS too small", 1);

	k = nsym;
	nsym++;
	sym[k] = zsym;
	sym[k].type = type;
	sym[k].staff = voice_tb[current_voice].staff;
	sym[k].StemDir = voice_tb[current_voice].StemDir;
	sym[k].EndTie = FALSE;
	for (i = 0; i < MAXHD; i++) sym[k].TabDeco[i] = 0;
	sym[k].ylo = sym[k].yhi = 0;
	sym[k].dc.bot = sym[k].dc.top = 0;
	sym[k].Voice = current_voice;
	sym[k].Unison = FALSE;
	sym[k].in_beam = 0;
	return k;
}

/* ----- get_clef: get a clef definition ----- */
/* sets the current clef in the current voice */
/* return a pointer to the next non blank char */
static char *get_clef(char *s)
{
	int clef;

	clef = -1;
	if (!strncmp(s,"bass", 4)) {
		clef = BASS;
		add_pitch = -14;
		s += 4;
	} else if (!strncmp(s,"treble", 6)) {
		clef = TREBLE;
		add_pitch = 0;
		s += 6;
	} else if (!strncmp(s,"alto", 4)) {
		clef = ALTO;
		add_pitch = -7;
		s += 4;
	} else	return s;

	voice_tb[current_voice].clef = clef;	/* current clef */
	if (voice_tb[current_voice].nsym == 0)
		staff_tb[(int) voice_tb[current_voice].staff].clef =
			clef;			/* initial clef */
	voice_tb[current_voice].forced_clef = 1;	/* don't change */
	while (isspace(*s))
		s++;
	return s;
}

/* ----- get_staves: get staves definition ----- */
void get_staves(char *p)
{
	int	i;
	int	staff;
	int	in_bracket = 0;
	int	in_parenth = 0;
	int	in_brace = 0;

	if (within_tune) {
		wng("Cannot have staves definition inside a tune", "");
		return;
	}
	memset(staff_tb, 0, sizeof staff_tb);
	for (staff = MAXSTAFF; --staff >= 0; )
		staff_tb[staff].nvoice = -1;
	staff = 0;
	while (*p != '\0') {
		switch (*p) {
		case ' ':
		case '\t':
			break;
		case '[':
			if (in_brace || in_bracket) {
				wng("Two opening brackets", "");
				return;
			}
			in_bracket++;
			if (staff >= MAXSTAFF) {
				wng("Too many staves", "");
				return;
			}
			staff_tb[staff].bracket = 1;
			break;
		case ']':
			if (--in_bracket < 0) {
				wng("No opening brackets", "");
				return;
			}
			if (staff > 0)
				staff_tb[staff - 1].bracket_end = 1;
			break;
		case '(':
			if (in_parenth) {
				wng("Two opening parenthesis", "");
				return;
			}
			in_parenth++;
			break;
		case ')':
			if (in_parenth == 0) {
				wng("No opening parenthesis", "");
				return;
			}
			in_parenth = 0;
			if (!staff_tb[staff].brace_end)
				staff++;
			break;
		case '{':
			if (in_parenth || in_brace) {
				wng("Two opening braces", "");
				return;
			}
			in_brace++;
			if (staff >= MAXSTAFF - 1) {
				wng("Too many staves", "");
				return;
			}
			staff_tb[staff].brace = 1;
			staff_tb[staff+1].brace_end = 1;
			break;
		case '}':
			if (in_parenth) {
				wng("Parenthesis not closed", "");
				return;
			}
			if (in_brace == 0) {
				wng("No opening braces", "");
				return;
			}
			in_brace = 0;
			if (!staff_tb[staff].brace_end)
				staff++;		/* skip the 2nd staff */
			staff++;
			break;
		default:
			if (!isalnum(*p)) {
				wng("Incorrect character", "");
				return;
			}
			if (staff >= MAXSTAFF) {
				wng("Too many staves", "");
				return;
			}
			i = get_voice(p);
			voice_tb[i].floating = 0;
			voice_tb[i].second = 0;
			switch (in_brace) {
			case 0:
				break;
			case 1:
				if (!in_parenth)
					voice_tb[i].floating = 1;
				in_brace++;
				break;
			case 2:
				voice_tb[i-1].floating = 0;
				in_brace++;
				break;
			case 3:
				if (!in_parenth
				    && staff_tb[staff].nvoice >= 0) {
					staff_tb[staff-1].nvoice++;
					staff_tb[staff].nvoice--;
					voice_tb[i-1].staff--;
					voice_tb[i-1].second = 1;
					voice_tb[i-1].floating = 1;
				}
				in_brace++;
				break;
			case 4:
				if (voice_tb[i-1].second) {
					wng("Too many voices in brace", "");
					return;
				}
				voice_tb[i-2].floating = 0;
				voice_tb[i-1].second = 1;
				in_brace++;
				break;
			default:
				wng("Too many voices in brace", "");
				return;
			}
			voice_tb[i].staff = staff;
			if (++staff_tb[staff].nvoice >= 2) {
				wng("Too many voices on one staff", "");
				return;
			}
			if (in_parenth) {
				if (in_parenth > 1) {
					if (staff_tb[staff].brace_end) {
						voice_tb[i-1].second = 1;
					} else {
						voice_tb[i].second = 1;
					}
				}
				in_parenth++;
			} else if (!staff_tb[staff].brace_end)
				++staff;
			while (isalnum(*p))
				p++;
			continue;
		}
		p++;
	}
	nstaff = staff - 1;
}

/* ----- get_voice: get a voice by name ----- */
/* the voice is created if it does not exist */
static int get_voice(char *p)
{
	char name[32];
	int i;
	int voice;
	struct SYMBOL *s_sym;
	int dlen;
	int meter1,meter2;
	int mflag,lflag;
	int sharps_flats;

	/* There was a bug that caused a crash when selectors were used to  */
	/* skip over tunes that used multiple voices.  When a selected tune */
	/* was then parsed, its voices would be preceded by bogus empty		*/
	/* voices.  The following line swats that bug. [JSA]				*/
	if (!do_this_tune) return 0;

	for (i = 0; i < sizeof name - 1; i++) {
		if (!isalnum(p[i]))
			break;
		name[i] = p[i];
	}
	name[i] = '\0';
	if (voice_tb[0].name[0] == '\0')
		voice = 0;		/* first voice */
	else {
		for (voice = 0; voice <= nvoice; voice++) {
			if (strcmp(name, voice_tb[voice].name) == 0)
				return voice;
		}
		if (voice >= MAXVOICE) {
			wng("Too many voices", "");
			voice--;
		}
	}
	nvoice = voice;
	s_sym = voice_tb[voice].sym;
	dlen = voice_tb[voice].dlen;
	meter1 = voice_tb[voice].meter1;
	meter2 = voice_tb[voice].meter2;
	mflag = voice_tb[voice].mflag;
	lflag = voice_tb[voice].lflag;
	sharps_flats = voice_tb[voice].sharps_flats;
	memset(&voice_tb[voice], 0, sizeof voice_tb[0]);
	voice_tb[voice].dlen = dlen;
	voice_tb[voice].meter1 = meter1;
	voice_tb[voice].meter2 = meter2;
	voice_tb[voice].mflag = mflag;
	voice_tb[voice].lflag = lflag;
	voice_tb[voice].sharps_flats = sharps_flats;
	if ((voice_tb[voice].sym = s_sym) == 0)
		voice_tb[voice].sym =
			malloc(MAXSYMS * sizeof(struct SYMBOL));
	voice_tb[voice].name = getarena(i);
	strcpy(voice_tb[voice].name, name);
	voice_tb[voice].staff = nstaff;
	VoiceSet = TRUE;
	return voice;
}

/* ----- voice_init: initialize the general tune characteristics of all potential voices ----- */
static void voice_init(void)
{
	int	i;

	for (i = MAXVOICE; --i >= 0;) {
		voice_tb[i].dlen = dlen;
		voice_tb[i].meter1 = meter1;
		voice_tb[i].meter2 = meter2;
		voice_tb[i].mflag = mflag;
		voice_tb[i].lflag = lflag;
		voice_tb[i].sharps_flats = sharps_flats;
		voice_tb[i].StemDir = 0;
	}
}

/* ----- voice_sw: start/continue parsing of an other voice ----- */
static void voice_sw(int i)
{
	voice_tb[current_voice].nsym = nsym;
	voice_tb[current_voice].nsym0 = nsym0;
	voice_tb[current_voice].end_slur_next_note = end_slur_next_note;
	voice_tb[current_voice].dlen = dlen;
	voice_tb[current_voice].meter1 = meter1;
	voice_tb[current_voice].meter2 = meter2;
	voice_tb[current_voice].mflag = mflag;
	voice_tb[current_voice].lflag = lflag;
	voice_tb[current_voice].sharps_flats = sharps_flats;

	current_voice = i;
	sym = voice_tb[i].sym;
	nsym = voice_tb[i].nsym;
	nsym0 = voice_tb[i].nsym0;
	end_slur_next_note = voice_tb[i].end_slur_next_note;
	dlen = voice_tb[i].dlen;
	meter1 = voice_tb[i].meter1;
	meter2 = voice_tb[i].meter2;
	mflag = voice_tb[i].mflag;
	lflag = voice_tb[i].lflag = lflag;
	sharps_flats = voice_tb[i].sharps_flats;
}

/* ----- get_xref: get xref from string ----- */
static int get_xref(char str[])
{
	int a;
	char *q;

	if (strlen(str)==0) {
		wng("xref string is empty", "");
		return 0;
	}
	q=str;
	while (*q != '\0') {
		if (!isdigit(*q)) {
			wng("xref string has invalid symbols: ", str);
			return 0;
		}
		q++;
	}

	sscanf(str, "%d", &a);

	return a;
}

/* ----- set_meter: set meter from string ---- */
static void set_meter(char str[])
{
	int m1,m2,m1a,m1b,m1c,d,l;
	char *q, buf[8];


	l=strlen(str);
	if (l==0) {
		wng("Empty meter string", "");
		return;
	}
	strncpy(buf, str, 4);
	buf[4] = 0;
	NoMeter = 0;
	if (!stricmp(buf, "none")) {
		/* M:none */
		dlen=EIGHTH;
		meter1 = meter2 = 4;	/* to avoid errors in the symbol spacing functions */
		NoMeter = 1;
		strcpy(meter_top, "free");
	} else if (str[0]=='C') {
		if (str[1]=='|') {
			meter1=2;
			mflag=2;
		} else {
			meter1=4;
			mflag=1;
		}
		meter2=4;
		dlen=EIGHTH;
		strcpy(meter_top,"C");
	} else {
		m1=m2=m1a=m1b=m1c=0;
		strcpy(meter_top, str);
		q=strchr(meter_top,'/');
		if (!q) {
			wng("Cannot identify meter, missing /: ", str);
			m1=m2=1;
			return;
		}
		*q='\0';
		if (strchr(meter_top,'+')) {
			sscanf(str,"%d+%d+%d/", &m1a, &m1b, &m1c);
			m1=m1a+m1b+m1c;
		} else {
			sscanf(str,"%d %d %d/", &m1a, &m1b, &m1c);
			m1=m1a;
			if (m1b>m1)
				m1=m1b;
			if (m1c>m1)
				m1=m1c;
			if (m1>30) {		/* handle things like 78/8 */
				m1a=m1/100;
				m1c=m1-100*m1a;
				m1b=m1c/10;
				m1c=m1c-10*m1b;
				m1=m1a;
				if (m1b>m1)
					m1=m1b;
				if (m1c>m1)
					m1=m1c;
			}
		}

		q++;
		sscanf(q, "%d", &m2);

		if (m1*m2 == 0) { 
			wng("Cannot identify meter: ", str);
			m1=m2=1;
			return;
		}

		d=BASE/m2;
		if (d*m2 != BASE) { 
			wng("Meter not recognized: ", str);
			return;
		}
		meter1=m1;
		meter2=m2;
		dlen=EIGHTH;
		if (4*meter1 < 3*meter2)
			dlen=SIXTEENTH;
		mflag=0;
	}

	/* handle old-style change of default length */
	lflag=0;
	if (str[l-1]=='s') {
		dlen *= 2;
		lflag=1;
	} else if (str[l-1]=='l') {
		dlen /= 2;
		lflag=-1;
	}

	if (verbose>=4) 
		printf("Meter <%s> is %d over %d with default length 1/%d\n", 
			str, meter1, meter2, BASE/dlen);
}

/* ----- get_sharps_flats: set sharps and flats from string ------ */
/* This part was adapted from abc2mtex by Chris Walshaw */
/* updated 03 Oct 1997 Wil Macaulay - support all modes */
/* sets the current clef in the current voice */
static void get_sharps_flats(char s[],
			     int *sh_fl)
{
	int c,sf,j;
	char w[81];

	/* check for "treble" "bass" "alto" with no other information */
	s = get_clef(s);
	if (*s == '\0')
		return;

	c=0;
	bagpipe=0;
	switch (s[c]) {
	case 'F':
		sf = -1;
		break;
	case 'C':
		sf = 0;
		break;
	case 'G':
		sf = 1;
		break;
	case 'D':
		sf = 2;
		break;
	case 'A':
		sf = 3;
		break;
	case 'E':
		sf = 4;
		break;
	case 'B':
		sf = 5;
		break;
	case 'H':
		bagpipe=1;
		c++;
		if (s[c] == 'P')
			sf=0;
		else if (s[c] == 'p')
			sf=2;
		else	wng("unknown bagpipe-like key: ",s);
		break;
	default:
		wng("Using C because key not recognised: ", s);
		sharps_flats=transpose;
		return;
	}
	c++;
	if (s[c] == '#') {
		sf += 7;
		c++;
	} else if (s[c] == 'b') {
		sf -= 7;
		c++;
	}

	/* loop over blank-delimited words: get the next token in lower case */
	for (;;) {
		while (isspace(s[c]))
			c++;
		if (s[c]=='\0')
			break;

		j=0;
		while (s[c] != '\0' && !isspace(s[c])) {
			w[j]=tolower(s[c]); c++; j++;
		}
		w[j]='\0';

		/* now identify this word */

		/* first check for mode specifier */
		if ((strncmp(w,"mix",3)) == 0)
			sf -= 1;
			/* dorian mode on the second note (D in C scale) */
		else if ((strncmp(w,"dor",3)) == 0)
			sf -= 2;
			/* phrygian mode on the third note (E in C scale) */
		else if ((strncmp(w,"phr",3)) == 0)
			sf -= 4;
			/* lydian mode on the fourth note (F in C scale) */
		else if ((strncmp(w,"lyd",3)) == 0)
			sf += 1;
			/* locrian mode on the seventh note (B in C scale) */
		else if ((strncmp(w,"loc",3)) == 0)
			sf -= 5;
			/* major and ionian are the same keysig */
		else if ((strncmp(w,"maj",3)) == 0)
			;
		else if ((strncmp(w,"ion",3)) == 0)
			;
			/* aeolian, m, minor are the same keysig - sixth note (A in C scale) */
		else if ((strncmp(w,"aeo",3)) == 0)
			sf -= 3;
		else if ((strncmp(w,"min",3)) == 0)
			sf -= 3;
		else if ((strcmp(w,"m")) == 0)
			sf -= 3;

		/* check for "bass" "treble" "alto" */
		else if (*get_clef(w) == '\0')
			;		/* clef eaten */

		/* check for "+8" or "-8" */
		/*fixme:does not work with multi-voices*/
		else if (!strcmp(w,"+8"))
			add_pitch = 7;
		else if (!strcmp(w,"-8"))
			add_pitch = -7;
		else	wng("Unknown token in key specifier: ",w);

	}  /* end of loop over blank-delimted words */

	if (verbose >= 4)
		printf("Key	 <%s> gives sharpsflats %d, cleftype %d\n",
		       s, sf, voice_tb[current_voice].clef);

	*sh_fl = sf;
}

/* ----- set_dlen: set default length from string ---- */
static void set_dlen(char str[])
{
	int l1,l2,d;

	l1=0;
	l2=1;
	sscanf(str,"%d/%d ", &l1, &l2);
	if (l1 == 0)
		return;		/* empty string.. don't change default length */

	d=BASE/l2;
	if (d*l2 != BASE) {
		wng("Length incompatible with BASE, using 1/8: ",str);
		dlen=BASE/8;
	} else 	dlen = d*l1;

	if (verbose>=4)
		printf("Dlen  <%s> sets default note length to %d/%d = 1/%d\n", 
			str, dlen, BASE, BASE/dlen);
}

/* ----- append_meter: add meter to list of symbols -------- */
static void append_meter(void)
{
	int kk;

	if ((nsym>0) && (sym[nsym-1].type==TIMESIG))
		kk=nsym-1;
	else	kk=add_sym(TIMESIG);
	sym[kk].u=meter1;
	sym[kk].v=meter2;
	sym[kk].w=mflag;
	sym[kk].text = getarena(strlen(meter_top));
	strcpy(sym[kk].text, meter_top);
/*	 if (vb>=30) printf("append_meter <%s> %d %d %d\n",
		      meter_top,meter1,meter2,mflag); */
}

/* ----- init_parse_params: initialize variables for parsing --- */
static void init_parse_params(void)
{
	insert_btype=0;
	slur=0;
	end_slur_next_note=0;
	nwline=0;
	ntinext=0;
}

/* ----- add_text ---- */
static void add_text(char str[],
		     int type)
{
	if (do_mode!=DO_OUTPUT)
		return;
	if (ntext>=NTEXT) {
		wng("No more room for text line <%s>", str);
		return;
	}
	text[ntext] = getarena(strlen(str));
	strcpy(text[ntext], str);
	text_type[ntext]=type;
	ntext++;
}

/* ----- reset_info ---- */
static void reset_info(struct ISTRUCT *inf) {
	int	i;

	/* reset all info fields except info.xref */
	inf->parts = empty_str;
	inf->area = empty_str;
	inf->book = empty_str;
	inf->ncomp = 0;
	inf->disc = empty_str;
	inf->group = empty_str;
	inf->hist = empty_str;
	inf->info = empty_str;
	inf->len = empty_str;
	inf->key = "C";
	inf->meter = "4/4";
	inf->notes = empty_str;
	inf->orig = empty_str;
	inf->rhyth = empty_str;
	inf->src = empty_str;
/*	inf->title = "(untitled)");	*/
	inf->title = empty_str;
	inf->title2 = empty_str;
	inf->title3 = empty_str;
	inf->trans = empty_str;
	inf->tempo = empty_str;
	/* For U: substitution [JSA] 05/00		*/
	/* ~ @ 0, H-Z @ 1-19, h-w @ 20-35		*/
	for (i = 0; i < 36; i++) inf->DecoTbl[i] = G_DecoTbl[i];
}

/* ----- get_default_info: set info to default, except xref field --- */
static void get_default_info(void) {
	int		i;

	info.parts = default_info.parts;
	info.area = default_info.area;
	info.book = default_info.book;
	info.ncomp = default_info.ncomp;
	info.disc = default_info.disc;
	info.group = default_info.group;
	info.hist = default_info.hist;
	info.info = default_info.info;
	info.len = default_info.len;
	info.key = default_info.key;
	info.meter = default_info.meter;
	info.notes = default_info.notes;
	info.orig = default_info.orig;
	info.rhyth = default_info.rhyth;
	info.src = default_info.src;
	info.title = default_info.title;
	info.title2 = default_info.title2;
	info.title3 = default_info.title3;
	info.trans = default_info.trans;
	info.tempo = default_info.tempo;
	/* For U: substitution [JSA] 05/00		*/
	/* ~ @ 0, H-Z @ 1-19, h-w @ 20-35		*/
	for (i = 0; i < 36; i++) info.DecoTbl[i] = default_info.DecoTbl[i];
}

/* ----- is_info_field: identify any type of info field ---- */
static int is_info_field(char str[])
{
	if (str[1] == ':'
	    && str[0] != '|')		/* not |: at start of music line */
		return 1;
	return 0;
}

/* ----- is_end_line: identify eof ----- */
static int is_end_line(char str[])
{
	if (str[0]=='E' && str[1]=='N' && str[2]=='D')
		return 1;
	return 0;
}

/* ----- is_pseudocomment ----- */
static int is_pseudocomment(char str[])
{
	if (str[0] == '%' && str[1] == '%') return 1;
	return 0;
}

/* ----- is_comment ----- */
static int is_comment(char str[])
{
	if (str[0]=='%'
	    || str[0]=='\\')
		return 1;
	return 0;
}

/* ----- trim_title: move trailing "The" to front ------------ */
static void trim_title(char *s)
{
	char *q, *r;
	char str[301];

	strcpy(str, s);

	if ((q = strchr(str, ',')) != 0) {
		if (*q != '\0') {
			r = q + 1;
			while (isspace(*r))
				r++;
			if (!strcmp(r, "The")) {
				*q = '\0';
				strcpy(s, "The ");
				strcat(s, str);
				return;
			}
		}
	}
}

/* ----- info_field: identify info line, store in proper place	---- */
/* switch within_block: either goes to default_info or info.
   Only xref ALWAYS goes to info. */
static int info_field(char str[]) {
	struct ISTRUCT	*inf;
	int				i;
	char			*t, buff[32];

	decomment_line(str);

	if (!is_info_field(str)) return 0;

	inf = within_block ? &info : &default_info;

	switch (str[0]) {
	case 'X':
		info.xref = strip(&str[2]);
		xrefnum = get_xref(info.xref);
		/* Initialize to default of one staff and one voice. */
		nstaff = 0;
		nvoice = 0;
		current_voice = 0;
		VoiceSet = FALSE;

		return XREF;
	case 'A':
		inf->area = strip(&str[2]);
		break;
	case 'B':
		inf->book = strip(&str[2]);
		break;
	case 'C':
		if (inf->ncomp>=NCOMP)
			wng("Too many composer lines","");
		else {
			inf->comp[inf->ncomp] = strip(&str[2]);
			inf->ncomp++;
		}
		break;
	case 'D':
		inf->disc = strip(&str[2]);
		add_text(&str[2], TEXT_D);
		break;
	case 'E':
		break;
	case 'G':
		inf->group = strip(&str[2]);
		break;
	case 'H':
		inf->hist = strip(&str[2]);
		add_text(&str[2], TEXT_H);
		return HISTORY;
	case 'I':
		inf->info = strip(&str[2]);
		break;
	case 'K':
		inf->key = strip(&str[2]);
		if (!VoiceSet) {
			strcpy(buff, "V:1 clef=treble");
			info_field(buff);
		}
		return KEY;
	case 'L':
		inf->len = strip(&str[2]);
		return DLEN;
	case 'M':
		inf->meter = strip(&str[2]);
		return METER;
	case 'N':
		inf->notes = strip(&str[2]);
		add_text(&str[2], TEXT_N);
		break;
	case 'O':
		inf->orig = strip(&str[2]);
		break;
	case 'P':
		inf->parts = strip(&str[2]);
		return PARTS;
	case 'Q':
		inf->tempo = strip(&str[2]);
		return TEMPO;
	case 'R':
		inf->rhyth = strip(&str[2]);
		break;
	case 'S':
		inf->src = strip(&str[2]);
		break;
	case 'T': {
		t = &str[2];
		numtitle++;
		if (numtitle>3)
			numtitle=3;
		if (numtitle==1)
			t = inf->title = strip(&str[2]);
		else if (numtitle==2)
			t = inf->title2 = strip(&str[2]);
		else /*if (numtitle==3)*/
			t = inf->title3 = strip(&str[2]);
		trim_title(t);
		return TITLE;
	}
	case 'U':
		/* [JSA] 05/00 handle assignable decoration shortcuts, add new decorations. */
		t = &str[2];
		while (isspace(*t)) t++;
		if (*t == '~') {
			i = 0;
		} else {
			if ((*t >= 'H') && (*t <= 'Z')) {
				i = *t - 'G';
			} else {
				if ((*t >= 'h') && (*t <= 'w')) {
					i = (*t - 'h') + 20;
				} else {
					i = -1;
				}
			}
		}
		if (i < 0) {
			printf("Warning non-assignable symbol [%c] in U: field!\n", *t);
			return 0;
		}
		t++;
		inf->DecoTbl[i] = IdentifyDeco(&t);
		break;
	case 'V': {
		int j;
		char *q;

		t = &str[2];
		while (isspace(*t)) t++;

		j = nvoice;
		i = get_voice(t);
		if (i > j) {			/* new voice */
			if (nstaff >= MAXSTAFF) {
				wng("Too many staves","");
				return 0;
			}
			memset(&staff_tb[++nstaff], 0, sizeof staff_tb[0]);
			voice_tb[nvoice].staff = nstaff;
		}
		voice_sw(i);
		if ((q = strstr(t, "clef=")) != 0)
			get_clef(q + 5);
		else {
			switch (voice_tb[current_voice].clef) {
			case TREBLE:
				add_pitch = 0; break;
			case BASS:
				add_pitch = -14; break;
			case ALTO:
				add_pitch = -7; break;
			}
		}

		/* [JSA] for forced stem directions */
		t = &str[2];
		while (isspace(*t)) t++;
		if ((q = strstr(t, "jsastem="))) {
			if (strstr(q+8, "dn")) {
				voice_tb[current_voice].StemDir = -1;
			} else if (strstr(q+8, "up")) {
				voice_tb[current_voice].StemDir = 1;
			} else {
				voice_tb[current_voice].StemDir = 0;
			}
		}
		
#if 0 /*fixme: keep the voice description*/
		inf->voice[i] = strip(t);
#endif
		return VOICE_D;
	}
	case 'W':
		add_text(&str[2], TEXT_W);
		return WORDS;
	case 'Z':
		inf->trans = strip(&str[2]);
		add_text(&str[2], TEXT_Z);
		break;
	default:
		return 0;
	}

	return INFO;
}

/* ----- numeric_pitch ------ */
/* taken from abc2mtex by Chris Walshaw  */
/* Modified to add the cpitch parameter  */
/* that provides pitch adjust to a scalar*/
/* note position (C=0) instead of a      */
/* scalar staff position.  JSA 05/01/00  */
static int numeric_pitch(char note)
{
	switch (note) {
	case 'z':
		return 14;
	case 'C':
	case 'D':
	case 'E':
	case 'F':
	case 'G':
		return note - 'C' + 16 + add_pitch;
	case 'A':
	case 'B':
		return note - 'A' + 21 + add_pitch;
	case 'c':
	case 'd':
	case 'e':
	case 'f':
	case 'g':
		return note - 'c' + 23 + add_pitch;
	case 'a':
	case 'b':
		return note - 'a' + 28 + add_pitch;
	}
	printf("numeric_pitch: cannot identify <%c>\n",
	       note);
	return 0;
}

/* ----- append_meter_change ------ */
static void append_meter_change(void)
{
	int m1,m2,f1,l1;

	m1=meter1;
	m2=meter2;
	f1=mflag;
	l1=lflag;

	set_meter(info.meter);

	/* if meter symbol was changed, write the meter symbol */
	if ((meter1!=m1) || (meter2!=m2) || (mflag!=f1)) {
		if (vb>=30)
			printf("append_meter_change case 1 %d %d %d\n",
				meter1,meter2,mflag);
		append_meter();
	}

	/* if meter symbol unchanged, write if not used to change default length */
	if ((meter1==m1) || (meter2==m2) || (mflag==f1)) {
		if (vb>=30)
			printf("append_meter_change case 2 %d %d %d\n",
				meter1,meter2,mflag);
		if (lflag==l1)
			append_meter();
	}
}

/* ----- append_key_change ------ */
static void append_key_change(void)
{
	int n1,n2,t1,t2,kk,kt;

	n1=sharps_flats; t1=A_SH;	 /* get type and sym number for old key */
	kt = voice_tb[current_voice].clef;
	if (n1<0) {
		n1=-n1; t1=A_FT;
	}
	get_sharps_flats(info.key, &sharps_flats);
	n2=sharps_flats;
	t2=A_SH;			 /* get type and sym number for new key */

	if (voice_tb[current_voice].clef != kt) {
		kk=add_sym(CLEF);
		sym[kk].u = voice_tb[current_voice].clef;
		sym[kk].v = 1;
	}

	if (n2<0) {
		n2=-n2; t2=A_FT;
	}
	if (t1==t2) {		/* here if old and new have same type */
		if (n2>n1) {		 /* more new symbols ..*/
			kk=add_sym(KEYSIG);	/* draw all of them */
			sym[kk].u=1;
			sym[kk].v=n2;
			sym[kk].w=100;
			sym[kk].t=t1;
		} else if (n2<n1) {		/* less new symbols .. */
			kk=add_sym(KEYSIG);	/* draw all new symbols and neutrals */
			sym[kk].u=1;
			sym[kk].v=n1;
			sym[kk].w=n2+1;
			sym[kk].t=t2;
		} else return;
	} else {		/* here for change s->f or f->s */
		kk=add_sym(KEYSIG);	/* neutralize all old symbols */
		sym[kk].u=1;
		sym[kk].v=n1;
		sym[kk].w=1;
		sym[kk].t=t1;
		kk=add_sym(KEYSIG);	/* add all new symbols */
		sym[kk].u=1;
		sym[kk].v=n2;
		sym[kk].w=100;
		sym[kk].t=t2;
	}
	sym[kk].z=sharps_flats;		/* save current keysig with symbol*/
}

/* ----- init_music_line: init sym list with clef, meter, key ------ */
void init_music_line(int voice)
{
	int kk,n,t;
static	int meter_f, btype_f;

	if (verbose>11)
		printf("init_music_line: s_f=%d, i_m=%d, i_b=%d\n",
		       sharps_flats, insert_meter, insert_btype);

	current_voice = voice;

	/* add clef */
	kk=add_sym(CLEF);
	voice_tb[voice].clef = staff_tb[(int) voice_tb[voice].staff].clef;
	sym[kk].u = voice_tb[voice].clef;
	sym[kk].v = 0;

	/* add keysig */
	kk = add_sym(KEYSIG);
	n = voice_tb[voice].sharps_flats;

	t = A_SH;
	if (n < 0) {
		n = -n;
		t = A_FT;
	}
	sym[kk].u=1;
	sym[kk].v=n;
	sym[kk].w=100;
	sym[kk].t=t;
	sym[kk].z=voice_tb[voice].sharps_flats;

	/* add time signature if needed */
	if (insert_meter || (voice != 0 && meter_f)) {
		append_meter();
	}
	if (voice == 0) {
		meter_f = insert_meter;
		insert_meter = 0;
	}

	/* add bar if needed */
	if (insert_btype
	    || (voice != 0 && btype_f)) {
		if (db > 3)
			printf("insert bar, type %d\n", insert_btype);
		kk = add_sym(BAR);
		sym[kk].u = insert_btype;
		sym[kk].v = insert_num;
	}
	if (voice == 0) {
		btype_f = insert_btype;
		insert_btype = 0;
		/* insert_num = 0; ??*/
	}
}

/* ----- handle_escape_sequence ------ */
static void handle_escape_sequence(void)
{
	if (db>3)
		printf("Handle escape sequence <%s>\n", escseq);

	switch (info_field(escseq)) {
	case 0:
		syntax("Unknown escape sequence", p-1);
		break;
	case METER:
		set_meter(info.meter);
		append_meter();
		break;
	case KEY:
		append_key_change();
		break;
	case VOICE_D:
		if (nsym == 0)
			init_music_line(current_voice);
		break;
	}
}

/* ----- parse_uint: parse for unsigned integer ----- */
static int parse_uint(void)
{
	int number,ndig;
	char num[21];

	if (!isdigit(*p))
		return 0;
	ndig=0;
	while (isdigit(*p)) {
		num[ndig]=*p++;
		ndig++;
	}
	num[ndig]=0;
	sscanf(num, "%d", &number);
	if (db>3)
		printf("  parsed unsigned int %d\n", number);
	return number;
}

/* ----- parse_bar: parse for some kind of bar ---- */
static int parse_bar(void)
{
	int k, u, v;

	u = 0;
	v = 0;
	switch (*p) {
	case '[':
		/* special cases: [1 or [2 without a preceeding bar, [| */
		switch (p[1]) {
		case '1':
		case '2':
			u = B_INVIS;
			p++;
			break;
		case '|':	/* code [| for thick-thin bar */
			u = B_FAT1;
			p += 2;
			break;
		default:
			return 0;
		}
		break;

		/* identify valid standard bar types */
	case '|':
		p++;
		switch (*p) {
		case '|':
			u = B_DBL;
			p++;
			break;
		case ':':
			u = B_LREP;
			p++;
			break;
		case ']':		/* code |] for fat end bar */
			u = B_FAT2;
			p++;
			break;
		default:
			u = B_SNGL;
		}
		break;
	case ':':
		p++;
		if (*p == '|') {
			u = B_RREP;
			p++;
		} else if (*p == ':') {
			u = B_DREP;
			p++;
		} else {
			syntax("Syntax error parsing bar", p-1);
			return 0;
		}
		break;
	default:
		return 0;
	}

	/* see if valid bar is followed by specifier for first or second ending */
	switch (*p) {
	case '1':
		v = 1;
		p++;
		break;
	case '2':
		v = 2;
		p++;
		break;
	case '[':
		if (p[1] == '1') {
			v = 1;
			p += 2;
		} else if (p[1] == '2') {
			v = 2;
			p += 2;
		}
		break;
	case ' ':
	case '\t':
		if (p[1] == '[') {
			if (p[2] == '1') {
				v = 1;
				p += 3;
			} else if (p[2] == '2') {
				v = 2;
				p += 3;
			}
		}
		break;
	}
	k = add_sym(BAR);
	sym[k].u = u;
	sym[k].v = v;

/*	 if (db>3) printf ("  parsed bar, type %d, end %d\n", u, v);  */

	return 1;
}

/* ----- parse_space: parse for whitespace ---- */
static int parse_space(void)
{
	int rc;

	rc=0;
	while (isspace(*p)) {
		rc=1;
		p++;
	}
	if (db>3
	    && rc)
		printf("  parsed whitespace\n");
	return rc;
}

/* ----- parse_esc: parse for escape sequence ----- */
static int parse_esc(void)
{
	int nseq;
	char *pp;

	if (*p == '\\') {			/* try for \...\ sequence */
		p++;
		nseq=0;
		while ((*p!='\\') && (*p!=0)) {
			escseq[nseq]=*p;
			nseq++;
			p++;
		}
		if (*p == '\\') {
			p++;
			escseq[nseq]=0;
			if (db>3)
				printf("  parsed esc sequence <%s>\n", escseq);
			return ESCSEQ;
		}
		if (cfmt.breakall)
			return DUMMY;
		if (db>3)
			printf("  parsed esc to EOL.. continuation\n");
		return CONTINUE;
	}

	/* next, try for [..] sequence */
	if ((*p=='[') && (*(p+1)>='A') && (*(p+1)<='Z') && (*(p+2)==':')) {
		pp=p;
		p++;
		nseq=0;
		while ((*p!=']') && (*p!=0)) {
			escseq[nseq]=*p;
			nseq++;
			p++;
		}
		if (*p == ']') {
			p++;
			escseq[nseq]=0;
			if (db>3)
				printf("  parsed esc sequence <%s>\n", escseq);
			return ESCSEQ;
		}
		syntax("Escape sequence [..] not closed", pp);
		return ESCSEQ;
	}
	return 0;
}

/* ----- parse_nl: parse for newline ----- */
static int parse_nl(void)
{
	if ((*p == '\\')&&(*(p+1)=='\\')) {
		p+=2;
		return 1;
	}
	return 0;
}

/* ----- parse_gchord: parse guitar chord, save in buffer ----- */
/****************************************************************
* Changed to recognize "text decorations" with formatting		*
* prefixes [JSA] 05/00											*
****************************************************************/
static void parse_gchord(int *ct, int dtype[10]) {
	char	*q, buff[256], *bp;
	int		n;
	tFIFO	*Fifo;
	BOOL	FormDone;

	if (*p != '"') return;

	q = p;
	p++;	/* Skip past opening double-quote */

	n = 0;
	while (*p && (*p != '"')) {
		buff[n] = *p;
		n++;
		if (n >= 255) {
			syntax("Text/Chord string too long", q);
			return;
		}
		p++;
	}
	buff[n] = '\0';
	if (*p == '"') p++;

	if (*p == 0) {
		syntax("EOL reached while parsing guitar chord", q);
		return;
	}

	/* We now have the entire quoted section copied into buff as a null-terminated	*/
	/* string.  If it is preceded by formatting characters we store it as a text    */
	/* decoration, otherwise it is a guitar chord.									*/
	/* Furthermore, we parse the formatting characters and store them as numeric	*/
	/* and status variables in the FIFO buffers.  By doing this here, instead of	*/
	/* waiting until the music is output, we can interpret them in the context of	*/
	/* the current spacing factors AND when we set up to draw the music we can		*/
	/* more easily preview the values to predict how much vertical space each staff	*/
	/* is going to need.															*/
	if (*ct > 9) return;	/* we can only have ten total decorations */
	bp = buff;
	if ((buff[0] == '^') || (buff[0] == '_') || (buff[0] == '@')) {
		dtype[*ct] = D_STEXT;
		Fifo = &voice_tb[current_voice].TextDecs;
		switch(buff[0]) {
		case '^':
			Fifo->anchor[Fifo->inidx] = TXTA_TOP;
			break;
		case '_':
			Fifo->anchor[Fifo->inidx] = TXTA_BOT;
			break;
		case '@':
			Fifo->anchor[Fifo->inidx] = TXTA_CLAMP;
			break;
		}
	} else if ((buff[0] == '<') || (buff[0] == '>')) {
		dtype[*ct] = D_STEXT;
		Fifo = &voice_tb[current_voice].TextHdDecs;
		if (buff[0] == '<') {
			Fifo->anchor[Fifo->inidx] = TXTA_LT;
		} else {
			Fifo->anchor[Fifo->inidx] = TXTA_RT;
		}
	} else {
		Fifo = &voice_tb[current_voice].Chords;
		if (strchr(buff, '/')) {
			dtype[*ct] = D_GCHORDB;
		} else {
			dtype[*ct] = D_GCHORD;
		}
	}
	Fifo->dx[Fifo->inidx] = Fifo->dy[Fifo->inidx] = 0;
	Fifo->just[Fifo->inidx] = TXTJ_LT;
	if (dtype[*ct] == D_STEXT) {
		/* Staff text get additional positioning characters. */
		bp++;
		FormDone = FALSE;
		while(*bp) {
			switch (*bp) {
			case '<':
				Fifo->dx[Fifo->inidx] -= cfmt.DcDsFont.size * cfmt.StaffTextHFac;
				break;
			case '>':
				Fifo->dx[Fifo->inidx] += cfmt.DcDsFont.size * cfmt.StaffTextHFac;
				break;
			case '^':
				Fifo->dy[Fifo->inidx] += cfmt.DcDsFont.size * cfmt.StaffTextVFac;
				break;
			case '_':
				Fifo->dy[Fifo->inidx] -= cfmt.DcDsFont.size * cfmt.StaffTextVFac;
				break;
			case '@':
				/* We are using any @ after the first character to	*/
				/* indicate the text should be centered.			*/
				Fifo->just[Fifo->inidx] = TXTJ_CN;
				break;
			case '{':
				/* We are using a left curly brace to indicate the	*/
				/* text should be flush right at the note.			*/
				Fifo->just[Fifo->inidx] = TXTJ_RT;
				break;
			case '}':
				/* We are using a right curly brace to indicate the	*/
				/* text should be flush left at the note (default).	*/
				Fifo->just[Fifo->inidx] = TXTJ_LT;
				break;
			default:
				FormDone = TRUE;
			}
			if (FormDone) { 
				break;
			} else {
				bp++;
			}
		}
	}
	if (dtype[*ct] == D_NTEXT) {
		/* Notehead text get additional positioning characters. */
		if (Fifo->anchor[Fifo->inidx] == TXTA_LT) Fifo->just[Fifo->inidx] = TXTJ_RT;
		bp++;
		FormDone = FALSE;
		while(*bp) {
			switch (*bp) {
			case '<':
				Fifo->dx[Fifo->inidx] -= 6 * cfmt.NoteTextFac;
				break;
			case '>':
				Fifo->dx[Fifo->inidx] += 6 * cfmt.NoteTextFac;
				break;
			case '^':
				Fifo->dy[Fifo->inidx] += 6 * cfmt.NoteTextFac;
				break;
			case '_':
				Fifo->dy[Fifo->inidx] -= 6 * cfmt.NoteTextFac;
				break;
			case '@':
				/* We are using any @ after the first character to	*/
				/* indicate the text should be centered.			*/
				Fifo->just[Fifo->inidx] = TXTJ_CN;
				break;
			case '{':
				/* We are using a left curly brace to indicate the	*/
				/* text should be flush right at the note.			*/
				Fifo->just[Fifo->inidx] = TXTJ_RT;
				break;
			case '}':
				/* We are using a right curly brace to indicate the	*/
				/* text should be flush left at the note (default).	*/
				Fifo->just[Fifo->inidx] = TXTJ_LT;
				break;
			default:
				FormDone = TRUE;
			}
			if (FormDone) { 
				break;
			} else {
				bp++;
			}
		}
	}
	if (Fifo->strings[Fifo->inidx]) {
		/* We have a FIFO buffer overflow.  Warn the user */
		printf("Non-fatal error - text decoration FIFO overflow on voice %d.\n", current_voice);
	} else {
		Fifo->strings[Fifo->inidx] = (char *)malloc(strlen(buff) + 1);
		if (Fifo->strings[Fifo->inidx]) {
			strcpy(Fifo->strings[Fifo->inidx], bp);
			Fifo->inidx = (Fifo->inidx + 1) % MAXSTRDECO;
			(*ct)++;
		}
	}
	if (db>3) printf("  parse staff text <%s>\n", bp);
}

/* [JSA] 05/00 -- implement reassignable decoration shortcuts and new decorations. */
static char IdentifyDeco(char **t) {
	char	buff[256];
	char	deco = 0;
	int		i = 0;
	BOOL	Unimplemented, Unknown;

	while(**t && (**t != '!')) (*t)++;
	if (**t) (*t)++;	// First character past opening "!"
	if (!**t) return deco;
	while(**t && (**t != '!') && (i < 255)) {
		buff[i] = **t;
		i++;
		(*t)++;
	}
	buff[i] = '\0';
	Unimplemented = FALSE;
	Unknown = TRUE;
	if (!stricmp(buff, "accent")) { deco = D_ACCENT; Unknown = FALSE; }
	if (!stricmp(buff, "emphasis")) { deco = D_ACCENT; Unknown = FALSE; }
	if (!stricmp(buff, "upbow")) { deco = D_UPBOW; Unknown = FALSE; }
	if (!stricmp(buff, "downbow")) { deco = D_DOWNBOW; Unknown = FALSE; }
	if (!stricmp(buff, "roll")) { deco = D_ROLL; Unknown = FALSE; }
	if (!stricmp(buff, "fermata")) { deco = D_HOLD; Unknown = FALSE; }
	if (!stricmp(buff, "trill")) { deco = D_TRILL; Unknown = FALSE; }
	if (!stricmp(buff, "0")) { deco = D_FINGER0; Unknown = FALSE; }
	if (!stricmp(buff, "1")) { deco = D_FINGER1; Unknown = FALSE; }
	if (!stricmp(buff, "2")) { deco = D_FINGER2; Unknown = FALSE; }
	if (!stricmp(buff, "3")) { deco = D_FINGER3; Unknown = FALSE; }
	if (!stricmp(buff, "4")) { deco = D_FINGER4; Unknown = FALSE; }
	if (!stricmp(buff, "5")) { deco = D_FINGER5; Unknown = FALSE; }
	if (!stricmp(buff, "tenuto")) { deco = D_EMBAR; Unknown = FALSE; }
	if (!stricmp(buff, "invertedfermata")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "d.s.")) { deco = D_DS; Unknown = FALSE; }
	if (!stricmp(buff, "d.c.")) { deco = D_DC; Unknown = FALSE; }
	if (!stricmp(buff, "coda")) { deco = D_CODA; Unknown = FALSE; }
	if (!stricmp(buff, "segno")) { deco = D_SEGNO; Unknown = FALSE; }
	if (!stricmp(buff, "longphrase")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "mediumphrase")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "shortphrase")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "breath")) { deco = D_BREATH; Unknown = FALSE; }
	if (!stricmp(buff, "turn")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "snap")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "thumb")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "open")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "wedge")) { deco = D_WEDGE; Unknown = FALSE; }
	if (!stricmp(buff, "+")) { deco = D_PLUS; Unknown = FALSE; }
	if (!stricmp(buff, "pralltriller")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "mordent")) { deco = D_LOWM; Unknown = FALSE; }
	if (!stricmp(buff, "lowermordent")) { deco = D_LOWM; Unknown = FALSE; }
	if (!stricmp(buff, "uppermordent")) { deco = D_UPM; Unknown = FALSE; }
	if (!stricmp(buff, "repeatbar2")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "repeatbar")) { Unimplemented = TRUE; Unknown = FALSE; }
	if (!stricmp(buff, "fine")) { deco = D_FINE; Unknown = FALSE; }
	if (!stricmp(buff, "sfz")) { deco = D_sfz; Unknown = FALSE; }
	if (!stricmp(buff, "ffff")) { deco = D_ffff; Unknown = FALSE; }
	if (!stricmp(buff, "fff")) { deco = D_fff; Unknown = FALSE; }
	if (!stricmp(buff, "ff")) { deco = D_ff; Unknown = FALSE; }
	if (!stricmp(buff, "f")) { deco = D_f; Unknown = FALSE; }
	if (!stricmp(buff, "mf")) { deco = D_mf; Unknown = FALSE; }
	if (!stricmp(buff, "p")) { deco = D_p; Unknown = FALSE; }
	if (!stricmp(buff, "pp")) { deco = D_pp; Unknown = FALSE; }
	if (!stricmp(buff, "ppp")) { deco = D_ppp; Unknown = FALSE; }
	if (!stricmp(buff, "pppp")) { deco = D_pppp; Unknown = FALSE; }
	if (!stricmp(buff, "diminuendo(")) { deco = D_STDIM; Unknown = FALSE; }
	if (!stricmp(buff, "diminuendo)")) { deco = D_ENDDIM; Unknown = FALSE; }
	if (!stricmp(buff, "crescendo)")) { deco = D_ENDCRESC; Unknown = FALSE; }
	if (!stricmp(buff, "crescendo(")) { deco = D_STCRESC; Unknown = FALSE; }
	if (Unimplemented) printf("Warning: !%s! not implemented - ignored\n", buff);
	if (Unknown) printf("Warning: !%s! not recognized - ignored\n", buff);
	return deco;
}

/* ----- parse_deco: parse for decoration on note ----- */
/* Changed to pass "deco" in as a parameter instead of  */
/* merely returning it because now we have to account   */
/* for text decorations which are parsed in a different */
/* function.                                            */
static void parse_deco(int *n, int dtype[10], unsigned short *tabdeco) { /* [JSA] */
	int		deco, val, i;
	BOOL	Done = FALSE;
	char	sp;

	*tabdeco = 0;	/* String tab modification 12/99 [JSA] */
	while (!Done) {
		deco=0;
		switch (*p) {
		case '~':
			if (DECO_IS_ROLL) {
				deco=D_ROLL;
			} else {
				deco=D_GRACE;
			}
			break;
		/* String tablature modification 12/99 [JSA] */
		case '@':
			p++;
			switch (*p) {
			case 'H': *tabdeco |= TD_HAMMER;	break;
			case 'P': *tabdeco |= TD_PULL; break;
			case '~': *tabdeco |= TD_TRILL; break;
			case 'B': *tabdeco &= ~TD_TYPEMASK; *tabdeco |= (TD_FULL | TD_UP | TD_BEND); break;
			case 'b': *tabdeco &= ~TD_TYPEMASK; *tabdeco |= (TD_UP | TD_BEND); *tabdeco &= ~TD_FULL; break;
			case 'R': *tabdeco &= ~TD_TYPEMASK; *tabdeco |= (TD_FULL | TD_BEND); *tabdeco &= ~TD_UP; break;
			case 'r': *tabdeco &= ~TD_TYPEMASK; *tabdeco |= TD_BEND; *tabdeco &= ~(TD_FULL | TD_UP); break;
			case '>':
			case '<':
				*tabdeco &= ~TD_TYPEMASK;
				sp = *p;
				p++;
				val = strtol(p, &p, 10);
				p--;

				if (val >= 0) {
					if (sp == '>') {
						*tabdeco |= TD_UP;
					} else {
						*tabdeco &= ~TD_UP;
					}
				} else {
					if (sp == '<') {
						*tabdeco |= TD_UP;
					} else {
						*tabdeco &= ~TD_UP;
					}
					val = 0 - val;
				}

				if (val > 0x001F) val = 1;
				*tabdeco |= (val << 4);
				if (sp == '>') {
					*tabdeco |= TD_SLIDETO;
				} else {
					*tabdeco |= TD_SLIDEFROM;
				}
				break;
			default:
				/* Must be a string number */
				val = strtol(p, &p, 10);
				p--;
				if (val > 8) val = 0;
				*tabdeco |= val;
			}
			break;
		case '!':	/* ABC 1.6 std JSA 4/21/00 */
			deco = IdentifyDeco(&p);
			break;
		case '.':	/* ABC 1.6 std JSA 5/00 */
			deco = D_STACC;
			break;
		default:
			/* [JSA] 05/00 Handle reassignable decoration symbols */
			i = -1;
			if (*p == '~') {
				i = 0;
			} else {
				if ((*p >= 'H') && (*p <= 'Z')) {
					i = *p - 'G';
				} else {
					if ((*p >= 'h') && (*p <= 'w')) i = (*p - 'h') + 20;
				}
			}
			if (i >= 0) {
				deco = info.DecoTbl[i];
			} else {
				Done = TRUE;
			}
		}

		if (deco) {
			dtype[*n]=deco;
			(*n)++;
		}

		if (!Done) p++;
	}
}

/* ----- parse_length: parse length specifer for note or rest --- */
int parse_length(void)
{
	int len,fac;

	len=dlen;			/* start with default length */

	if (isdigit(*p)) {		/* multiply note length */
		if ((fac = parse_uint()) != 0)
			len *= fac;
	}

	while (*p=='/') {		/* divide note length */
		p++;
		if (isdigit(*p))
			fac=parse_uint();
		else	fac=2;
		if (len%fac) {
			syntax("Bad length divisor", p-1);
			return len;
		}
		len /= fac;
	}

	return len;
}

/* ----- parse_grace_sequence --------- */
static int parse_grace_sequence(int pgr[],
				int agr[])
{
	char *p0;
	int n;
	int pit, len, acc;

	p0=p;
	if (*p != '{')
		return 0;
	p++;

	n=0;
	while (*p != '}') {
		if (*p == '\0') {
			syntax("Unbalanced grace note sequence", p0);
			return 0;
		}
		if (!isnote(*p)) {
			syntax("Unexpected symbol in grace note sequence", p);
			p++;
		}
		parse_basic_note(&pit, &len, &acc);
		agr[n] = acc;
		pgr[n] = pit;
		/* ignore length */
		n++;
	}

	p++;
	return n;
}

/* ----- identify_note: set head type, dots, flags for note --- */
void identify_note(struct SYMBOL *s,
		   char *q)
{
	int head,base,len,flags,dots;

	if (s->len==0)
		s->len=s->lens[0];
	len=s->len;

	head=H_FULL;
	flags=0;
	if (len>=WHOLE)
		base=WHOLE;
	else if (len>=HALF)
		base=HALF;
	else if (len>=QUARTER)
		base=QUARTER;
	else if (len>=EIGHTH)
		base=EIGHTH;
	else if (len>=SIXTEENTH)
		base=SIXTEENTH;
	else if (len>=THIRTYSECOND)
		base=THIRTYSECOND;
	else if (len>=SIXTYFOURTH)
		base=SIXTYFOURTH;
	else	syntax("Cannot identify head for note",q);

	switch (base) {
	case WHOLE:
		head=H_OVAL; break;
	case HALF:
		head=H_EMPTY; break;
	case SIXTYFOURTH:
		flags=4; break;
	case THIRTYSECOND:
		flags=3; break;
	case SIXTEENTH:
		flags=2; break;
	case EIGHTH:
		flags=1; break;
	}

	dots=0;
	if (len==base)
		dots=0;
	else if (2*len==3*base)
		dots=1;
	else if (4*len==7*base)
		dots=2;
	else if (8*len==15*base)
		dots=3;
	else	syntax("Cannot handle note length for note",q);

/*  printf ("identify_note: length %d gives head %d, dots %d, flags %d\n",
	  len,head,dots,flags);  */

	s->head=head;
	s->dots=dots;
	s->flags=flags;
}

/* ----- double_note: change note length for > or < char --- */
/* Note: if sym[i] is a chord, the length shifted to the following
   note is taken from the first note head. Problem: the crazy syntax 
   permits different lengths within a chord. */
static void double_note(int i,
			int num,
			int sign,
			char *q)
{
	int m,shift,j,len;

	if ((sym[i].type!=NOTE) && (sym[i].type!=REST))
		bug("sym is not NOTE or REST in double_note", 1);

	shift=0;
	len=sym[i].lens[0];
	for (j=0;j<num;j++) {
		len /= 2;
		shift -= sign*len;
		sym[i].len += sign*len;
		for (m=0;m<sym[i].npitch;m++)
			sym[i].lens[m] += sign*len;
	}
	identify_note(&sym[i],q);
	carryover += shift;
}

/* ----- parse_basic_note: parse note or rest with pitch and length --*/
static int parse_basic_note(int *pitch, int *length, int *accidental)
{
	int pit,len,acc;

	acc=pit=0;
	/* look for accidental sign */
	if (*p == '^') {
		if (*(p+1)=='^') {
			acc=A_DS;
			p++;
		} else	acc=A_SH;
	} else if (*p == '=')
		acc=A_NT;
	else if (*p == '_') {
		if (*(p+1)=='_') {
			acc=A_DF;
			p++;
		} else	acc=A_FT;
	}
	if (acc) {
		p++;
		if (!strchr("CDEFGABcdefgab",*p)) {
			syntax("Missing note after accidental", p-1);
			return 0;
		}
	}
	if (!isnote(*p)) {
		syntax("Expecting note", p);
		p++;
		return 0;
	}

	pit = numeric_pitch(*p);	/* basic pitch */
	p++;

	while (*p == '\'') {		/* eat up following ' chars */
		pit += 7;
		p++;
	}

	while (*p == ',') {		/* eat up following , chars */
		pit -= 7;
		p++;
	}

	len=parse_length();

	*pitch=pit;
	*length=len;
	*accidental=acc;

	if (db>3)
		printf("  parsed basic note,"
		       "length %d/%d = 1/%d, pitch %d\n",
		       len,BASE,BASE/len,pit);
	return 1;
}

/* ----- parse_note: parse for one note or rest with all trimmings --- */
static int parse_note()
{
	int k,deco,i,chord,m,type,sl1,sl2,j,adj_sharps_flats;
	int pitch,length,accidental,invis, adj_pitch;
	int ngr,pgr[30],agr[30],dtype[10];
	char *q,*q0;
	unsigned short TabDeco;

	deco = 0;
	parse_gchord(&deco, dtype);						/* guitar chord */
	if (*p == ' ') p++;								/* (for compatibility) */

	ngr=parse_grace_sequence(pgr,agr);				/* grace notes */

	parse_gchord(&deco, dtype);						/* permit chord after graces */

	parse_deco(&deco, dtype, &TabDeco);				/* decorations */

	parse_gchord(&deco, dtype);						/* permit chord after deco */

	chord=0;										
	/* determine if chord */
	q=p;
	if ((*p=='+') || (*p=='[')) {
		chord=1;
		p++;
	}

	type=invis=0;
	switch (*p) {
	case 'x':
	case 'y':
		invis = 1;
	case 'z':
		type = REST;
		break;
	default:
		if (isnote(*p) || 
			(chord && ((*p=='(') || (*p == ')') || (*p == '@')))) {	/* this just for better error msg */
			type=NOTE;
		} else {
			return 0;
		}
		break;
	}

	k=add_sym(type);			/* add new symbol to list */
	
	sym[k].dc.n=deco;			/* copy over pre-parsed stuff */
	sym[k].TabDeco[0] = TabDeco;
	for (i=0;i<deco;i++)
		sym[k].dc.t[i]=dtype[i];
	sym[k].gr.n=ngr;
	for (i=0;i<ngr;i++) {
		sym[k].gr.p[i]=pgr[i];
		sym[k].gr.a[i]=agr[i];
	}
	if (strlen(gch)>0) {
		sym[k].text = getarena(strlen(gch));
		strcpy(sym[k].text, gch);
		gch[0] = '\0';
	}

	q0=p;
	if (type==REST) {
		p++;
		sym[k].lens[0] = parse_length();
		sym[k].npitch=1;
		sym[k].invis=invis;
		if (db>3)
			printf("  parsed rest, length %d/%d = 1/%d\n",
				sym[k].lens[0],BASE,BASE/sym[k].lens[0]);
	} else {
		m=0;				/* get pitch and length */
		sl1=sl2=0;
		for (;;) {
			if (chord && (*p=='(')) {
				sl1++;
				sym[k].sl1[m]=sl1;
				p++;
			}
			deco = 0;
			parse_deco(&deco, dtype, &TabDeco);	/* for extra decorations within chord */
			for (i=0;i<deco;i++) sym[k].dc.t[i+sym[k].dc.n] = dtype[i];
			sym[k].dc.n += deco;

			if (parse_basic_note(&pitch, &length, &accidental) == 0) {
				nsym--;
				return 0;
			}
			sym[k].pits[m] = pitch;
			sym[k].lens[m] = length;
			sym[k].accs[m] = accidental;
			/* We need the following conditional to avoid overwriting a     */
			/* decoration that preceded a chord bracket with zero if there  */
			/* was not a decoration immediately following the bracket and to*/
			/* avoid overwriting the tab decoration for single notes.       */
			if (TabDeco) sym[k].TabDeco[m] = TabDeco;
			/****************************************************************/
			/* Tablature modification, 10/99, J. S. Atchley                 */
			/* At this point the pitch has been adjusted for clef but not   */
			/* for key signature.  Create an "absolute" pitch for later use*/
			/* by tablature routines.                                       */
			adj_pitch = (pitch - 2) % 7;		/* "C" = 0                  */
			if (adj_pitch < 3) {
				adj_pitch = adj_pitch * 2;		/* for c, d, e              */
			} else {
				adj_pitch = (adj_pitch * 2) - 1;/* for f - b                */
			}
			sym[k].abspitch[m] = adj_pitch + (12 * ((pitch - 2) / 7));
			/* Adjust absolute pitch using the current key (sharps_flats).  */
			adj_sharps_flats = sharps_flats + 7;	/* convert -7-7 to 0-15 */
			if (adj_sharps_flats < 0) {
				adj_sharps_flats = 0;
				wng("More than seven flats.", "");
			}
			if (adj_sharps_flats > 14) {
				adj_sharps_flats = 14;
				wng("More than seven sharps.", "");
			}
			sym[k].abspitch[m] += key_convert[adj_sharps_flats][(pitch - 2) % 7];
			/* Adjust absolute pitch for accidentals.                       */
			switch (accidental) {
			case A_DS:
				sym[k].abspitch[m]++;
			case A_SH:
				sym[k].abspitch[m]++;
				break;
			case A_DF:
				sym[k].abspitch[m]--;
			case A_FT:
				sym[k].abspitch[m]--;
				break;
			case A_NT:
				/* Determine from key (sharps_flats) whether a natural */
				/* increases, decreases, or doesn't change the pitch.  */
				if (key_convert[adj_sharps_flats][(pitch - 2) % 7] == -1 ) {
					/* The keysig flats this note, increase the pitch. */
					sym[k].abspitch[m]++;
				} else if (key_convert[adj_sharps_flats][(pitch - 2) % 7] == 1) {
					/* The keysig sharps this note, decrease the pitch.*/
					sym[k].abspitch[m]--;
				}
				break;
			default:
				k = k;
			}
			/* End tablature modification, 10/99, J. S. Atchley             */
			/****************************************************************/

			sym[k].ti1[m]  = sym[k].ti2[m] = 0;
			for (j=0;j<ntinext;j++)
				if (tinext[j]==sym[k].pits[m])
					sym[k].ti2[m]=1;

			if (chord) {
				if (*p=='-') {
					sym[k].ti1[m]=1;
					PendingTies[sym[k].pits[m]] = TRUE;
					p++;
				}
				if (*p==')') {
					sl2++;
					sym[k].sl2[m]=sl2;
					p++;
				}
				if (*p=='-') {
					sym[k].ti1[m]=1;
					PendingTies[sym[k].pits[m]] = TRUE;
					p++;
				}
			}
			m++;

			if (!chord)
				break;
			if ((*p=='+')||(*p==']')) {
				p++;
				break;
			}
			if (*p=='\0') {
				if (chord)
					syntax("Chord not closed", q);
				return type;
			}
		}
		ntinext=0;
		for (j=0;j<m;j++)
			if (sym[k].ti1[j]) {
				tinext[ntinext]=sym[k].pits[j];
				ntinext++;
			}
		sym[k].npitch=m;
	}

	for (m = sym[k].npitch; --m >= 0; ) {	/* add carryover from previous > or < */
		if (sym[k].lens[m]+carryover<=0)
			syntax("> leads to zero or negative note length",q0);
		else	sym[k].lens[m] += carryover;
	}
	carryover=0;

	if (db>3)
		printf("  parsed note, decos %d, text <%s>\n",
			sym[k].dc.n, sym[k].text);
	identify_note(&sym[k],q0);
	return type;
}

/* ----- parse_sym: parse a symbol and return its type -------- */
static int parse_sym()
{
	int i;

	if (parse_bar())
		return BAR;
	if (parse_space())
		return SPACE;
	if (parse_nl())
		return NEWLINE;
	if ((i=parse_esc()))
		return i;
	if ((i=parse_note()))
		return i;
	return 0;
}

/* ----- add_wd ----- */
static char *add_wd(char str[])
{
	char *rp;
	int l;

	if ((l = strlen(str)) == 0)
		return 0;
	rp = getarena(l);
	strcpy(rp, str);
	return rp;
}

/* ----- parse_vocals: parse words below a line of music ----- */
/* Use '^' to mark a '-' between syllables - hope nobody needs '^' ! */
static int parse_vocals(char *line)
{
	int isym;
	char *c,*c1,*w;
	char word[81];

	if ((line[0]!='w') || (line[1]!=':'))
		return 0;
	p0=line;

	isym = nsym0-1;
	c=line+2;
	for (;;) {
		while (isspace(*c))
			c++;
		if (*c=='\0')
			break;
		c1=c;
		if ((*c=='_') || (*c=='*') || (*c=='|') || (*c=='-')) {
			word[0]=*c;
			if (*c=='-')
				word[0]='^';
			word[1]='\0';
			c++;
		} else {
			w=word;
			*w='\0';
			while (*c!=' ' && *c!='\t' && *c!='\0') {
				if (*c=='_' || *c=='*' || *c=='|')
					break;
				if (*c=='-') {
					if (*(c-1) != '\\')
						break;
					w--;
					*w='-';
				}
				*w=*c; w++; c++;
			}
			if (*c=='-') {
				*w='^' ; w++; c++;
			}
			*w='\0';
		}

		/* now word contains a word, possibly with trailing '^',
		   or one of the special characters * | _ - */
		if (!strcmp(word,"|")) {		/* skip forward to next bar */
			isym++;
			while (sym[isym].type!=BAR && isym<nsym)
				isym++;
			if (isym>=nsym) {
				syntax("Not enough bar lines for |",c1);
				break;
			}
		}

		else {					/* store word in next note */
			w=word;
			while (*w!='\0') {		/* replace * and ~ by space */
				if ((*w=='*') || (*w=='~')) *w=' ';
				w++;
			}
			isym++;
			while (sym[isym].type != NOTE
			       && isym < nsym)
				isym++;
			if (isym>=nsym) {
				syntax("Not enough notes for words",c1);
				break;
			}
			sym[isym].wordp[nwline]=add_wd(word);
		}

		if (*c=='\0')
			break;
	}

	nwline++;
	isym = voice_tb[current_voice].staff;
	if (staff_tb[isym].nvocal < nwline)
		staff_tb[isym].nvocal = nwline;
	return 1;
}

/* ----- parse_music_line: parse a music line into symbols ----- */
static int parse_music_line(char line[])
{
	int type,num,nbr;
	char msg[81];
	char *p1,*pmx;
static char qtb[10] = {1, 1, 3, 2, 3, 0, 2, 0, 3, 0};

	if (nsym == 0)
		init_music_line(current_voice);
	nwline=0;
	nsym0=nsym;
	nbr=0;
	p=p0=line;
	pmx=p+strlen(p);

	while (*p != 0) {
		if (p>pmx)
			break;		/* emergency exit */
		type = parse_sym();
		if ((db>4) && type)
			printf("   sym[%d] code (%d,%d)\n",
				nsym-1,sym[nsym-1].type,sym[nsym-1].u);

		switch (type) {
		case NEWLINE:
			if ((nsym>0) && !cfmt.continueall && !cfmt.barsperstaff) {
				if (current_voice == 0)
					sym[nsym-1].eoln=1;
				if (word) {
					sym[last_note].word_end=1;
					word=0;
				}
			}
			break;
		case ESCSEQ:
			handle_escape_sequence();
			break;
		case REST:
			if (pplet) {		/* n-plet can start on rest */
				sym[nsym-1].p_plet=pplet;
				sym[nsym-1].q_plet=qplet;
				sym[nsym-1].r_plet=rplet;
				pplet=0;
			}
			last_note=nsym-1;	/* need this so > and < work */
			p1=p;
			break;
		case NOTE:
			if (!word) {
				sym[nsym-1].word_st=1;
				word=1;
			}
			sym[nsym-1].slur_st+=nbr;
			nbr=0;
			if (end_slur_next_note)
				sym[nsym-1].slur_end++;
			end_slur_next_note=0;

			if (pplet) {		/* start of n-plet */
				sym[nsym-1].p_plet=pplet;
				sym[nsym-1].q_plet=qplet;
				sym[nsym-1].r_plet=rplet;
				pplet=0;
			}
			last_note=last_real_note=nsym-1;
			if (PendingTies[sym[last_note].pits[0]]) {
				PendingTies[sym[last_note].pits[0]] = FALSE;
				sym[last_note].EndTie = TRUE;
			}
			p1=p;
			break;
		case BAR:
		case SPACE:
			if (word) {
				if (last_real_note>0)
					sym[last_real_note].word_end=1;
				word=0;
			}
			break;
		case 0:				/* no type */
			switch (*p) {
			case '-':		/* a-b tie */
				sym[last_note].slur_st++;
				PendingTies[sym[last_note].pits[0]] = TRUE;
				end_slur_next_note=1;
				p++;
				break;
			case '(':
				p++;
				if (isdigit(*p)) {
					pplet = *p-'0';
					if ((qplet = qtb[pplet]) == 0)
						qplet = meter1 % 3 == 0 ? 3 : 2;
					rplet = pplet;
					p++;
					if (*p == ':') {
						p++;
						if (isdigit(*p)) {
							qplet=*p-'0';
							p++;
						}
						if (*p == ':') {
							p++;
							if (isdigit(*p)) {
								rplet=*p-'0';
								p++;
							}
						}
					}
				} else	nbr++;
				break;
			case ')':
				if (last_note>0)
					sym[last_note].slur_end++;
				else	syntax("Unexpected symbol",p);
				p++;
				break;
			case '>':
				num=1;
				p++;
				while (*p == '>') {
					num++; p++;
				}
				if (last_note<0)
					syntax("No note before > sign", p);
				else	double_note(last_note, num, 1, p1);
				break;
			case '<':
				num=1;
				p++;
				while (*p == '<') {
					num++; p++;
				}
				if (last_note<0)
					syntax("No note before < sign", p);
				else	double_note(last_note, num, -1, p1);
				break;
			case '*':		/* ignore stars for now  */
			case '!':		/* ditto for '!' */
				p++;
				break;
			default:
				if (*p != '\0')
					sprintf(msg, "Unreexpected symbol \'%c\'", *p);
				else	sprintf(msg, "Unexpected end of line");
				syntax(msg, p);
				p++;
				break;
			}
		}
	}

	/* maybe set end-of-line marker, if symbols were added */
	if (nsym>nsym0) {
		if (type == CONTINUE
		    || cfmt.barsperstaff
		    || cfmt.continueall)
			sym[nsym-1].eoln = 0;
		else	sym[nsym-1].eoln = 1;
	}

	/* break words at end of line */
	if (word && sym[nsym-1].eoln) {
		sym[last_note].word_end=1;
		word=0;
	}

	return TO_BE_CONTINUED;
}

/* ----- is_selected: check selection for current info fields ---- */
static int is_selected(char xref_str[],
		       int npat,
		       char pat[][STRL1],
		       int select_all,
		       int search_field)
{
	int i,a,b,m;

	if (verbose >= 99) printf("is_selected(%s, %d, s[][], %d, %d)\n", xref_str, npat, select_all, search_field);
	/* true if select_all or if no selectors given */
	if (select_all)
		return 1;
	if (isblankstr(xref_str) && npat == 0)
		return 1;

	for (i=0;i<npat;i++) {		/*patterns */
		if (search_field==S_COMPOSER)
			m=match(info.comp[0], pat[i]);	/*fixme*/
		else if (search_field==S_SOURCE)
			m=match(info.src,pat[i]);
		else if (search_field==S_RHYTHM)
			m=match(info.rhyth,pat[i]);
		else {
			m=match(info.title,pat[i]);
			if (!m && (numtitle>=2))
				m=match(info.title2,pat[i]);
			if (!m && (numtitle>=3))
				m=match(info.title3,pat[i]);
		}
		if (m)
			return 1;
	}

	/* check xref against string of numbers */
	p = xref_str;
	while (*p != 0) {
		parse_space();
		a=parse_uint();
		if (!a)
			return 0;	/* can happen if invalid chars in string */
		parse_space();
		if (*p == '-') {
			p++;
			parse_space();
			b=parse_uint();
			if (!b) {
				if (xrefnum>=a)
					return 1;
			} else	for (i=a;i<=b;i++)
				if (xrefnum==i)
					return 1;
		} else {
			if (xrefnum==a)
				return 1;
		}
		if (*p == ',')
			p++;
	}
	return 0;
}

/* ----- rehash_selectors: split selectors into patterns and xrefs -- */
int rehash_selectors(char sel_str[],
		     char xref_str[],
		     char pat[][STRL1])
{
	char *q;
	char arg[501];
	int i,npat;

	npat=0;
	xref_str[0] = '\0';
	q=sel_str;

	i=0;
	for (;;) {
		if (*q==' ' || *q=='\t' || *q=='\0') {
			arg[i]='\0';
			i=0;
			if (!isblankstr(arg)) {
				if (arg[0]=='-')	/* skip any flags */
					;
				else if (is_xrefstr(arg)) {
					strcat(xref_str, arg);
					strcat(xref_str, " ");
				} else {		/* pattern with * or + */
					if ((strchr(arg,'*')) || (strchr(arg,'+'))) {
						strcpy(pat[npat],arg);
					} else {		/* simple pattern */
						strcpy(pat[npat],"*");
						strcat(pat[npat],arg);
						strcat(pat[npat],"*");
					}
					npat++;
				}
			}
		} else {
			arg[i]=*q;
			i++;
		}
		if (*q=='\0')
			break;
		q++;
	}
	return npat;
}

/* ----- decomment_line: cut off after % ----- */
static void decomment_line(char *p)
{
	int i;

	for (i = strlen(p); --i >= 0; )
		if (*p++ == '%') {
			p[-1] = '\0';
			break;
		}
}

/* ----- get_line: read line, do first operations on it ----- */
static int get_line(FILE *fp,
		    char ln[])
{
	int l;

	ln[0] = '\0';
	if (feof(fp))
		return 0;

	fgets(ln, BSIZE, fp);
	linenum++;
	l=strlen(ln);
	if (l>STRL) {
		if (verbose<=2)
			printf("\n");
		printf("++++ Line %d too long, truncate from %d to %d chars\n",
			linenum, l, STRL);
		l=STRL-1;
		ln[l]='\0';
	}
	if (is_end_line(ln))
		return 0;
	if (ln[l-1]=='\n')
		ln[l-1]='\0';

	if (verbose >= 5 || vb >= 10)
		printf("%3d %s\n", linenum, ln);

	return 1;
}

/* ----- read_line: returns type of line scanned --- */
static int read_line(FILE *fp, int do_music, char line[BSIZE]) {
	int type,s0;

	if (!get_line(fp, line)) return E_O_F;

	if (isblankstr(line)) return BLANK;
	if (is_pseudocomment(line)) return PSCOMMENT;
	if (is_comment(line)) return COMMENT;
	decomment_line(line);

	if ((type = info_field(line)) != 0) {
		/* skip after history field. Nightmarish syntax, that. */
		if (type != HISTORY) {
			if (type == XREF) current_voice = 0;
			return type;
		}
		for (;;) {
			if (!get_line(fp,line)) return E_O_F;
			if (isblankstr(line)) return BLANK;
			if (is_info_field(line)) break;
			add_text(line, TEXT_H);
		}
		return info_field(line);
	}

	if (!do_music) return COMMENT;

	if (parse_vocals(line)) return MWORDS;

	s0 = nsym;
	type = parse_music_line(line);
	if (db > 1) printf("  parsed music symbols %d to %d\n", s0, nsym);

	return type;
}

/* ----- do_index: print index of abc file ------ */
void do_index(FILE *fp,
	      char xref_str[],
	      int npat,
	      char pat[][STRL1],
	      int select_all,
	      int search_field)
{
	int type,within_tune;
	char line[BSIZE];

	linenum=0;
	verbose=vb;
	numtitle=0;
	write_history=0;
	within_tune=within_block=do_this_tune=0;
	reset_info(&default_info);
	info=default_info;

	for (;;) {
		if (!get_line(fp, line))
			break;
		if (is_comment(line))
			continue;
		decomment_line(line);
		type=info_field(line);

		switch (type) {
		case XREF:
			if (within_block)
				printf("++++ Tune %d not closed properly \n",
				       xrefnum);
			clrarena();
			numtitle=0;
			within_tune=0;
			within_block=1;
			ntext=0;
			break;

		case KEY:
			if (!within_block)
				break;
			if (!within_tune) {
				tnum2++;
				if (is_selected(xref_str,npat,pat,select_all,search_field)) {
					get_sharps_flats(info.key, &sharps_flats);
					set_meter(info.meter);
					set_dlen(info.len);
					printf("  %-4d %-5s %-4s", xrefnum, info.key, info.meter);
					if (search_field==S_SOURCE)
						printf("  %-15s", info.src);
					else if (search_field==S_RHYTHM)
						printf("  %-8s", info.rhyth);
					else if (search_field==S_COMPOSER)
						printf("  %-15s", info.comp[0]);
					if (numtitle >= 1) {
						printf("  %s", info.title);
						if (numtitle >= 2) {
							printf(" - %s", info.title2);
							if (numtitle >= 3)
								printf(" - %s", info.title3);
						}
					}

					printf("\n");
					tnum1++;
				}
				within_tune=1;
			}
			break;
		}

		if (isblankstr(line)) {
			if (within_block && !within_tune)
				printf("++++ Header not closed in tune %d\n", xrefnum);
			within_tune=0;
			within_block=0;
			info=default_info;
		}
	}
	if (within_block && !within_tune)
		printf("++++ Header not closed in for tune %d\n", xrefnum);
}

/* ----- reset_music_line: init pars for parsing music line ------ */
static void reset_music_line(void)
{
	/* nsym=0 tells parser to initialize line with clef and key */
	nsym = 0;
	word = 0;
	carryover = 0;
	last_note = last_real_note = -1;
	pplet = qplet = rplet = 0;
	gch[0] = '\0';
	insert_meter = 1;
	nsym0 = 0;
	reset_gen();
}

/* ----- check_selected ----- */
static void check_selected(FILE *fp,
			   char xref_str[],
			   int npat,
			   char pat[][STRL1],
			   int sel_all,
			   int search_field)
{
	if (!do_this_tune) {
		if (is_selected(xref_str,npat,pat,sel_all,search_field)) {
			do_this_tune=1;
			verbose=vb;
			clear_buffer();
/* set to 0 to permit staff breaks in a tune */
			use_buffer = !cfmt.ManualPages;		/* let user control this [JSA] */
			if (epsf) use_buffer = 1;
			write_tunetop();
		}
	}
}

/* ----- process_file ----- */
void process_file(FILE *fpin,
		  FILE *fpout,
		  char xref_str[],
		  int npat,
		  char pat[][STRL1],
		  int sel_all,
		  int search_field)
{
	char line[BSIZE];
	int type;
static char *type_txt[] = {
	"",
	"COMMENT",
	"MUSIC",
	"TO_BE_CONTINUED",
	"E_O_F",
	"INFO",
	"TITLE",
	"METER",
	"PARTS",
	"KEY",
	"XREF",
	"DLEN",
	"HISTORY",
	"BLANK",
	"WORDS",
	"MWORDS",
	"PSCOMMENT",
	"TEMPO",
	"VOICE_D"
};
	int	i, v;
	tFIFO	*Fifo, *HdFifo, *ChdFifo;

	for (i = 0; i < MAXPITCHVAL; i++) PendingTies[i] = FALSE;
	within_tune=within_block=do_this_tune=0;
	linenum=0;
	numtitle=0;
	reset_info(&default_info);
	info=default_info;
	verbose=0;
	if (vb>=20)
		db=3;
	else if (vb>=25)
		db=5;

	for (;;) {
		type = read_line(fpin, do_this_tune, line);
		if ((vb>15) || ((verbose>10)&&within_block)) {
			printf("process_line, type %d %s\n", type,
			       (unsigned) type >= sizeof type_txt / sizeof type_txt[0]
			       ? "UNKNOWN"
			       : type_txt[type]);
		}
		switch (type) {
		case PSCOMMENT:
			process_pscomment(fpin, fpout, line);
			break;
		case XREF:			/* start of new block */
			if (!epsf) write_buffer(fpout);	/* flush stuff left from %% lines */
			if (within_block) printf("\n++++ Last tune not closed properly\n");
			/* Clear the "^text" FIFO objects. */
			for (v = 0; v < MAXVOICE; v++) {
				Fifo = &voice_tb[v].TextDecs;
				HdFifo = &voice_tb[v].TextHdDecs;
				ChdFifo = &voice_tb[v].Chords;
				for (i = 0; i < MAXSTRDECO; i++) {
					if (Fifo->strings[i]) free(Fifo->strings[i]);
					if (HdFifo->strings[i]) free(HdFifo->strings[i]);
					if (ChdFifo->strings[i]) free(HdFifo->strings[i]);
					Fifo->strings[i] = HdFifo->strings[i] = ChdFifo ->strings[i] = NULL;
				}
				Fifo->inidx = Fifo->outidx = 0;
				HdFifo->inidx = HdFifo->outidx = 0;
				ChdFifo->inidx = ChdFifo->outidx = 0;
			}
			clrarena();
			get_default_info();
			within_block = 1;
			within_tune = 0;
			do_this_tune = 0;
			numtitle=0;
			ntext=0;
			init_pdims();
			cfmt=dfmt;
			NoMeter = FALSE;
			break;
		case TITLE:
		case TEMPO:
			if (!within_block)
				break;
			if (within_tune) {		/* title or tempo within tune */
				if (do_this_tune) {
					output_music(fpout);
					if (type == TITLE)
						write_inside_title();
					else	write_inside_tempo();
					reset_music_line();
				}
			} else	check_selected(fpout, xref_str, npat, pat,
					       sel_all, search_field);
			break;
		case KEY:
			if (!within_block)
				break;
			if (within_tune) {		/* key change within tune */
				if (!do_this_tune)
					break;
				if (nsym>0)
					append_key_change();
				else	get_sharps_flats(info.key,
							 &sharps_flats);
			} else {			/* end of header.. start now */
				check_selected(fpout, xref_str, npat, pat,
					       sel_all, search_field);
				if (do_this_tune) {
					tunenum++;
					if (verbose>=3)
						printf("---- start %d (%s) ----\n",
						       xrefnum, info.title);
					fflush(stdout);
					get_sharps_flats(info.key,
							 &sharps_flats);
					set_meter(info.meter);
					set_dlen(info.len);
					check_margin(cfmt.leftmargin);
					write_heading();
					init_parse_params();
					reset_music_line();
					voice_init();
				}
				within_tune = 1;
			}
			break;
		case METER:
			if (!within_block) break;
			if (do_this_tune && within_tune) {
				if (nsym > 0 && !sym[nsym-1].eoln)
					append_meter_change();
				else {
					int voice_s, i;

					voice_s = current_voice;
					set_meter(info.meter);
					for (i = 0; i <= nvoice; i++) {
						voice_sw(i);
						if (nsym == 0) init_music_line(i);
						append_meter();
					}
					voice_sw(voice_s);
				}
			}
			break;

		case DLEN:
			if (!within_block)
				break;
			if (do_this_tune && within_tune) {
				set_dlen(info.len);
				if (nsym == 0 || sym[nsym-1].eoln
				    || sym[nsym-2].eoln) {	/* (may have a keysig) */
					/*fixme: have some other test for setting all the voices*/
					int i;

					for (i = MAXVOICE; --i >= 0; )
						voice_tb[i].dlen = dlen;
				}
			}
			break;
		case PARTS:
			if (!within_block) break;
			if (do_this_tune && within_tune) {
				output_music(fpout);
				reset_music_line();
				write_parts();
			}
			break;
		case BLANK:			/* end of block or file */
		case E_O_F:
			if (do_this_tune) {
				output_music(fpout);
				put_words(fpout);
				if (cfmt.ShowSource) PutSource(fpout);
				if (cfmt.writehistory) put_history(fpout);
				if (epsf) {
					FILE *feps;
					char fnm[81], finf[MAXINF];

					close_output_file();
					if (choose_outname) {
						epsf_title(info.title, fnm);
						strcat(fnm,".eps");
					} else {
						nepsf++;
						sprintf(fnm, "%s%03d.eps", outf, nepsf);
					}
					sprintf(finf, "%s (%d)", in_file[0], xrefnum);
					if ((feps = fopen(fnm,"w")) == NULL)
						rx("Cannot open output file ", fnm);
					init_ps(feps, finf, 1,
						cfmt.leftmargin-5, posy+bposy-5,
						cfmt.leftmargin+cfmt.staffwidth+5,
						cfmt.pageheight-cfmt.topmargin);
					init_epsf(feps);
					write_buffer(feps);
					printf("\n[%s] %s", fnm, info.title);
					close_epsf(feps);
					fclose(feps);
					in_page=0;
					init_pdims();
				} else {
					buffer_eob(fpout);
					write_buffer(fpout);
					if ((verbose==0) && (tunenum%10==0))
						printf(".");
					if (verbose==2)
						printf("%s - ", info.title);
				}
				verbose=0;
			}
			info = default_info;
			if (within_block && !within_tune)
				printf("\n++++ Header not closed in tune %d\n", xrefnum);
			within_tune = within_block = do_this_tune = 0;
			break;
		}

		if (type == E_O_F) break;
	}
	if (!epsf) {
		buffer_eob(fpout);
		write_buffer(fpout);
	}
}
