/* * abc2midi - program to convert abc files to MIDI 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 * */ /* store.c * This file handles all event_X() calls from parseabc.c and stores them * in various arrays and data structures. The midi is then generated by * calling mfwrite() which in turn uses the routine writetrack(). * This file is part of abc2midi. * * James Allwright * * Macintosh Port 30th July 1996 * Wil Macaulay (wil@syndesis.com) */ #include "abc.h" #include "parseabc.h" #include "parser2.h" #include "midifile.h" #include "genmidi.h" #include #ifdef __MWERKS__ #define __MACINTOSH__ 1 #endif /* __MWERKS__ */ #ifdef __MACINTOSH__ int setOutFileCreator(char *fileName,unsigned long theType, unsigned long theCreator); #endif /* __MACINTOSH__ */ /* define USE_INDEX if your C libraries have index() instead of strchr() */ #ifdef USE_INDEX #define strchr index #endif #ifdef ANSILIBS #include #include #include #else extern char* strchr(); extern void reduce(); #endif #define MAXLINE 500 #define INITTEXTS 20 #define INITWORDS 20 #define MAXCHANS 16 /* global variables grouped roughly by function */ FILE *fp; /* parsing stage */ int tuplecount, tfact_num, tfact_denom, tnote_num, tnote_denom; int specialtuple; int gracenotes; int headerpartlabel; int dotune, pastheader; int hornpipe, last_num, last_denom; int timesigset; int retain_accidentals; int ratio_a, ratio_b; struct voicecontext { /* maps of accidentals for each stave line */ char basemap[7], workmap[7]; int basemul[7], workmul[7]; int default_length; int voiceno; int indexno; int hasgchords; int haswords; int inslur; int ingrace; int octaveshift; /* chord handling */ int inchord, chordcount; int chord_num, chord_denom; /* details of last 2 notes/chords to apply length-modifiers to */ int laststart, lastend, thisstart, thisend; /* broken rythm handling */ int brokentype, brokenmult, brokenpending; int broken_stack[7]; struct voicecontext* next; }; struct voicecontext global; struct voicecontext* v; struct voicecontext* head; int voicecount; /* storage structure for strings */ int maxtexts = INITTEXTS; char** atext; int ntexts = 0; /* Named guitar chords */ char chordname[MAXCHORDNAMES][8]; int chordnotes[MAXCHORDNAMES][6]; int chordlen[MAXCHORDNAMES]; int chordsnamed = 0; /* general purpose storage structure */ int maxnotes; int *pitch, *num, *denom; featuretype *feature; int notes; int verbose = 0; int titlenames = 0; int got_titlename; int namelimit; int xmatch; int sf, mi; int gchordvoice, wordvoice, drumvoice; int gchordtrack, drumtrack; /* Part handling */ struct vstring part; extern int parts, partno, partlabel; extern int part_start[26], part_count[26]; int voicesused; /* Tempo handling (Q: field) */ int time_num, time_denom; int mtime_num, mtime_denom; long tempo; int tempo_num, tempo_denom; int relative_tempo, Qtempo; extern int division; extern int div_factor; /* output file generation */ int userfilename = 0; char *outname = NULL; char *outbase = NULL; int check; int ntracks; /* bar length checking */ extern int bar_num, bar_denom; int barchecking; /* generating MIDI output */ int middle_c; extern int channels[MAXCHANS + 3]; extern int global_transpose; extern int additive; int gfact_num, gfact_denom; /* karaoke handling */ int karaoke, wcount; char** words; int maxwords = INITWORDS; extern long writetrack(); static struct voicecontext* newvoice(n) /* allocate and initialize the data for a new voice */ int n; { struct voicecontext *s; int i; s = (struct voicecontext*) checkmalloc(sizeof(struct voicecontext)); voicecount = voicecount + 1; s->voiceno = n; s->indexno = voicecount; s->default_length = global.default_length; s->hasgchords = 0; s->haswords = 0; s->inslur = 0; s->ingrace = 0; s->inchord = 0; s->chordcount = 0; s->laststart = -1; s->lastend = -1; s->thisstart = -1; s->thisend = -1; s->brokenpending = -1; s->next = NULL; for (i=0; i<7; i++) { s->basemap[i] = global.basemap[i]; s->basemul[i] = global.basemul[i]; s->workmap[i] = global.workmap[i]; s->workmul[i] = global.workmul[i]; }; s->octaveshift = global.octaveshift; return(s); } static struct voicecontext* getvoicecontext(n) /* find the data structure for a given voice number */ int n; { struct voicecontext *p; struct voicecontext *q; p = head; q = NULL; while ((p != NULL) && (p->voiceno != n)) { q = p; p = p->next; }; if (p == NULL) { p = newvoice(n); if (q != NULL) { q->next = p; }; }; if (head == NULL) { head = p; }; return(p); } static void clearvoicecontexts() /* free up all the memory allocated to voices */ { struct voicecontext *p; struct voicecontext *q; p = head; while (p != NULL) { q = p->next; free(p); p = q; }; head = NULL; } static int getchordnumber(s) /* looks through list of known chords for chord name given in s */ char *s; { int i; int chordnumber; chordnumber = 0; i = 1; while ((i <= chordsnamed) && (chordnumber == 0)) { if (strcmp(s, chordname[i]) == 0) { chordnumber = i; } else { i = i + 1; }; }; return(chordnumber); } static void addchordname(s, len, notes) /* adds chord name and note set to list of known chords */ char *s; int notes[]; int len; { int i, j, done; if (strlen(s) > 7) { event_error("Chord name cannot exceed 7 characters"); return; }; if (len > 6) { event_error("Named chord cannot have more than 6 notes"); return; }; i = 0; done = 0; while ((i= MAXCHORDNAMES-1) { event_error("Too many Guitar Chord Names used"); } else { chordsnamed = chordsnamed + 1; strcpy(chordname[chordsnamed], s); chordlen[chordsnamed] = len; for (j=0; j [reference number] [-c] [-v] "); printf("[-o filename]\n"); printf(" [-t] [-n ]\n"); printf(" [reference number] selects a tune\n"); printf(" -c selects checking only\n"); printf(" -v selects verbose option\n"); printf(" -o selects output filename\n"); printf(" -t selects filenames derived from tune titles\n"); printf(" -n set limit for length of filename stem\n"); printf(" The default action is to write a MIDI file for each abc tune\n"); printf(" with the filename N.mid, where is the filestem\n"); printf(" of the abc file and N is the tune reference number. If the -o\n"); printf(" option is used, only one file is written. This is the tune\n"); printf(" specified by the reference number or, if no reference number\n"); printf(" is given, the first tune in the file.\n"); exit(0); } else { xmatch = 0; if ((argc >= 3) && (isdigit(*argv[2]))) { xmatch = readnumf(argv[2]); }; *filename = argv[1]; outbase = addstring(argv[1]); for (j = 0; j= j+1) { namelimit = 0; sscanf(argv[j], "%d", &namelimit); if ((namelimit < 3) || (namelimit > 252)) { event_fatal_error("filename stem limit must be in the range 3 - 252"); }; } else { event_error("No number given, ignoring -n option"); }; }; /* look for user-supplied output filename */ j = getarg("-o", argc, argv); if (j != -1) { if (argc >= j+1) { outname = addstring(argv[j]); userfilename = 1; if (xmatch == 0) { xmatch = -1; }; if (titlenames == 1) { event_warning("-o option over-rides -t option"); titlenames = 0; }; } else { event_error("No filename given, ignoring -o option"); }; }; dotune = 0; parseroff(); setup_chordnames(); } void event_text(s) /* text found in abc file */ char *s; { char msg[200]; sprintf(msg, "Ignoring text: %s", s); event_warning(msg); } void event_x_reserved(p) /* reserved character H-Z found in abc file */ char p; { char msg[200]; sprintf(msg, "Ignoring reserved character %c", p); event_warning(msg); } void event_abbreviation(symbol, string, container) /* abbreviation encountered - this is handled within the parser */ char symbol; char *string; char container; { } void event_tex(s) /* TeX command found - ignore it */ char *s; { } void event_fatal_error(s) /* print error message and halt */ char *s; { event_error(s); exit(1); } void event_error(s) /* generic error handler */ char *s; { #ifdef NOFTELL extern int nullpass; if (nullpass != 1) { printf("Error in line %d : %s\n", lineno, s); }; #else printf("Error in line %d : %s\n", lineno, s); #endif } void event_warning(s) /* generic warning handler - for flagging possible errors */ char *s; { #ifdef NOFTELL extern int nullpass; if (nullpass != 1) { printf("Warning in line %d : %s\n", lineno, s); }; #else printf("Warning in line %d : %s\n", lineno, s); #endif } static int autoextend(maxnotes) /* increase the number of abc elements the program can cope with */ int maxnotes; { int newlimit; int *ptr; featuretype *fptr; int i; if (verbose) { event_warning("Extending note capacity"); }; newlimit = maxnotes*2; fptr = (featuretype*) checkmalloc(newlimit*sizeof(featuretype)); for(i=0;i= maxnotes) { maxnotes = autoextend(maxnotes); }; } void event_linebreak() /* reached end of line in abc */ { addfeature(LINENUM, lineno, 0, 0); } void event_startmusicline() /* starting to parse line of abc music */ { addfeature(MUSICLINE, 0, 0, 0); } void event_endmusicline(endchar) /* finished parsing line of abc music */ char endchar; { addfeature(MUSICSTOP, 0, 0, 0); } static void textfeature(type, s) /* called while parsing abc - stores an item which requires an */ /* associared string */ int type; char* s; { atext[ntexts] = addstring(s); addfeature(type, ntexts, 0, 0); ntexts = ntexts + 1; if (ntexts >= maxtexts) { maxtexts = textextend(maxtexts, &atext); }; } void event_comment(s) /* comment found in abc */ char *s; { if (dotune) { if (pastheader) { textfeature(TEXT, s); } else { textfeature(TEXT, s); }; }; } void event_specific(package, s) /* package-specific command found i.e. %%NAME */ /* only %%MIDI commands are actually handled */ char *package, *s; { char msg[200], command[40]; char *p; if (strcmp(package, "MIDI") == 0) { int ch; int done; int trans, rtrans; p = s; done = 0; skipspace(&p); readstr(command, &p, 40); if (strcmp(command, "channel") == 0) { skipspace(&p); ch = readnump(&p) - 1; addfeature(CHANNEL, ch, 0, 0); done = 1; }; trans = strcmp(command, "transpose"); rtrans = strcmp(command, "rtranspose"); if ((trans == 0) || (rtrans == 0)) { int neg, val; skipspace(&p); neg = 0; if (*p == '+') p = p + 1; if (*p == '-') { p = p + 1; neg = 1; }; skipspace(&p); val = readnump(&p); if (neg) val = - val; if (pastheader) { if (trans == 0) { addfeature(TRANSPOSE, val, 0, 0); } else { addfeature(RTRANSPOSE, val, 0, 0); }; } else { if (trans == 0) { global_transpose = val; } else { global_transpose = global_transpose + val; }; }; done = 1; }; if (strcmp(command, "C") == 0) { int val; skipspace(&p); val = readnump(&p); middle_c = val; done = 1; }; if (strcmp(command, "nobarlines") == 0) { retain_accidentals = 0; done = 1; }; if (strcmp(command, "barlines") == 0) { retain_accidentals = 1; done = 1; }; if (strcmp(command, "ratio") == 0) { int a, b; skipspace(&p); b = readnump(&p); skipspace(&p); a = readnump(&p); if ((a > 0) && (b > 0)) { ratio_a = a; ratio_b = b; if (ratio_a + ratio_b % 2 == 1) { ratio_a = 2 * a; ratio_b = 2 * b; }; } else { event_error("Invalid ratio"); }; done = 1; }; if (strcmp(command, "grace") == 0) { int a, b; char msg[40]; skipspace(&p); a = readnump(&p); if (*p != '/') { event_error("Need / in MIDI grace command"); } else { p = p + 1; }; b = readnump(&p); if ((a < 1) || (b < 1) || (a >= b)) { sprintf(msg, "%d/%d is not a suitable fraction", a, b); event_error(msg); } else { if (pastheader) { addfeature(SETGRACE, a, 0, b); } else { gfact_num = a; gfact_denom = b; }; }; done = 1; }; if (strcmp(command, "gchordon") == 0) { addfeature(GCHORDON, 0, 0, 0); done = 1; }; if (strcmp(command, "gchordoff") == 0) { addfeature(GCHORDOFF, 0, 0, 0); done = 1; }; if (strcmp(command, "chordname") == 0) { char name[20]; int i, notes[6]; skipspace(&p); i = 0; while ((i<19) && (*p != ' ') && (*p != '\0')) { name[i] = *p; p = p + 1; i = i + 1; }; name[i] = '\0'; if (*p != ' ') { event_error("Bad format for chordname command"); } else { i = 0; while ((i<=6) && (*p == ' ')) { skipspace(&p); notes[i] = readnump(&p); i = i + 1; }; addchordname(name, i, notes); }; done = 1; }; if (done == 0) { /* add as a command to be interpreted later */ textfeature(DYNAMIC, s); }; } else { strcpy(msg, "%"); strcat(msg, package); strcat(msg, s); event_comment(msg); }; } void event_startinline() /* start of in-line field in abc music line */ { } void event_closeinline() /* end of in-line field in abc music line */ { } void extract_filename(char *f) /* work out filename stem from tune title */ /* name length cannot exceed namelimit characters */ { char buffer[256]; int i; char *p; i = 0; p = f; skipspace(&p); /* avoid initial 'The' or 'the' */ if ((strncmp(p, "The", 3) == 0) || (strncmp(p, "the", 3) == 0)) { p = p + 3; skipspace(&p); }; while ((*p != '\0') && (i < namelimit)) { if (isalnum(*p)) { buffer[i] = *p; i = i + 1; }; p = p + 1; }; buffer[i] = '\0'; if (i == 0) { strcpy(buffer, "notitle"); buffer[namelimit] = '\0'; }; strcpy(&buffer[strlen(buffer)], ".mid"); if (outname != NULL) { free(outname); }; outname = addstring(buffer); got_titlename = 1; } void event_field(k, f) /* Handles R: T: and any other field not handled elsewhere */ char k; char *f; { if (dotune) { switch (k) { case 'T': textfeature(TITLE, f); if (titlenames && (!got_titlename)) { extract_filename(f); }; break; case 'R': { char* p; p = f; skipspace(&p); if ((strncmp(p, "Hornpipe", 8) == 0) || (strncmp(p, "hornpipe", 8) == 0)) { hornpipe = 1; }; }; break; default: { char buff[100]; if (strlen(f) < 98) { sprintf(buff, "%c:%s", k, f); textfeature(TEXT, buff); }; }; }; } else { if (k == 'T') { event_warning("T: outside tune body - possible missing X:"); }; }; } void event_words(p, continuation) /* handles a w: field in the abc */ char* p; int continuation; { int l; karaoke = 1; v->haswords = 1; if ((wordvoice != 0) && (wordvoice != v->indexno)) { event_warning("More than one voice with words in"); }; wordvoice = v->indexno; words[wcount] = addstring(p); addfeature(WORDLINE, wcount, 0, 0); if (continuation == 0) { addfeature(WORDSTOP, 0, 0, 0); }; wcount = wcount + 1; if (wcount >= maxwords) { maxwords = textextend(maxwords, &words); }; } static void checkbreak() /* check that we are in not in chord, grace notes or tuple */ /* called at voice change */ { if (tuplecount != 0) { event_error("Previous voice has an unfinished tuple"); tuplecount = 0; }; if (v->inchord != 0) { event_error("Previous voice has incomplete chord"); event_chordoff(); }; if (v->ingrace != 0) { event_error("Previous voice has unfinished grace notes"); v->ingrace = 0; }; } static void char_out(part, ch) /* routine for building up part list */ struct vstring* part; char ch; { /* if (*out - list >= MAXPARTS) { event_error("Expanded part is too large"); } else { **out = ch; *out = *out + 1; parts = parts + 1; }; */ addch(ch, part); parts = parts + 1; } static void read_spec(spec, part) /* converts a P: field to a list of part labels */ /* e.g. P:A(AB)3(CD)2 becomes P:AABABABCDCD */ /* A '+' indicates 'additive' behaviour (a part may include repeats). */ /* A '-' indicates 'non-additive' behaviour (repeat marks in the music */ /* are ignored and only repeats implied by the part order statement */ /* are played). */ char spec[]; struct vstring* part; { char* in; int i, j; int stackptr; char* stack[10]; char lastch; stackptr = 0; in = spec; while (((*in >= 'A') && (*in <= 'Z')) || (*in == '(') || (*in == '.') || (*in == ')') || (*in == '+') || (*in == '-') || ((*in >= '0') && (*in <= '9'))) { if (*in == '.') { in = in + 1; }; if (*in == '+') { additive = 1; in = in + 1; }; if (*in == '-') { additive = 0; in = in + 1; }; if ((*in >= 'A') && (*in <= 'Z')) { char_out(part, *in); lastch = *in; in = in + 1; }; if (*in == '(') { if (stackptr < 10) { stack[stackptr] = part->st + strlen(part->st); stackptr = stackptr + 1; } else { event_error("nesting too deep in part specification"); }; in = in + 1; }; if (*in == ')') { in = in + 1; if (stackptr > 0) { int repeats; char* start; char* stop; if ((*in >= '0') && (*in <= '9')) { repeats = readnump(&in); } else { repeats = 1; }; stackptr = stackptr - 1; start = stack[stackptr]; stop = part->st + strlen(part->st); for (i=1; i= '0') && (*in <= '9')) { int repeats; repeats = readnump(&in); if (part->len > 0) { for (i = 1; i 'Z')) { event_error("Part must be one of A-Z"); return; }; if ((headerpartlabel == 1) && (part.st[0] == *p)) { /* P: field in header is not a label */ headerpartlabel = 0; /* remove speculative part label */ feature[part_start[(int)*p - (int)'A']] = NONOTE; } else { if (part_start[(int)*p - (int)'A'] != -1) { event_error("Part defined more than once"); }; }; part_start[(int)*p - (int)'A'] = notes; addfeature(PART, (int)*p, 0, 0); checkbreak(); v = getvoicecontext(1); } else { parts = 0; read_spec(p, &part); if (parts == 1) { /* might be a label not a specificaton */ headerpartlabel = 1; }; }; }; } void event_voice(n, s) /* handles a V: field in the abc */ int n; char *s; { if (pastheader) { voicesused = 1; checkbreak(); v = getvoicecontext(n); addfeature(VOICE, v->indexno, 0, 0); } else { event_warning("V: in header ignored"); }; } void event_length(n) /* handles an L: field in the abc */ int n; { if (pastheader) { v->default_length = n; } else { global.default_length = n; }; } static void tempounits(t_num, t_denom) /* interprets Q: once default length is known */ int *t_num, *t_denom; { /* calculate unit for tempo */ if (tempo_num == 0) { *t_num = 1; *t_denom = global.default_length; } else { if (relative_tempo) { *t_num = tempo_num; *t_denom = tempo_denom*global.default_length; } else { *t_num = tempo_num; *t_denom = tempo_denom; }; }; } void event_tempo(n, a, b, rel, pre, post) /* handles a Q: field e.g. Q: a/b = n or Q: Ca/b = n */ /* strings before and after are ignored */ int n; int a, b, rel; char *pre; char *post; { int t_num, t_denom; int new_div; long new_tempo; int tempo_l, tempo_h; if ((n == 0) || ((a!=0) && (b == 0))) { event_error("malformed Q: field ignored"); } else { if (dotune) { if (pastheader) { tempo_num = a; tempo_denom = b; relative_tempo = rel; tempounits(&t_num, &t_denom); new_tempo = (long) 60*1000000*t_denom/(n*4*t_num); /* split up into short ints */ tempo_l = new_tempo & 0xffff; tempo_h = new_tempo >> 16; new_div = (int) ((float)DIV*(float)new_tempo/(float)tempo + 0.5); addfeature(TEMPO, new_div, tempo_h, tempo_l); } else { Qtempo = n; tempo_num = a; tempo_denom = b; relative_tempo = rel; }; }; }; } void event_timesig(n, m, dochecking) /* handles an M: field M:n/m */ int n, m, dochecking; { if (dotune) { if (pastheader) { addfeature(TIME, dochecking, n, m); } else { time_num = n; time_denom = m; timesigset = 1; barchecking = dochecking; }; }; } void event_octave(num) /* used internally by other routines when octave=N is encountered */ /* in I: or K: fields */ int num; { if (dotune) { if (pastheader) { v->octaveshift = num; } else { global.octaveshift = num; }; }; } void event_info_key(key, value) char* key; char* value; { int num; if (strcmp(key, "octave")==0) { num = readsnumf(value); event_octave(num); }; } static void stack_broken(v) /* store away broken rhythm context on encountering grace notes */ struct voicecontext* v; { v->broken_stack[0] = v->laststart; v->broken_stack[1] = v->lastend; v->broken_stack[2] = v->thisstart; v->broken_stack[3] = v->thisend; v->broken_stack[4] = v->brokentype; v->broken_stack[5] = v->brokenmult; v->broken_stack[6] = v->brokenpending; v->laststart = -1; v->lastend = -1; v->thisstart = -1; v->thisend = -1; v->brokenpending = -1; } static void restore_broken(v) /* remember any broken rhythm context after grace notes */ struct voicecontext* v; { if (v->brokenpending != -1) { event_error("Unresolved broken rhythm in grace notes"); }; v->laststart = v->broken_stack[0]; v->lastend = v->broken_stack[1]; v->thisstart = v->broken_stack[2]; v->thisend = v->broken_stack[3]; v->brokentype = v->broken_stack[4]; v->brokenmult = v->broken_stack[5]; v->brokenpending = v->broken_stack[6]; } void event_graceon() /* a { in the abc */ { if (gracenotes) { event_error("Nested grace notes not allowed"); } else { if (v->inchord) { event_error("Grace notes not allowed in chord"); } else { gracenotes = 1; addfeature(GRACEON, 0, 0, 0); v->ingrace = 1; stack_broken(v); }; }; } void event_graceoff() /* a } in the abc */ { if (!gracenotes) { event_error("} without matching {"); } else { gracenotes = 0; addfeature(GRACEOFF, 0, 0, 0); v->ingrace = 0; restore_broken(v); }; } void event_rep1() /* [1 in the abc */ { addfeature(PLAY_ON_REP, 0, 0, 1); /* if ((notes == 0) || (feature[notes-1] != SINGLE_BAR)) { event_error("[1 must follow a single bar"); } else { feature[notes-1] = BAR1; }; */ } void event_rep2() /* [2 in the abc */ { addfeature(PLAY_ON_REP, 0, 0, 2); /* if ((notes == 0) || (feature[notes-1] != REP_BAR)) { event_error("[2 must follow a :| "); } else { feature[notes-1] = REP_BAR2; }; */ } void event_playonrep(s) char* s; /* [X in the abc, where X is a list of numbers */ { int num, converted; char seps[2]; converted = sscanf(s, "%d%1[,-]", &num, seps); if (converted == 0) { event_error("corrupted variant ending"); } else { if ((converted == 1) && (num != 0)) { addfeature(PLAY_ON_REP, 0, 0, num); } else { textfeature(PLAY_ON_REP, s); }; }; } static void slurtotie() /* converts a pair of identical slurred notes to tied notes */ { int last1, slurtie, last2, failed; int j; if ((!v->ingrace) && (!v->inchord)) { j = notes-1; failed = 0; last1 = -1; while ((j>=0) && (!failed) && (last1 == -1)) { if (feature[j] == NOTE) { last1 = j; }; if ((j<=0) || (feature[j] == REST) || (feature[j] == CHORDOFF) || (feature[j] == GRACEOFF) || (feature[j] == SLUR_ON)) { failed = 1; }; j = j - 1; }; slurtie = -1; while ((j>=0) && (!failed) && (slurtie == -1)) { if (feature[j] == SLUR_TIE) { slurtie = j; }; if ((j<=0) || (feature[j] == REST) || (feature[j] == CHORDOFF) || (feature[j] == GRACEOFF) || (feature[j] == SLUR_ON) || (feature[j] == NOTE)) { failed = 1; }; j = j - 1; }; last2 = -1; while ((j>=0) && (!failed) && (last2 == -1)) { if (feature[j] == NOTE) { last2 = j; }; if ((j<=0) || (feature[j] == REST) || (feature[j] == CHORDOFF) || (feature[j] == GRACEOFF) || (feature[j] == SLUR_ON)) { failed = 1; }; j = j - 1; }; if ((!failed) && (pitch[last1] == pitch[last2])) { /* promote SLUR_TIE to tie */ feature[slurtie] = TIE; event_warning("Slur in abc taken to mean a tie"); } else { if (verbose) { event_warning("Slur ignored"); }; }; }; } void event_sluron(t) /* called when ( is encountered in the abc */ int t; { if (t == 1) { addfeature(SLUR_ON, 0, 0, 0); v->inslur = 1; }; } void event_sluroff(t) /* called when ) is encountered */ int t; { if (t == 0) { slurtotie(); addfeature(SLUR_OFF, 0, 0, 0); v->inslur = 0; }; } void event_tie() /* a tie - has been encountered in the abc */ { addfeature(TIE, 0, 0, 0); } void event_space() /* space character in the abc is ignored by abc2midi */ { /* ignore */ /* printf("Space event\n"); */ } void event_lineend(ch, n) /* called when \ or ! or * or ** is encountered at the end of a line */ char ch; int n; { /* ignore */ } void event_broken(type, mult) /* handles > >> >>> < << <<< in the abc */ int type, mult; { if (v->inchord) { event_error("Broken rhythm not allowed in chord"); } else { if (v->ingrace) { event_error("Broken rhythm not allowed in grace notes"); } else { if ((hornpipe) && (feature[notes-1] == GT)) { /* remove any superfluous hornpiping */ notes = notes - 1; }; /* addfeature(type, mult, 0, 0); */ v->brokentype = type; v->brokenmult = mult; v->brokenpending = 0; }; }; } void event_tuple(n, q, r) /* handles triplets (3 and general tuplets (n:q:r in the abc */ int n, q, r; { if (tuplecount > 0) { event_error("nested tuples"); } else { if (r == 0) { specialtuple = 0; tuplecount = n; } else { specialtuple = 1; tuplecount = r; }; if (q != 0) { tfact_num = q; tfact_denom = n; } else { if ((n < 2) || (n > 9)) { event_error("Only tuples (2 - (9 allowed"); tfact_num = 1; tfact_denom = 1; tuplecount = 0; } else { /* deduce tfact_num using standard abc rules */ if ((n == 2) || (n == 4) || (n == 8)) tfact_num = 3; if ((n == 3) || (n == 6)) tfact_num = 2; if ((n == 5) || (n == 7) || (n == 9)) { if ((time_num % 3) == 0) { tfact_num = 3; } else { tfact_num = 2; }; }; tfact_denom = n; }; }; tnote_num = 0; tnote_denom = 0; }; } void event_chord() /* a + has been encountered in the abc */ { if (v->inchord) { event_chordoff(); } else { event_chordon(); }; } static void lenmul(n, a, b) /* multiply note length by a/b */ int n, a, b; { if ((feature[n] == NOTE) || (feature[n] == REST) || (feature[n] == CHORDOFF)) { num[n] = num[n] * a; denom[n] = denom[n] * b; reduce(&num[n], &denom[n]); }; } static void applybroken(place, type, n) int place, type, n; /* adjust lengths of broken notes */ { int num1, num2, denom12; int j; int forechord, forestart, foreend, backchord, backstart, backend; int failed, lastnote; j = place; failed = 0; forestart = -1; foreend = -1; forechord = 0; /* find following note or chord */ while ((!failed) && (forestart == -1)) { if ((feature[j] == NOTE) || (feature[j] == REST)) { forestart = j; if (forechord) { lastnote = forestart; } else { foreend = forestart; }; }; if ((feature[j] == GRACEON) || (feature[j] == TIE)) { event_error("Unexpected item following broken rhythm"); }; if (feature[j] == CHORDON) { forechord = 1; }; j = j + 1; if (j>=notes) { failed = 1; }; }; /* look for extend of chord if there is one */ while ((!failed) && (foreend == -1)) { if ((feature[j] == NOTE) || (feature[j] == REST)) { lastnote = j; }; if ((feature[j] == GRACEON) || (feature[j] == TIE)) { event_error("Unexpected item following broken rhythm"); }; if (feature[j] == CHORDOFF) { foreend = j; }; j = j + 1; if (j>=notes) { failed = 1; }; }; /* look for note or chord before broken rhythm symbol */ j = place; backend = -1; backstart = -1; backchord = 0; while ((!failed) && (backend == -1)) { if ((feature[j] == NOTE) || (feature[j] == REST)) { backend = j; if (backchord) { lastnote = backend; } else { backstart = backend; }; }; if ((feature[j] == GRACEOFF) || (feature[j] == TIE)) { event_error("Unexpected item preceding broken rhythm"); }; if (feature[j] == CHORDOFF) { backchord = 1; backend = j; }; j = j - 1; if (j<0) { failed = 1; }; }; /* look for extent of chord if there is one */ while ((!failed) && (backstart == -1)) { if ((feature[j] == NOTE) || (feature[j] == REST)) { lastnote = j; }; if ((feature[j] == GRACEON) || (feature[j] == TIE)) { event_error("Unexpected item following broken rhythm"); }; if (feature[j] == CHORDON) { backstart = lastnote; }; j = j - 1; if (j<0) { failed = 1; }; }; switch(n) { case 1: num1 = 4; num2 = 2; break; case 2: num1 = 7; num2 = 1; break; case 3: num1 = 15; num2 = 1; break; }; denom12 = (num1 + num2)/2; if (type == LT) { j = num1; num1 = num2; num2 = j; }; /* check for same length notes */ if ((num[backstart]*denom[forestart]) != (num[forestart]*denom[backstart])) { failed = 1; }; if (failed) { event_error("Cannot apply broken rhythm"); } else { for (j=backstart; j<=backend; j++) { lenmul(j, num1, denom12); }; for (j=forestart; j<=foreend; j++) { lenmul(j, num2, denom12); }; }; } static void brokenadjust() /* adjust lengths of broken notes */ { int num1, num2, denom12; int j; int failed; switch(v->brokenmult) { case 1: num1 = ratio_b; num2 = ratio_a; break; case 2: num1 = 7; num2 = 1; break; case 3: num1 = 15; num2 = 1; break; }; denom12 = (num1 + num2)/2; if (v->brokentype == LT) { j = num1; num1 = num2; num2 = j; }; failed = 0; if ((v->laststart == -1) || (v->lastend == -1) || (v->thisstart == -1) || (v->thisend == -1)) { failed = 1; } else { /* check for same length notes */ if ((num[v->laststart]*denom[v->thisstart]) != (num[v->thisstart]*denom[v->laststart])) { failed = 1; }; }; if (failed) { event_error("Cannot apply broken rhythm"); } else { /* printf("Adjusting %d to %d and %d to %d\n", v->laststart, v->lastend, v->thisstart, v->thisend); */ for (j=v->laststart; j<=v->lastend; j++) { lenmul(j, num1, denom12); }; for (j=v->thisstart; j<=v->thisend; j++) { lenmul(j, num2, denom12); }; }; } static void marknotestart() /* voice data structure keeps a record of last few notes encountered */ /* in order to process broken rhythm. This is called at the start of */ /* a note or chord */ { v->laststart = v->thisstart; v->lastend = v->thisend; v->thisstart = notes-1; } static void marknoteend() /* voice data structure keeps a record of last few notes encountered */ /* in order to process broken rhythm. This is called at the end of */ /* a note or chord */ { v->thisend = notes-1; if (v->brokenpending != -1) { v->brokenpending = v->brokenpending + 1; if (v->brokenpending == 1) { brokenadjust(); v->brokenpending = -1; }; }; } static void marknote() /* when handling a single note, not a chord, marknotestart() and */ /* marknoteend() can be called together */ { marknotestart(); marknoteend(); } void event_rest(n,m) /* rest of n/m in the abc */ int n, m; { int num, denom; num = n; denom = m; if (v == NULL) { event_fatal_error("Internal error : no voice allocated"); }; if (v->inchord) v->chordcount = v->chordcount + 1; if (tuplecount > 0) { num = num * tfact_num; denom = denom * tfact_denom; if (tnote_num == 0) { tnote_num = num; tnote_denom = denom; } else { if (tnote_num * denom != num * tnote_denom) { if (!specialtuple) { event_warning("Different length notes in tuple"); }; }; }; if ((!gracenotes) && ((!v->inchord) || ((v->inchord) && (v->chordcount == 1)))) { tuplecount = tuplecount - 1; }; }; if (v->chordcount == 1) { v->chord_num = num*4; v->chord_denom = denom*(v->default_length); }; if ((!v->ingrace) && ((!v->inchord)||(v->chordcount==1))) { addunits(num, denom*(v->default_length)); }; last_num = 3; /* hornpiping (>) cannot follow rest */ addfeature(REST, 0, num*4, denom*(v->default_length)); if (!v->inchord ) { marknote(); }; } void event_mrest(n,m) /* multiple bar rest of n/m in the abc */ /* we check for m == 1 in the parser */ int n, m; { int i; for (i=0; idefault_length), time_denom); if (i != n-1) { event_bar(SINGLE_BAR, ""); }; }; } void event_chordon() /* handles a chord start [ in the abc */ { if (v->inchord) { event_error("Attempt to nest chords"); } else { addfeature(CHORDON, 0, 0, 0); v->inchord = 1; v->chordcount = 0; v->chord_num = 0; v->chord_denom = 1; marknotestart(); }; } void event_chordoff() /* handles a chord close ] in the abc */ { if (!v->inchord) { event_error("Chord already finished"); } else { addfeature(CHORDOFF, 0, v->chord_num, v->chord_denom); v->inchord = 0; v->chordcount = 0; marknoteend(); }; } void event_finger(p) /* a 1, 2, 3, 4 or 5 has been found in a guitar chord field */ char *p; { /* does nothing */ } static int pitchof(note, accidental, mult, octave, propogate_accs) /* finds MIDI pitch value for note */ /* if propogate_accs is 1, apply any accidental to all instances of */ /* that note in the bar. If propogate_accs is 0, accidental does not */ /* apply to other notes */ char note, accidental; int mult, octave; int propogate_accs; { int p; char acc; int mul, noteno; static int scale[7] = {0, 2, 4, 5, 7, 9, 11}; char *anoctave = "cdefgab"; p = (int) ((long) strchr(anoctave, note) - (long) anoctave); p = scale[p]; acc = accidental; mul = mult; noteno = (int)note - 'a'; if (acc == ' ') { acc = v->workmap[noteno]; mul = v->workmul[noteno]; } else { if ((retain_accidentals) && (propogate_accs)) { v->workmap[noteno] = acc; v->workmul[noteno] = mul; }; }; if (acc == '^') p = p + mul; if (acc == '_') p = p - mul; return p + 12*octave + middle_c; } static void doroll(note, octave, n, m, pitch) /* applies a roll to a note */ char note; int octave, n, m; int pitch; { char up, down; int t; int upoct, downoct, pitchup, pitchdown; char *anoctave = "cdefgab"; upoct = octave; downoct = octave; t = (int) ((long) strchr(anoctave, note) - (long) anoctave); up = *(anoctave + ((t+1) % 7)); down = *(anoctave + ((t+6) % 7)); if (up == 'c') upoct = upoct + 1; if (down == 'b') downoct = downoct - 1; pitchup = pitchof(up, v->basemap[(int)up - 'a'], 1, upoct, 0); pitchdown = pitchof(down, v->basemap[(int)down - 'a'], 1, downoct, 0); addfeature(NOTE, pitch, n*4, m*(v->default_length)*5); marknotestart(); addfeature(NOTE, pitchup, n*4, m*(v->default_length)*5); addfeature(NOTE, pitch, n*4, m*(v->default_length)*5); addfeature(NOTE, pitchdown, n*4, m*(v->default_length)*5); addfeature(NOTE, pitch, n*4, m*(v->default_length)*5); marknoteend(); } static void dotrill(note, octave, n, m, pitch) /* applies a trill to a note */ char note; int octave, n, m; int pitch; { char up; int i, t; int upoct, pitchup; char *anoctave = "cdefgab"; int a, b, count; upoct = octave; t = (int) ((long) strchr(anoctave, note) - (long) anoctave); up = *(anoctave + ((t+1) % 7)); if (up == 'c') upoct = upoct + 1; pitchup = pitchof(up, v->basemap[(int)up - 'a'], 1, upoct, 0); a = 4; b = m*(v->default_length); count = n; while ((tempo*a)/((long)b) > 100000L) { count = count*2; b = b*2; }; i = 0; while (i < count) { if (i == count - 1) { marknotestart(); }; if (i%2 == 0) { addfeature(NOTE, pitchup, a, b); } else { addfeature(NOTE, pitch, a, b); }; i = i + 1; }; marknoteend(); } static void hornp(num, denom) /* If we have used R:hornpipe, this routine modifies the rhythm by */ /* applying appropriate broken rhythm */ int num, denom; { if ((hornpipe) && (notes > 0) && (feature[notes-1] != GT)) { if ((num*last_denom == last_num*denom) && (num == 1) && (denom*time_num == 32)) { if (((time_num == 4) && (bar_denom == 8)) || ((time_num == 2) && (bar_denom == 16))) { /* addfeature(GT, 1, 0, 0); */ v->brokentype = GT; v->brokenmult = 1; v->brokenpending = 0; }; }; last_num = num; last_denom = denom; }; } void event_note(decorators, accidental, mult, note, xoctave, n, m) /* handles a note in the abc */ int decorators[DECSIZE]; int mult; char accidental, note; int xoctave, n, m; { int pitch; int num, denom; int octave; if (v == NULL) { event_fatal_error("Internal error - no voice allocated"); }; octave = xoctave + v->octaveshift; num = n; denom = m; if (v->inchord) v->chordcount = v->chordcount + 1; if (tuplecount > 0) { num = num * tfact_num; denom = denom * tfact_denom; if (tnote_num == 0) { tnote_num = num; tnote_denom = denom; } else { if (tnote_num * denom != num * tnote_denom) { if (!specialtuple) { event_warning("Different length notes in tuple"); }; }; }; if ((!gracenotes) && ((!v->inchord) || ((v->inchord) && (v->chordcount == 1)))) { tuplecount = tuplecount - 1; }; }; if ((!v->ingrace) && (!v->inchord)) { hornp(num, denom*(v->default_length)); } else { last_num = 3; /* hornpiping (>) cannot follow chord or grace notes */ }; if ((!v->ingrace) && ((!v->inchord)||(v->chordcount==1))) { addunits(num, denom*(v->default_length)); }; pitch = pitchof(note, accidental, mult, octave, 1); if (decorators[FERMATA]) { num = num*2; }; if (v->chordcount == 1) { v->chord_num = num*4; v->chord_denom = denom*(v->default_length); }; if ((decorators[ROLL]) || (decorators[ORNAMENT]) || (decorators[TRILL])) { if (v->inchord) { event_error("Rolls and trills not supported in chords"); } else { if (decorators[TRILL]) { dotrill(note, octave, num, denom, pitch); } else { doroll(note, octave, num, denom, pitch); }; }; } else { if (decorators[STACCATO]) { if (v->inchord) { if (v->chordcount == 1) { addfeature(REST, pitch, num*4, denom*(v->default_length)); }; addfeature(NOTE, pitch, num*4, denom*2*(v->default_length)); } else { addfeature(NOTE, pitch, num*4, denom*2*(v->default_length)); marknotestart(); addfeature(REST, pitch, num*4, denom*2*(v->default_length)); marknoteend(); }; } else { addfeature(NOTE, pitch, num*4, denom*(v->default_length)); if (!v->inchord) { marknote(); }; if ((v->inslur) && (!v->ingrace)) { addfeature(SLUR_TIE, 0, 0, 0); }; }; }; } char *get_accidental(place, accidental) /* read in accidental - used by event_handle_gchord() */ char *place; /* place in string being parsed */ char *accidental; /* pointer to char variable */ { char *p; p = place; *accidental = '='; if (*p == '#') { *accidental = '^'; p = p + 1; }; if (*p == 'b') { *accidental = '_'; p = p + 1; }; return(p); } void event_handle_gchord(s) /* handler for the guitar chords */ char* s; { int basepitch; char accidental, note; char* p; char name[9]; int i; int chordno; int bassnote; int inversion; if ((*s >= '0') && (*s <= '5')) { event_finger(s); return; }; p = s; if ((*p >= 'A') && (*p <= 'G')) { note = *p - (int) 'A' + (int) 'a'; bassnote = 0; p = p + 1; } else { if ((*p >= 'a') && (*p <= 'g')) { note = *p; bassnote = 1; p = p + 1; } else { /* Experimental feature supports "_ignored words" */ if (strchr("_^<>@", (int)*p) == NULL) { event_error("Guitar chord does not start with A-G or a-g"); }; return; }; }; p = get_accidental(p, &accidental); basepitch = pitchof(note, accidental, 1, 0, 0) - middle_c; i = 0; while ((i<9) && (*p != ' ') && (*p != '\0') && (*p != '(') && (*p != '/')) { name[i] = *p; i = i+1; p = p + 1; }; inversion = -1; if (*p == '/') { p = p + 1; if ((*p < 'A') || (*p > 'G')) { event_error(" / must be followed by A-G in chord"); } else { note = (int)*p - 'A' + 'a'; p = p + 1; p = get_accidental(p, &accidental); inversion = pitchof(note, accidental, 1, 0, 0) - middle_c; }; }; name[i] = '\0'; chordno = getchordnumber(name); if (chordno == 0) { char msg[40]; sprintf(msg, "Unrecognized chord name \"%s\"", name); event_error(msg); chordno = 1; /* defaults to major */ } else { /* only record voice as having chords if we recognize chord type */ v->hasgchords = 1; if ((gchordvoice != 0) && (gchordvoice != v->indexno)) { event_warning("More than one voice with guitar chords in"); }; gchordvoice = v->indexno; }; if (bassnote) { chordno = -1; }; addfeature(GCHORD, basepitch, inversion, chordno); } void event_handle_instruction(s) /* handler for ! ! instructions */ /* does ppp pp p mp mf f ff fff */ /* also does !drum! and !nodrum! */ char* s; { char buff[MAXLINE]; char* p; char* q; int done; p = s; /* remove any leading spaces */ skipspace(&p); /* remove any trailing spaces */ q = p; while ((*q != '\0') && (*q != ' ')) { q = q + 1; }; if (*q == ' ') { *q = '\0'; }; done = 0; if (strcmp(p, "ppp") == 0) { event_specific("MIDI", "beat 30 20 10 1"); done = 1; }; if (strcmp(p, "pp") == 0) { event_specific("MIDI", "beat 45 35 20 1"); done = 1; }; if (strcmp(p, "p") == 0) { event_specific("MIDI", "beat 60 50 35 1"); done = 1; }; if (strcmp(p, "mp") == 0) { event_specific("MIDI", "beat 75 65 50 1"); done = 1; }; if (strcmp(p, "mf") == 0) { event_specific("MIDI", "beat 90 80 65 1"); done = 1; }; if (strcmp(p, "f") == 0) { event_specific("MIDI", "beat 105 95 80 1"); done = 1; }; if (strcmp(p, "ff") == 0) { event_specific("MIDI", "beat 120 110 95 1"); done = 1; }; if (strcmp(p, "fff") == 0) { event_specific("MIDI", "beat 127 125 110 1"); done = 1; }; if (strcmp(p, "drum") == 0) { addfeature(DRUMON, 0, 0, 0); if ((drumvoice != 0) && (drumvoice != v->indexno)) { event_warning("Implementation limit: drums only supported in one voice"); }; drumvoice = v->indexno; done = 1; }; if (strcmp(p, "nodrum") == 0) { addfeature(DRUMOFF, 0, 0, 0); done = 1; }; if (done == 0) { sprintf(buff, "instruction !%s! ignored", s); event_warning(buff); }; } static void setmap(sf, map, mult) /* work out accidentals to be applied to each note */ int sf; /* number of sharps in key signature -7 to +7 */ char map[7]; int mult[7]; { int j; for (j=0; j<7; j++) { map[j] = '='; mult[j] = 1; }; if (sf >= 1) map['f'-'a'] = '^'; if (sf >= 2) map['c'-'a'] = '^'; if (sf >= 3) map['g'-'a'] = '^'; if (sf >= 4) map['d'-'a'] = '^'; if (sf >= 5) map['a'-'a'] = '^'; if (sf >= 6) map['e'-'a'] = '^'; if (sf >= 7) map['b'-'a'] = '^'; if (sf <= -1) map['b'-'a'] = '_'; if (sf <= -2) map['e'-'a'] = '_'; if (sf <= -3) map['a'-'a'] = '_'; if (sf <= -4) map['d'-'a'] = '_'; if (sf <= -5) map['g'-'a'] = '_'; if (sf <= -6) map['c'-'a'] = '_'; if (sf <= -7) map['f'-'a'] = '_'; } static void altermap(v, modmap, modmul) /* apply modifiers to a set of accidentals */ struct voicecontext* v; char modmap[7]; int modmul[7]; { int i; for (i=0; i<7; i++) { if (modmap[i] != ' ') { v->basemap[i] = modmap[i]; v->basemul[i] = modmul[i]; }; }; } static void copymap(v) /* sets up working map at the start of each bar */ struct voicecontext* v; { int j; for (j=0; j<7; j++) { v->workmap[j] = v->basemap[j]; v->workmul[j] = v->basemul[j]; }; } /* workaround for problems with PCC compiler */ /* data may be written to an internal buffer */ int myputc(c) char c; { return (putc(c,fp)); } static void addfract(xnum, xdenom, a, b) /* add a/b to the count of units in the bar */ int *xnum; int *xdenom; int a, b; { *xnum = (*xnum)*b + a*(*xdenom); *xdenom = (*xdenom) * b; reduce(xnum, xdenom); } static void dotie(j, xinchord) /* called in preprocessing stage to handle ties */ int j, xinchord; { int tienote, place; int tietodo, done; int lastnote, lasttie; int inchord; int tied_num, tied_denom; /* find note to be tied */ tienote = j; while ((tienote > 0) && (feature[tienote] != NOTE) && (feature[tienote] != REST)) { tienote = tienote - 1; }; if (feature[tienote] != NOTE) { event_error("Cannot find note before tie"); } else { inchord = xinchord; /* change NOTE + TIE to TNOTE + REST */ feature[tienote] = TNOTE; feature[j] = REST; num[j] = num[tienote]; denom[j] = denom[tienote]; place = j; tietodo = 1; lasttie = j; tied_num = num[tienote]; tied_denom = denom[tienote]; lastnote = -1; done = 0; while ((place < notes) && (tied_num >=0) && (done == 0)) { switch (feature[place]) { case NOTE: lastnote = place; if ((tied_num == 0) && (tietodo == 0)) { done = 1; }; if ((pitch[place] == pitch[tienote]) && (tietodo == 1)) { /* tie in note */ if (tied_num != 0) { event_error("Time mismatch at tie"); }; tietodo = 0; /* add time to tied time */ addfract(&tied_num, &tied_denom, num[place], denom[place]); /* add time to tied note */ addfract(&num[tienote], &denom[tienote], num[place], denom[place]); /* change note to a rest */ feature[place] = REST; /* get rid of tie */ if (lasttie != j) { feature[lasttie] = OLDTIE; }; }; if (inchord == 0) { /* subtract time from tied time */ addfract(&tied_num, &tied_denom, -num[place], denom[place]); }; break; case REST: if ((tied_num == 0) && (tietodo == 0)) { done = 1; }; if (inchord == 0) { /* subtract time from tied time */ addfract(&tied_num, &tied_denom, -num[place], denom[place]); }; break; case TIE: if (lastnote == -1) { event_error("Bad tie: possibly two ties in a row"); } else { if (pitch[lastnote] == pitch[tienote]) { lasttie = place; tietodo = 1; }; }; break; case CHORDON: inchord = 1; break; case CHORDOFF: inchord = 0; /* subtract time from tied time */ addfract(&tied_num, &tied_denom, -num[place], denom[place]); break; default: break; }; place = place + 1; }; if (tietodo == 1) { event_error("Could not find note to be tied"); }; }; } static void tiefix() /* connect up tied notes */ { int j; int inchord; int chord_num, chord_denom; j = 0; inchord = 0; while (j 0) { event_playonrep(replist); }; /* if (type == BAR1) { addfeature(PLAY_ON_REP, 0, 0, 1); }; if (type == REP_BAR2) { addfeature(PLAY_ON_REP, 0, 0, 2); }; */ } static void delendrep(j) int j; /* remove bogus repeat */ { event_error("spurious repeat after second ending"); switch(feature[j]) { case REP_BAR: feature[j] = DOUBLE_BAR; break; case DOUBLE_REP: feature[j] = BAR_REP; break; default: break; }; } static void placeendrep(j) /* patch up missing repeat */ int j; { event_warning("Assuming repeat"); switch(feature[j]) { case DOUBLE_BAR: feature[j] = REP_BAR; break; case SINGLE_BAR: feature[j] = REP_BAR; break; case BAR_REP: feature[j] = DOUBLE_REP; break; default: event_error("Internal error - please report"); break; }; } static void placestartrep(j) /* patch up missing repeat */ int j; { event_warning("Assuming repeat"); switch(feature[j]) { case DOUBLE_BAR: feature[j] = BAR_REP; break; case SINGLE_BAR: feature[j] = BAR_REP; break; case REP_BAR: feature[j] = DOUBLE_REP; break; case BAR_REP: event_error("Too many end repeats"); break; case DOUBLE_REP: event_error("Too many end repeats"); break; default: event_error("Internal error - please report"); break; }; } static void fixreps() /* find and correct missing repeats in music */ /* abc2midi places an extra || at the start of the music */ /* This can be converted to |: if necessary. */ { int j; int rep_point; /* where to assume a repeat starts */ int expect_repeat; int expect_norepeat; int use_next; expect_repeat = 0; expect_norepeat = 0; use_next = 0; j = 0; while (j < notes) { switch(feature[j]) { case SINGLE_BAR: if (use_next) { rep_point = j; use_next = 0; }; break; case DOUBLE_BAR: rep_point = j; use_next = 0; break; case BAR_REP: if (expect_repeat) { event_error("|: found when end repeat expected"); placeendrep(j); }; expect_repeat = 1; expect_norepeat = 0; use_next = 0; break; case REP_BAR: if (expect_norepeat) { delendrep(j); } else { if (!expect_repeat) { placestartrep(rep_point); }; }; expect_repeat = 0; expect_norepeat = 0; rep_point = j; use_next = 0; break; case PLAY_ON_REP: if (denom[j] == 1) { /* case REP1: */ if (!expect_repeat) { placestartrep(rep_point); }; expect_repeat = 1; expect_norepeat = 0; break; }; if (denom[j] == 2) { /* case REP2: */ if (expect_repeat) { event_error("require :|2 for second ending"); }; expect_repeat = 0; expect_norepeat = 1; break; }; case REP_BAR2: /* cannot happen! */ if (!expect_repeat) { placestartrep(rep_point); }; expect_repeat = 0; use_next = 1; break; case DOUBLE_REP: if (expect_norepeat) { delendrep(j); } else { if (!expect_repeat) { placestartrep(rep_point); }; }; expect_repeat = 1; expect_norepeat = 0; break; default: break; }; j = j + 1; }; } static void startfile() /* called at the beginning of an abc tune by event_refno */ /* This sets up all the default values */ { int j; if (verbose) { printf("scanning tune\n"); }; /* set up defaults */ sf = 0; mi = 0; setmap(0, global.basemap, global.basemul); copymap(&global); global.octaveshift = 0; voicecount = 0; head = NULL; v = NULL; got_titlename = 0; time_num = 4; time_denom = 4; mtime_num = 4; mtime_denom = 4; timesigset = 0; barchecking = 1; global.default_length = -1; event_tempo(120, 1, 4, 0, NULL, NULL); notes = 0; ntexts = 0; gfact_num = 1; gfact_denom = 3; global_transpose = 0; hornpipe = 0; karaoke = 0; retain_accidentals = 1; ratio_a = 2; ratio_b = 4; wcount = 0; parts = -1; middle_c = 60; for (j=0; j<26; j++) { part_start[j] = -1; }; headerpartlabel = 0; additive = 1; initvstring(&part); for (j=0; j<16;j++) { channels[j] = 0; }; set_gchords("z"); gchordvoice = 0; set_drums("z"); drumvoice = 0; wordvoice = 0; } static void setbeat() /* default accompaniment patterns for various time signatures */ { /* set up chord/fundamental sequence if not already set */ if ((time_num == 2) && (time_denom == 2)) { set_gchords("fzczfzcz"); }; if (((time_num == 2) || (time_num == 4)) && (time_denom == 4)) { set_gchords("fzczfzcz"); }; if ((time_num == 3) && (time_denom == 4)) { set_gchords("fzczcz"); }; if ((time_num == 6) && (time_denom == 8)) { set_gchords("fzcfzc"); }; if ((time_num == 9) && (time_denom == 8)) { set_gchords("fzcfzcfzc"); }; } static void headerprocess() /* called after the K: field has been reached, signifying the end of */ /* the header and the start of the tune */ { int t_num, t_denom; if (headerpartlabel == 1) { part_start[(int)part.st[0] - (int)'A'] = notes; addfeature(PART, part.st[0], 0, 0); }; addfeature(DOUBLE_BAR, 0, 0, 0); pastheader = 1; gracenotes = 0; /* not in a grace notes section */ if (!timesigset) { event_warning("No M: in header, using default"); }; /* calculate time for a default length note */ if (global.default_length == -1) { if (((float) time_num)/time_denom < 0.75) { global.default_length = 16; } else { global.default_length = 8; }; }; bar_num = 0; bar_denom = 1; set_meter(time_num, time_denom); if (hornpipe) { if ((time_denom != 4) || ((time_num != 2) && (time_num != 4))) { event_error("Hornpipe must be in 2/4 or 4/4 time"); hornpipe = 0; }; }; tempounits(&t_num, &t_denom); /* make tempo in terms of 1/4 notes */ tempo = (long) 60*1000000*t_denom/(Qtempo*4*t_num); div_factor = division; setbeat(); voicesused = 0; } void event_key(sharps, s, minor, modmap, modmul, gotkey, gotclef, clefname, octave, transpose, gotoctave, gottranspose) /* handles a K: field */ int sharps; /* sharps is number of sharps in key signature */ int minor; /* a boolean 0 or 1 */ char *s; /* original string following K: */ char modmap[7]; /* array of accidentals to be applied */ int modmul[7]; /* array giving multiplicity of each accent (1 or 2) */ int gotkey, gotclef; int octave, transpose, gotoctave, gottranspose; char* clefname; { if ((dotune) && gotkey) { if (pastheader) { setmap(sharps, v->basemap, v->basemul); altermap(v, modmap, modmul); copymap(v); addfeature(KEY, sharps, 0, minor); if (gottranspose) { addfeature(TRANSPOSE, transpose, 0, 0); }; } else { if (gottranspose) { global_transpose = transpose; }; setmap(sharps, global.basemap, global.basemul); altermap(&global, modmap, modmul); copymap(&global); sf = sharps; mi = minor; headerprocess(); v = newvoice(1); head = v; }; if (gotoctave) { event_octave(octave); }; }; } static void finishfile() /* end of tune has been reached - write out MIDI file */ { extern int nullputc(); clearvoicecontexts(); if (!pastheader) { event_error("No valid K: field found at start of tune"); } else { int i; if (parts > -1) { addfeature(PART, ' ', 0, 0); }; if (headerpartlabel == 1) { event_error("P: field in header should go after K: field"); }; if (verbose) { printf("handling grace notes\n"); }; dograce(); tiefix(); if ((parts == -1) && (voicecount == 1)) { if (verbose) { printf("fixing repeats\n"); }; fixreps(); }; if ((voicesused == 0) && (!karaoke) && (gchordvoice == 0) && (drumvoice == 0)) { ntracks = 1; } else { ntracks = voicecount + karaoke + 1; if (gchordvoice != 0) { gchordtrack = ntracks; ntracks = ntracks + 1; }; if (drumvoice != 0) { drumtrack = ntracks; ntracks = ntracks + 1; }; }; if (check) { Mf_putc = nullputc; if (ntracks == 1) { writetrack(0); } else { for (i=0; i namelimit - 1) { numstr[namelimit - 1] = '\0'; }; if (strlen(outbase) + strlen(numstr) > namelimit) { strncpy(newname, outbase, namelimit - strlen(numstr)); strcpy(&newname[namelimit - strlen(numstr)], numstr); strcpy(&newname[strlen(newname)], ".mid"); } else { sprintf(newname, "%s%s.mid", outbase, numstr); }; outname = addstring(newname); /* outname = (char*)checkmalloc(strlen(outbase) + 22 + strlen(".mid")); */ /* sprintf(outname, "%s%d.mid", outbase, n); */ }; startfile(); }; } void event_eof() /* end of abc file encountered */ { if (dotune) { dotune = 0; parseroff(); finishfile(); }; if (verbose) { printf("End of File reached\n"); }; free(pitch); free(num); free(denom); free(feature); free(words); free(outname); free(outbase); }