mirror of
https://github.com/sshlien/abcmidi.git
synced 2025-12-06 06:55:06 +00:00
3475 lines
74 KiB
C
3475 lines
74 KiB
C
/*
|
|
* parseabc.c - code to parse an abc file. This file is used by the
|
|
* following 3 programs :
|
|
* abc2midi - program to convert abc files to MIDI files.
|
|
* abc2abc - program to manipulate abc files.
|
|
* yaps - program to convert abc to PostScript music files.
|
|
* Copyright (C) 1999 James Allwright
|
|
* e-mail: J.R.Allwright@westminster.ac.uk
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*
|
|
*/
|
|
|
|
|
|
/* Macintosh port 30th July 1996 */
|
|
/* DropShell integration 27th Jan 1997 */
|
|
/* Wil Macaulay (wil@syndesis.com) */
|
|
|
|
|
|
#define TAB 9
|
|
#include "abc.h"
|
|
#include "parseabc.h"
|
|
#include "music_utils.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
/* [JM] 2018-02-22 to handle strncasecmp() */
|
|
#include <string.h>
|
|
|
|
/* #define SIZE_ABBREVIATIONS ('Z' - 'H' + 1) [SS] 2016-09-20 */
|
|
#define SIZE_ABBREVIATIONS 58
|
|
|
|
/* [SS] 2015-09-28 changed _snprintf_s to _snprintf */
|
|
#ifdef _MSC_VER
|
|
#define snprintf _snprintf
|
|
#define strncasecmp strnicmp
|
|
#endif
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
#define ANSILIBS
|
|
#define casecmp stricmp
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#else
|
|
#define casecmp strcasecmp
|
|
#endif
|
|
#define stringcmp strcmp
|
|
|
|
#ifdef __MWERKS__
|
|
#define __MACINTOSH__ 1
|
|
#endif /* __MWERKS__ */
|
|
|
|
#ifdef __MACINTOSH__
|
|
#define main macabc2midi_main
|
|
#define STRCHR
|
|
#endif /* __MACINTOSH__ */
|
|
|
|
/* define USE_INDEX if your C libraries have index() instead of strchr() */
|
|
#ifdef USE_INDEX
|
|
#define strchr index
|
|
#endif
|
|
|
|
#ifdef ANSILIBS
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#else
|
|
extern char *malloc ();
|
|
extern char *strchr ();
|
|
#endif
|
|
|
|
int lineno;
|
|
int parsing_started = 0;
|
|
int parsing, slur;
|
|
int ignore_line = 0; /* [SS] 2017-04-12 */
|
|
int inhead, inbody;
|
|
int parserinchord;
|
|
static int ingrace = 0; /* [SS] 2020-06-01 */
|
|
int chorddecorators[DECSIZE];
|
|
char decorations[] = ".MLRH~Tuv'OPS"; /* 2020-05-01 */
|
|
char *abbreviation[SIZE_ABBREVIATIONS];
|
|
|
|
int num_voices = 0; /* [JA] 2020-10-12 */
|
|
int repcheck = 1; /* enables/ disables repeat checking */
|
|
/* abc2midi disables repeat checking because it does its own thing */
|
|
voice_context_t voicecode[MAX_VOICES];
|
|
timesig_details_t master_timesig; /* [JA] 2020-12-10 */
|
|
cleftype_t master_clef;
|
|
int has_timesig;
|
|
int master_unitlen; /* L: field value is 1/unitlen */
|
|
int voicenum; /* current voice number */
|
|
int has_voice_fields = 0;
|
|
|
|
int decorators_passback[DECSIZE];
|
|
/* this global array is linked as an external to store.c and
|
|
* yaps.tree.c and is used to pass back decorator information
|
|
* from event_instruction to parsenote.
|
|
*/
|
|
|
|
char inputline[512]; /* [SS] 2011-06-07 2012-11-22 */
|
|
char *linestart; /* [SS] 2011-07-18 */
|
|
int lineposition; /* [SS] 2011-07-18 */
|
|
char timesigstring[16]; /* [SS] 2011-08-19 links with stresspat.c */
|
|
|
|
int nokey = 0; /* K: none was encountered */
|
|
int nokeysig = 0; /* links with toabc.c [SS] 2016-03-03 */
|
|
int chord_n, chord_m; /* for event_chordoff */
|
|
int fileline_number = 1;
|
|
int intune = 1;
|
|
int inchordflag; /* [SS] 2012-03-30 */
|
|
struct fraction setmicrotone; /* [SS] 2014-01-07 */
|
|
int microtone; /* [SS] 2014-01-19 */
|
|
int temperament = 0; /* [SS] 2020-06-25 */
|
|
|
|
extern programname fileprogram;
|
|
int oldchordconvention = 0;
|
|
char * abcversion = "2.0"; /* [SS] 2014-08-11 */
|
|
char lastfieldcmd = ' '; /* [SS] 2014-08-15 */
|
|
|
|
/* tables mode and modeshift moved to music_utils.c */
|
|
|
|
int modeminor[10] = { 0, 1, 1,
|
|
1, 0, 0, 0, 0, 0, 0
|
|
};
|
|
int modekeyshift[10] = { 0, 5, 5, 5, 6, 0, 1, 2, 3, 4 };
|
|
|
|
int *
|
|
checkmalloc (bytes)
|
|
/* malloc with error checking */
|
|
int bytes;
|
|
{
|
|
int *p;
|
|
|
|
p = (int *) malloc (bytes);
|
|
if (p == NULL)
|
|
{
|
|
printf ("Out of memory error - malloc failed!\n");
|
|
exit (0);
|
|
};
|
|
return (p);
|
|
}
|
|
|
|
char *
|
|
addstring (s)
|
|
/* create space for string and store it in memory */
|
|
char *s;
|
|
{
|
|
char *p;
|
|
|
|
p = (char *) checkmalloc (strlen (s) + 1);
|
|
strcpy (p, s);
|
|
return (p);
|
|
}
|
|
|
|
/* [SS] 2014-08-16 [SDG] 2020-06-03 */
|
|
char * concatenatestring(s1,s2)
|
|
char * s1;
|
|
char * s2;
|
|
{
|
|
int len = strlen(s1) + strlen(s2) + 1;
|
|
char *p = (char *) checkmalloc(len);
|
|
#ifdef NO_SNPRINTF
|
|
sprintf(p, "%s%s",s1,s2); /* [SS] 2020-11-01 */
|
|
#else
|
|
snprintf(p,len, "%s%s",s1,s2);
|
|
#endif
|
|
return p;
|
|
}
|
|
|
|
|
|
void
|
|
initvstring (s)
|
|
struct vstring *s;
|
|
/* initialize vstring (variable length string data structure) */
|
|
{
|
|
s->len = 0;
|
|
s->limit = 40;
|
|
s->st = (char *) checkmalloc (s->limit + 1);
|
|
*(s->st) = '\0';
|
|
}
|
|
|
|
void
|
|
extendvstring (s)
|
|
struct vstring *s;
|
|
/* doubles character space available in string */
|
|
{
|
|
char *p;
|
|
|
|
if (s->limit > 0)
|
|
{
|
|
s->limit = s->limit * 2;
|
|
p = (char *) checkmalloc (s->limit + 1);
|
|
strcpy (p, s->st);
|
|
free (s->st);
|
|
s->st = p;
|
|
}
|
|
else
|
|
{
|
|
initvstring (s);
|
|
};
|
|
}
|
|
|
|
void
|
|
addch (ch, s)
|
|
char ch;
|
|
struct vstring *s;
|
|
/* appends character to vstring structure */
|
|
{
|
|
if (s->len >= s->limit)
|
|
{
|
|
extendvstring (s);
|
|
};
|
|
*(s->st + s->len) = ch;
|
|
*(s->st + (s->len) + 1) = '\0';
|
|
s->len = (s->len) + 1;
|
|
}
|
|
|
|
void
|
|
addtext (text, s)
|
|
char *text;
|
|
struct vstring *s;
|
|
/* appends a string to vstring data structure */
|
|
{
|
|
int newlen;
|
|
|
|
newlen = s->len + strlen (text);
|
|
while (newlen >= s->limit)
|
|
{
|
|
extendvstring (s);
|
|
};
|
|
strcpy (s->st + s->len, text);
|
|
s->len = newlen;
|
|
}
|
|
|
|
void
|
|
clearvstring (s)
|
|
struct vstring *s;
|
|
/* set string to empty */
|
|
/* does not deallocate memory ! */
|
|
{
|
|
*(s->st) = '\0';
|
|
s->len = 0;
|
|
}
|
|
|
|
void
|
|
freevstring (s)
|
|
struct vstring *s;
|
|
/* deallocates memory allocated for string */
|
|
{
|
|
if (s->st != NULL)
|
|
{
|
|
free (s->st);
|
|
s->st = NULL;
|
|
};
|
|
s->len = 0;
|
|
s->limit = 0;
|
|
}
|
|
|
|
void
|
|
parseron ()
|
|
{
|
|
parsing = 1;
|
|
slur = 0;
|
|
parsing_started = 1;
|
|
}
|
|
|
|
void
|
|
parseroff ()
|
|
{
|
|
parsing = 0;
|
|
slur = 0;
|
|
}
|
|
|
|
/* [SS] 2017-04-12 */
|
|
void handle_abc2midi_parser (line)
|
|
char *line;
|
|
{
|
|
char *p;
|
|
p = line;
|
|
if (strncasecmp(p,"%%MidiOff",9) == 0) {
|
|
ignore_line = 1;
|
|
}
|
|
if (strncasecmp(p,"%%MidiOn",8) == 0) {
|
|
ignore_line = 0;
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
getarg (option, argc, argv)
|
|
/* look for argument 'option' in command line */
|
|
char *option;
|
|
char *argv[];
|
|
int argc;
|
|
{
|
|
int j, place;
|
|
|
|
place = -1;
|
|
for (j = 0; j < argc; j++)
|
|
{
|
|
if (strcmp (option, argv[j]) == 0)
|
|
{
|
|
place = j + 1;
|
|
};
|
|
};
|
|
return (place);
|
|
}
|
|
|
|
void
|
|
skipspace (p)
|
|
char **p;
|
|
{
|
|
/* skip space and tab */
|
|
while (((int) **p == ' ') || ((int) **p == TAB))
|
|
*p = *p + 1;
|
|
}
|
|
|
|
void
|
|
skiptospace (p)
|
|
char **p;
|
|
{
|
|
while (((int) **p != ' ') && ((int) **p != TAB) && (int) **p != '\0')
|
|
*p = *p + 1;
|
|
}
|
|
|
|
int
|
|
readnumf (num)
|
|
char *num;
|
|
/* read integer from string without advancing character pointer */
|
|
{
|
|
int t;
|
|
char *p;
|
|
|
|
p = num;
|
|
if (!isdigit (*p))
|
|
{
|
|
event_error ("Missing Number");
|
|
};
|
|
t = 0;
|
|
while (((int) *p >= '0') && ((int) *p <= '9'))
|
|
{
|
|
t = t * 10 + (int) *p - '0';
|
|
p = p + 1;
|
|
};
|
|
return (t);
|
|
}
|
|
|
|
int
|
|
readsnumf (s)
|
|
char *s;
|
|
/* reads signed integer from string without advancing character pointer */
|
|
{
|
|
char *p;
|
|
|
|
p = s;
|
|
if (*p == '-')
|
|
{
|
|
p = p + 1;
|
|
skipspace (&p);
|
|
return (-readnumf (p));
|
|
}
|
|
else
|
|
{
|
|
return (readnumf (p));
|
|
}
|
|
}
|
|
|
|
int
|
|
readnump (p)
|
|
char **p;
|
|
/* read integer from string and advance character pointer */
|
|
{
|
|
int t;
|
|
|
|
t = 0;
|
|
while (((int) **p >= '0') && ((int) **p <= '9'))
|
|
{
|
|
t = t * 10 + (int) **p - '0';
|
|
*p = *p + 1;
|
|
};
|
|
return (t);
|
|
}
|
|
|
|
int
|
|
readsnump (p)
|
|
char **p;
|
|
/* reads signed integer from string and advance character pointer */
|
|
{
|
|
if (**p == '-')
|
|
{
|
|
*p = *p + 1;
|
|
skipspace (p);
|
|
return (-readnump (p));
|
|
}
|
|
else
|
|
{
|
|
return (readnump (p));
|
|
}
|
|
}
|
|
|
|
/* [JA] 2020-12-10 */
|
|
int check_power_of_two(int denom)
|
|
{
|
|
int t;
|
|
char error_message[80];
|
|
|
|
t = denom;
|
|
while (t > 1) {
|
|
if (t % 2 != 0) {
|
|
snprintf(error_message, 80, "%d b is not a power of 2", denom);
|
|
event_error (error_message);
|
|
return -1;
|
|
} else {
|
|
t = t / 2;
|
|
}
|
|
}
|
|
return denom;
|
|
}
|
|
/* [JA] 2020-12-10 */
|
|
/* read the numerator of a time signature in M: field
|
|
*
|
|
* abc standard 2.2 allows M:(a + b + c + ...)/d
|
|
* This indicates how note lenths within a bar are to be grouped.
|
|
* abc standard also allows a+b+c/d to mean the same thing but this
|
|
* goes against the convention that division takes precendence
|
|
* over addition i.e. a+b+c/d normally means a + b + (c/d).
|
|
*/
|
|
static int read_complex_has_timesig(char **place, timesig_details_t *timesig)
|
|
{
|
|
int n;
|
|
int total;
|
|
int count;
|
|
int has_bracket = 0;
|
|
|
|
if (**place == '(') {
|
|
*place = *place + 1;
|
|
has_bracket = 1;
|
|
skipspace(place);
|
|
}
|
|
count = 0;
|
|
total = 0;
|
|
skipspace(place);
|
|
while ((**place != '\0') && (isdigit(**place))) {
|
|
n = readnump(place);
|
|
timesig->complex_values[count] = n;
|
|
total = total + n;
|
|
count = count + 1;
|
|
if (count > 8) {
|
|
event_error("Too many parts to complex time (maximum 8)");
|
|
return 0;
|
|
}
|
|
skipspace(place);
|
|
if (**place == '+') {
|
|
*place = *place + 1;
|
|
skipspace(place);
|
|
}
|
|
}
|
|
if (**place == ')') {
|
|
*place = *place + 1; /* advance over ')' */
|
|
skipspace(place);
|
|
if (!has_bracket) {
|
|
event_warning("Missing ( in complex time");
|
|
}
|
|
has_bracket = 0;
|
|
}
|
|
if (has_bracket) {
|
|
event_warning("Missing ) in complex time");
|
|
}
|
|
/* we have reached the end of the numerator */
|
|
timesig->num_values = count;
|
|
timesig->num = total;
|
|
if (timesig->num_values == 1)
|
|
{
|
|
timesig->type = TIMESIG_NORMAL;
|
|
} else {
|
|
timesig->type = TIMESIG_COMPLEX;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* read time signature (meter) from M: field */
|
|
void readsig (char **sig, timesig_details_t *timesig)
|
|
/* upgraded [JA] 2020-12-10 */
|
|
{
|
|
int valid_num;
|
|
|
|
if ((strncmp (*sig, "none", 4) == 0) ||
|
|
(strncmp (*sig, "None", 4) == 0)) {
|
|
timesig->num = 4;
|
|
timesig->denom = 4;
|
|
timesig->type = TIMESIG_FREE_METER;
|
|
return;
|
|
}
|
|
/* [SS] 2012-08-08 cut time (C| or c|) is 2/2 not 4/4 */
|
|
if (((**sig == 'C') || (**sig == 'c')) &&
|
|
(*(*sig + 1) == '|')) {
|
|
timesig->num = 2;
|
|
timesig->denom = 2;
|
|
timesig->type = TIMESIG_CUT;
|
|
return;
|
|
}
|
|
if ((**sig == 'C') || (**sig == 'c')) {
|
|
timesig->num = 4;
|
|
timesig->denom = 4;
|
|
timesig->type = TIMESIG_COMMON;
|
|
return;
|
|
}
|
|
valid_num = read_complex_has_timesig(sig, timesig);
|
|
if (!valid_num) {
|
|
/* An error message will have been generated by read_complex_has_timesig */
|
|
timesig->num = 4;
|
|
timesig->denom = 4;
|
|
timesig->type = TIMESIG_FREE_METER;
|
|
return;
|
|
}
|
|
|
|
if ((int)**sig != '/') {
|
|
event_warning ("No / found, assuming denominator of 1");
|
|
timesig->denom = 1;
|
|
} else {
|
|
*sig = *sig + 1;
|
|
skipspace(sig);
|
|
if (!isdigit(**sig)) {
|
|
event_warning ("Number not found for M: denominator");
|
|
}
|
|
timesig->denom = readnump (sig);
|
|
}
|
|
if ((timesig->num == 0) || (timesig->denom == 0)) {
|
|
event_error ("Expecting fraction in form A/B");
|
|
} else {
|
|
timesig->denom = check_power_of_two(timesig->denom);
|
|
}
|
|
}
|
|
|
|
void
|
|
readlen (a, b, p)
|
|
int *a, *b;
|
|
char **p;
|
|
/* read length part of a note and advance character pointer */
|
|
{
|
|
int t;
|
|
|
|
*a = readnump (p);
|
|
if (*a == 0)
|
|
{
|
|
*a = 1;
|
|
};
|
|
*b = 1;
|
|
if (**p == '/')
|
|
{
|
|
*p = *p + 1;
|
|
*b = readnump (p);
|
|
if (*b == 0)
|
|
{
|
|
*b = 2;
|
|
while (**p == '/')
|
|
{
|
|
*b = *b * 2;
|
|
*p = *p + 1;
|
|
};
|
|
};
|
|
};
|
|
*b = check_power_of_two(*b);
|
|
}
|
|
|
|
/* [JA] 2020-12-10 */
|
|
static void read_L_unitlen(int *num, int *denom, char **place)
|
|
{
|
|
if (!isdigit(**place)) {
|
|
event_warning("No digit at the start of L: field");
|
|
}
|
|
*num = readnump (place);
|
|
if (*num == 0) {
|
|
*num = 1;
|
|
}
|
|
if ((int)**place != '/') {
|
|
event_error ("Missing / ");
|
|
*denom = 1;
|
|
} else {
|
|
*place = *place + 1;
|
|
skipspace(place);
|
|
*denom = readnump (place);
|
|
}
|
|
if ((*num == 0) || (*denom == 0)) {
|
|
event_error ("Expecting fraction in form A/B");
|
|
} else {
|
|
*denom = check_power_of_two(*denom);
|
|
}
|
|
}
|
|
|
|
void
|
|
read_microtone_value (a, b, p)
|
|
int *a, *b;
|
|
char **p;
|
|
/* read length part of a note and advance character pointer */
|
|
{
|
|
int t;
|
|
|
|
*a = readnump (p);
|
|
if (*a == 0)
|
|
{
|
|
*a = 1;
|
|
};
|
|
*b = 1;
|
|
if (**p == '/')
|
|
{
|
|
*p = *p + 1;
|
|
*b = readnump (p);
|
|
if (*b == 0)
|
|
{
|
|
*b = 2;
|
|
while (**p == '/')
|
|
{
|
|
*b = *b * 2;
|
|
*p = *p + 1;
|
|
};
|
|
};
|
|
} else {
|
|
*b = 0; /* [SS] 2020-06-23 */
|
|
/* To signal that the microtone value is not a fraction */
|
|
} ;
|
|
t = *b;
|
|
while (t > 1)
|
|
{
|
|
if (t % 2 != 0)
|
|
{
|
|
/*event_warning("divisor not a power of 2"); */
|
|
t = 1;
|
|
}
|
|
else
|
|
{
|
|
t = t / 2;
|
|
};
|
|
};
|
|
}
|
|
|
|
int
|
|
ismicrotone (p, dir)
|
|
char **p;
|
|
int dir;
|
|
{
|
|
int a, b;
|
|
char *chp; /* [HL] 2020-06-20 */
|
|
chp = *p;
|
|
read_microtone_value (&a, &b, p);
|
|
/* readlen_nocheck advances past microtone indication if present */
|
|
if (chp != *p) /* [HL] 2020-06-20 */
|
|
{
|
|
/* printf("event_microtone a = %d b = %d\n",a,b); */
|
|
event_microtone (dir, a, b);
|
|
return 1;
|
|
}
|
|
setmicrotone.num = 0;
|
|
setmicrotone.denom = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* part of K: parsing - looks for a clef in K: field */
|
|
/* format is K:string where string is treble, bass, baritone, tenor, */
|
|
/* alto, mezzo, soprano or K:clef=arbitrary */
|
|
/* revised by James Allwright [JA] 2020-10-18 */
|
|
int isclef (char *s, cleftype_t * new_clef,
|
|
int *gotoctave, int *octave, int expect_clef)
|
|
{
|
|
int gotclef;
|
|
|
|
gotclef = 0;
|
|
new_clef->octave_offset = 0;
|
|
gotclef = get_standard_clef (s, new_clef);
|
|
if (!gotclef && expect_clef) {
|
|
/* do we have a clef in letter format ? e.g. C1, F3, G3 */
|
|
gotclef = get_extended_clef_details (s, new_clef);
|
|
if (new_clef->basic_clef == basic_clef_none) {
|
|
event_warning ("Found clef=none, but a clef is required. Ignoring");
|
|
gotclef = 0;
|
|
}
|
|
}
|
|
if (expect_clef && !gotclef) {
|
|
char error_message[80];
|
|
|
|
#ifdef NO_SNPRINTF
|
|
sprintf (error_message, "clef %s not recognized", s);
|
|
#else
|
|
snprintf (error_message, 80, "clef %s not recognized", s);
|
|
#endif
|
|
event_warning (error_message);
|
|
}
|
|
return (gotclef);
|
|
}
|
|
|
|
char *
|
|
readword (word, s)
|
|
/* part of parsekey, extracts word from input line */
|
|
/* besides the space, the symbols _, ^, and = are used */
|
|
/* as separators in order to handle key signature modifiers. */
|
|
/* [SS] 2010-05-24 */
|
|
char word[];
|
|
char *s;
|
|
{
|
|
char *p;
|
|
int i;
|
|
|
|
p = s;
|
|
i = 0;
|
|
/* [SS] 2015-04-08 */
|
|
while ((*p != '\0') && (*p != ' ') && (*p != '\t') && ((i == 0) ||
|
|
((*p != '='))))
|
|
{
|
|
if (i >1 && *p == '^') break; /* allow for double sharps and flats */
|
|
if (i >1 && *p == '_') break;
|
|
if (i < 29)
|
|
{
|
|
word[i] = *p;
|
|
i = i + 1;
|
|
};
|
|
p = p + 1;
|
|
};
|
|
word[i] = '\0';
|
|
return (p);
|
|
}
|
|
|
|
void
|
|
lcase (s)
|
|
/* convert word to lower case */
|
|
char *s;
|
|
{
|
|
char *p;
|
|
|
|
p = s;
|
|
while (*p != '\0')
|
|
{
|
|
if (isupper (*p))
|
|
{
|
|
*p = *p + 'a' - 'A';
|
|
};
|
|
p = p + 1;
|
|
};
|
|
}
|
|
|
|
/* initialize a timesig structure to default values */
|
|
void init_timesig(timesig_details_t *timesig)
|
|
{
|
|
timesig->type = TIMESIG_FREE_METER;
|
|
timesig->num = 4;
|
|
timesig->denom = 4;
|
|
timesig->complex_values[0] = 4;
|
|
timesig->num_values = 1;
|
|
}
|
|
|
|
/* [JA] 2020-10-12 */
|
|
void init_voice_contexts (void)
|
|
{
|
|
int i;
|
|
cleftype_t default_clef; /* [JA] 2020-11-01 */
|
|
|
|
/* we use treble clef when no clef is explicitly specified */
|
|
get_standard_clef ("treble", &default_clef); /* default to treble clef */
|
|
for (i = 0; i < MAX_VOICES; i++) { /* [SS} 2015-03-15 */
|
|
voicecode[i].label[0] = '\0';
|
|
voicecode[i].expect_repeat = 0;
|
|
voicecode[i].repeat_count = 0;
|
|
copy_clef(&voicecode[i].clef, &default_clef); /* [JA] 2020-11-01 */
|
|
}
|
|
}
|
|
|
|
/* copy one timesig_details_t struct to another [JA] 2020-12-10 */
|
|
void copy_timesig(timesig_details_t *destination, timesig_details_t *source)
|
|
{
|
|
int i;
|
|
|
|
destination->type = source->type;
|
|
destination->num = source->num;
|
|
destination->denom = source->denom;
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
destination->complex_values[i] = source->complex_values[i];
|
|
}
|
|
destination->num_values = source->num_values;
|
|
}
|
|
|
|
/* [JA] 2020-10-12 */
|
|
/* called at the start of each tune */
|
|
static void reset_parser_status (void)
|
|
{
|
|
cleftype_t default_clef;
|
|
|
|
init_timesig(&master_timesig);
|
|
get_standard_clef ("treble", &master_clef); /* default to treble clef */
|
|
has_timesig = 0;
|
|
master_unitlen = -1;
|
|
voicenum = 1;
|
|
has_voice_fields = 0;
|
|
num_voices = 1;
|
|
parserinchord = 0;
|
|
ingrace = 0;
|
|
slur = 0;
|
|
init_voice_contexts ();
|
|
}
|
|
|
|
|
|
void
|
|
print_voicecodes ()
|
|
{
|
|
int i;
|
|
if (num_voices == 0)
|
|
return;
|
|
printf ("voice mapping:\n");
|
|
for (i = 0; i < num_voices; i++)
|
|
{
|
|
if (i % 4 == 3)
|
|
printf ("\n");
|
|
printf ("%s %d ", voicecode[i].label, i + 1);
|
|
}
|
|
printf ("\n");
|
|
}
|
|
|
|
/* [JA] 2020-10-12 */
|
|
int interpret_voice_label (char *s, int num, int *is_new)
|
|
/* We expect a numeric value indicating the voice number.
|
|
* The assumption is that these will ocuur in the order in which voices
|
|
* appear, so that we have V:1, V:2, ... V:N if there are N voices.
|
|
* The abc standard 2.2 allows strings instead of these numbers to
|
|
* represent voices.
|
|
* This function should be called with either
|
|
* an empty string and a valid num or
|
|
* a valid string and num set to 0.
|
|
*
|
|
* If num is non-zero, we check that it is valid and return it.
|
|
* If the number is one above the number of voices currently in use,
|
|
* we allocate a new voice.
|
|
*
|
|
* If num is zero and the string is not empty, we check if string
|
|
* has been assigned to one of the existing voices. If not, we
|
|
* allocate a new voice and assign the string to it.
|
|
*
|
|
* If we exceed MAX_VOICES voices, we report an error.
|
|
*
|
|
* we return a voice number in the range 1 - num_voices
|
|
*/
|
|
{
|
|
int i;
|
|
char code[32];
|
|
char msg[80]; /* [PHDM] 2012-11-22 */
|
|
char *c;
|
|
c = readword (code, s);
|
|
|
|
if (num > 0)
|
|
{
|
|
if (num > num_voices + 1)
|
|
{
|
|
char error_message[80];
|
|
|
|
#ifdef NO_SNPRINT
|
|
sprintf(error_message, "V:%d out of sequence, treating as V:%d",
|
|
num, num_voices); /* [SS] 2020-10-01 */
|
|
#else
|
|
snprintf(error_message, 80, "V:%d out of sequence, treating as V:%d",
|
|
num, num_voices);
|
|
#endif
|
|
event_warning(error_message);
|
|
num = num_voices + 1;
|
|
}
|
|
/* declaring a new voice */
|
|
if (num == num_voices + 1)
|
|
{
|
|
*is_new = 1;
|
|
if (num_voices >= MAX_VOICES)
|
|
{
|
|
event_warning("Number of available voices exceeded");
|
|
return 1;
|
|
}
|
|
num_voices = num_voices + 1;
|
|
voicecode[num_voices - 1].label[0] = '\0';
|
|
} else {
|
|
/* we are using a previously declared voice */
|
|
*is_new = 0;
|
|
}
|
|
return num;
|
|
}
|
|
/* [PHDM] 2012-11-22 */
|
|
if (*c != '\0' && *c != ' ' && *c != ']') {
|
|
sprintf (msg, "invalid character `%c' in Voice ID", *c);
|
|
event_error (msg);
|
|
}
|
|
/* [PHDM] 2012-11-22 */
|
|
|
|
if (code[0] == '\0')
|
|
{
|
|
event_warning("Empty V: field, treating as V:1");
|
|
return 1;
|
|
}
|
|
/* Has supplied label been used for one of the existing voices ? */
|
|
if (has_voice_fields)
|
|
{
|
|
for (i = 0; i < num_voices; i++) {
|
|
if (strcmp (code, voicecode[i].label) == 0) {
|
|
return i + 1;
|
|
}
|
|
}
|
|
}
|
|
/* if we have got here, we have a new voice */
|
|
if ((num_voices + 1) > MAX_VOICES) {/* [SS] 2015-03-16 */
|
|
event_warning("Number of available voices exceeded");
|
|
return 1;
|
|
}
|
|
/* First V: field is a special case. We are already on voice 1,
|
|
* so we don't increment the number of voices, but we will set
|
|
* status->has_voice_fields on returning from this function.
|
|
*/
|
|
if (has_voice_fields)
|
|
{
|
|
*is_new = 1;
|
|
num_voices++;
|
|
} else {
|
|
*is_new = 0; /* we have already started as voice 1 */
|
|
}
|
|
strncpy (voicecode[num_voices - 1].label, code, 31);
|
|
return num_voices;
|
|
}
|
|
|
|
/* The following three functions parseclefs, parsetranspose,
|
|
* parseoctave are used to parse the K: field which not
|
|
* only specifies the key signature but also other descriptors
|
|
* used for producing a midi file or postscript file.
|
|
*
|
|
* The char* word contains the particular token that
|
|
* is being interpreted. If the token can be understood,
|
|
* other parameters are extracted from char ** s and
|
|
* s is advanced to point to the next token.
|
|
*/
|
|
|
|
|
|
int
|
|
parseclef (s, word, gotclef, clefstr, newclef, gotoctave, octave)
|
|
char **s;
|
|
char *word;
|
|
int *gotclef;
|
|
char *clefstr; /* [JA] 2020-10-19 */
|
|
cleftype_t * newclef;
|
|
int *gotoctave, *octave;
|
|
/* extracts string clef= something */
|
|
{
|
|
int successful;
|
|
skipspace (s);
|
|
*s = readword (word, *s);
|
|
successful = 0;
|
|
if (casecmp (word, "clef") == 0)
|
|
{
|
|
skipspace (s);
|
|
if (**s != '=')
|
|
{
|
|
event_error ("clef must be followed by '='");
|
|
}
|
|
else
|
|
{
|
|
*s = *s + 1;
|
|
skipspace (s);
|
|
*s = readword (clefstr, *s);
|
|
if (isclef (clefstr, newclef, gotoctave, octave, 1))
|
|
{
|
|
*gotclef = 1;
|
|
};
|
|
};
|
|
successful = 1;
|
|
}
|
|
else if (isclef (word, newclef, gotoctave, octave, 0))
|
|
{
|
|
*gotclef = 1;
|
|
strcpy (clefstr, word);
|
|
successful = 1;
|
|
};
|
|
return successful;
|
|
}
|
|
|
|
|
|
int
|
|
parsetranspose (s, word, gottranspose, transpose)
|
|
/* parses string transpose= number */
|
|
char **s;
|
|
char *word;
|
|
int *gottranspose;
|
|
int *transpose;
|
|
{
|
|
if (casecmp (word, "transpose") != 0)
|
|
return 0;
|
|
skipspace (s);
|
|
if (**s != '=')
|
|
{
|
|
event_error ("transpose must be followed by '='");
|
|
}
|
|
else
|
|
{
|
|
*s = *s + 1;
|
|
skipspace (s);
|
|
*transpose = readsnump (s);
|
|
*gottranspose = 1;
|
|
};
|
|
return 1;
|
|
};
|
|
|
|
|
|
int
|
|
parseoctave (s, word, gotoctave, octave)
|
|
/* parses string octave= number */
|
|
char **s;
|
|
char *word;
|
|
int *gotoctave;
|
|
int *octave;
|
|
{
|
|
if (casecmp (word, "octave") != 0)
|
|
return 0;
|
|
skipspace (s);
|
|
if (**s != '=')
|
|
{
|
|
event_error ("octave must be followed by '='");
|
|
}
|
|
else
|
|
{
|
|
*s = *s + 1;
|
|
skipspace (s);
|
|
*octave = readsnump (s);
|
|
*gotoctave = 1;
|
|
};
|
|
return 1;
|
|
};
|
|
|
|
|
|
int
|
|
parsename (s, word, gotname, namestring, maxsize)
|
|
/* parses string name= "string" in V: command
|
|
for compatability of abc2abc with abcm2ps
|
|
*/
|
|
char **s;
|
|
char *word;
|
|
int *gotname;
|
|
char namestring[];
|
|
int maxsize;
|
|
{
|
|
int i;
|
|
i = 0;
|
|
if (casecmp (word, "name") != 0)
|
|
return 0;
|
|
skipspace (s);
|
|
if (**s != '=')
|
|
{
|
|
event_error ("name must be followed by '='");
|
|
}
|
|
else
|
|
{
|
|
*s = *s + 1;
|
|
skipspace (s);
|
|
if (**s == '"') /* string enclosed in double quotes */
|
|
{
|
|
namestring[i] = (char) **s;
|
|
*s = *s + 1;
|
|
i++;
|
|
while (i < maxsize && **s != '"' && **s != '\0')
|
|
{
|
|
namestring[i] = (char) **s;
|
|
*s = *s + 1;
|
|
i++;
|
|
}
|
|
namestring[i] = (char) **s; /* copy double quotes */
|
|
i++;
|
|
namestring[i] = '\0';
|
|
}
|
|
else /* string not enclosed in double quotes */
|
|
{
|
|
while (i < maxsize && **s != ' ' && **s != '\0')
|
|
{
|
|
namestring[i] = (char) **s;
|
|
*s = *s + 1;
|
|
i++;
|
|
}
|
|
namestring[i] = '\0';
|
|
}
|
|
*gotname = 1;
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
int
|
|
parsesname (s, word, gotname, namestring, maxsize)
|
|
/* parses string name= "string" in V: command
|
|
for compatability of abc2abc with abcm2ps
|
|
*/
|
|
char **s;
|
|
char *word;
|
|
int *gotname;
|
|
char namestring[];
|
|
int maxsize;
|
|
{
|
|
int i;
|
|
i = 0;
|
|
if (casecmp (word, "sname") != 0)
|
|
return 0;
|
|
skipspace (s);
|
|
if (**s != '=')
|
|
{
|
|
event_error ("name must be followed by '='");
|
|
}
|
|
else
|
|
{
|
|
*s = *s + 1;
|
|
skipspace (s);
|
|
if (**s == '"') /* string enclosed in double quotes */
|
|
{
|
|
namestring[i] = (char) **s;
|
|
*s = *s + 1;
|
|
i++;
|
|
while (i < maxsize && **s != '"' && **s != '\0')
|
|
{
|
|
namestring[i] = (char) **s;
|
|
*s = *s + 1;
|
|
i++;
|
|
}
|
|
namestring[i] = (char) **s; /* copy double quotes */
|
|
i++;
|
|
namestring[i] = '\0';
|
|
}
|
|
else /* string not enclosed in double quotes */
|
|
{
|
|
while (i < maxsize && **s != ' ' && **s != '\0')
|
|
{
|
|
namestring[i] = (char) **s;
|
|
*s = *s + 1;
|
|
i++;
|
|
}
|
|
namestring[i] = '\0';
|
|
}
|
|
*gotname = 1;
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
int
|
|
parsemiddle (s, word, gotmiddle, middlestring, maxsize)
|
|
/* parse string middle=X in V: command
|
|
for abcm2ps compatibility
|
|
*/
|
|
char **s;
|
|
char *word;
|
|
int *gotmiddle;
|
|
char middlestring[];
|
|
int maxsize;
|
|
{
|
|
int i;
|
|
i = 0;
|
|
if (casecmp (word, "middle") != 0)
|
|
return 0;
|
|
skipspace (s);
|
|
if (**s != '=')
|
|
{
|
|
event_error ("middle must be followed by '='");
|
|
}
|
|
else
|
|
{
|
|
*s = *s + 1;
|
|
skipspace (s);
|
|
/* we really ought to check the we have a proper note name; for now, just copy non-space
|
|
characters */
|
|
while (i < maxsize && **s != ' ' && **s != '\0')
|
|
{
|
|
middlestring[i] = (char) **s;
|
|
*s = *s + 1;
|
|
++i;
|
|
}
|
|
middlestring[i] = '\0';
|
|
*gotmiddle = 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
parseother (s, word, gotother, other, maxsize) /* [SS] 2011-04-18 */
|
|
/* parses any left overs in V: command (eg. stafflines=1) */
|
|
char **s;
|
|
char *word;
|
|
int *gotother;
|
|
char other[];
|
|
int maxsize;
|
|
{
|
|
if (word[0] != '\0')
|
|
{
|
|
if ( (int) strlen (other) < maxsize) /* [SS] 2015-10-08 added (int) */
|
|
strncat (other, word, maxsize);
|
|
if (**s == '=')
|
|
{ /* [SS] 2011-04-19 */
|
|
*s = readword (word, *s);
|
|
if ( (int) strlen (other) < maxsize) /* [SS] 2015-10-08 added (int) */
|
|
strncat (other, word, maxsize);
|
|
}
|
|
strncat (other, " ", maxsize); /* in case other codes follow */
|
|
*gotother = 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void process_microtones (int *parsed, char word[],
|
|
char modmap[], int modmul[], struct fraction modmicrotone[])
|
|
{
|
|
int a, b; /* for microtones [SS] 2014-01-06 */
|
|
char c;
|
|
int j;
|
|
int success;
|
|
|
|
/* shortcuts such as ^/4G instead of ^1/4G not allowed here */
|
|
|
|
success = sscanf (&word[1], "%d/%d%c", &a, &b, &c);
|
|
if (success == 3) /* [SS] 2016-04-10 */
|
|
{
|
|
*parsed = 1;
|
|
j = (int) c - 'A';
|
|
if (j > 7) {
|
|
j = (int) c - 'a';
|
|
}
|
|
if (j > 7 || j < 0) {
|
|
event_error ("Not a valid microtone");
|
|
return;
|
|
}
|
|
if (word[0] == '_') a = -a;
|
|
/* printf("%s fraction microtone %d/%d for %c\n",word,a,b,c); */
|
|
} else {
|
|
success = sscanf (&word[1], "%d%c", &a, &c); /* [SS] 2020-06-25 */
|
|
if (success == 2)
|
|
{
|
|
b = 0;
|
|
/* printf("%s integer microtone %d%c\n",word,a,c); */
|
|
if (temperament != 1) { /* [SS] 2020-06-25 2020-07-05 */
|
|
event_warning("do not use integer microtone without calling %%MIDI temperamentequal");
|
|
}
|
|
*parsed = 1;
|
|
}
|
|
}
|
|
/* if (parsed ==1) [SS] 2020-09-30 */
|
|
if (success > 0) {
|
|
j = (int) c - 'A';
|
|
if (j > 7) {
|
|
j = (int) c - 'a';
|
|
}
|
|
if (j > 7 || j < 0) {
|
|
event_error ("Not a valid microtone");
|
|
return;
|
|
}
|
|
if (word[0] == '_') a = -a;
|
|
modmap[j] = word[0];
|
|
modmicrotone[j].num = a;
|
|
modmicrotone[j].denom = b;
|
|
/* printf("%c microtone = %d/%d\n",modmap[j],modmicrotone[j].num,modmicrotone[j].denom); */
|
|
}
|
|
} /* finished ^ = _ */
|
|
|
|
int
|
|
parsekey (str)
|
|
/* parse contents of K: field */
|
|
/* this works by picking up a strings and trying to parse them */
|
|
/* returns 1 if valid key signature found, 0 otherwise */
|
|
/* This is unfortunately a complicated function because it does alot.
|
|
* It prepares the data:
|
|
* sf = number of sharps or flats in key signature
|
|
* modeindex:= major 0, minor 1, ... dorian, etc.
|
|
* modmap: eg "^= _^ " corresponds to A# B Cb D#
|
|
* modmul: 2 signals double sharp or flat
|
|
* modmicrotone: eg {2/4 0/0 0/0 2/0 0/0 -3/0 0/0} for ^2/4A ^2D _3F
|
|
* clefstr: treble, bass, treble+8, ...
|
|
* octave: eg. 1,2,-1 ...
|
|
* transpose: 1,2 for handling certain clefs
|
|
* and various flags like explict, gotoctave, gottranspose, gotclef, gotkey
|
|
* which are all sent to abc2midi (via store.c), yaps (via yapstree), abc2abc
|
|
* via (toabc.c), using the function call event_key().
|
|
*
|
|
* The variables sf, modeindex, modmul, and modmicrotone control which notes
|
|
* are sharpened or flatten in a musical measure.
|
|
* The variable clefstr selects one of the clefs, (treble, bass, ...)
|
|
* The variable octave allows you to shift everything up and down by an octave
|
|
* The variable transpose allows you to automatically transpose every note.
|
|
*
|
|
* All of this information is extracted from the string str from the
|
|
* K: command.
|
|
*/
|
|
char *str;
|
|
{
|
|
char *s;
|
|
char word[30];
|
|
int parsed = 0; /* [SDG] 2020-06-03 */
|
|
int gotclef, gotkey, gotoctave, gottranspose;
|
|
int explict; /* [SS] 2010-05-08 */
|
|
int modnotes; /* [SS] 2010-07-29 */
|
|
int foundmode;
|
|
int transpose, octave;
|
|
char clefstr[30];
|
|
cleftype_t newclef;
|
|
char modestr[30];
|
|
char msg[80];
|
|
char *moveon;
|
|
int sf = -1, minor = -1;
|
|
char modmap[7];
|
|
int modmul[7];
|
|
struct fraction modmicrotone[7];
|
|
int i, j;
|
|
int cgotoctave, coctave;
|
|
char *key = "FCGDAEB";
|
|
int modeindex;
|
|
int a, b; /* for microtones [SS] 2014-01-06 */
|
|
int success;
|
|
char c;
|
|
|
|
clefstr[0] = (char) 0;
|
|
modestr[0] = (char) 0;
|
|
s = str;
|
|
transpose = 0;
|
|
gottranspose = 0;
|
|
octave = 0;
|
|
gotkey = 0;
|
|
gotoctave = 0;
|
|
gotclef = 0;
|
|
cgotoctave = 0;
|
|
coctave = 0;
|
|
init_new_clef (&newclef);
|
|
modeindex = 0;
|
|
explict = 0;
|
|
modnotes = 0;
|
|
nokey = nokeysig; /* [SS] 2016-03-03 */
|
|
for (i = 0; i < 7; i++)
|
|
{
|
|
modmap[i] = ' ';
|
|
modmul[i] = 1;
|
|
modmicrotone[i].num = 0; /* [SS] 2014-01-06 */
|
|
modmicrotone[i].denom = 0;
|
|
};
|
|
word[0] = 0; /* in case of empty string [SDG] 2020-06-04 */
|
|
while (*s != '\0')
|
|
{
|
|
parsed = parseclef (&s, word, &gotclef, clefstr, &newclef, &cgotoctave, &coctave);
|
|
if (gotclef) {
|
|
/* make clef an attribute of current voice */
|
|
if (inhead) {
|
|
copy_clef (&master_clef, &newclef);
|
|
}
|
|
if (inbody){
|
|
copy_clef (&voicecode[voicenum - 1].clef, &newclef);
|
|
}
|
|
}
|
|
/* parseclef also scans the s string using readword(), placing */
|
|
/* the next token into the char array word[]. */
|
|
if (!parsed)
|
|
parsed = parsetranspose (&s, word, &gottranspose, &transpose);
|
|
|
|
if (!parsed)
|
|
parsed = parseoctave (&s, word, &gotoctave, &octave);
|
|
|
|
if ((parsed == 0) && (casecmp (word, "Hp") == 0))
|
|
{
|
|
sf = 2;
|
|
minor = 0;
|
|
gotkey = 1;
|
|
parsed = 1;
|
|
};
|
|
|
|
if ((parsed == 0) && (casecmp (word, "none") == 0))
|
|
{
|
|
gotkey = 1;
|
|
parsed = 1;
|
|
nokey = 1;
|
|
minor = 0;
|
|
sf = 0;
|
|
}
|
|
|
|
if (casecmp (word, "exp") == 0)
|
|
{
|
|
explict = 1;
|
|
parsed = 1;
|
|
}
|
|
|
|
/* if K: not 'none', 'Hp' or 'exp' then look for key signature
|
|
* like Cmaj Amin Ddor ...
|
|
* The key signature is expressed by sf which indicates the
|
|
* number of sharps (if positive) or flats (if negative) */
|
|
|
|
if ((parsed == 0) && ((word[0] >= 'A') && (word[0] <= 'G')))
|
|
{
|
|
gotkey = 1;
|
|
parsed = 1;
|
|
/* parse key itself */
|
|
sf = strchr (key, word[0]) - key - 1;
|
|
j = 1;
|
|
/* deal with sharp/flat */
|
|
if (word[1] == '#')
|
|
{
|
|
sf += 7;
|
|
j = 2;
|
|
}
|
|
else
|
|
{
|
|
if (word[1] == 'b')
|
|
{
|
|
sf -= 7;
|
|
j = 2;
|
|
};
|
|
}
|
|
minor = 0;
|
|
foundmode = 0;
|
|
if ((int) strlen (word) == j)
|
|
{
|
|
/* look at next word for mode */
|
|
skipspace (&s);
|
|
moveon = readword (modestr, s);
|
|
lcase (modestr);
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
if (strncmp (modestr, mode[i], 3) == 0)
|
|
{
|
|
foundmode = 1;
|
|
sf = sf + modeshift[i];
|
|
minor = modeminor[i];
|
|
modeindex = i;
|
|
};
|
|
};
|
|
if (foundmode)
|
|
{
|
|
s = moveon;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
strcpy (modestr, &word[j]);
|
|
lcase (modestr);
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
if (strncmp (modestr, mode[i], 3) == 0)
|
|
{
|
|
foundmode = 1;
|
|
sf = sf + modeshift[i];
|
|
minor = modeminor[i];
|
|
modeindex = i;
|
|
};
|
|
};
|
|
if (!foundmode)
|
|
{
|
|
sprintf (msg, "Unknown mode '%s'", &word[j]);
|
|
event_error (msg);
|
|
modeindex = 0;
|
|
};
|
|
};
|
|
};
|
|
if (gotkey)
|
|
{
|
|
if (sf > 7)
|
|
{
|
|
event_warning ("Unusual key representation");
|
|
sf = sf - 12;
|
|
};
|
|
if (sf < -7)
|
|
{
|
|
event_warning ("Unusual key representation");
|
|
sf = sf + 12;
|
|
};
|
|
};
|
|
|
|
|
|
/* look for key signature modifiers
|
|
* For example K:D _B
|
|
* which will include a Bb in the D major key signature
|
|
* */
|
|
|
|
if ((word[0] == '^') || (word[0] == '_') || (word[0] == '='))
|
|
{
|
|
modnotes = 1;
|
|
if ((strlen (word) == 2) && (word[1] >= 'a') && (word[1] <= 'g'))
|
|
{
|
|
j = (int) word[1] - 'a';
|
|
modmap[j] = word[0];
|
|
modmul[j] = 1;
|
|
parsed = 1;
|
|
}
|
|
else
|
|
{ /*double sharp or double flat */
|
|
if ((strlen (word) == 3) && (word[0] != '=')
|
|
&& (word[0] == word[1]) && (word[2] >= 'a')
|
|
&& (word[2] <= 'g'))
|
|
{
|
|
j = (int) word[2] - 'a';
|
|
modmap[j] = word[0];
|
|
modmul[j] = 2;
|
|
parsed = 1;
|
|
};
|
|
};
|
|
};
|
|
|
|
/* if (explict) for compatibility with abcm2ps 2010-05-08 2010-05-20 */
|
|
if ((word[0] == '^') || (word[0] == '_') || (word[0] == '='))
|
|
{
|
|
modnotes = 1;
|
|
if ((strlen (word) == 2) && (word[1] >= 'A') && (word[1] <= 'G'))
|
|
{
|
|
j = (int) word[1] - 'A';
|
|
modmap[j] = word[0];
|
|
modmul[j] = 1;
|
|
parsed = 1;
|
|
}
|
|
else if /*double sharp or double flat */
|
|
((strlen (word) == 3) && (word[0] != '=') && (word[0] == word[1])
|
|
&& (word[2] >= 'A') && (word[2] <= 'G'))
|
|
{
|
|
j = (int) word[2] - 'A';
|
|
modmap[j] = word[0];
|
|
modmul[j] = 2;
|
|
parsed = 1;
|
|
};
|
|
}
|
|
/* microtone? */
|
|
/* shortcuts such as ^/4G instead of ^1/4G not allowed here */
|
|
/* parsed =0; [SS] 2020-09-30 */
|
|
process_microtones (&parsed, word,
|
|
modmap, modmul, modmicrotone);
|
|
}
|
|
|
|
if ((parsed == 0) && (strlen (word) > 0))
|
|
{
|
|
sprintf (msg, "Ignoring string '%s' in K: field", word);
|
|
event_warning (msg);
|
|
};
|
|
if (cgotoctave)
|
|
{
|
|
gotoctave = 1;
|
|
octave = coctave;
|
|
}
|
|
if (modnotes & !gotkey)
|
|
{ /*[SS] 2010-07-29 for explicit key signature */
|
|
sf = 0;
|
|
/*gotkey = 1; [SS] 2010-07-29 */
|
|
explict = 1; /* [SS] 2010-07-29 */
|
|
}
|
|
event_key (sf, str, modeindex, modmap, modmul, modmicrotone, gotkey,
|
|
gotclef, clefstr, &newclef, octave, transpose, gotoctave, gottranspose,
|
|
explict);
|
|
return (gotkey);
|
|
}
|
|
|
|
static void set_voice_from_master(int voice_num)
|
|
{
|
|
voice_context_t *current_voice;
|
|
|
|
current_voice = &voicecode[voice_num - 1];
|
|
copy_timesig(¤t_voice->timesig, &master_timesig);
|
|
copy_clef(¤t_voice->clef, &master_clef);
|
|
current_voice->unitlen = master_unitlen;
|
|
}
|
|
|
|
void
|
|
parsevoice (s)
|
|
char *s;
|
|
{
|
|
int num; /* voice number */
|
|
struct voice_params vparams;
|
|
char word[64]; /* 2017-10-11 */
|
|
int parsed;
|
|
int coctave, cgotoctave;
|
|
int is_new = 0;
|
|
|
|
vparams.transpose = 0;
|
|
vparams.gottranspose = 0;
|
|
vparams.octave = 0;
|
|
vparams.gotoctave = 0;
|
|
vparams.gotclef = 0;
|
|
cgotoctave = 0;
|
|
coctave = 0;
|
|
vparams.gotname = 0;
|
|
vparams.gotsname = 0;
|
|
vparams.gotmiddle = 0;
|
|
vparams.gotother = 0; /* [SS] 2011-04-18 */
|
|
vparams.other[0] = '\0'; /* [SS] 2011-04-18 */
|
|
|
|
skipspace (&s);
|
|
num = 0;
|
|
if (isdigit(*s)) { /* [JA] 2020-10-12 */
|
|
num = readnump (&s);
|
|
}
|
|
num = interpret_voice_label (s, num, &is_new);
|
|
if (is_new) {
|
|
/* declaring voice for the first time.
|
|
* initialize with values set in the tune header */
|
|
set_voice_from_master(num);
|
|
}
|
|
has_voice_fields = 1;
|
|
skiptospace (&s);
|
|
voicenum = num;
|
|
skipspace (&s);
|
|
while (*s != '\0') {
|
|
parsed =
|
|
parseclef (&s, word, &vparams.gotclef, vparams.clefname,
|
|
&vparams.new_clef, &cgotoctave, &coctave);
|
|
if (vparams.gotclef) {
|
|
/* make clef an attribute of current voice */
|
|
copy_clef (&voicecode[num - 1].clef, &vparams.new_clef);
|
|
}
|
|
if (!parsed)
|
|
parsed =
|
|
parsetranspose (&s, word, &vparams.gottranspose,
|
|
&vparams.transpose);
|
|
if (!parsed)
|
|
parsed = parseoctave (&s, word, &vparams.gotoctave, &vparams.octave);
|
|
if (!parsed)
|
|
parsed =
|
|
parsename (&s, word, &vparams.gotname, vparams.namestring,
|
|
V_STRLEN);
|
|
if (!parsed)
|
|
parsed =
|
|
parsesname (&s, word, &vparams.gotsname, vparams.snamestring,
|
|
V_STRLEN);
|
|
if (!parsed)
|
|
parsed =
|
|
parsemiddle (&s, word, &vparams.gotmiddle, vparams.middlestring,
|
|
V_STRLEN);
|
|
if (!parsed)
|
|
parsed = parseother (&s, word, &vparams.gotother, vparams.other, V_STRLEN); /* [SS] 2011-04-18 */
|
|
}
|
|
/* [SS] 2015-05-13 allow octave= to change the clef= octave setting */
|
|
/* cgotoctave may be set to 1 by a clef=. vparams.gotoctave is set */
|
|
/* by octave= */
|
|
|
|
if (cgotoctave && vparams.gotoctave == 0)
|
|
{
|
|
vparams.gotoctave = 1;
|
|
vparams.octave = coctave;
|
|
}
|
|
event_voice (num, s, &vparams);
|
|
|
|
/*
|
|
if (gottranspose) printf("transpose = %d\n", vparams.transpose);
|
|
if (gotoctave) printf("octave= %d\n", vparams.octave);
|
|
if (gotclef) printf("clef= %s\n", vparams.clefstr);
|
|
if (gotname) printf("parsevoice: name= %s\n", vparams.namestring);
|
|
if(gotmiddle) printf("parsevoice: middle= %s\n", vparams.middlestring);
|
|
*/
|
|
}
|
|
|
|
|
|
void
|
|
parsenote (s)
|
|
char **s;
|
|
/* parse abc note and advance character pointer */
|
|
{
|
|
int decorators[DECSIZE];
|
|
int i, t;
|
|
int mult;
|
|
char accidental, note;
|
|
int octave, n, m;
|
|
char msg[80];
|
|
|
|
mult = 1;
|
|
microtone = 0;
|
|
accidental = ' ';
|
|
note = ' ';
|
|
for (i = 0; i < DECSIZE; i++)
|
|
{
|
|
decorators[i] = decorators_passback[i];
|
|
if (!inchordflag)
|
|
decorators_passback[i] = 0; /* [SS] 2012-03-30 */
|
|
}
|
|
while (strchr (decorations, **s) != NULL)
|
|
{
|
|
t = strchr (decorations, **s) - decorations;
|
|
decorators[t] = 1;
|
|
*s = *s + 1;
|
|
};
|
|
/*check for decorated chord */
|
|
if (**s == '[')
|
|
{
|
|
lineposition = *s - linestart; /* [SS] 2011-07-18 */
|
|
if (fileprogram == YAPS)
|
|
event_warning ("decorations applied to chord");
|
|
for (i = 0; i < DECSIZE; i++)
|
|
chorddecorators[i] = decorators[i];
|
|
event_chordon (chorddecorators);
|
|
if (fileprogram == ABC2ABC)
|
|
for (i = 0; i < DECSIZE; i++)
|
|
decorators[i] = 0;
|
|
parserinchord = 1;
|
|
*s = *s + 1;
|
|
skipspace (s);
|
|
};
|
|
if (parserinchord)
|
|
{
|
|
/* inherit decorators */
|
|
if (fileprogram != ABC2ABC)
|
|
for (i = 0; i < DECSIZE; i++)
|
|
{
|
|
decorators[i] = decorators[i] | chorddecorators[i];
|
|
};
|
|
};
|
|
|
|
/* [SS] 2011-12-08 to catch fermata H followed by a rest */
|
|
|
|
if (**s == 'z')
|
|
{
|
|
*s = *s + 1;
|
|
readlen (&n, &m, s);
|
|
event_rest (decorators, n, m, 0);
|
|
return;
|
|
}
|
|
if (**s == 'x')
|
|
{
|
|
*s = *s + 1;
|
|
readlen (&n, &m, s);
|
|
event_rest (decorators, n, m, 1);
|
|
return;
|
|
}
|
|
|
|
/* read accidental */
|
|
switch (**s)
|
|
{
|
|
case '_':
|
|
accidental = **s;
|
|
*s = *s + 1;
|
|
if (**s == '_')
|
|
{
|
|
*s = *s + 1;
|
|
mult = 2;
|
|
};
|
|
microtone = ismicrotone (s, -1);
|
|
if (microtone)
|
|
{
|
|
if (mult == 2)
|
|
mult = 1;
|
|
else
|
|
accidental = ' ';
|
|
}
|
|
break;
|
|
case '^':
|
|
accidental = **s;
|
|
*s = *s + 1;
|
|
if (**s == '^')
|
|
{
|
|
*s = *s + 1;
|
|
mult = 2;
|
|
};
|
|
microtone = ismicrotone (s, 1);
|
|
if (microtone)
|
|
{
|
|
if (mult == 2)
|
|
mult = 1;
|
|
else
|
|
accidental = ' ';
|
|
}
|
|
|
|
break;
|
|
case '=':
|
|
accidental = **s;
|
|
*s = *s + 1;
|
|
/* if ((**s == '^') || (**s == '_')) {
|
|
accidental = **s;
|
|
}; */
|
|
if (**s == '^')
|
|
{
|
|
accidental = **s;
|
|
*s = *s + 1;
|
|
microtone = ismicrotone (s, 1);
|
|
if (microtone == 0)
|
|
accidental = '^';
|
|
}
|
|
else if (**s == '_')
|
|
{
|
|
accidental = **s;
|
|
*s = *s + 1;
|
|
microtone = ismicrotone (s, -1);
|
|
if (microtone == 0)
|
|
accidental = '_';
|
|
}
|
|
break;
|
|
default:
|
|
microtone = ismicrotone (s, 1); /* [SS] 2014-01-19 */
|
|
break;
|
|
};
|
|
if ((**s >= 'a') && (**s <= 'g'))
|
|
{
|
|
note = **s;
|
|
octave = 1;
|
|
*s = *s + 1;
|
|
while ((**s == '\'') || (**s == ','))
|
|
{
|
|
if (**s == '\'')
|
|
{
|
|
octave = octave + 1;
|
|
*s = *s + 1;
|
|
};
|
|
if (**s == ',')
|
|
{
|
|
sprintf (msg, "Bad pitch specifier , after note %c", note);
|
|
event_error (msg);
|
|
octave = octave - 1;
|
|
*s = *s + 1;
|
|
};
|
|
};
|
|
}
|
|
else
|
|
{
|
|
octave = 0;
|
|
if ((**s >= 'A') && (**s <= 'G'))
|
|
{
|
|
note = **s + 'a' - 'A';
|
|
*s = *s + 1;
|
|
while ((**s == '\'') || (**s == ','))
|
|
{
|
|
if (**s == ',')
|
|
{
|
|
octave = octave - 1;
|
|
*s = *s + 1;
|
|
};
|
|
if (**s == '\'')
|
|
{
|
|
sprintf (msg, "Bad pitch specifier ' after note %c",
|
|
note + 'A' - 'a');
|
|
event_error (msg);
|
|
octave = octave + 1;
|
|
*s = *s + 1;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (note == ' ')
|
|
{
|
|
event_error ("Malformed note : expecting a-g or A-G");
|
|
}
|
|
else
|
|
{
|
|
readlen (&n, &m, s);
|
|
event_note (decorators, &voicecode[voicenum - 1].clef, accidental, mult, note, octave, n, m);
|
|
if (!microtone)
|
|
event_normal_tone (); /* [SS] 2014-01-09 */
|
|
};
|
|
}
|
|
|
|
char *
|
|
getrep (p, out)
|
|
char *p;
|
|
char *out;
|
|
/* look for number or list following [ | or :| */
|
|
{
|
|
char *q;
|
|
int digits;
|
|
int done;
|
|
int count;
|
|
|
|
q = p;
|
|
count = 0;
|
|
done = 0;
|
|
digits = 0;
|
|
while (!done)
|
|
{
|
|
if (isdigit (*q))
|
|
{
|
|
out[count] = *q;
|
|
count = count + 1;
|
|
q = q + 1;
|
|
digits = digits + 1;
|
|
/* [SS] 2013-04-21 */
|
|
if (count > 50)
|
|
{
|
|
event_error ("malformed repeat");
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (((*q == '-') || (*q == ',')) && (digits > 0)
|
|
&& (isdigit (*(q + 1))))
|
|
{
|
|
out[count] = *q;
|
|
count = count + 1;
|
|
q = q + 1;
|
|
digits = 0;
|
|
/* [SS] 2013-04-21 */
|
|
if (count > 50)
|
|
{
|
|
event_error ("malformed repeat");
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
done = 1;
|
|
};
|
|
};
|
|
};
|
|
out[count] = '\0';
|
|
return (q);
|
|
}
|
|
|
|
int
|
|
checkend (s)
|
|
char *s;
|
|
/* returns 1 if we are at the end of the line 0 otherwise */
|
|
/* used when we encounter '\' '*' or other special line end characters */
|
|
{
|
|
char *p;
|
|
int atend;
|
|
|
|
p = s;
|
|
skipspace (&p);
|
|
if (*p == '\0')
|
|
{
|
|
atend = 1;
|
|
}
|
|
else
|
|
{
|
|
atend = 0;
|
|
};
|
|
return (atend);
|
|
}
|
|
|
|
void
|
|
readstr (out, in, limit)
|
|
char out[];
|
|
char **in;
|
|
int limit;
|
|
/* copy across alpha string */
|
|
{
|
|
int i;
|
|
|
|
i = 0;
|
|
while ((isalpha (**in)) && (i < limit - 1))
|
|
{
|
|
out[i] = **in;
|
|
i = i + 1;
|
|
*in = *in + 1;
|
|
};
|
|
out[i] = '\0';
|
|
}
|
|
|
|
/* [SS] 2015-06-01 required for parse_mididef() in store.c */
|
|
/* Just like readstr but also allows anything except white space */
|
|
int readaln (out, in, limit)
|
|
char out[];
|
|
char **in;
|
|
int limit;
|
|
/* copy across alphanumeric string */
|
|
{
|
|
int i;
|
|
|
|
i = 0;
|
|
while ((!isspace (**in)) && (**in) != 0 && (i < limit - 1))
|
|
{
|
|
out[i] = **in;
|
|
i = i + 1;
|
|
*in = *in + 1;
|
|
};
|
|
out[i] = '\0';
|
|
return i;
|
|
}
|
|
|
|
void
|
|
parse_precomment (s)
|
|
char *s;
|
|
/* handles a comment field */
|
|
{
|
|
char package[40];
|
|
char *p;
|
|
int success;
|
|
|
|
success = sscanf (s, "%%%%abc-version %3s", abcversion); /* [SS] 2014-08-11 */
|
|
if (*s == '%')
|
|
{
|
|
p = s + 1;
|
|
readstr (package, &p, 40);
|
|
event_specific (package, p);
|
|
}
|
|
else
|
|
{
|
|
event_comment (s);
|
|
};
|
|
}
|
|
|
|
/* [SS] 2017-12-10 */
|
|
FILE * parse_abc_include (s)
|
|
char *s;
|
|
{
|
|
char includefilename[80];
|
|
FILE *includehandle;
|
|
int success;
|
|
success = sscanf (s, "%%%%abc-include %79s", includefilename); /* [SS] 2014-08-11 */
|
|
if (success == 1) {
|
|
/* printf("opening include file %s\n",includefilename); */
|
|
includehandle = fopen(includefilename,"r");
|
|
if (includehandle == NULL)
|
|
{
|
|
printf ("Failed to open include file %s\n", includefilename);
|
|
};
|
|
return includehandle;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
parse_tempo (place)
|
|
char *place;
|
|
/* parse tempo descriptor i.e. Q: field */
|
|
{
|
|
char *p;
|
|
int a, b;
|
|
int n;
|
|
int relative;
|
|
char *pre_string;
|
|
char *post_string;
|
|
|
|
relative = 0;
|
|
p = place;
|
|
pre_string = NULL;
|
|
if (*p == '"')
|
|
{
|
|
p = p + 1;
|
|
pre_string = p;
|
|
while ((*p != '"') && (*p != '\0'))
|
|
{
|
|
p = p + 1;
|
|
};
|
|
if (*p == '\0')
|
|
{
|
|
event_error ("Missing closing double quote");
|
|
}
|
|
else
|
|
{
|
|
*p = '\0';
|
|
p = p + 1;
|
|
place = p;
|
|
};
|
|
};
|
|
while ((*p != '\0') && (*p != '='))
|
|
p = p + 1;
|
|
if (*p == '=')
|
|
{
|
|
p = place;
|
|
skipspace (&p);
|
|
if (((*p >= 'A') && (*p <= 'G')) || ((*p >= 'a') && (*p <= 'g')))
|
|
{
|
|
relative = 1;
|
|
p = p + 1;
|
|
};
|
|
readlen (&a, &b, &p);
|
|
skipspace (&p);
|
|
if (*p != '=')
|
|
{
|
|
event_error ("Expecting = in tempo");
|
|
};
|
|
p = p + 1;
|
|
}
|
|
else
|
|
{
|
|
a = 1; /* [SS] 2013-01-27 */
|
|
/*a = 0; [SS] 2013-01-27 */
|
|
b = 4;
|
|
p = place;
|
|
};
|
|
skipspace (&p);
|
|
n = readnump (&p);
|
|
post_string = NULL;
|
|
if (*p == '"')
|
|
{
|
|
p = p + 1;
|
|
post_string = p;
|
|
while ((*p != '"') && (*p != '\0'))
|
|
{
|
|
p = p + 1;
|
|
};
|
|
if (*p == '\0')
|
|
{
|
|
event_error ("Missing closing double quote");
|
|
}
|
|
else
|
|
{
|
|
*p = '\0';
|
|
p = p + 1;
|
|
};
|
|
};
|
|
event_tempo (n, a, b, relative, pre_string, post_string);
|
|
}
|
|
|
|
void appendfield(char *); /* links with store.c and yapstree.c */
|
|
|
|
void append_fieldcmd (key, s) /* [SS] 2014-08-15 */
|
|
char key;
|
|
char *s;
|
|
{
|
|
appendfield(s);
|
|
}
|
|
|
|
void
|
|
preparse_words (s)
|
|
char *s;
|
|
/* takes a line of lyrics (w: field) and strips off */
|
|
/* any continuation character */
|
|
{
|
|
int continuation;
|
|
int l;
|
|
|
|
/* printf("Parsing %s\n", s); */
|
|
/* strip off any trailing spaces */
|
|
l = strlen (s) - 1;
|
|
while ((l >= 0) && (*(s + l) == ' '))
|
|
{
|
|
*(s + l) = '\0';
|
|
l = l - 1;
|
|
};
|
|
if (*(s + l) != '\\')
|
|
{
|
|
continuation = 0;
|
|
}
|
|
else
|
|
{
|
|
/* [SS] 2014-08-14 */
|
|
event_warning ("\\n continuation no longer supported in w: line");
|
|
continuation = 1;
|
|
/* remove continuation character */
|
|
*(s + l) = '\0';
|
|
l = l - 1;
|
|
while ((l >= 0) && (*(s + l) == ' '))
|
|
{
|
|
*(s + l) = '\0';
|
|
l = l - 1;
|
|
};
|
|
};
|
|
event_words (s, continuation);
|
|
}
|
|
|
|
void
|
|
init_abbreviations ()
|
|
/* initialize mapping of H-Z to strings */
|
|
{
|
|
int i;
|
|
|
|
/* for (i = 0; i < 'Z' - 'H'; i++) [SS] 2016-09-25 */
|
|
for (i = 0; i < 'z' - 'A'; i++) /* [SS] 2016-09-25 */
|
|
{
|
|
abbreviation[i] = NULL;
|
|
};
|
|
}
|
|
|
|
void
|
|
record_abbreviation (char symbol, char *string)
|
|
/* update record of abbreviations when a U: field is encountered */
|
|
{
|
|
int index;
|
|
|
|
/* if ((symbol < 'H') || (symbol > 'Z')) [SS] 2016-09-20 */
|
|
if ((symbol < 'A') || (symbol > 'z'))
|
|
{
|
|
return;
|
|
};
|
|
index = symbol - 'A';
|
|
if (abbreviation[index] != NULL)
|
|
{
|
|
free (abbreviation[index]);
|
|
};
|
|
abbreviation[index] = addstring (string);
|
|
}
|
|
|
|
char *
|
|
lookup_abbreviation (char symbol)
|
|
/* return string which s abbreviates */
|
|
{
|
|
/* if ((symbol < 'H') || (symbol > 'Z')) [SS] 2016-09-25 */
|
|
if ((symbol < 'A') || (symbol > 'z'))
|
|
{
|
|
return (NULL);
|
|
}
|
|
else
|
|
{
|
|
return (abbreviation[symbol - 'A']); /* [SS] 2016-09-20 */
|
|
};
|
|
}
|
|
|
|
void
|
|
free_abbreviations ()
|
|
/* free up any space taken by abbreviations */
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SIZE_ABBREVIATIONS; i++)
|
|
{
|
|
if (abbreviation[i] != NULL)
|
|
{
|
|
free (abbreviation[i]);
|
|
};
|
|
};
|
|
}
|
|
|
|
/* function to resolve unit note length when the
|
|
* L: field is missing in the header [JA] 2020-12-10
|
|
*
|
|
* From the abc standard 2.2:
|
|
* If there is no L: field defined, a unit note length is set by default,
|
|
* based on the meter field M:. This default is calculated by computing
|
|
* the meter as a decimal: if it is less than 0.75 the default unit note
|
|
* length is a sixteenth note; if it is 0.75 or greater, it is an eighth
|
|
* note. For example, 2/4 = 0.5, so, the default unit note length is a
|
|
* sixteenth note, while for 4/4 = 1.0, or 6/8 = 0.75, or 3/4= 0.75,
|
|
* it is an eighth note. For M:C (4/4), M:C| (2/2) and M:none (free meter),
|
|
* the default unit note length is 1/8.
|
|
*
|
|
*/
|
|
static void resolve_unitlen()
|
|
{
|
|
if (master_unitlen == -1)
|
|
{
|
|
if (has_timesig == 0)
|
|
{
|
|
event_default_length(8);
|
|
master_unitlen = 8;
|
|
}
|
|
else
|
|
{
|
|
if (((4 * master_timesig.num)/master_timesig.denom) >= 3)
|
|
{
|
|
event_default_length(8);
|
|
master_unitlen = 8;
|
|
}
|
|
else
|
|
{
|
|
event_default_length(16);
|
|
master_unitlen = 16;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
parsefield (key, field)
|
|
char key;
|
|
char *field;
|
|
/* top-level routine handling all lines containing a field */
|
|
{
|
|
char *comment;
|
|
char *place;
|
|
char *xplace;
|
|
int iscomment;
|
|
int foundkey;
|
|
|
|
if (key == 'X')
|
|
{
|
|
int x;
|
|
|
|
xplace = field;
|
|
skipspace (&xplace);
|
|
x = readnumf (xplace);
|
|
if (inhead)
|
|
{
|
|
event_error ("second X: field in header");
|
|
};
|
|
if (inbody)
|
|
{
|
|
/* [JA] 2020-10-14 */
|
|
event_error ("Missing blank line before new tune");
|
|
}
|
|
event_refno (x);
|
|
ignore_line =0; /* [SS] 2017-04-12 */
|
|
reset_parser_status(); /* [JA] 2020-10-12 */
|
|
inhead = 1;
|
|
inbody = 0;
|
|
parserinchord = 0;
|
|
return;
|
|
};
|
|
|
|
if (parsing == 0)
|
|
return;
|
|
|
|
/*if ((inbody) && (strchr ("EIKLMPQTVdswW", key) == NULL)) [SS] 2014-08-15 */
|
|
if ((inbody) && (strchr ("EIKLMPQTVdrswW+", key) == NULL)) /* [SS] 2015-05-11 */
|
|
{
|
|
event_error ("Field not allowed in tune body");
|
|
};
|
|
comment = field;
|
|
iscomment = 0;
|
|
while ((*comment != '\0') && (*comment != '%'))
|
|
{
|
|
comment = comment + 1;
|
|
};
|
|
if (*comment == '%')
|
|
{
|
|
iscomment = 1;
|
|
*comment = '\0';
|
|
comment = comment + 1;
|
|
};
|
|
place = field;
|
|
skipspace (&place);
|
|
switch (key)
|
|
{
|
|
case 'K':
|
|
if (inhead)
|
|
{
|
|
/* First K: is the end of the header and the start of the body.
|
|
* make sure we set up default for unit length
|
|
* if L: fields was missing in the header.
|
|
*/
|
|
resolve_unitlen();
|
|
/* set voice parameters using values from header */
|
|
set_voice_from_master(1);
|
|
}
|
|
foundkey = parsekey (place);
|
|
if (inhead || inbody) {
|
|
if (foundkey)
|
|
{
|
|
inbody = 1;
|
|
inhead = 0;
|
|
}
|
|
else
|
|
{
|
|
if (inhead)
|
|
{
|
|
event_error ("First K: field must specify key signature");
|
|
};
|
|
};
|
|
}
|
|
else
|
|
{
|
|
event_error ("No X: field preceding K:");
|
|
};
|
|
break;
|
|
case 'M':
|
|
{
|
|
timesig_details_t timesig;
|
|
|
|
/* strncpy (timesigstring, place, 16); [SS] 2011-08-19 */
|
|
#ifdef NO_SNPRINT
|
|
sprintf(timesigstring,"%s",place); /* [SEG] 2020-06-07 */
|
|
#else
|
|
snprintf(timesigstring,sizeof(timesigstring),"%s",place); /* [SEG] 2020-06-07 */
|
|
#endif
|
|
readsig (&place, ×ig);
|
|
if ((*place == 's') || (*place == 'l')) {
|
|
event_error ("s and l in M: field not supported");
|
|
};
|
|
if ((timesig.num == 0) || (timesig.denom == 0)) {
|
|
event_warning ("Invalid time signature ignored");
|
|
} else {
|
|
if (inhead) {
|
|
/* copy timesig into master_timesig */
|
|
copy_timesig(&master_timesig, ×ig);
|
|
}
|
|
if (inbody) {
|
|
/* update timesig in current voice */
|
|
voice_context_t *current_voice;
|
|
|
|
current_voice = &voicecode[voicenum - 1];
|
|
copy_timesig(¤t_voice->timesig, ×ig);
|
|
}
|
|
event_timesig (×ig);
|
|
has_timesig = 1;
|
|
}
|
|
}
|
|
break;
|
|
case 'L':
|
|
{
|
|
int num, denom;
|
|
|
|
read_L_unitlen(&num, &denom, &place);
|
|
if (num != 1)
|
|
{
|
|
event_error ("Default length must be 1/X");
|
|
}
|
|
else
|
|
{
|
|
if (denom > 0)
|
|
{
|
|
event_length (denom);
|
|
if (inhead)
|
|
{
|
|
master_unitlen = denom;
|
|
}
|
|
if (inbody) {
|
|
voice_context_t *current_voice;
|
|
|
|
current_voice = &voicecode[voicenum - 1];
|
|
current_voice->unitlen = denom;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
event_error ("invalid denominator");
|
|
};
|
|
};
|
|
break;
|
|
};
|
|
case 'P':
|
|
event_part (place);
|
|
break;
|
|
case 'I':
|
|
event_info (place);
|
|
break;
|
|
case 'V':
|
|
parsevoice (place);
|
|
break;
|
|
case 'Q':
|
|
parse_tempo (place);
|
|
break;
|
|
case 'U':
|
|
{
|
|
char symbol;
|
|
char container;
|
|
char *expansion;
|
|
|
|
skipspace (&place);
|
|
/* if ((*place >= 'H') && (*place <= 'Z')) [SS] 2016-09-20 */
|
|
if ((*place >= 'A') && (*place <= 'z')) /* [SS] 2016-09-20 */
|
|
{
|
|
symbol = *place;
|
|
place = place + 1;
|
|
skipspace (&place);
|
|
if (*place == '=')
|
|
{
|
|
place = place + 1;
|
|
skipspace (&place);
|
|
if (*place == '!')
|
|
{
|
|
place = place + 1;
|
|
container = '!';
|
|
expansion = place;
|
|
while ((!iscntrl (*place)) && (*place != '!'))
|
|
{
|
|
place = place + 1;
|
|
};
|
|
if (*place != '!')
|
|
{
|
|
event_error ("No closing ! in U: field");
|
|
};
|
|
*place = '\0';
|
|
}
|
|
else
|
|
{
|
|
container = ' ';
|
|
expansion = place;
|
|
while (isalnum (*place))
|
|
{
|
|
place = place + 1;
|
|
};
|
|
*place = '\0';
|
|
};
|
|
if (strlen (expansion) > 0)
|
|
{
|
|
record_abbreviation (symbol, expansion);
|
|
event_abbreviation (symbol, expansion, container);
|
|
}
|
|
else
|
|
{
|
|
event_error ("Missing term in U: field");
|
|
};
|
|
}
|
|
else
|
|
{
|
|
event_error ("Missing '=' U: field ignored");
|
|
};
|
|
}
|
|
else
|
|
{
|
|
event_warning ("only 'H' - 'Z' supported in U: field");
|
|
};
|
|
};
|
|
break;
|
|
case 'w':
|
|
preparse_words (place);
|
|
break;
|
|
case 'd':
|
|
/* decoration line in abcm2ps */
|
|
event_field (key, place); /* [SS] 2010-02-23 */
|
|
break;
|
|
case 's':
|
|
event_field (key, place); /* [SS] 2010-02-23 */
|
|
break;
|
|
case '+':
|
|
if (lastfieldcmd == 'w')
|
|
append_fieldcmd (key, place); /*[SS] 2014-08-15 */
|
|
break; /* [SS] 2014-09-07 */
|
|
default:
|
|
event_field (key, place);
|
|
};
|
|
if (iscomment)
|
|
{
|
|
parse_precomment (comment);
|
|
};
|
|
if (key == 'w') lastfieldcmd = 'w'; /* [SS] 2014-08-15 */
|
|
else lastfieldcmd = ' '; /* [SS[ 2014-08-15 */
|
|
}
|
|
|
|
char *
|
|
parseinlinefield (p)
|
|
char *p;
|
|
/* parse field within abc line e.g. [K:G] */
|
|
{
|
|
char *q;
|
|
|
|
event_startinline ();
|
|
q = p;
|
|
while ((*q != ']') && (*q != '\0'))
|
|
{
|
|
q = q + 1;
|
|
};
|
|
if (*q == ']')
|
|
{
|
|
*q = '\0';
|
|
parsefield (*p, p + 2);
|
|
q = q + 1;
|
|
}
|
|
else
|
|
{
|
|
event_error ("missing closing ]");
|
|
parsefield (*p, p + 2);
|
|
};
|
|
event_closeinline ();
|
|
return (q);
|
|
}
|
|
|
|
/* this function is used by toabc.c [SS] 2011-06-10 */
|
|
void
|
|
print_inputline_nolinefeed ()
|
|
{
|
|
if (inputline[sizeof inputline - 1] != '\0')
|
|
{
|
|
/*
|
|
* We are called exclusively by toabc.c,
|
|
* and when we are called, event_error is muted,
|
|
* so, event_error("input line truncated") does nothing.
|
|
* Simulate it with a plain printf. [PHDM 2012-12-01]
|
|
*/
|
|
printf ("%%Error : input line truncated\n");
|
|
}
|
|
printf ("%s", inputline);
|
|
}
|
|
|
|
/* this function is used by toabc.c [SS] 2011-06-07 */
|
|
void
|
|
print_inputline ()
|
|
{
|
|
print_inputline_nolinefeed ();
|
|
printf ("\n");
|
|
}
|
|
|
|
/* [JA] 2020-10-12 */
|
|
/* Look for problems in the use of repeats */
|
|
static void check_bar_repeats (int bar_type, char *replist)
|
|
{
|
|
voice_context_t *cv = &voicecode[voicenum];
|
|
|
|
switch (bar_type) {
|
|
case SINGLE_BAR:
|
|
break;
|
|
case DOUBLE_BAR:
|
|
break;
|
|
case THIN_THICK:
|
|
break;
|
|
case THICK_THIN:
|
|
break;
|
|
case BAR_REP:
|
|
if (cv->expect_repeat) {
|
|
event_warning ("Expecting repeat, found |:");
|
|
};
|
|
cv->expect_repeat = 1;
|
|
cv->repeat_count = cv->repeat_count + 1;
|
|
break;
|
|
case REP_BAR:
|
|
if (!cv->expect_repeat) {
|
|
char error_message[80];
|
|
|
|
if (cv->repeat_count == 0)
|
|
{
|
|
#ifdef NO_SNPRINT
|
|
sprintf(error_message, "Missing repeat at start ? Unexpected :|%s found", replist);
|
|
#else
|
|
snprintf(error_message, 80, "Missing repeat at start ? Unexpected :|%s found", replist);
|
|
#endif
|
|
event_warning (error_message);
|
|
}
|
|
else
|
|
{
|
|
#ifdef NO_SNPRINT
|
|
sprintf(error_message, "Unexpected :|%s found", replist);
|
|
#else
|
|
snprintf(error_message, 80, "Unexpected :|%s found", replist);
|
|
#endif
|
|
|
|
event_warning (error_message);
|
|
}
|
|
};
|
|
cv->expect_repeat = 0;
|
|
cv->repeat_count = cv->repeat_count + 1;
|
|
break;
|
|
case BAR1:
|
|
if (!cv->expect_repeat) {
|
|
if (cv->repeat_count == 0)
|
|
{
|
|
event_warning ("Missing repeat at start ? found |1");
|
|
}
|
|
else
|
|
{
|
|
event_warning ("found |1 in non-repeat section");
|
|
}
|
|
};
|
|
break;
|
|
case REP_BAR2:
|
|
if (!cv->expect_repeat) {
|
|
if (cv->repeat_count == 0)
|
|
{
|
|
event_warning ("Missing repeat at start ? found :|2");
|
|
}
|
|
else
|
|
{
|
|
event_warning ("No repeat expected, found :|2");
|
|
}
|
|
};
|
|
cv->expect_repeat = 0;
|
|
cv->repeat_count = cv->repeat_count + 1;
|
|
break;
|
|
case DOUBLE_REP:
|
|
if (!cv->expect_repeat) {
|
|
if (cv->repeat_count == 0)
|
|
{
|
|
event_warning ("Missing repeat at start ? found ::");
|
|
}
|
|
else
|
|
{
|
|
event_warning ("No repeat expected, found ::");
|
|
}
|
|
};
|
|
cv->expect_repeat = 1;
|
|
cv->repeat_count = cv->repeat_count + 1;
|
|
break;
|
|
};
|
|
}
|
|
|
|
/* [JA] 2020-10-12 */
|
|
static void check_and_call_bar(int bar_type, char *replist)
|
|
{
|
|
if (repcheck)
|
|
{
|
|
check_bar_repeats (bar_type, replist);
|
|
}
|
|
event_bar (bar_type, replist);
|
|
}
|
|
|
|
void
|
|
parsemusic (field)
|
|
char *field;
|
|
/* parse a line of abc notes */
|
|
{
|
|
char *p;
|
|
char c; /* [SS] 2017-04-19 */
|
|
char *comment;
|
|
char endchar;
|
|
int iscomment;
|
|
int starcount;
|
|
int i;
|
|
char playonrep_list[80];
|
|
int decorators[DECSIZE];
|
|
|
|
for (i = 0; i < DECSIZE; i++)
|
|
decorators[i] = 0; /* [SS] 2012-03-30 */
|
|
|
|
event_startmusicline ();
|
|
endchar = ' ';
|
|
comment = field;
|
|
iscomment = 0;
|
|
while ((*comment != '\0') && (*comment != '%'))
|
|
{
|
|
comment = comment + 1;
|
|
};
|
|
if (*comment == '%')
|
|
{
|
|
iscomment = 1;
|
|
*comment = '\0';
|
|
comment = comment + 1;
|
|
};
|
|
|
|
p = field;
|
|
skipspace (&p);
|
|
while (*p != '\0')
|
|
{
|
|
lineposition = p - linestart; /* [SS] 2011-07-18 */
|
|
|
|
if (*p == '.' && *(p+1) == '(') { /* [SS] 2015-04-28 dotted slur */
|
|
p = p+1;
|
|
event_sluron (1);
|
|
p = p+1;
|
|
}
|
|
|
|
if (((*p >= 'a') && (*p <= 'g')) || ((*p >= 'A') && (*p <= 'G')) ||
|
|
(strchr ("_^=", *p) != NULL) || (strchr (decorations, *p) != NULL))
|
|
{
|
|
parsenote (&p);
|
|
}
|
|
else
|
|
{
|
|
c = *p; /* [SS] 2017-04-19 */
|
|
switch (*p)
|
|
{
|
|
case '"':
|
|
{
|
|
struct vstring gchord;
|
|
|
|
p = p + 1;
|
|
initvstring (&gchord);
|
|
while ((*p != '"') && (*p != '\0'))
|
|
{
|
|
addch (*p, &gchord);
|
|
p = p + 1;
|
|
};
|
|
if (*p == '\0')
|
|
{
|
|
event_error ("Guitar chord name not properly closed");
|
|
}
|
|
else
|
|
{
|
|
p = p + 1;
|
|
};
|
|
event_gchord (gchord.st);
|
|
freevstring (&gchord);
|
|
break;
|
|
};
|
|
case '|':
|
|
p = p + 1;
|
|
switch (*p)
|
|
{
|
|
case ':':
|
|
check_and_call_bar (BAR_REP, "");
|
|
p = p + 1;
|
|
break;
|
|
case '|':
|
|
check_and_call_bar (DOUBLE_BAR, "");
|
|
p = p + 1;
|
|
break;
|
|
case ']':
|
|
check_and_call_bar (THIN_THICK, "");
|
|
p = p + 1;
|
|
break;
|
|
default:
|
|
p = getrep (p, playonrep_list);
|
|
check_and_call_bar (SINGLE_BAR, playonrep_list);
|
|
};
|
|
break;
|
|
case ':':
|
|
p = p + 1;
|
|
switch (*p)
|
|
{
|
|
case ':':
|
|
check_and_call_bar (DOUBLE_REP, "");
|
|
p = p + 1;
|
|
break;
|
|
case '|':
|
|
p = p + 1;
|
|
p = getrep (p, playonrep_list);
|
|
check_and_call_bar (REP_BAR, playonrep_list);
|
|
if (*p == ']')
|
|
p = p + 1; /* [SS] 2013-10-31 */
|
|
break;
|
|
/* [JM] 2018-02-22 dotted bar line ... this is legal */
|
|
default:
|
|
/* [SS] 2018-02-08 introducing DOTTED_BAR */
|
|
check_and_call_bar (DOTTED_BAR,"");
|
|
};
|
|
break;
|
|
case ' ':
|
|
event_space ();
|
|
skipspace (&p);
|
|
break;
|
|
case TAB:
|
|
event_space ();
|
|
skipspace (&p);
|
|
break;
|
|
case '(':
|
|
p = p + 1;
|
|
{
|
|
int t, q, r;
|
|
|
|
t = 0;
|
|
q = 0;
|
|
r = 0;
|
|
t = readnump (&p);
|
|
if ((t != 0) && (*p == ':'))
|
|
{
|
|
p = p + 1;
|
|
q = readnump (&p);
|
|
if (*p == ':')
|
|
{
|
|
p = p + 1;
|
|
r = readnump (&p);
|
|
};
|
|
};
|
|
if (t == 0)
|
|
{
|
|
if (*p == '&') {
|
|
p = p+1;
|
|
event_start_extended_overlay(); /* [SS] 2015-03-23 */
|
|
}
|
|
else
|
|
event_sluron (1);
|
|
}
|
|
else /* t != 0 */
|
|
{
|
|
event_tuple (t, q, r);
|
|
};
|
|
};
|
|
break;
|
|
case ')':
|
|
p = p + 1;
|
|
event_sluroff (0);
|
|
break;
|
|
case '{':
|
|
p = p + 1;
|
|
event_graceon ();
|
|
ingrace = 1;
|
|
break;
|
|
case '}':
|
|
p = p + 1;
|
|
event_graceoff ();
|
|
ingrace = 0;
|
|
break;
|
|
case '[':
|
|
p = p + 1;
|
|
switch (*p)
|
|
{
|
|
case '|':
|
|
p = p + 1;
|
|
check_and_call_bar (THICK_THIN, "");
|
|
if (*p == ':') { /* [SS] 2015-04-13 [SDG] 2020-06-04 */
|
|
check_and_call_bar (BAR_REP, "");
|
|
p = p + 1;
|
|
}
|
|
break;
|
|
default:
|
|
if (isdigit (*p))
|
|
{
|
|
p = getrep (p, playonrep_list);
|
|
event_playonrep (playonrep_list);
|
|
}
|
|
else
|
|
{
|
|
if (isalpha (*p) && (*(p + 1) == ':'))
|
|
{
|
|
p = parseinlinefield (p);
|
|
}
|
|
else
|
|
{
|
|
lineposition = p - linestart; /* [SS] 2011-07-18 */
|
|
/* [SS] 2012-03-30 */
|
|
for (i = 0; i < DECSIZE; i++)
|
|
chorddecorators[i] =
|
|
decorators[i] | decorators_passback[i];
|
|
event_chordon (chorddecorators);
|
|
parserinchord = 1;
|
|
};
|
|
};
|
|
break;
|
|
};
|
|
break;
|
|
case ']':
|
|
p = p + 1;
|
|
/*readlen (&chord_n, &chord_m, &p); [SS] 2019-06-06 */
|
|
/*if (!inchordflag && *p == '|') { [SS] 2018-12-21 not THICK_THIN bar line*/
|
|
if (!parserinchord && *p == '|') { /* [SS] 2019-06-06 not THICK_THIN bar line*/
|
|
p = p + 1; /* skip over | */
|
|
check_and_call_bar (THIN_THICK, "");}
|
|
else {
|
|
readlen (&chord_n, &chord_m, &p); /* [SS] 2019-06-06 */
|
|
event_chordoff (chord_n, chord_m);
|
|
parserinchord = 0;
|
|
}
|
|
for (i = 0; i < DECSIZE; i++)
|
|
{
|
|
chorddecorators[i] = 0;
|
|
decorators_passback[i] = 0; /* [SS] 2012-03-30 */
|
|
}
|
|
break;
|
|
case '$':
|
|
p = p + 1;
|
|
event_score_linebreak ('$'); /* [JA] 2020-10-07 */
|
|
break;
|
|
/* hidden rest */
|
|
case 'x':
|
|
{
|
|
int n, m;
|
|
|
|
p = p + 1;
|
|
readlen (&n, &m, &p);
|
|
/* in order to handle a fermata applied to a rest we must
|
|
* pass decorators to event_rest.
|
|
*/
|
|
for (i = 0; i < DECSIZE; i++)
|
|
{
|
|
decorators[i] = decorators_passback[i];
|
|
decorators_passback[i] = 0;
|
|
}
|
|
event_rest (decorators, n, m, 1);
|
|
decorators[FERMATA] = 0; /* [SS] 2014-11-17 */
|
|
break;
|
|
};
|
|
/* regular rest */
|
|
case 'z':
|
|
{
|
|
int n, m;
|
|
|
|
p = p + 1;
|
|
readlen (&n, &m, &p);
|
|
/* in order to handle a fermata applied to a rest we must
|
|
* pass decorators to event_rest.
|
|
*/
|
|
for (i = 0; i < DECSIZE; i++)
|
|
{
|
|
decorators[i] = decorators_passback[i];
|
|
decorators_passback[i] = 0;
|
|
}
|
|
event_rest (decorators, n, m, 0);
|
|
decorators[FERMATA] = 0; /* [SS] 2014-11-17 */
|
|
break;
|
|
};
|
|
case 'y': /* used by Barfly and abcm2ps to put space */
|
|
/* I'm sure I've seen somewhere that /something/ allows a length
|
|
* specifier with y to enlarge the space length. Allow it anyway; it's
|
|
* harmless.
|
|
*/
|
|
{
|
|
int n, m;
|
|
|
|
p = p + 1;
|
|
readlen (&n, &m, &p);
|
|
event_spacing (n, m);
|
|
break;
|
|
};
|
|
/* full bar rest */
|
|
case 'Z':
|
|
case 'X': /* [SS] 2012-11-15 */
|
|
|
|
{
|
|
int n, m;
|
|
|
|
p = p + 1;
|
|
readlen (&n, &m, &p);
|
|
if (m != 1)
|
|
{
|
|
event_error
|
|
("X or Z must be followed by a whole integer");
|
|
};
|
|
event_mrest (n, m, c);
|
|
decorators[FERMATA] = 0; /* [SS] 2014-11-17 */
|
|
break;
|
|
};
|
|
case '>':
|
|
{
|
|
int n;
|
|
|
|
n = 0;
|
|
while (*p == '>')
|
|
{
|
|
n = n + 1;
|
|
p = p + 1;
|
|
};
|
|
if (n > 3)
|
|
{
|
|
event_error ("Too many >'s");
|
|
}
|
|
else
|
|
{
|
|
event_broken (GT, n);
|
|
};
|
|
break;
|
|
};
|
|
case '<':
|
|
{
|
|
int n;
|
|
|
|
n = 0;
|
|
while (*p == '<')
|
|
{
|
|
n = n + 1;
|
|
p = p + 1;
|
|
};
|
|
if (n > 3)
|
|
{
|
|
event_error ("Too many <'s");
|
|
}
|
|
else
|
|
{
|
|
event_broken (LT, n);
|
|
};
|
|
break;
|
|
};
|
|
case 's':
|
|
if (slur == 0)
|
|
{
|
|
slur = 1;
|
|
}
|
|
else
|
|
{
|
|
slur = slur - 1;
|
|
};
|
|
event_slur (slur);
|
|
p = p + 1;
|
|
break;
|
|
case '-':
|
|
event_tie ();
|
|
p = p + 1;
|
|
break;
|
|
case '\\':
|
|
p = p + 1;
|
|
if (checkend (p))
|
|
{
|
|
event_lineend ('\\', 1);
|
|
endchar = '\\';
|
|
}
|
|
else
|
|
{
|
|
event_error ("'\\' in middle of line ignored");
|
|
};
|
|
break;
|
|
case '+':
|
|
if (oldchordconvention)
|
|
{
|
|
lineposition = p - linestart; /* [SS] 2011-07-18 */
|
|
event_chord ();
|
|
parserinchord = 1 - parserinchord;
|
|
if (parserinchord == 0)
|
|
{
|
|
for (i = 0; i < DECSIZE; i++)
|
|
chorddecorators[i] = 0;
|
|
};
|
|
p = p + 1;
|
|
break;
|
|
}
|
|
/* otherwise we fall through into the next case statement */
|
|
case '!':
|
|
{
|
|
struct vstring instruction;
|
|
char *s;
|
|
char endcode;
|
|
|
|
endcode = *p;
|
|
p = p + 1;
|
|
s = p;
|
|
initvstring (&instruction);
|
|
while ((*p != endcode) && (*p != '\0'))
|
|
{
|
|
addch (*p, &instruction);
|
|
p = p + 1;
|
|
};
|
|
if (*p != endcode)
|
|
{
|
|
p = s;
|
|
if (checkend (s))
|
|
{
|
|
event_lineend ('!', 1);
|
|
endchar = '!';
|
|
}
|
|
else
|
|
{
|
|
event_error ("'!' or '+' in middle of line ignored");
|
|
};
|
|
}
|
|
else
|
|
{
|
|
event_instruction (instruction.st);
|
|
p = p + 1;
|
|
};
|
|
freevstring (&instruction);
|
|
}
|
|
break;
|
|
case '*':
|
|
p = p + 1;
|
|
starcount = 1;
|
|
while (*p == '*')
|
|
{
|
|
p = p + 1;
|
|
starcount = starcount + 1;
|
|
};
|
|
if (checkend (p))
|
|
{
|
|
event_lineend ('*', starcount);
|
|
endchar = '*';
|
|
}
|
|
else
|
|
{
|
|
event_error ("*'s in middle of line ignored");
|
|
};
|
|
break;
|
|
case '/':
|
|
p = p + 1;
|
|
if (ingrace)
|
|
event_acciaccatura ();
|
|
else
|
|
event_error ("stray / not in grace sequence");
|
|
break;
|
|
case '&':
|
|
p = p + 1;
|
|
if (*p == ')') {
|
|
p = p + 1;
|
|
event_stop_extended_overlay(); /* [SS] 2015-03-23 */
|
|
break;
|
|
}
|
|
else
|
|
event_split_voice ();
|
|
break;
|
|
|
|
|
|
default:
|
|
{
|
|
char msg[40];
|
|
|
|
if ((*p >= 'A') && (*p <= 'z')) /* [SS] 2016-09-20 */
|
|
{
|
|
event_reserved (*p);
|
|
}
|
|
else
|
|
{
|
|
sprintf (msg, "Unrecognized character: %c", *p);
|
|
event_error (msg);
|
|
};
|
|
};
|
|
p = p + 1;
|
|
};
|
|
};
|
|
};
|
|
event_endmusicline (endchar);
|
|
if (iscomment)
|
|
{
|
|
parse_precomment (comment);
|
|
};
|
|
}
|
|
|
|
void
|
|
parseline (line)
|
|
char *line;
|
|
/* top-level routine for handling a line in abc file */
|
|
{
|
|
char *p, *q;
|
|
|
|
/* [SS] 2020-01-03 */
|
|
if (strcmp(line,"%%begintext") == 0) {
|
|
ignore_line = 1;
|
|
}
|
|
if (strcmp(line,"%%endtext") == 0) {
|
|
ignore_line = 0;
|
|
}
|
|
/* [SS] 2021-05-09 */
|
|
if (strcmp(line,"%%beginps") == 0) {
|
|
ignore_line = 1;
|
|
}
|
|
if (strcmp(line,"%%endps") == 0) {
|
|
ignore_line = 0;
|
|
}
|
|
|
|
if ((strncmp(line,"%%temperament",12) == 0) && fileprogram == ABC2MIDI) {
|
|
event_temperament(line);
|
|
}
|
|
|
|
handle_abc2midi_parser (line); /* [SS] 2020-03-25 */
|
|
if (ignore_line == 1 && fileprogram == ABC2MIDI) return;
|
|
|
|
/* handle_abc2midi_parser (line); [SS] 2017-04-12 2020-03-25 */
|
|
if (ignore_line == 1) { /* [JM] 2018-02-22 */
|
|
/* JM 20180219 Do a flush of the blocked lines in case of MidiOff
|
|
and abc2abc */
|
|
if (fileprogram == ABC2ABC)
|
|
printf ("%s",line);
|
|
return; /* [SS] 2017-04-12 */
|
|
}
|
|
|
|
|
|
/*printf("%d parsing : %s\n", lineno, line);*/
|
|
strncpy (inputline, line, sizeof inputline); /* [SS] 2011-06-07 [PHDM] 2012-11-27 */
|
|
|
|
p = line;
|
|
linestart = p; /* [SS] 2011-07-18 */
|
|
ingrace = 0;
|
|
skipspace (&p);
|
|
if (strlen (p) == 0)
|
|
{
|
|
event_blankline ();
|
|
inhead = 0;
|
|
inbody = 0;
|
|
return;
|
|
};
|
|
if ((int) *p == '\\')
|
|
{
|
|
if (parsing)
|
|
{
|
|
event_tex (p);
|
|
};
|
|
return;
|
|
};
|
|
if ((int) *p == '%')
|
|
{
|
|
parse_precomment (p + 1);
|
|
if (!parsing)
|
|
event_linebreak ();
|
|
return;
|
|
};
|
|
/*if (strchr ("ABCDEFGHIKLMNOPQRSTUVdwsWXZ", *p) != NULL) [SS] 2014-08-15 */
|
|
if (strchr ("ABCDEFGHIKLMNOPQRSTUVdwsWXZ+", *p) != NULL)
|
|
{
|
|
q = p + 1;
|
|
skipspace (&q);
|
|
if ((int) *q == ':')
|
|
{
|
|
if (*(line + 1) != ':')
|
|
{
|
|
event_warning ("whitespace in field declaration");
|
|
};
|
|
if ((*(q + 1) == ':') || (*(q + 1) == '|'))
|
|
{
|
|
event_warning ("Potentially ambiguous line - either a :| repeat or a field command -- cannot distinguish.");
|
|
/* [SS] 2013-03-20 */
|
|
/* }; */
|
|
/* parsefield(*p,q+1); */
|
|
|
|
/* [SS} 2013-03-20 start */
|
|
/* malformed field command try processing it as a music line */
|
|
if (inbody && *p != 'w') /* [SS] 2017-10-23 make exception for w: field*/
|
|
{
|
|
if (parsing)
|
|
parsemusic (p);
|
|
}
|
|
else if (inbody) preparse_words (p); /* [SS] 2017-10-23 */
|
|
else {
|
|
if (parsing)
|
|
event_text (p);
|
|
};
|
|
}
|
|
else
|
|
parsefield (*p, q + 1); /* not field command malformed */
|
|
/* [SS] 2013-03-20 end */
|
|
|
|
}
|
|
else
|
|
{
|
|
if (inbody)
|
|
{
|
|
if (parsing)
|
|
parsemusic (p);
|
|
}
|
|
else
|
|
{
|
|
if (parsing)
|
|
event_text (p);
|
|
};
|
|
};
|
|
}
|
|
else
|
|
{
|
|
if (inbody)
|
|
{
|
|
if (parsing)
|
|
parsemusic (p);
|
|
}
|
|
else
|
|
{
|
|
if (parsing)
|
|
event_text (p);
|
|
};
|
|
};
|
|
}
|
|
|
|
void
|
|
parsefile (name)
|
|
char *name;
|
|
/* top-level routine for parsing file */
|
|
/* [SS] 2017-12-10 In order to allow including the directive
|
|
"%%abc-include includefile.abc" to insert the includedfile.abc,
|
|
we switch the file handle fp to link to the includefile.abc and switch
|
|
back to the original file handle when we reach the end of file
|
|
of includefile.abc. Thus we keep track of the original handle
|
|
using fp_last.
|
|
*/
|
|
{
|
|
FILE *fp;
|
|
FILE *fp_last,*fp_include; /* [SS] 2017-12-10 */
|
|
int reading;
|
|
int fileline;
|
|
int last_position = 0; /* [SDG] 2020-06-03 */
|
|
struct vstring line;
|
|
/* char line[MAXLINE]; */
|
|
int t;
|
|
int lastch, done_eol;
|
|
int err;
|
|
|
|
fp_last = NULL; /* [SS] 2017-12-10 */
|
|
|
|
/* printf("parsefile called %s\n", name); */
|
|
/* The following code permits abc2midi to read abc from stdin */
|
|
if ((strcmp (name, "stdin") == 0) || (strcmp (name, "-") == 0))
|
|
{
|
|
fp = stdin;
|
|
}
|
|
else
|
|
{
|
|
fp = fopen (name, "r");
|
|
};
|
|
if (fp == NULL)
|
|
{
|
|
printf ("Failed to open file %s\n", name);
|
|
exit (1);
|
|
};
|
|
inhead = 0;
|
|
inbody = 0;
|
|
parseroff ();
|
|
reading = 1;
|
|
line.limit = 4;
|
|
initvstring (&line);
|
|
fileline = 1;
|
|
done_eol = 0;
|
|
lastch = '\0';
|
|
while (reading)
|
|
{
|
|
t = getc (fp);
|
|
if (t == EOF)
|
|
{
|
|
reading = 0;
|
|
if (line.len > 0)
|
|
{
|
|
parseline (line.st);
|
|
fileline = fileline + 1;
|
|
lineno = fileline;
|
|
if (parsing)
|
|
event_linebreak ();
|
|
};
|
|
if (fp_last != NULL) { /* [SS] 2017-12-10 */
|
|
fclose(fp);
|
|
fp = fp_last;
|
|
err = fseek(fp,last_position,SEEK_SET);
|
|
/*printf("fseek return err = %d\n",err);*/
|
|
reading = 1;
|
|
fp_last = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* recognize \n or \r or \r\n or \n\r as end of line */
|
|
/* should work for DOS, unix and Mac files */
|
|
if ((t != '\n') && (t != '\r'))
|
|
{
|
|
addch ((char) t, &line);
|
|
done_eol = 0;
|
|
}
|
|
else
|
|
{
|
|
if ((done_eol) && (((t == '\n') && (lastch == '\r')) ||
|
|
((t == '\r') && (lastch == '\n'))))
|
|
{
|
|
done_eol = 0;
|
|
/* skip this character */
|
|
}
|
|
else
|
|
{
|
|
/* reached end of line */
|
|
fp_include = parse_abc_include (line.st);/* [SS] 2017-12-10 */
|
|
if (fp_include == NULL) {
|
|
parseline (line.st);
|
|
clearvstring (&line);
|
|
if (fp_last == NULL) {
|
|
fileline = fileline + 1;
|
|
lineno = fileline;
|
|
}
|
|
if (parsing)
|
|
event_linebreak ();
|
|
done_eol = 1;
|
|
} else {
|
|
if (fp_last == NULL) {
|
|
last_position = ftell(fp);
|
|
/*printf("last position = %d\n",last_position);*/
|
|
fp_last = fp;
|
|
fp = fp_include;
|
|
if (parsing)
|
|
event_linebreak ();
|
|
done_eol = 1;
|
|
clearvstring (&line);
|
|
} else {
|
|
event_error ("Not allowed to recurse include file");
|
|
}
|
|
}
|
|
};
|
|
};
|
|
lastch = t;
|
|
};
|
|
};
|
|
fclose (fp);
|
|
event_eof ();
|
|
freevstring (&line);
|
|
if (parsing_started == 0)
|
|
event_error ("No tune processed. Possible missing X: field");
|
|
}
|
|
|
|
|
|
int
|
|
parsetune (FILE * fp)
|
|
/* top-level routine for parsing file */
|
|
{
|
|
struct vstring line;
|
|
/* char line[MAXLINE]; */
|
|
int t;
|
|
int lastch, done_eol;
|
|
|
|
inhead = 0;
|
|
inbody = 0;
|
|
parseroff ();
|
|
intune = 1;
|
|
line.limit = 4;
|
|
initvstring (&line);
|
|
done_eol = 0;
|
|
lastch = '\0';
|
|
do
|
|
{
|
|
t = getc (fp);
|
|
if (t == EOF)
|
|
{
|
|
if (line.len > 0)
|
|
{
|
|
printf ("%s\n", line.st);
|
|
parseline (line.st);
|
|
fileline_number = fileline_number + 1;
|
|
lineno = fileline_number;
|
|
event_linebreak ();
|
|
};
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* recognize \n or \r or \r\n or \n\r as end of line */
|
|
/* should work for DOS, unix and Mac files */
|
|
if ((t != '\n') && (t != '\r'))
|
|
{
|
|
addch ((char) t, &line);
|
|
done_eol = 0;
|
|
}
|
|
else
|
|
{
|
|
if ((done_eol) && (((t == '\n') && (lastch == '\r')) ||
|
|
((t == '\r') && (lastch == '\n'))))
|
|
{
|
|
done_eol = 0;
|
|
/* skip this character */
|
|
}
|
|
else
|
|
{
|
|
/* reached end of line */
|
|
parseline (line.st);
|
|
clearvstring (&line);
|
|
fileline_number = fileline_number + 1;
|
|
lineno = fileline_number;
|
|
event_linebreak ();
|
|
done_eol = 1;
|
|
};
|
|
};
|
|
lastch = t;
|
|
};
|
|
}
|
|
while (intune);
|
|
freevstring (&line);
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
int getline ()
|
|
{
|
|
return (lineno);
|
|
}
|
|
*/
|