/*
 * Generic ABC parser.
 *
 * This file is part of abcm2ps.
 *
 * Copyright (C) 1998-2021 Jean-François Moine (http://moinejf.free.fr)
 * Adapted from abc2ps, Copyright (C) 1996-1998 Michael Methfessel
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 */

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "abcm2ps.h"

/* global values */
int severity;			/* error severity */

static int ulen;		/* unit note length set by M: or L: */
static short meter;		/* upper value of time sig for n-plets */
static unsigned char microscale; /* current microtone scale */
static signed char vover;	/* voice overlay (1: single bar, -1: multi-bar */
static char lyric_started;	/* lyric started */
static char *gchord;		/* guitar chord */
static struct decos dc;		/* decorations */
static struct SYMBOL *deco_start; /* 1st note of the line for d: / s: */
static struct SYMBOL *deco_cont; /* current symbol when d: / s: continuation */

static int g_abc_vers, g_ulen, g_microscale;
static char g_char_tb[128];
static char *g_deco_tb[128];		/* global decoration names */
static unsigned short g_micro_tb[MAXMICRO]; /* global microtone values */

static char *abc_fn;		/* current source file name */
static int linenum;		/* current source line number */
static int colnum;		/* current source column number */
static char *abc_line;		/* line being parsed */
static struct SYMBOL *last_sym;	/* last symbol for errors */

static short nvoice;		/* number of voices (0..n-1) */
struct VOICE_S *curvoice;	/* current voice while parsing */

struct parse parse;

/* char table for note line parsing */
#define CHAR_BAD 0
#define CHAR_IGN 1
#define CHAR_NOTE 2
#define CHAR_GR_ST 3
#define CHAR_DECO 4
#define CHAR_GCHORD 5
#define CHAR_BSLASH 6
#define CHAR_OBRA 7
#define CHAR_BAR 8
#define CHAR_OPAR 9
#define CHAR_VOV 10
#define CHAR_SPAC 11
#define CHAR_MINUS 12
#define CHAR_CPAR 13
#define CHAR_BRHY 14
#define CHAR_DECOS 15
#define CHAR_SLASH 16
#define CHAR_GR_EN 17
#define CHAR_LINEBREAK 18
static char char_tb[256] = {
	0, 0, 0, 0, 0, 0, 0, 0,				/* 00 - 07 */
	0, CHAR_SPAC, CHAR_LINEBREAK, 0, 0, 0, 0, 0,	/* 08 - 0f */
	0, 0, 0, 0, 0, 0, 0, 0,				/* 10 - 17 */
	0, 0, 0, 0, 0, 0, 0, 0,				/* 18 - 1f */
	CHAR_SPAC, CHAR_DECOS, CHAR_GCHORD, CHAR_BAD,	/* (sp) ! " # */
	CHAR_BAD, CHAR_BAD, CHAR_VOV, CHAR_BAD, 	/* $ % & ' */
	CHAR_OPAR, CHAR_CPAR, CHAR_BAD, CHAR_DECOS, 	/* ( ) * + */
	CHAR_BAD, CHAR_MINUS, CHAR_DECO, CHAR_SLASH, 	/* , - . / */
	CHAR_BAD, CHAR_BAD, CHAR_BAD, CHAR_BAD, 	/* 0 1 2 3 */
	CHAR_BAD, CHAR_BAD, CHAR_BAD, CHAR_BAD, 	/* 4 5 6 7 */
	CHAR_BAD, CHAR_BAD, CHAR_BAR, CHAR_BAD, 	/* 8 9 : ; */
	CHAR_BRHY, CHAR_NOTE, CHAR_BRHY, CHAR_BAD, 	/* < = > ? */
	CHAR_BAD, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, 	/* @ A B C */
	CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, 	/* D E F G */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* H I J K */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* L M N O */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* P Q R S */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* T U V W */
	CHAR_NOTE, CHAR_DECO, CHAR_NOTE, CHAR_OBRA, 	/* X Y Z [ */
	CHAR_BSLASH, CHAR_BAR, CHAR_NOTE, CHAR_NOTE, 	/* \ ] ^ _ */
	CHAR_IGN, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, 	/* ` a b c */
	CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, 	/* d e f g */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* h i j k */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* l m n o */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* p q r s */
	CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, 	/* t u v w */
	CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, CHAR_GR_ST, 	/* x y z { */
	CHAR_BAR, CHAR_GR_EN, CHAR_DECO, CHAR_BAD, 	/* | } ~ (del) */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* 80 - 8f */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* 90 - 9f */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* a0 - af */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* b0 - bf */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* c0 - cf */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* d0 - df */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* e0 - ef */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/* f0 - ff */
};

static const char all_notes[] = "CDEFGABcdefgab";

static int parse_info(char *p);
static char *parse_gchord(char *p);
static int parse_line(char *p);
static char *parse_note(char *p,
			int flags);
static void syntax(char *msg, char *q);
static void vover_new(void);

/* -- abcMIDI like errors -- */
static void print_error(char *s, int col)
{
	if (col >= 0)
		fprintf(stderr, "%s:%d:%d: error: %s\n", abc_fn, linenum, col, s);
	else
		fprintf(stderr, "%s:%d: error: %s\n", abc_fn, linenum, s);
}

/* -- new symbol -- */
static struct SYMBOL *abc_new(int type, char *text)
{
	struct SYMBOL *s;

	s = getarena(sizeof(struct SYMBOL));
	memset(s, 0, sizeof(struct SYMBOL));
	if (text) {
		s->text = getarena(strlen(text) + 1);
		strcpy(s->text, text);
	}
	if (!parse.last_sym) {
		parse.first_sym = s;
	} else {
		if ((s->abc_next = parse.last_sym->abc_next) != NULL)
			s->abc_next->abc_prev = s;
		parse.last_sym->abc_next = s;
		s->abc_prev = parse.last_sym;
	}
	last_sym = parse.last_sym = s;
	s->abc_type = type;
	s->state = parse.abc_state;
	s->fn = abc_fn;
	s->linenum = linenum;
	s->colnum = colnum;
	return s;
}

/* -- parse an ABC line -- */
void abc_parse(char *p, char *fname, int ln)
{
	abc_fn = fname;
	linenum = ln;
	abc_line = p;

	/* parse the music line */
	switch (parse_line(p)) {
	case 2:				/* start of tune (X:) */
		g_abc_vers = parse.abc_vers;
		g_ulen = ulen;
		g_microscale = microscale;

		meter = 2;
		memcpy(g_char_tb, char_tb, sizeof g_char_tb);
		memcpy(g_deco_tb, parse.deco_tb, sizeof g_deco_tb);
		memcpy(g_micro_tb, parse.micro_tb, sizeof g_micro_tb);
		break;
	case 1:				/* end of tune */
		if (parse.first_sym) {
			do_tune();
			parse.first_sym = parse.last_sym = NULL;
		}
		parse.abc_state = ABC_S_GLOBAL;
		parse.abc_vers = g_abc_vers;
		ulen = g_ulen;
		microscale = g_microscale;
		memcpy(char_tb, g_char_tb, sizeof g_char_tb);
		memcpy(parse.deco_tb, g_deco_tb, sizeof parse.deco_tb);
		memcpy(parse.micro_tb, g_micro_tb, sizeof parse.micro_tb);
		lvlarena(0);
		if (dc.n > 0)
			syntax("Decoration without symbol", 0);
		dc.n = 0;
		break;
	}
}

/* treat the end of file */
void abc_eof(void)
{
//	if (parse.abc_state == ABC_S_HEAD)
//		severity = 1;
	do_tune();
	parse.first_sym = parse.last_sym = NULL;
	if (parse.abc_state != ABC_S_GLOBAL) {
		parse.abc_vers = g_abc_vers;
		ulen = g_ulen;
		microscale = g_microscale;
		memcpy(char_tb, g_char_tb, sizeof g_char_tb);
	}
}

/* -- treat the broken rhythm '>' and '<' -- */
static void broken_rhythm(struct SYMBOL *s,
			  int num)	/* >0: do dot, <0: do half */
{
	struct notes *notes = &s->u.note;
	int l, m, n;

	num *= 2;
	if (num > 0) {
		if (num == 6)
			num = 8;
		n = num * 2 - 1;
		for (m = 0; m <= s->nhd; m++)
			notes->notes[m].len = (notes->notes[m].len * n) / num;
	} else {
		n = -num;
		if (n == 6)
			n = 8;
		for (m = 0; m <= s->nhd; m++)
			notes->notes[m].len /= n;
	}
	l = notes->notes[0].len;
	for (m = 1; m <= s->nhd; m++)
		if (notes->notes[m].len < l)
			l = notes->notes[m].len;
}

/* -- check for the '!' as end of line (ABC2Win) -- */
static int check_nl(char *p)
{
	while (*p != '\0') {
		switch (*p++) {
		case '!':
			return 0;
		case '|':
		case '[':
		case ':':
		case ']':
		case ' ':
		case '\t':
			return 1;
		}
	}
	return 1;
}

/* -- parse extra K: or V: definitions (clef, octave and microscale  -- */
static char *parse_extra(char *p,
			char **p_name,
			char **p_middle,
			char **p_stlines,
			char **p_scale,
			char **p_octave,
			char **p_cue,
			char **p_map)
{
	for (;;) {
		if (strncmp(p, "clef=", 5) == 0
		 || strncmp(p, "bass", 4) == 0
		 || strncmp(p, "treble", 6) == 0
		 || strncmp(p, "alto", 4) == 0
		 || strncmp(p, "tenor", 5) == 0
		 || strncmp(p, "perc", 4) == 0) {
			if (*p_name)
				syntax("Double clef name", p);
			*p_name = p;
		} else if (strncmp(p, "microscale=", 11) == 0
			|| strncmp(p, "uscale=", 7) == 0) {
			int i;

			p += p[0] == 'm' ? 11 : 7;
			i = atoi(p);
			if (i < 4 || i >= 256)
				syntax("Invalid value in microscale=", p);
			else
				microscale = i;
		} else if (strncmp(p, "middle=", 7) == 0
			|| strncmp(p, "m=", 2) == 0) {
			if (*p_middle)
				syntax("Double clef middle", p);
			*p_middle = p + (p[1] == '=' ? 2 : 7);
		} else if (strncmp(p, "octave=", 7) == 0) {
			if (*p_octave)
				syntax("Double octave=", p);
			*p_octave = p + 7;
		} else if (strncmp(p, "stafflines=", 11) == 0) {
			int l;
			char *q;

			if (*p_stlines)
				syntax("Double stafflines", p);
			p += 11;
			if (isdigit((unsigned char) *p)) {
				switch (atoi(p)) {
				case 0: *p_stlines = "..."; break;
				case 1: *p_stlines = "..|"; break;
				case 2: *p_stlines = ".||"; break;
				case 3: *p_stlines = ".|||"; break;
				case 4: *p_stlines = "||||"; break;
				case 5: *p_stlines = "|||||"; break;
				case 6: *p_stlines = "||||||"; break;
				case 7: *p_stlines = "|||||||"; break;
				case 8: *p_stlines = "||||||||"; break;
				default:
					syntax("Bad number of lines", p);
					break;
				}
			} else {
				q = p;
				while (!isspace((unsigned char) *p) && *p != '\0')
					p++;
				l = p - q;
				*p_stlines = getarena(l + 1);
				strncpy(*p_stlines, q, l);
				(*p_stlines)[l] = '\0';
			}
		} else if (strncmp(p, "staffscale=", 11) == 0) {
			if (*p_scale)
				syntax("Double staffscale", p);
			*p_scale = p + 11;
		} else if (strncmp(p, "cue=", 4) == 0) {
			if (*p_cue)
				syntax("Double cue", p);
			*p_cue = p + 4;
		} else if (strncmp(p, "map=", 4) == 0) {
			if (*p_map)
				syntax("Double map", p);
			*p_map = p + 4;
//		} else if (strncmp(p, "transpose=", 10) == 0
//			|| strncmp(p, "t=", 2) == 0) {
//			;		/* ignored - abcMIDI */
		} else {
			break;
		}
		while (!isspace((unsigned char) *p) && *p != '\0')
			p++;
		while (isspace((unsigned char) *p))
			p++;
		if (*p == '\0')
			break;
	}
	return p;
}

/* -- parse a decoration 'xxx<decosep>' -- */
static char *get_deco(char *p,
		      unsigned char *p_dc)
{
	char *q, sep, **t;
	unsigned i, l;

	*p_dc = 0;
	q = p;
	sep = q[-1];
	if (char_tb[(unsigned char) sep] == CHAR_DECOS) {
		if (sep == '+') {
			if (*p == '+' && p[1] == '+')
				p++;		/* special case "+++" */
		}
	} else {
		sep = '\0';			/* Barfly U: */
	}
	while (*p != sep) {
		if (*p == '\0') {
			syntax("Decoration not terminated", q);
			return p;
		}
		p++;
	}
	l = p - q;
	if (*p == sep)
		p++;
	for (i = 1, t = &parse.deco_tb[1];
	     *t && i < DC_NAME_SZ;
	     i++, t++) {
		if (strlen(*t) == l
		 && strncmp(*t, q, l) == 0) {
			*p_dc = i + 128;
			return p;
		}
	}

	/* new decoration */
	if (i < DC_NAME_SZ) {
//		if (parse.abc_state != ABC_S_GLOBAL)
//			lvlarena(0);
		*t = getarena(l + 1);
//		if (parse.abc_state != ABC_S_GLOBAL)
//			lvlarena(1);
		memcpy(*t, q, l);
		(*t)[l] = '\0';
		*p_dc = i + 128;
	} else {
		syntax("Too many decoration types", q);
	}
	return p;
}

/* -- parse a list of accidentals (K:) -- */
static char *parse_acc(char *p,
			struct SYMBOL *s)
{
	int pit = 0, acc;
	unsigned nacc;

	nacc = 0;
	for (;;) {
		if (nacc >= sizeof s->u.key.pits) {
			syntax("Too many accidentals", p);
			break;
		}
		p = parse_acc_pit(p, &pit, &acc);
		if (acc < 0)
			break;
		s->u.key.pits[nacc] = pit;
		s->u.key.accs[nacc++] = acc;
		while (isspace((unsigned char) *p))
			p++;
		if (*p == '\0')
			break;
		if (*p != '^' && *p != '_' && *p != '=')
			break;
	}
	s->u.key.microscale = microscale;
	if (s->u.key.empty != 2)
		s->u.key.nacc = nacc;
	return p;
}

/* -- parse a clef (K: or V:) -- */
static void parse_clef(struct SYMBOL *s,
			char *name,
			char *middle)
{
	int clef = -1;
	int transpose = 0;
	int clef_line = 2;
	char *warn = NULL;
	char str[80];

	str[0] = '\0';
	if (name && strncmp(name, "clef=", 5) == 0) {
		name += 5;
		switch (*name) {
		case '\"':
			name = get_str(str, name, sizeof str);
			s->u.clef.name = getarena(strlen(str) + 1);
			strcpy(s->u.clef.name, str);
			clef = TREBLE;
			break;
		case 'g':
			warn = name;
			transpose = -7;
		case 'G':
			clef = TREBLE;
			break;
		case 'f':
			warn = name;
			transpose = -14;
			clef = BASS;
			clef_line = 4;
			break;
		case 'F':
			if (name[1] == ',')	/* abc2.1.1 clef=F == clef=F, */
				transpose = -7;
			clef = BASS;
			clef_line = 4;
			break;
		case 'c':
			warn = name;
			transpose = -7;
		case 'C':
			clef = ALTO;
			clef_line = 3;
			break;
		case 'P':
			clef = PERC;
			clef_line = 3;
			break;
		}
		if (clef >= 0) {
			name++;
			if (*name == ',' || *name== '\'')
				warn = name;
			while (*name == ',') {
				transpose += 7;
				name++;
			}
			while (*name == '\'') {
				transpose -= 7;
				name++;
			}
		}
	}
	if (name && clef < 0) {
		if (!strncmp(name, "bass", 4)) {
			clef = BASS;
			clef_line = 4;
			s->u.clef.check_pitch = 1;
			name += 4;
		} else if (!strncmp(name, "treble", 6)) {
			clef = TREBLE;
			name += 6;
		} else if (!strncmp(name, "alto", 4)
			|| !strncmp(name, "tenor", 5)) {
			clef = ALTO;
			clef_line = *name == 'a' ? 3 : 4;
			s->u.clef.check_pitch = 1;
			if (*name == 'a')
				name += 4;
			else
				name += 5;
		} else if (!strncmp(name, "perc", 4)) {
			clef = PERC;
			clef_line = 3;
			name += 4;
		} else if (!strncmp(name, "auto", 4)) {
			clef = AUTOCLEF;
			name += 4;
		} else if (strncmp(name, "none", 4) == 0) {
			clef = TREBLE;
			s->u.clef.invis = 1;
			s->flags |= ABC_F_INVIS;
			name += 4;
		} else {
			syntax("Unknown clef", name);
			clef = TREBLE;
		}
	}

	if (clef >= 0) {
		if (isdigit((unsigned char) *name))
			clef_line = *name++ - '0';
		if (name[1] == '8') {
			switch (*name) {
			case '^':
				transpose -= 7;
			case '+':
				s->u.clef.octave = 1;
				break;
			case '_':
				transpose += 7;
			case '-':
				s->u.clef.octave = -1;
				break;
			}
		}
	}

	if (middle) {
		int pit = 0, acc, l;
		static const char line_tb[7] =
			{ALTO, TREBLE, ALTO, BASS, ALTO, BASS, ALTO};

		warn = middle;
		/* 'middle=<note pitch>' */
		parse_acc_pit(middle, &pit, &acc);
		if (acc < 0)			// if error
			pit = 22;

		if (clef < 0)
			clef = line_tb[(pit + 7) % 7];
	
		switch (clef) {
		default:
			l = 20 + 4;
			break;
		case ALTO:
			l = 16 + 4;
			break;
		case BASS:
			l = 12 + 4;
			break;
		}
		clef_line = (l - pit + 28) % 7;
		if (clef_line & 1) {
			syntax("Bad 'middle' value for the clef", middle);
			pit++;
		}
		clef_line = clef_line / 2 + 1;

		transpose = l - (clef_line - 1) * 2 - pit;
		s->u.clef.check_pitch = 0;
	}

	s->u.clef.type = clef;
	s->u.clef.line = clef_line;
	s->u.clef.transpose = transpose;
	if (warn) {
		int sev_sav;

		sev_sav = severity;
		syntax("Warning: Deprecated or non-standard item", warn);
		severity = sev_sav;
	}
}

/* get the octave= value */
static int parse_octave(char *p)
{
	int oct;

	if (p) {
		oct = 1;
		if (*p == '-') {
			oct = -1;
			p++;
		}
		if (*p >= '0' && *p <= '4')
			return oct * (*p - '0');
		syntax("Bad octave value", p);
	}
	return NO_OCTAVE;
}

/* -- parse a 'K:' -- */
static void parse_key(char *p,
		      struct SYMBOL *s)
{
	int sf, empty, instr;
//	int mode;
	char *clef_name, *clef_middle, *clef_stlines, *clef_scale;
	char *p_octave, *p_cue, *p_map;

	// set important default values
//	s->u.key.stafflines = "|||||";
	s->u.key.octave = NO_OCTAVE;

	if (*p == '\0') {
		s->u.key.empty = 1;
		return;
	}
	sf = 0;
//	mode = 0;
	empty = 0;
	instr = 0;
	switch (*p++) {
	case 'F': sf = -1; break;
	case 'B': sf++;
	case 'E': sf++;
	case 'A': sf++;
	case 'D': sf++;
	case 'G': sf++;
	case 'C': break;
	case 'H':
		if (*p == 'P') {
			instr = K_HP;
			p++;
		} else if (*p == 'p') {
			instr = K_Hp;
			sf = 2;
			p++;
		} else {
			syntax("Unknown bagpipe-like key", p);
		}
		break;
	case 'P':
		instr = K_DRUM;
		p++;
		break;
	case 'n':
		if (strncmp(p, "one", 3) == 0) {	// none
			empty = 2;
			p += 3;
			while (isspace((unsigned char) *p))
				p++;
			if (*p == '\0') {
				s->u.key.empty = empty;
				return;
			}
			break;
		}
		// fall thru
	default:
		p--;
		empty = 1;
		break;
	}
	s->u.key.empty = empty;

	if (!empty) {
		if (*p == '#') {
			sf += 7;
			p++;
		} else if (*p == 'b') {
			sf -= 7;
			p++;
		}
		while (isspace((unsigned char) *p))
			p++;
		switch (*p) {
		case 'a':
		case 'A':
			if (strncasecmp(p, "aeo", 3) == 0) {
				sf -= 3;
//				mode = 5;
				break;
			}
			goto unk;
		case 'd':
		case 'D':
			if (strncasecmp(p, "dor", 3) == 0) {
				sf -= 2;
//				mode = 1;
				break;
			}
			goto unk;
		case 'i':
		case 'I':
			if (strncasecmp(p, "ion", 3) == 0) {
//				mode = 0;
				break;
			}
			goto unk;
		case 'l':
		case 'L':
			if (strncasecmp(p, "loc", 3) == 0) {
				sf -= 5;
//				mode = 6;
				break;
			}
			if (strncasecmp(p, "lyd", 3) == 0) {
				sf += 1;
//				mode = 3;
				break;
			}
			goto unk;
		case 'm':
		case 'M':
			if (strncasecmp(p, "maj", 3) == 0)
				break;
			if (strncasecmp(p, "mix", 3) == 0) {
				sf -= 1;
//				mode = 4;
				break;
			}
			if (strncasecmp(p, "min", 3) == 0
			 || !isalpha((unsigned char) p[1])) { /* 'm' alone */
				sf -= 3;
//				mode = 5;
				break;
			}
			goto unk;
		case 'p':
		case 'P':
			if (strncasecmp(p, "phr", 3) == 0) {
				sf -= 4;
//				mode = 2;
				break;
			}
			goto unk;
		default:
unk:
			empty = 1;			// (local value)
			break;
		}
		if (!empty) {
			while (isalpha((unsigned char) *p))
				p++;
			while (isspace((unsigned char) *p))
				p++;
		}

		// [exp] accidentals
		if (strncmp(p, "exp ", 4) == 0) {
			p += 4;
			while (isspace((unsigned char) *p))
				p++;
			if (*p == '\0')
				syntax("no accidental after 'exp'", p);
			s->u.key.exp = 1;
		}
		if (s->u.key.exp && strncmp(p, "none", 4) == 0) {
			sf = 0;
			p += 4;
			while (isspace((unsigned char) *p))
				p++;
		} else switch (*p) {
			case '^':
			case '_':
			case '=':
				p = parse_acc(p, s);		/* accidentals */
				break;
		}
	}

	if (sf > 7 || sf < -7) {
		syntax("Too many sharps/flats", p);
		if (sf > 0)
			sf -= 12;
		else
			sf += 12;
	}

	// extra parameters
	clef_name = clef_middle = clef_stlines = clef_scale = NULL;
	p_octave = p_cue = p_map = NULL;
	parse_extra(p, &clef_name, &clef_middle, &clef_stlines,
			&clef_scale, &p_octave, &p_cue, &p_map);

	s->u.key.sf = sf;
//	s->u.key.mode = mode;
	s->u.key.instr = instr;
	s->u.key.octave = parse_octave(p_octave);
	if (p_cue) {
		if (strncmp(p_cue, "on", 2) == 0)
			s->u.key.cue = 1;
		else
			s->u.key.cue = -1;
	}
	if (clef_stlines)
		s->u.key.stafflines = clef_stlines;
	if (clef_scale) {
		float sc;

		sc = atof(clef_scale);
		if (sc >= 0.5 && sc <= 3)
			s->u.key.staffscale = sc;
		else
			syntax("Bad value of staffscale", clef_scale);
	}
	if (clef_name || clef_middle) {
		s = abc_new(ABC_T_CLEF, NULL);
		parse_clef(s, clef_name, clef_middle);
	}
	if (p_map) {
		strcpy(tex_buf, "%%voicemap ");
		get_str(&tex_buf[11], p_map, TEX_BUF_SZ - 12);
		abc_new(ABC_T_PSCOM, tex_buf);
	}
}

/* -- set default length from 'L:' -- */
static char *get_len(char *p,
		     struct SYMBOL *s)
{
	int l1, l2, d;
	char *error_txt = NULL;

	if (strcmp(p, "auto") == 0) {		/* L:auto */
		ulen = 15120;			// 2*2*2*2*3*3*3*5*7
		s->u.length.base_length = -1;
		return error_txt;
	}
	l1 = 0;
	l2 = 1;
	if (sscanf(p, "%d /%d ", &l1, &l2) != 2
	 || l1 == 0) {
		s->u.length.base_length = ulen ? ulen : BASE_LEN / 8;
		return "Bad unit note length: unchanged";
	}

	if (l2 == 0) {
		error_txt = "Bad length divisor, set to 4";
		l2 = 4;
	}
	d = BASE_LEN / l2;
	if (d * l2 != BASE_LEN) {
		error_txt = "Length incompatible with BASE, using 1/8";
		d = BASE_LEN / 8;
	} else 	{
		d *= l1;
		if (l1 != 1
		 || (l2 & (l2 - 1))) {
			error_txt = "Incorrect unit note length, using 1/8";
			d = BASE_LEN / 8;
		}
	}
	s->u.length.base_length = d;
	return error_txt;
}

/* -- parse a 'M:' -- */
static char *parse_meter(char *p,
				struct SYMBOL *s)
{
	int m1, m2, d, wmeasure, nm, in_parenth;
	unsigned i;
	char *q;
static char top_err[] = "Cannot identify meter top";

	if (*p == '\0')
		return "Empty meter string";
	nm = 0;
	in_parenth = 0;
	m1 = 0;
	if (strncmp(p, "none", 4) == 0) {
		p += 4;				/* no meter */
		wmeasure = 1;	/* simplify measure numbering and MREST conversion */
	} else {
	    wmeasure = 0;
	    while (*p != '\0') {
		if (*p == '=')
			break;
		if (nm >= MAX_MEASURE)
			return "Too many values in M:";
		switch (*p) {
		case 'C':
			s->u.meter.meter[nm].top[0] = *p++;
			if (*p == '|')
				s->u.meter.meter[nm].top[1] = *p++;
			m1 = 4;
			m2 = 4;
			break;
		case 'c':
		case 'o':
			if (*p == 'c')
				m1 = 4;
			else
				m1 = 3;
			m2 = 4;
			s->u.meter.meter[nm].top[0] = *p++;
			if (*p == '.')
				s->u.meter.meter[nm].top[1] = *p++;
			break;
		case '(':
			if (p[1] == '(') {	/* "M:5/4 ((2+3)/4)" */
				in_parenth = 1;
				s->u.meter.meter[nm++].top[0] = *p++;
			}
			q = p + 1;
			while (*q != '\0') {
				if (*q == ')' || *q == '/')
					break;
				q++;
			}
			if (*q == ')' && q[1] == '/') {	/* "M:5/4 (2+3)/4" */
				p++;		/* remove the parenthesis */
				continue;
			}			/* "M:5 (2+3)" */
			/* fall thru */
		case ')':
			in_parenth = *p == '(';
			s->u.meter.meter[nm++].top[0] = *p++;
			continue;
		default:
			if (sscanf(p, "%d", &m1) != 1
			 || m1 <= 0)
				return top_err;
			i = 0;
			m2 = 2;			/* default when no bottom value */
			for (;;) {
				while (isdigit((unsigned char) *p)
				    && i < sizeof s->u.meter.meter[0].top)
					s->u.meter.meter[nm].top[i++] = *p++;
				if (*p == ')') {
					if (p[1] != '/')
						break;
					p++;
				}
				if (*p == '/') {
					p++;
					if (sscanf(p, "%d", &m2) != 1
					 || m2 <= 0)
						return "Cannot identify meter bottom";
					i = 0;
					while (isdigit((unsigned char) *p)
					    && i < sizeof s->u.meter.meter[0].bot)
						s->u.meter.meter[nm].bot[i++] = *p++;
					break;
				}
				if (*p != ' ' && *p != '+')
					break;
				if (*p == '\0' || p[1] == '(')	/* "M:5 (2/4+3/4)" */
					break;
				if (i < sizeof s->u.meter.meter[0].top)
					s->u.meter.meter[nm].top[i++] = *p++;
				if (sscanf(p, "%d", &d) != 1
				 || d <= 0)
					return top_err;
				if (p[-1] == ' ') {
					if (d > m1)
						m1 = d;
				} else {
					m1 += d;
				}
			}
			break;
		}
		if (!in_parenth)
			wmeasure += m1 * BASE_LEN / m2;
		nm++;
		if (*p == ' ')
			p++;
		else if (*p == '+')
			s->u.meter.meter[nm++].top[0] = *p++;
	    }
	}
	meter = m1;
	if (*p == '=') {
		if (sscanf(++p, "%d/%d", &m1, &m2) != 2
		 || m1 <= 0
		 || m2 <= 0)
			return "Cannot identify meter explicit duration";
		wmeasure = m1 * BASE_LEN / m2;
		s->u.meter.expdur = 1;
	}
	if (wmeasure > BASE_LEN * 16 || wmeasure < 0)
		return "Too big meter value";
	s->u.meter.wmeasure = wmeasure;
	s->u.meter.nmeter = nm;

	/* in the tune header, change the unit note length */
	if (parse.abc_state == ABC_S_HEAD && ulen == 0) {
		if (wmeasure >= BASE_LEN * 3 / 4
		 || wmeasure <= 1)
			ulen = BASE_LEN / 8;
		else
			ulen = BASE_LEN / 16;
	}
	return 0;
}

/* -- get a possibly quoted string -- */
char *get_str(char *d,		/* destination */
	      char *s,		/* source */
	      int maxlen)	/* max length */
{
	char c;

	maxlen--;		/* have place for the EOS */
	while (isspace((unsigned char) *s))
		s++;
	if (*s == '"') {
		s++;
		while ((c = *s) != '\0') {
			if (c == '"') {
				s++;
				break;
			}
			if (c == '\\') {
				if (--maxlen > 0)
					*d++ = c;
				c = *++s;
			}
			if (--maxlen > 0)
				*d++ = c;
			s++;
		}
	} else {
		while ((c = *s) != '\0') {
			if (isspace((unsigned char) c))
				break;
			if (--maxlen > 0)
				*d++ = c;
			s++;
		}
	}
	*d = '\0';
	while (isspace((unsigned char) *s))
		s++;
	return s;
}

/* -- parse a tempo (Q:) -- */
static char *parse_tempo(char *p,
			 struct SYMBOL *s)
{
	char c, str[80];
	int i, l, n, top, bot;

	/* string before */
	if (*p == '"') {
		p = get_str(str, p, sizeof str);
		s->u.tempo.str1 = getarena(strlen(str) + 1);
		strcpy(s->u.tempo.str1, str);
	}

	/* beat */
	if (*p == 'C' || *p == 'c'
	 || *p == 'L' || *p == 'l') {
		s->u.tempo.beats[0] = ulen;
		if (parse.abc_vers >= (2 << 16))
			syntax("Deprecated Q: value", p);
		p++;
		while (isspace((unsigned char) *p))
			p++;
		if (*p != '=')
			goto inval;
		c = '=';
		p--;
	} else if (isdigit((unsigned char) *p)) {
		if (strchr(p, '/') != NULL) {
			i = 0;
			while (isdigit((unsigned char) *p)) {
				if (sscanf(p, "%d/%d%n", &top, &bot, &n) != 2
				 || bot <= 0)
					goto inval;
				l = (BASE_LEN * top) / bot;
				if (l <= 0
				 || i >= sizeof s->u.tempo.beats
						/ sizeof s->u.tempo.beats[0])
					goto inval;
				s->u.tempo.beats[i++] = l;
				p += n;
				while (isspace((unsigned char) *p))
					p++;
			}
			c = *p;
			if (c != '=')
				goto inval;
		} else {
			s->u.tempo.beats[0] = ulen;
			if (parse.abc_vers >= (2 << 16))
				syntax("Deprecated Q: value", p);
			c = '=';
			p--;
		}
	} else {
		c = '\0';
	}

	/* tempo value */
	if (c == '=') {
		p++;
		if (strncmp(p, "ca. ", 4) == 0) {
			s->u.tempo.circa = 1;
			p += 4;
		}
		if (sscanf(p, "%d/%d%n", &top, &bot, &n) == 2) {
			if (bot <= 0)
				goto inval;
			l = (BASE_LEN * top) / bot;
			if (l <= 0)
				goto inval;
			s->u.tempo.new_beat = l;
		} else {
			if (sscanf(p, "%d%n", &top, &n) != 1)
				goto inval;
			s->u.tempo.tempo = top;
		}
		p += n;
		while (isspace((unsigned char) *p))
			p++;
	}

	/* string after */
	if (*p == '"') {
		p = get_str(str, p, sizeof str);
		s->u.tempo.str2 = getarena(strlen(str) + 1);
		strcpy(s->u.tempo.str2, str);
	}

	return 0;
inval:
	return "Invalid tempo";
}

/* -- get a user defined symbol (U:) -- */
static char *get_user(char *p,
		      struct SYMBOL *s)
{
	unsigned char c;
	char *value;

	c = (unsigned char) *p++;
	if (c == '\\') {
		c = (unsigned char) *p++;
		switch (c) {
		case 'n':
			c = '\n';
			break;
		case 't':
			c = '\t';
			break;
		}
	}
	switch (char_tb[c]) {
	default:
		return "Bad decoration character";
	case CHAR_DECO:
		break;
	case CHAR_BAD:
	case CHAR_IGN:
	case CHAR_SPAC:
	case CHAR_DECOS:
	case CHAR_LINEBREAK:
		char_tb[c] = CHAR_DECO;
		break;
	}
	s->u.user.symbol = c;

	/* skip '=' */
	while (isspace((unsigned char) *p) || *p == '=')
		p++;
	if (char_tb[(unsigned char) *p] == CHAR_DECOS)
		p++;
/*fixme: 'U: <char> = "text"' is not treated */
	get_deco(p, &s->u.user.value);
	if (!s->u.user.value)
		return 0;

	/* treat special pseudo decorations */
	value = parse.deco_tb[s->u.user.value - 128];
	if (strcmp(value, "beambreak") == 0)
		char_tb[c] = CHAR_SPAC;
	else if (strcmp(value, "ignore") == 0)
		char_tb[c] = CHAR_IGN;
	else if (strcmp(value, "nil") == 0
	      || strcmp(value, "none") == 0)
		char_tb[c] = CHAR_BAD;
	else
		return 0;
	s->u.user.value	= 0;		/* not a decoration */
	return 0;
}

/* -- parse the voice parameters (V:) -- */
static char *parse_voice(char *p,
			 struct SYMBOL *s)
{
	int voice;
	char *error_txt = NULL;
	char *clef_name, *clef_middle, *clef_stlines, *clef_scale;
	char *p_octave, *p_cue, *p_map;
	signed char *p_stem;
static struct kw_s {
	char *name;
	short len;
	short index;
} kw_tb[] = {
	{"name=", 5, 0},
	{"nm=", 3, 0},
	{"subname=", 8, 1},
	{"sname=", 6, 1},
	{"snm=", 4, 1},
	{"merge", 5, 2},
	{"up", 2, 3},
	{"down", 4, 4},
	{"stem=", 5, 5},
	{"gstem=", 6, 6},
	{"auto", 4, 7},
	{"dyn=", 4, 8},
	{"lyrics=", 7, 9},
	{"scale=", 6, 10},
	{"gchord=", 7, 11},
	{0}
};
	struct kw_s *kw;

	/* save the parameters of the previous voice */
	curvoice->ulen = ulen;
	curvoice->microscale = microscale;

	if (voice_tb[0].id[0] == '\0') {
		switch (s->abc_prev->abc_type) {
		case ABC_T_EOLN:
		case ABC_T_NOTE:
		case ABC_T_REST:
		case ABC_T_BAR:
			/* the previous voice was implicit (after K:) */
			voice_tb[0].id[0] = '1';
			break;
		}
	}
	{
		char *id, sep;

		id = p;
		while (isalnum((unsigned char) *p) || *p == '_')
			p++;
		sep = *p;
		*p = '\0';
		if (voice_tb[0].id[0] == '\0') {
			voice = 0;			/* first voice */
		} else {
			for (voice = 0; voice <= nvoice; voice++) {
				if (strcmp(id, voice_tb[voice].id) == 0)
					goto found;
			}
			if (voice >= MAXVOICE) {
				syntax("Too many voices", id);
				voice--;
			}
		}
		nvoice = voice;
		strncpy(voice_tb[voice].id, id, sizeof voice_tb[voice].id - 1);
		voice_tb[voice].mvoice = voice;
	found:
		strcpy(s->u.voice.id, voice_tb[voice].id);
		*p = sep;
	}
	curvoice = &voice_tb[voice];
	s->u.voice.voice = voice;

	/* if in tune, set the voice parameters */
	if (parse.abc_state == ABC_S_TUNE) {
		ulen = curvoice->ulen;
		microscale = curvoice->microscale;
	}

	/* parse the other parameters */
	clef_name = clef_middle = clef_stlines = clef_scale = NULL;
	p_octave = p_cue = p_map = NULL;
	p_stem = &s->u.voice.stem;
	for (;;) {
		while (isspace((unsigned char) *p))
			p++;
		if (*p == '\0')
			break;
		p = parse_extra(p, &clef_name, &clef_middle, &clef_stlines,
				&clef_scale, &p_octave, &p_cue, &p_map);
		if (*p == '\0')
			break;
		for (kw = kw_tb; kw->name; kw++) {
			if (strncmp(p, kw->name, kw->len) == 0)
				break;
		}
		if (!kw->name) {
			while (!isspace((unsigned char) *p) && *p != '\0')
				p++;	/* ignore unknown keywords */
			continue;
		}
		p += kw->len;
		switch (kw->index) {
		case 0:			/* name */
			p = get_str(tex_buf, p, TEX_BUF_SZ);
			s->u.voice.fname = getarena(strlen(tex_buf) + 1);
			strcpy(s->u.voice.fname, tex_buf);
			break;
		case 1:			/* subname */
			p = get_str(tex_buf, p, TEX_BUF_SZ);
			s->u.voice.nname = getarena(strlen(tex_buf) + 1);
			strcpy(s->u.voice.nname, tex_buf);
			break;
		case 2:			/* merge */
			s->u.voice.merge = 1;
			break;
		case 3:			/* up */
			*p_stem = 1;
			break;
		case 4:			/* down */
			*p_stem = -1;
			break;
		case 5:			/* stem= */
			p_stem = &s->u.voice.stem;
			break;
		case 6:			/* gstem= */
			p_stem = &s->u.voice.gstem;
			break;
		case 7:			/* auto */
			*p_stem = 2;
			break;
		case 8:			/* dyn= */
			p_stem = &s->u.voice.dyn;
			break;
		case 9:			/* lyrics= */
			p_stem = &s->u.voice.lyrics;
			break;
		case 10: {		/* scale= */
			float sc;

			sc = atof(p);
			if (sc >= 0.5 && sc <= 2)
				s->u.voice.scale = sc;
			else
				error_txt = "Bad value for voice scale";
			while (!isspace((unsigned char) *p) && *p != '\0')
				p++;
			break;
		    }
		case 11:		/* gchord= */
			p_stem = &s->u.voice.gchord;
			break;
		}
	}

	s->u.voice.octave = parse_octave(p_octave);
	if (p_cue) {
		if (strncmp(p_cue, "on", 2) == 0)
			s->u.voice.cue = 1;
		else
			s->u.voice.cue = -1;
	}
	if (clef_stlines)
		s->u.voice.stafflines = clef_stlines;
//	else
//		s->u.voice.stafflines = "|||||";
	if (clef_scale) {
		float sc;

		sc = atof(clef_scale);
		if (sc >= 0.5 && sc <= 3)
			s->u.voice.staffscale = sc;
		else
			syntax("Bad value of staffscale", clef_scale);
	}
	if (clef_name || clef_middle) {
		s = abc_new(ABC_T_CLEF, NULL);
		parse_clef(s, clef_name, clef_middle);
	}
	if (p_map) {
		strcpy(tex_buf, "%%voicemap ");
		get_str(&tex_buf[11], p_map, TEX_BUF_SZ - 12);
		abc_new(ABC_T_PSCOM, tex_buf);
	}
	return error_txt;
}

/* -- parse a bar -- */
static char *parse_bar(char *p)
{
	struct SYMBOL *s;
	char *q;
	int bar_type, i;
	char repeat_value[32];

	q = --p;			// keep the first char
	bar_type = 0;
	for (;;) {
		switch (*p++) {
		case '|':
			bar_type <<= 4;
			bar_type |= B_BAR;
			continue;
		case '[':
			bar_type <<= 4;
			bar_type |= B_OBRA;
			continue;
		case ']':
			bar_type <<= 4;
			bar_type |= B_CBRA;
			continue;
		case ':':
			bar_type <<= 4;
			bar_type |= B_COL;
			continue;
		default:
			break;
		}
		break;
	}
	p--;

	/* if the last element is '[', it may start
	 * a chord, an embedded header or an other bar */
	if ((bar_type & 0x0f) == B_OBRA && bar_type != B_OBRA
	 && *p != ' ') {
		bar_type >>= 4;
		p--;
	}

	if (bar_type == (B_OBRA << 8) + (B_BAR << 4) + B_CBRA)	/* [|] */
		bar_type = (B_OBRA << 4) + B_CBRA;		/* [] */

/*	curvoice->last_note = NULL; */
	if (vover > 0) {
		curvoice = &voice_tb[curvoice->mvoice];
		vover = 0;
	}
	s = abc_new(ABC_T_BAR, gchord);
	if (gchord)
		gchord = NULL;

	/* handle the repeat sequences */
	if (bar_type == B_COL) {
		bar_type = B_BAR;
		s->u.bar.dotted = 1;
	} else {
		if (*q == ']') {		/* repeat bar stop */
			i = p - q - 1;
			if (i > 0)		/* remove the starting ']' */
				s->u.bar.type &= (1 << (i * 4)) - 1;
			s->flags |= ABC_F_RBSTOP;
			s->sflags |= S_RBSTOP;
		} else if ((bar_type & 0x0f) == B_COL	/* left or */
			|| *q == ':') {			/* right repeat bar */
			s->flags |= ABC_F_RBSTOP;
			s->sflags |= S_RBSTOP;
			if (*q == ':')		/* right repeat bar */
				s->sflags |= S_RRBAR;
		}
	}

	s->u.bar.type = bar_type;

	if (dc.n > 0) {
		memcpy(&s->u.bar.dc, &dc, sizeof s->u.bar.dc);
		dc.n = 0;
	}

	if (!lyric_started) {
		lyric_started = 1;
		s->flags |= ABC_F_LYRIC_START;
	}

	if (!isdigit((unsigned char) *p)	/* if not a repeat bar */
	 && (*p != '"' || p[-1] != '['))	/* ('["' only) */
		return p;

	if (*p == '"') {
		p = get_str(repeat_value, p, sizeof repeat_value);
	} else {
		char *q;

		q = repeat_value;
		while (isdigit((unsigned char) *p)
		    || *p == ','
		    || *p == '-'
		    || (*p == '.' && isdigit((unsigned char) p[1]))) {
			if (q < &repeat_value[sizeof repeat_value - 1])
				*q++ = *p++;
			else
				p++;
		}
		*q = '\0';
	}
	if (bar_type != B_OBRA
	 || s->text) {
		s = abc_new(ABC_T_BAR, repeat_value);
		s->u.bar.type = B_OBRA;
	} else {
		s->text = getarena(strlen(repeat_value) + 1);
		strcpy(s->text, repeat_value);
	}
	s->u.bar.repeat_bar = 1;
	s->flags |= ABC_F_RBSTART | ABC_F_RBSTOP;
	s->sflags |= S_RBSTART | S_RBSTOP;
	return p;
}

// parse the note accidental and pitch
char *parse_acc_pit(char *p,
		int *pit,
		int *acc)
{
	/* look for accidental sign */
	switch (*p) {
	case '^':
		p++;
		if (*p == '^') {
			p++;
			*acc = A_DS;
		} else {
			*acc = A_SH;
		}
		break;
	case '=':
		p++;
		*acc = A_NT;
		break;
	case '_':
		p++;
		if (*p == '_') {
			p++;
			*acc = A_DF;
		} else {
			*acc = A_FT;
		}
		break;
	default:
		*acc = 0;
	}

	/* look for microtone value */
	if (*acc != 0
	 && (isdigit((unsigned char) *p)
	  || (*p == '/' && microscale == 0))) {
		int n, d;
		char *q;

		n = d = 1;
		if (*p != '/') {
			n = strtol(p, &q, 10);
			p = q;
		}
		if (*p == '/') {
			p++;
			if (!isdigit((unsigned char) *p)) {
				d = 2;
			} else {
				d = strtol(p, &q, 10);
				p = q;
			}
		}
		if (microscale == 0) {
			d--;
			d += (n - 1) << 8;	/* short [ (n-1) | (d-1) ] */
			if (d == 0) {
				n = MAXMICRO - 1;
			} else {
			    for (n = 1; n < MAXMICRO; n++) {
				if (parse.micro_tb[n] == d)
					break;
				if (parse.micro_tb[n] == 0) {
					parse.micro_tb[n] = d;
					break;
				}
			    }
			}
			if (n == MAXMICRO) {
				syntax("Too many microtone accidentals", p);
				n = 0;
			}
		}
		*acc += (n << 3);
	}

	/* get the pitch */
	{
		char *p_n;

		p_n = strchr(all_notes, *p);
		if (!p_n || *p == '\0') {
			syntax(*acc ? "Missing note after accidental"
				   : "Not a note", p);
			*acc = -1;
			if (*p == '\0')
				p--;
		} else {
			*pit = p_n - all_notes + 16;
		}
		p++;
	}
	while (*p == '\'') {		/* eat up following ' chars */
		*pit += 7;
		p++;
	}
	while (*p == ',') {		/* eat up following , chars */
		*pit -= 7;
		p++;
	}
	return p;
}

/* -- parse the decorations of notes and bars -- */
static char *parse_deco(char *p,
			 struct decos *deco,
			 int m)			/* note index / -1 */
{
	int n;
	unsigned char t;

	n = deco->n;
	for (;;) {
		t = (unsigned char) *p++;
		if (char_tb[t] != CHAR_DECO && char_tb[t] != CHAR_DECOS)
			break;
		if (char_tb[t] == CHAR_DECOS)
			p = get_deco(p, &t);
		if (n >= MAXDC) {
			syntax("Too many decorations for the note", p);
		} else if (t != 0) {
			deco->tm[n].t = t;
			deco->tm[n++].m = m;
		}
	}
	deco->n = n;
	return p - 1;
}

/* -- parse a decoration line (d: or s:) -- */
static char *parse_decoline(char *p)
{
	struct SYMBOL *is;
	unsigned char t;
	int n;

	if ((is = deco_cont) == NULL)
		is = deco_start;
	else
		deco_cont = NULL;

	/* scan the decoration line */
	while (*p != '\0') {
		while (isspace((unsigned char) *p))
			p++;
		if (*p == '\0')
			break;
		switch (*p) {
		case '|':
			while (is && (is->abc_type != ABC_T_BAR
					|| is->u.bar.type == B_OBRA))
				is = is->abc_next;
			if (!is) {
				syntax("Not enough bar lines for deco line", p);
				return NULL;
			}
			is = is->abc_next;
			p++;
			continue;
		case '*':
			while (is && is->abc_type != ABC_T_NOTE)
				is = is->abc_next;
			if (!is) {
				syntax("Not enough notes for deco line", p);
				return NULL;
			}
			is = is->abc_next;
			p++;
			continue;
		case '\\':
			if (p[1] == '\0') {
				if (!is)
					return "Not enough notes for deco line";
				deco_cont = is;
				return NULL;
			}
			syntax("'\\' ignored", p);
			p++;
			continue;
		case '"':
			p = parse_gchord(p + 1);
			break;
		default:
			if (char_tb[(unsigned char) *p] == CHAR_DECOS)
				p = get_deco(p + 1, &t);
			else
				t = (unsigned char) *p++;
			break;
		}

		/* store the decoration and gchord/annotation in the next note */
		while (is && (is->abc_type != ABC_T_NOTE
				|| (is->flags & ABC_F_GRACE)))
			is = is->abc_next;
		if (!is)
			return "Not enough notes for deco line";

		if (gchord) {
			if (is->text) {
				char *gch;

				n = strlen(is->text);
				gch = getarena(n + strlen(gchord) + 2);
				strcpy(gch, is->text);
				gch[n] = '\n';
				strcpy(gch + n + 1, gchord);
				gchord = gch;
			}
			is->text = gchord;
			gchord = NULL;
		} else {
			n = is->u.note.dc.n;
			if (n >= MAXDC) {
				syntax("Too many decorations for the note", p);
			} else if (t != 0) {
				is->u.note.dc.tm[n].t = t;
				is->u.note.dc.tm[n].m = -1;
				is->u.note.dc.n = ++n;
			}
		}
		is = is->abc_next;
	}
	return NULL;
}

/* -- parse a guitar chord / annotation -- */
static char *parse_gchord(char *p)
{
	char *q;
	int l, l2;

	q = p;
	while (*p != '"') {
		if (*p == '\\')
			p++;
		if (*p == '\0') {
			syntax("No end of guitar chord", p);
			break;
		}
		p++;
	}
	l = p - q;
	if (gchord) {
		char *gch;

		/* many guitar chords: concatenate with '\n' */
		l2 = strlen(gchord);
		gch = getarena(l2 + 1 + l + 1);
		strcpy(gch, gchord);
		gch[l2++] = '\n';
		strncpy(&gch[l2], q, l);
		gch[l2 + l] = '\0';
		gchord = gch;
	} else {
		gchord = getarena(l + 1);
		strncpy(gchord, q, l);
		gchord[l] = '\0';
	}
	if (*p != '\0')
		p++;
	return p;
}

/* -- parse a note length -- */
static char *parse_len(char *p,
			int dur_u,
			int *p_len)
{
	int len, fac;
	int err = 0;
	char *q;

	len = dur_u;
	if (isdigit((unsigned char) *p)) {
		len *= strtol(p, &q, 10);
		p = q;
	}
    if (*p == '/') {
	if (isdigit((unsigned char) p[1])) {
		fac = strtol(p + 1, &q, 10);
		p = q;
		if (fac == 0 || (fac & (fac - 1)))
			err = 1;
		else
			len /= fac;
	} else {
		while (*p == '/') {
			if (len & 1)
				err = 1;
			len /= 2;
			p++;
		}
	}
	if (err || !len) {
		syntax("Bad length divisor", p - 1);
		len = dur_u;
	}
    }
	if (len <= 0 || len > 10000) {
		syntax("Bad length", p);
		len = dur_u;
	}
	*p_len = len;
	return p;
}

/* -- parse a ABC line -- */
/* return 1 on end of tune, and 2 on start of new tune */
static int parse_line(char *p)
{
	struct SYMBOL *s;
	char *q, c;
	char *dot = NULL;
	struct SYMBOL *last_note_sav = NULL;
	struct decos dc_sav;
	int i, flags, flags_sav = 0, slur;
	static char qtb[10] = {0, 1, 3, 2, 3, 0, 2, 0, 3, 0};

	colnum = 0;
	switch (*p) {
	case '\0':			/* blank line */
		switch (parse.abc_state) {
		case ABC_S_GLOBAL:
			if (parse.last_sym
			 && parse.last_sym->abc_type != ABC_T_NULL)
				abc_new(ABC_T_NULL, NULL);
		case ABC_S_HEAD:	/*fixme: may have blank lines in headers?*/
			return 0;
		}
		return 1;
	case '%':
		if (p[1] == '%') {
			s = abc_new(ABC_T_PSCOM, p);
			p += 2;				/* skip '%%' */
			if (strncasecmp(p, "decoration ", 11) == 0) {
				p += 11;
				while (isspace((unsigned char) *p))
					p++;
				switch (*p) {
				case '!':
					char_tb['!'] = CHAR_DECOS;
					char_tb['+'] = CHAR_BAD;
					break;
				case '+':
					char_tb['+'] = CHAR_DECOS;
					char_tb['!'] = CHAR_BAD;
					break;
				}
				return 0;
			}
			if (strncasecmp(p, "linebreak ", 10) == 0) {
				for (i = 0; i < sizeof char_tb; i++) {
					if (char_tb[i] == CHAR_LINEBREAK)
						char_tb[i] = i != '!' ?
								CHAR_BAD :
								CHAR_DECOS;
				}
				p += 10;
				for (;;) {
					while (isspace((unsigned char) *p))
						p++;
					if (*p == '\0')
						break;
					switch (*p) {
					case '!':
					case '$':
					case '*':
					case ';':
					case '?':
					case '@':
						char_tb[(unsigned char) *p++]
								= CHAR_LINEBREAK;
						break;
					case '<':
						if (strncmp(p, "<none>", 6) == 0)
							return 0;
						if (strncmp(p, "<EOL>", 5) == 0) {
							char_tb['\n'] = CHAR_LINEBREAK;
							p += 5;
							break;
						}
						/* fall thru */
					default:
						if (strcmp(p, "lock") != 0)
							syntax("Invalid character in %%%%linebreak",
								p);
						return 0;
					}
				}
				return 0;
			}
			if (strncasecmp(p, "microscale ", 11) == 0) {
				int v;

				p += 11;
				while (isspace((unsigned char) *p))
					p++;
				sscanf(p, "%d", &v);
				if (v < 4 || v >= 256 || v & 1)
					syntax("Invalid value in %%microscale", p);
				else
					microscale = v;
				return 0;
			}
			if (strncasecmp(p, "user ", 5) == 0) {
				p += 5;
				while (isspace((unsigned char) *p))
					p++;
				get_user(p, s);
				return 0;
			}
			return 0;
		}
		/* fall thru */
	case '\\':				/* abc2mtex specific lines */
		return 0;			/* skip */
	}

	/* header fields */
	if (p[1] == ':'
	 && *p != '|' && *p != ':') {		/* not '|:' nor '::' */
		int new_tune;

		new_tune = parse_info(p);

		/* handle BarFly voice definition */
		/* 'V:n <note line ending with a bar>' */
		if (*p != 'V'
		 || parse.abc_state != ABC_S_TUNE)
			return new_tune;		/* (normal return) */
		c = p[strlen(p) - 1];
		if (c != '|' && c != ']')
			return new_tune;
		while (!isspace((unsigned char) *p) && *p != '\0')
			p++;
		while (isspace((unsigned char) *p))
			p++;
	}
	if (parse.abc_state != ABC_S_TUNE)
		return 0;

	/* music */
	flags = 0;
	if (parse.abc_vers <= (2 << 16))
		lyric_started = 0;
	deco_start = deco_cont = NULL;
	slur = 0;
	while (*p != '\0') {
		colnum = p - abc_line;
		switch (char_tb[(unsigned char) *p++]) {
		case CHAR_GCHORD:			/* " */
			if (flags & ABC_F_GRACE)
				goto bad_char;
			p = parse_gchord(p);
			break;
		case CHAR_GR_ST:		/* '{' */
			if (flags & ABC_F_GRACE)
				goto bad_char;
			last_note_sav = curvoice->last_note;
			curvoice->last_note = NULL;
			memcpy(&dc_sav, &dc, sizeof dc);
			dc.n = 0;
			flags_sav = flags;
			flags = ABC_F_GRACE;
			if (*p == '/') {
				flags |= ABC_F_SAPPO;
				p++;
			}
			break;
		case CHAR_GR_EN:		/* '}' */
			if (!(flags & ABC_F_GRACE))
				goto bad_char;
			parse.last_sym->flags |= ABC_F_GR_END;
			if (dc.n != 0)
				syntax("Decoration ignored", p);
			curvoice->last_note = last_note_sav;
			memcpy(&dc, &dc_sav, sizeof dc);
			flags = flags_sav;
			break;
		case CHAR_DECOS:
			if (p[-1] == '!'
			 && char_tb['\n'] == CHAR_LINEBREAK
			 && check_nl(p)) {
				s = abc_new(ABC_T_EOLN, NULL);	/* abc2win EOL */
				s->u.eoln.type = 2;
				break;
			}
			/* fall thru */
		case CHAR_DECO:
			if (p[-1] == '.') {
				if (*p == '(' || *p == '-') {
					dot = p;
					break;
				}
//				if (*p == '|') {
//					p = parse_bar(p + 1);
//					parse.last_sym->u.bar.dotted = 1;
//					break;
//				}
			}
			p = parse_deco(p - 1, &dc, -1);
			break;
		case CHAR_LINEBREAK:
			s = abc_new(ABC_T_EOLN, NULL);
//			s->u.eoln.type = 0;
			break;
		case CHAR_NOTE:
			p = parse_note(p - 1, flags);
			flags &= ABC_F_GRACE;
			if (slur && parse.last_sym->u.note.notes[0].len) {
				parse.last_sym->u.note.slur_st = slur;
				slur = 0;
			}
			break;
		case CHAR_SLASH:		/* '/' */
			if (flags & ABC_F_GRACE)
				goto bad_char;
			if (char_tb[(unsigned char) p[-1]] != CHAR_BAR)
				goto bad_char;
			q = p;
			while (*q == '/')
				q++;
			if (char_tb[(unsigned char) *q] != CHAR_BAR)
				goto bad_char;
			s = abc_new(ABC_T_MREP, NULL);
			s->u.bar.type = 0;
			s->u.bar.len = q - p + 1;
			syntax("Non standard measure repeat syntax", p - 1);
			p = q;
			break;
		case CHAR_BSLASH:		/* '\\' */
			if (*p == '\0')
				break;
			syntax("'\\' ignored", p - 1);
			break;
		case CHAR_OBRA:			/* '[' */
			if (*p == '|' || *p == ']' || *p == ':'
			 || isdigit((unsigned char) *p) || *p == '"'
			 || *p == ' ') {
				if (flags & ABC_F_GRACE)
					goto bad_char;
				p = parse_bar(p);
				break;
			}
			if (p[1] != ':') {
				p = parse_note(p - 1, flags); /* chord */
				flags &= ABC_F_GRACE;
				if (slur && parse.last_sym->u.note.notes[0].len) {
					parse.last_sym->u.note.slur_st = slur;
					slur = 0;
				}
				break;
			}

			/* embedded information field */
#if 0
/*fixme:OK for [I:staff n], ?? for other headers*/
			if (flags & ABC_F_GRACE)
				goto bad_char;
#endif
			while (p[2] == ' ') {		/* remove the spaces */
				p[2] = ':';
				p[1] = *p;
				p++;
			}
			c = ']';
			q = p;
			while (*p != '\0' && *p != c)
				p++;
			if (*p == '\0') {
				syntax("Escape sequence [..] not closed", q);
				c = '\0';
			} else {
				*p = '\0';
			}
			parse_info(q);
			*p = c;
			if (c != '\0')
				p++;
			break;
		case CHAR_BAR:			/* '|', ':' or ']' */
			if (flags & ABC_F_GRACE)
				goto bad_char;
			p = parse_bar(p);
			break;
		case CHAR_OPAR:			/* '(' */
			if (*p > '0' && *p <= '9') {
				int pplet, qplet, rplet;

				pplet = strtol(p, &q, 10);
				p = q;
				if ((unsigned) pplet < sizeof qtb / sizeof qtb[0])
					qplet = qtb[pplet];
				else
					qplet = qtb[0];
				rplet = pplet;
				if (*p == ':') {
					p++;
					if (isdigit((unsigned char) *p)) {
						qplet = strtol(p, &q, 10);
						p = q;
					}
					if (*p == ':') {
						p++;
						if (isdigit((unsigned char) *p)) {
							rplet = strtol(p, &q, 10);
							p = q;
						}
					}
				}
				if (rplet < 1) {
					syntax("Invalid 'r' in tuplet", p);
					break;
				}
				if (pplet >= 128 || qplet >= 128 || rplet >= 128) {
					syntax("Invalid 'p:q:r' in tuplet", p);
					break;
				}
				if (qplet == 0)
					qplet = meter % 3 == 0 ? 3 : 2;
				s = abc_new(ABC_T_TUPLET, NULL);
				s->u.tuplet.p_plet = pplet;
				s->u.tuplet.q_plet = qplet;
				s->u.tuplet.r_plet = rplet;
				s->flags |= flags;
				break;
			}
			if (*p == '&') {
				if (flags & ABC_F_GRACE)
					goto bad_char;
				p++;
				if (vover != 0) {
					syntax("Nested voice overlay", p - 1);
					break;
				}
				s = abc_new(ABC_T_V_OVER, NULL);
				s->u.v_over.type = V_OVER_S;
				s->u.v_over.voice = curvoice - voice_tb;
				vover = -1;		/* multi-bars */
				break;
			}
			slur <<= 4;
			if (p == dot + 1 && dc.n == 0)
				slur |= SL_DOTTED;
			switch (*p) {
			case '\'':
				slur += SL_ABOVE;
				p++;
				break;
			case ',':
				slur += SL_BELOW;
				p++;
				break;
			default:
				slur += SL_AUTO;
				break;
			}
			break;
		case CHAR_CPAR:			/* ')' */
			switch (parse.last_sym->abc_type) {
			case ABC_T_NOTE:
			case ABC_T_REST:
				break;
			default:
				goto bad_char;
			}
			parse.last_sym->u.note.slur_end++;
			break;
		case CHAR_VOV:			/* '&' */
			if (flags & ABC_F_GRACE)
				goto bad_char;
			if (*p != ')'
			 || vover == 0) {		/*??*/
				if (!curvoice->last_note) {
					syntax("Bad start of voice overlay", p);
					break;
				}
				s = abc_new(ABC_T_V_OVER, NULL);
				/*s->u.v_over.type = V_OVER_V; */
				vover_new();
				s->u.v_over.voice = curvoice - voice_tb;
				if (vover == 0)
					vover = 1;	/* single bar */
				break;
			}
			p++;
			vover = 0;
			s = abc_new(ABC_T_V_OVER, NULL);
			s->u.v_over.type = V_OVER_E;
			s->u.v_over.voice = curvoice->mvoice;
			curvoice->last_note = NULL;	/* ?? */
			curvoice = &voice_tb[curvoice->mvoice];
			break;
		case CHAR_SPAC:			/* ' ' and '\t' */
			flags |= ABC_F_SPACE;
			break;
		case CHAR_MINUS: {		/* '-' */
			int tie_pos;

			if (!curvoice->last_note
			 || curvoice->last_note->abc_type != ABC_T_NOTE)
				goto bad_char;
			if (p == dot + 1 && dc.n == 0)
				tie_pos = SL_DOTTED;
			else
				tie_pos = 0;
			switch (*p) {
			case '\'':
				tie_pos += SL_ABOVE;
				p++;
				break;
			case ',':
				tie_pos += SL_BELOW;
				p++;
				break;
			default:
				tie_pos += SL_AUTO;
				break;
			}
			for (i = 0; i <= curvoice->last_note->nhd; i++) {
				if (curvoice->last_note->u.note.notes[i].ti1 == 0)
					curvoice->last_note->u.note.notes[i].ti1 = tie_pos;
				else if (curvoice->last_note->nhd == 0)
					syntax("Too many ties", p);
			}
			break;
		    }
		case CHAR_BRHY:			/* '>' and '<' */
			if (!curvoice->last_note)
				goto bad_char;
			i = 1;
			while (*p == p[-1]) {
				i++;
				p++;
			}
			if (i > 3) {
				syntax("Bad broken rhythm", p - 1);
				i = 3;
			}
			if (p[-1] == '<')
				i = -i;
			curvoice->last_note->u.note.brhythm = i;
			break;
		case CHAR_IGN:			/* '*' & '`' */
			break;
		default:
		bad_char:
			syntax((flags & ABC_F_GRACE)
					? "Bad character in grace note sequence"
					: "Bad character",
				p - 1);
			break;
		}
	}

/*fixme: may we have grace notes across lines?*/
	if (flags & ABC_F_GRACE) {
		syntax("EOLN in grace note sequence", p - 1);
		if (curvoice->last_note)
			curvoice->last_note->flags |= ABC_F_GR_END;
		curvoice->last_note = last_note_sav;
		memcpy(&dc, &dc_sav, sizeof dc);
	}

	/* add eoln */
	s = abc_new(ABC_T_EOLN, NULL);
	if (flags & ABC_F_SPACE)
		s->flags |= ABC_F_SPACE;
	if (p[-1] == '\\'
	 || char_tb['\n'] != CHAR_LINEBREAK)
		s->u.eoln.type = 1;		/* no break */

	return 0;
}

/* -- parse a note or a rest -- */
static char *parse_note(char *p,
			int flags)
{
	struct SYMBOL *s;
	char *q;
	int pit = 0, len, acc, nostem, chord, j, m, n;

	if (flags & ABC_F_GRACE) {	/* in a grace note sequence */
		s = abc_new(ABC_T_NOTE, NULL);
	} else {
		s = abc_new(ABC_T_NOTE, gchord);
		if (gchord)
			gchord = NULL;
	}
	s->flags |= flags;
	s->u.note.notes[0].color = -1;

	if (!lyric_started) {
		lyric_started = 1;
		s->flags |= ABC_F_LYRIC_START;
	}
	if (*p != 'X' && *p != 'Z'
	 && !(flags & ABC_F_GRACE)) {
		if (!deco_start)
			deco_start = s;
	}
	chord = 0;

	/* rest */
	switch (*p) {
	case 'X':
		s->flags |= ABC_F_INVIS;
	case 'Z':			/* multi-rest */
		s->abc_type = ABC_T_MREST;
		p++;
		len = 1;
		if (isdigit((unsigned char) *p)) {
			len = strtol(p, &q, 10);
			if (len == 0 || len > 100) {
				syntax("Bad number of measures", p);
				len = 1;
			}
			p = q;
		}
		s->u.bar.type = 0;
		s->u.bar.len = len;
		goto add_deco;
	case 'y':			/* space (BarFly) */
		s->abc_type = ABC_T_REST;
		s->flags |= ABC_F_INVIS;
		p++;
		if (isdigit((unsigned char) *p)		/* number of points */
		 || *p == '-') {			/* accept negative offset... */
			len = strtol(p, &q, 10);
			if (len < -100 || len > 100) {
				syntax("Bad width of y (space)", p);
				len = 10;
			}
			p = q;
		} else {
			len = 10;			// default
		}
		s->u.note.notes[0].shhd = len;
		goto add_deco;
	case 'x':			/* invisible rest */
		s->flags |= ABC_F_INVIS;
		/* fall thru */
	case 'z':
		s->abc_type = ABC_T_REST;
		p = parse_len(p + 1, ulen, &len);
		s->u.note.notes[0].len = len;
		goto do_brhythm;
	case '[':			/* '[..]' = chord */
		chord = 1;
		p++;
		break;
	}

	q = p;

	/* get pitch, length and possible accidental */
	m = 0;
	nostem = 0;
	for (;;) {
		if (chord) {
			if (m >= MAXHD) {
				syntax("Too many notes in chord", p);
				m--;
			}
			n = 0;
			if (*p == '.') {
				n = SL_DOTTED;
				p++;
			}
			if (*p == '(') {
				p++;
				switch (*p) {
				case '\'':
					n += SL_ABOVE;
					p++;
					break;
				case ',':
					n += SL_BELOW;
					p++;
					break;
				default:
					n += SL_AUTO;
					break;
				}
				s->u.note.notes[m].sl1 = (s->u.note.notes[m].sl1 << 3)
							+ n;
			}
		}
		p = parse_deco(p, &dc, m);	/* note head decorations */
		p = parse_acc_pit(p, &pit, &acc);
		if (*p == '0') {
			nostem = 1;
			p++;
		}
		p = parse_len(p, (flags & ABC_F_GRACE) ?
					BASE_LEN / 8 :	// for grace note alone
					ulen,
				&len);
		s->u.note.notes[m].pit = pit;
		s->pits[m] = pit;
		s->u.note.notes[m].len = len;
		s->u.note.notes[m].acc = acc;
		s->u.note.notes[m].color = -1;

		if (chord) {
			for (;;) {
				if (*p == '.') {
					if (p[1] != '-')
						break;
					p++;
				}
				if (*p == '-') {
					switch (p[1]) {
					case '\'':
						s->u.note.notes[m].ti1 = SL_ABOVE;
						p++;
						break;
					case ',':
						s->u.note.notes[m].ti1 = SL_BELOW;
						p++;
						break;
					default:
						s->u.note.notes[m].ti1 = SL_AUTO;
						break;
					}
				} else if (*p == ')') {
					s->u.note.notes[m].sl2++;
				} else {
					break;
				}
				p++;
			}
		}
		if (acc >= 0)			/* if no error */
			m++;			/* normal case */

		if (!chord)
			break;
		if (*p == ']') {
			p++;
			if (*p == '0') {
				nostem = 1;
				p++;
			}
			if (*p == '/' || isdigit((unsigned char) *p)) {
				p = parse_len(p, ulen, &len);
				for (j = 0; j < m; j++) {
					s->u.note.notes[j].len =
						len * s->u.note.notes[j].len / ulen;
				}
			}
			break;
		}
		if (*p == '\0') {
			syntax("Chord not closed", q);
			break;
		}
	}
	if (nostem)
		s->flags |= ABC_F_STEMLESS;

	if (m == 0)			/* if no note (or error) */
		goto err;

	s->u.note.microscale = microscale;
	s->nhd = m - 1;

do_brhythm:
	if (curvoice->last_note
	 && curvoice->last_note->u.note.brhythm != 0) {
		broken_rhythm(curvoice->last_note,
				curvoice->last_note->u.note.brhythm);
		broken_rhythm(s, -curvoice->last_note->u.note.brhythm);
	}
add_deco:
	if (dc.n > 0) {
		memcpy(s->abc_type != ABC_T_MREST ? &s->u.note.dc
				: &s->u.bar.dc,
			&dc, sizeof dc);
		dc.n = 0;
	}

	/* forbid rests in grace note sequences */
	if (s->abc_type != ABC_T_NOTE && (flags & ABC_F_GRACE)) {
		syntax("Not a note in grace note sequence", p);
		goto err;
	}
	if (s->u.note.notes[0].len > 0) /* if not space */
		curvoice->last_note = s;
	return p;

err:
	if ((parse.last_sym = s->abc_prev) == NULL) {
		parse.first_sym = NULL;
	} else {
		s->abc_prev->abc_next = NULL;
		s->abc_prev->flags |= (s->flags & ABC_F_ERROR);
	}
	return p;
}

/* -- parse an information field -- */
/* return 2 on start of new tune */
static int parse_info(char *p)
{
	struct SYMBOL *s;
	char info_type = *p;
	char *error_txt = NULL;

	s = abc_new(ABC_T_INFO, p);

	p += 2;

	switch (info_type) {
	case 'd':
	case 's':
		if (parse.abc_state == ABC_S_GLOBAL)
			break;
		if (!deco_start) {
			error_txt = "Erroneous 'd:'/'s:'";
			break;
		}
		error_txt = parse_decoline(p);
		break;
	case 'K':
		if (parse.abc_state == ABC_S_GLOBAL)
			break;
		parse_key(p, s);
		if (parse.abc_state == ABC_S_HEAD) {
			int i;

			parse.abc_state = ABC_S_TUNE;
			if (ulen == 0)
				ulen = BASE_LEN / 8;
			for (i = MAXVOICE; --i >= 0; )
				voice_tb[i].ulen = ulen;
			lyric_started = 0;
		}
		break;
	case 'L':
		error_txt = get_len(p, s);
		if (s->u.length.base_length > 0)
			ulen = s->u.length.base_length;
		break;
	case 'M':
		error_txt = parse_meter(p, s);
		break;
	case 'Q':
		error_txt = parse_tempo(p, s);
		break;
	case 'U':
		error_txt = get_user(p, s);
		break;
	case 'V':
		if (parse.abc_state == ABC_S_GLOBAL)
			break;
		error_txt = parse_voice(p, s);
		break;
	case 'X':
		memset(voice_tb, 0, sizeof voice_tb);
		nvoice = 0;
		curvoice = voice_tb;
		parse.abc_state = ABC_S_HEAD;
		lvlarena(1);
		return 2;
	}
	if (error_txt)
		syntax(error_txt, p);
	return 0;
}

/* -- print a syntax error message -- */
static void syntax(char *msg,
		   char *q)
{
	int n, len, m1, m2, pp;
	int maxcol = 73;

	severity = 1;
	n = q - abc_line;
	len = strlen(abc_line);
	if ((unsigned) n > (unsigned) len)
		n = -1;
	print_error(msg, n);
	if (n < 0) {
		if (q && *q != '\0')
			fprintf(stderr, " (near '%s')\n", q);
		return;
	}
	m1 = 0;
	m2 = len;
	if (m2 > maxcol) {
		if (n < maxcol) {
			m2 = maxcol;
		} else {
			m1 = n - 20;
			m2 = m1 + maxcol;
			if (m2 > len)
				m2 = len;
		}
	}

	fprintf(stderr, "%4d ", linenum);
	pp = 6;
	if (m1 > 0) {
		fprintf(stderr, "...");
		pp += 3;
	}
	fprintf(stderr, "%.*s", m2 - m1, &abc_line[m1]);
	if (m2 < len)
		fprintf(stderr, "...");
	fprintf(stderr, "\n");

	if ((unsigned) n < 200)
		fprintf(stderr, "%*s\n", n + pp - m1, "^");

	if (last_sym)
		last_sym->flags |= ABC_F_ERROR;
}

/* -- switch to a new voice overlay -- */
static void vover_new(void)
{
	int voice, mvoice;

	mvoice = curvoice->mvoice;
	for (voice = curvoice - voice_tb + 1; voice <= nvoice; voice++)
		if (voice_tb[voice].mvoice == mvoice)
			break;
	if (voice > nvoice) {
		if (nvoice >= MAXVOICE) {
			syntax("Too many voices", 0);
			return;
		}
		nvoice = voice;
		voice_tb[voice].id[0] = '&';
		voice_tb[voice].mvoice = mvoice;
	}
	voice_tb[voice].ulen = curvoice->ulen;
	voice_tb[voice].microscale = curvoice->microscale;
	curvoice = &voice_tb[voice];
}
