/* parseabc.c */ /* code to parse an abc file */ /* this is part of the abc2midi abc to MIDI converter */ /* James Allwright (jra@ecs.soton.ac.uk) */ /* Department of Electronics and computer Science, */ /* University of Southampton, UK */ /* 19th August 1996 */ /* Macintosh port 30th July 1996 */ /* DropShell integration 27th Jan 1997 */ /* Wil Macaulay (wil@syndesis.com) */ #define TAB 9 #include "abc.h" #include #ifdef __MWERKS__ #define __MACINTOSH__ 1 #endif /* __MWERKS__ */ #ifdef __MACINTOSH__ #define main macabc2midi_main #include #define STRCHR #endif /* __MACINTOSH__ */ #ifdef STRCHR #define index strchr #endif 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; { s->len = 0; s->limit = 40; s->st = (char*) checkmalloc(s->limit + 1); *(s->st) = '\0'; } extendvstring(s) struct vstring *s; { char* p; s->limit = s->limit * 2; p = (char*) checkmalloc(s->limit + 1); strcpy(p, s->st); free(s->st); s->st = p; } addch(ch, s) char ch; struct vstring *s; { 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; { 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; { *(s->st) = '\0'; s->len = 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; { int t; char* p; extern char* index(); 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 readnump(p) char **p; { 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; { int t; char errmsg[40]; if ((int)**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; { 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; }; }; t = *b; while (t > 1) { if (t%2 != 0) { event_warning("divisor not a power of 2"); t = 1; } else { t = t/2; }; }; } void parsekey(str) 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]; minor = 0; s=str; if ((strncmp(s, "Hp", 2) == 0) || (strncmp(s, "HP", 2) == 0)) { sf = 2; s = s + 2; } else { if (index(key, *s) != NULL) { int foundmode; /* 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); /* 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 { event_error("Key not recognized"); }; }; if (isalpha(*s)) { event_error("Unrecognized characters in key"); }; 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)) { event_error("Spurious character in key"); 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; }; }; }; event_key(sf, str, minor, modmap, modmul); } parsenote(s) char **s; { int decorators[DECSIZE]; int i, t; int mult; char accidental, note; int octave, n, m; mult = 1; accidental = ' '; note = ' '; for (i = 0; i= 'a') && (**s <= 'g')) { note = **s; octave = 1; *s = *s + 1; while (**s == '\'') { octave = octave + 1; *s = *s + 1; }; } else { if ((**s >= 'A') && (**s <= 'G')) { note = **s + 'a' - 'A'; octave = 0; *s = *s + 1; while (**s == ',') { 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); }; } parsemusic(field) char* field; { char* p; char* comment; char endchar; int iscomment; int i; 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) { 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: 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 '\\': event_lineend('\\', 1); endchar = '\\'; p = p + 1; 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; event_lineend('!', 1); endchar = '!'; } else { event_instruction(instruction.st); p = p + 1; }; free(instruction.st); }; break; case '*': p = p + 1; endchar = '*'; if (*p == '*') { event_lineend('*', 2); p = p + 1; } else { event_lineend('*', 1); }; 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; { 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; { 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, dochecking; 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 'V': { int num; skipspace(&place); num = readnump(&place); 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; { 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; { 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; { 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 == '%') { if (parsing) { 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; { FILE *fp; int reading; int fileline; struct vstring line; /* char line[MAXLINE]; */ int t; /* 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; 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 { /* throw away carriage return in DOS input */ if ((t != '\n') && (t != '\r')) { addch((char) t, &line); } else { if (t == '\n') { parseline(line.st); clearvstring(&line); fileline = fileline + 1; lineno = fileline; event_linebreak(); }; }; }; }; fclose(fp); event_eof(); free(line.st); } 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); } */