/* * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* parseabc.c */ /* code to parse an abc file */ /* this is part of the abc2midi abc to MIDI converter */ /* Macintosh port 30th July 1996 */ /* DropShell integration 27th Jan 1997 */ /* Wil Macaulay (wil@syndesis.com) */ #define TAB 9 #include "abc.h" #include #ifndef PCCFIX #include #include #include #endif #ifdef __MWERKS__ #define __MACINTOSH__ 1 #endif /* __MWERKS__ */ #ifdef __MACINTOSH__ #define main macabc2midi_main #define STRCHR #endif /* __MACINTOSH__ */ #ifdef STRCHR #define index strchr #endif /* extern char* index(); */ int lineno; int parsing, slur; int inhead, inbody; int parserinchord; int chorddecorators[DECSIZE]; int* checkmalloc(bytes) /* malloc with error checking */ int bytes; { int *p; char* malloc(); p = (int*) malloc(bytes); if (p == NULL) { printf("Out of memory error - malloc failed!\n"); exit(0); }; return (p); } 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'; } 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); }; } 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; } 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; } clearvstring(s) struct vstring *s; /* set string to empty */ /* does not deallocate memory ! */ { *(s->st) = '\0'; s->len = 0; } 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; } parseron() { parsing = 1; slur = 0; } parseroff() { parsing = 0; slur = 0; } skipspace(p) char **p; { /* skip space and tab */ while(((int)**p == ' ') || ((int)**p == TAB)) *p = *p + 1; } int readnumf(num) char *num; /* read integer from string without advancing character pointer */ { int t; char* p; p =num; if (index("0123456789", *p) == NULL) { 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(num) char* num; /* reads signed integer from string */ { char* p; p = num; printf("string=>%s<\n", p); if (*p == '-') { p = p+1; skipspace(&p); printf("string=%s\n", 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); } void readsig(a, b, sig) int *a, *b; char **sig; /* read time signature (meter) from M: field */ { int t; if ((**sig == 'C') || (**sig == 'c')) { *a = 4; *b = 4; return; }; *a = readnump(sig); if ((int)**sig != '/') { event_error("Missing / "); } else { *sig = *sig + 1; }; *b = readnump(sig); if ((*a == 0) || (*b == 0)) { event_error("Expecting fraction in form A/B"); } else { t = *b; while (t > 1) { if (t%2 != 0) { event_error("divisor must be a power of 2"); t = 1; *b = 0; } else { t = t/2; }; }; }; } void readlen(a, b, p) int *a, *b; char **p; /* read length part of a note and advance character pointer */ { int t; *a = readnump(p); if (*a == 0) { *a = 1; }; *b = 1; if (**p == '/') { *p = *p + 1; *b = readnump(p); if (*b == 0) { *b = 2; while (**p == '/') { *b = *b * 2; *p = *p + 1; }; }; }; t = *b; while (t > 1) { if (t%2 != 0) { event_warning("divisor not a power of 2"); t = 1; } else { t = t/2; }; }; } char* parseclef(gotclef, s, clefname) int* gotclef; char* s; char* clefname; /* 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 */ { char* place; int i; place = s; if (strncmp(place, "bass", 4) == 0) { strcpy(clefname, "bass"); *gotclef= 1; }; if (strncmp(place, "treble", 6) == 0) { strcpy(clefname, "treble"); *gotclef= 1; }; if (strncmp(place, "baritone", 8) == 0) { strcpy(clefname, "baritone"); *gotclef= 1; }; if (strncmp(place, "tenor", 5) == 0) { strcpy(clefname, "tenor"); *gotclef= 1; }; if (strncmp(place, "alto", 4) == 0) { strcpy(clefname, "alto"); *gotclef= 1; }; if (strncmp(place, "mezzo", 5) == 0) { strcpy(clefname, "mezzo"); *gotclef= 1; }; if (strncmp(place, "soprano", 7) == 0) { strcpy(clefname, "soprano"); *gotclef= 1; }; if (*gotclef) { place = place + strlen(clefname); } else { if (strncmp(place, "clef", 4) == 0) { place = place + 4; skipspace(&place); if (*place == '=') { place = place + 1; skipspace(&place); *gotclef= 1; i = 0; while (isalpha(*place) && (i<19)) { clefname[i] = *place; i = i + 1; place = place + 1; }; clefname[i] = '\0'; }; }; }; skipspace(&place); return(place); } static errline = 0; void parsekey(str) /* parse contents of K: field */ char* str; { int sf, minor; char *s; static char *key = "FCGDAEB"; static char *mode[10] = {"maj", "min", "m", "aeo", "loc", "ion", "dor", "phr", "lyd", "mix"}; static int modeshift[10] = {0, -3, -3, -3, -5, 0, -2, -4, 1, -1 }; static int modeminor[10] = {0, 1, 1, 1, 0, 0, 0, 0, 0, 0}; char modestr[4]; int i; char modmap[7]; int modmul[7]; char clefstr[20]; int gotclef, gotkey; minor = 0; s=str; gotclef = 0; gotkey = 0; s = parseclef(&gotclef, s, clefstr); if ((strncmp(s, "Hp", 2) == 0) || (strncmp(s, "HP", 2) == 0)) { sf = 2; s = s + 2; gotkey = 1; } else { if ((*s >= 'A') && (*s <= 'G')) { int foundmode; gotkey = 1; /* parse key itself */ sf = (int) index(key, *s) - (int) &key[0] - 1; s = s + 1; /* deal with sharp/flat */ if (*s == '#') { sf += 7; s += 1; } else if (*s == 'b') { sf -= 7; s += 1; } skipspace(&s); if (!gotclef) { s = parseclef(&gotclef, s, clefstr); }; /* now get mode */ i = 0; while (isalpha(*s) && (i<3)) { if (isupper(*s)) { modestr[i] = tolower(*s); } else { modestr[i] = *s; }; s = s + 1; i = i + 1; }; /* skip any alphabetic characters after the first 3 */ while ((i == 3) && (isalpha(*s))) s = s + 1; modestr[i] = '\0'; foundmode = 0; for (i = 0; i<10; i++) { if (strcmp(modestr, mode[i]) == 0) { foundmode = 1; sf = sf + modeshift[i]; minor = modeminor[i]; }; }; if ((strlen(modestr) > 0) && (!foundmode)) { event_error("Key mode not recognized"); }; } else { if (!gotclef) { event_error("Key not recognized"); }; }; }; if (!gotclef) { s = parseclef(&gotclef, s, clefstr); }; if (isalpha(*s)) { event_error("Unrecognized characters in key"); }; if (gotkey) { if (sf > 7) { event_warning("Unusual key representation"); sf = sf - 12; } ; if (sf < -7) { event_warning("Unusual key representation"); sf = sf + 12; }; /* now look for modifiers */ for (i=0; i<7; i++) { modmap[i] = ' '; modmul[i] = 1; }; skipspace(&s); while (*s != '\0') { int count; char acc; int j; while (((int)*s != '\0') && (index("^_=", *s) == NULL)) { if (errline != lineno) event_error("Spurious character in key"); errline = lineno; s = s + 1; }; if (index("^_=", *s) != NULL) { acc = *s; count = 1; s = s + 1; while (*s == acc) { count = count + 1; s = s + 1; }; if (((int)*s >= 'a') && ((int)*s <= 'g')) { j = (int)*s - 'a'; s = s + 1; if ((count > 2) || ((count > 1) && (acc == '='))) { event_error("accent must be __ _ = ^ or ^^"); count = 1; }; skipspace(&s); modmap[j] = acc; modmul[j] = count; } else { event_error("accent must be applied to a-g"); s = s + 1; }; }; }; }; if (!gotclef) { s = parseclef(&gotclef, s, clefstr); }; event_key(sf, str, minor, modmap, modmul, gotkey, gotclef, clefstr); } 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; accidental = ' '; note = ' '; for (i = 0; i= '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 { if ((**s >= 'A') && (**s <= 'G')) { note = **s + 'a' - 'A'; octave = 0; *s = *s + 1; while ((**s == '\'') || (**s == ',')) { if (**s == ',') { octave = octave - 1; *s = *s + 1; }; if (**s == '\'') { sprintf(msg, "Bad pitch specifier ' after note %c", note + 'A' - 'a'); event_error(msg); octave = octave + 1; *s = *s + 1; }; }; }; }; if (note == ' ') { event_error("Malformed note : expecting a-g or A-G"); } else { readlen(&n, &m, s); event_note(decorators, accidental, mult, note, octave, n, m); }; } 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); } 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; } else { if (((*q == '-')||(*q == ','))&&(digits > 0)&&(isdigit(*(q+1)))) { out[count] = *q; count = count + 1; q = q + 1; digits = 0; } 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); } parsemusic(field) char* field; /* parse a line of abc notes */ { char* p; char* comment; char endchar; int iscomment; int starcount; int i; char playonrep_list[80]; 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') { if (((*p >= 'a') && (*p <= 'g')) || ((*p >= 'A') && (*p <= 'G')) || (index("_^=", *p) != NULL) || (index(decorations, *p) != NULL)) { parsenote(&p); } else { switch(*p) { case '+': event_chord(); parserinchord = 1 - parserinchord; if (parserinchord == 0) { for (i = 0; i 0) { event_warning("Slur within slur"); }; slur = slur + 1; event_sluron(slur); } else { event_tuple(t, q, r); }; }; break; case ')': p = p + 1; if (slur == 0) { event_error("No slur to close"); } else { slur = slur - 1; }; event_sluroff(slur); break; case '{': p = p + 1; event_graceon(); break; case '}': p = p + 1; event_graceoff(); break; case '[': p = p + 1; switch(*p) { /* following lines are now redundant */ /* case '1': p = p + 1; event_rep1(); break; case '2': p = p + 1; event_rep2(); break; */ case '|': p = p + 1; event_bar(THICK_THIN, ""); break; default: if (isdigit(*p)) { p = getrep(p, playonrep_list); event_playonrep(playonrep_list); } else { if (isalpha(*p) && (*(p+1) == ':')) { p = parseinlinefield(p); } else { event_chordon(); parserinchord = 1; }; }; break; }; break; case ']': p = p + 1; event_chordoff(); parserinchord = 0; for (i = 0; i': { 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 '!': { struct vstring instruction; char *s; p = p + 1; s = p; initvstring(&instruction); while ((*p != '!') && (*p != '\0')) { addch(*p, &instruction); p = p + 1; }; if (*p != '!') { p = s; if (checkend(s)) { event_lineend('!', 1); endchar = '!'; } else { event_error("'!' 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; default: { char msg[40]; if ((*p >= 'H') && (*p <= 'Z')) { event_reserved(*p); } else { sprintf(msg, "Unrecognized character: %c", *p); event_error(msg); }; }; p = p + 1; }; }; }; event_endmusicline(endchar); if (iscomment) { event_precomment(comment); }; } parse_tempo(place) char* place; /* parse tempo descriptor i.e. Q: field */ { char* p; int a, b; int relative; relative = 0; p = place; 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 = 0; b = 0; p = place; }; skipspace(&p); event_tempo(readnumf(p), a, b, relative); } parsefield(key, field) char key; char* field; /* top-level routine handling all lines containing a field */ { char* comment; char* place; int iscomment; if ((inbody) && (index("EIKLMPQTVwW", key) == NULL)) { event_error("Field not allowed in tune body"); }; comment = field; iscomment = 0; while ((*comment != '\0') && (*comment != '%')) { comment = comment + 1; }; if (*comment == '%') { iscomment = 1; *comment = '\0'; comment = comment + 1; }; place =field; skipspace(&place); switch (key) { case 'X': { int x; x = readnumf(place); if (inhead) { event_error("second X: field in header"); }; event_refno(x); inhead = 1; inbody = 0; parserinchord = 0; break; }; case 'K': parsekey(place); if (inhead || inbody) { inbody = 1; inhead = 0; } else { event_error("No X: field preceding K:"); }; break; case 'M': { int num, denom; if (strncmp(place, "none", 4) == 0) { event_timesig(4, 4, 0); } else { readsig(&num, &denom, &place); if ((*place == 's') || (*place == 'l')) { event_error("s and l in M: field not supported"); }; if ((num != 0) && (denom != 0)) { event_timesig(num, denom, 1); }; }; break; }; case 'L': { int num, denom; readsig(&num, &denom, &place); if (num != 1) { event_error("Default length must be 1/X"); }; event_length(denom); break; }; case 'P': event_part(place); break; case 'I': event_info(place); break; case 'V': { int num; skipspace(&place); if ((*place >= '0') && (*place <= '9')) { num = readnump(&place); } else { num = 0; event_error("No voice number in V: field"); }; skipspace(&place); event_voice(num, place); break; }; case 'Q': parse_tempo(place); break; case 'w': event_words(place); break; default: event_field(key, place); }; if (iscomment) { event_precomment(comment); }; } readstr(out, in) char *out; char **in; /* copy across alphanumeric string */ { char *p; p = out; while (((**in >= 'a') && (**in <= 'z')) || ((**in >= 'A') && (**in <= 'Z'))) { *p = **in; p = p + 1; *in = *in + 1; }; *p = '\0'; } event_precomment(s) char* s; /* handles a comment field */ { char package[40]; char *p; if (*s == '%') { p = s+1; readstr(package, &p); event_specific(package, p); } else { event_comment(s); }; } parseline(line) char* line; /* top-level routine for handling a line in abc file */ { char *p, *q; /* printf("%d parsing : %s\n", lineno, line); */ p = line; 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 == '%') { event_precomment(p+1); return; }; if (index("ABCDEFGHIKLMNOPQRSTVwWXZ", *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"); }; parsefield(*p, q+1); } else { if (inbody) { if (parsing) parsemusic(p); } else { event_text(p); }; }; } else { if (inbody) { if (parsing) parsemusic(p); } else { event_text(p); }; }; } parsefile(name) char* name; /* top-level routine for parsing file */ { FILE *fp; int reading; int fileline; struct vstring line; /* char line[MAXLINE]; */ int t; int lastch, done_eol; /* printf("parsefile called %s\n", name); */ /* The following code permits abc2midi to read abc from stdin */ if ((strcmp(name, "stdin") == 0) || (strcmp(name, "-") == 0)) { fp = stdin; } else { fp = fopen(name, "r"); }; if (fp == NULL) { printf("Failed to open file %s\n", name); exit(1); }; inhead = 0; inbody = 0; parseroff(); reading = 1; line.limit = 4; initvstring(&line); fileline = 1; done_eol = 0; lastch = '\0'; while (reading) { t = getc(fp); if (t == EOF) { reading = 0; if (line.len>0) { parseline(line.st); fileline = fileline + 1; lineno = fileline; event_linebreak(); }; } else { /* recognize \n or \r or \r\n or \n\r as end of line */ /* should work for DOS, unix and Mac files */ if ((t != '\n') && (t != '\r')) { addch((char) t, &line); done_eol = 0; } else { if ((done_eol) && (((t == '\n') && (lastch == '\r')) || ((t == '\r') && (lastch == '\n')))) { done_eol = 0; /* skip this character */ } else { /* reached end of line */ parseline(line.st); clearvstring(&line); fileline = fileline + 1; lineno = fileline; event_linebreak(); done_eol = 1; }; }; lastch = t; }; }; fclose(fp); event_eof(); freevstring(&line); } main(argc,argv) int argc; char *argv[]; { char *filename; event_init(argc, argv, &filename); if (argc < 2) { /* printf("argc = %d\n", argc); */ } else { parsefile(filename); }; exit(0); } /* int getline () { return (lineno); } */