💾 Archived View for runjimmyrunrunyoufuckerrun.com › src › foreign › abcmidi › doc › programming › a… captured on 2021-12-17 at 13:26:06.
-=-=-=-=-=-=-
Some Notes on the abc2midi Code ------------------------------- written by Seymour Shlien Abc2midi.txt - last updated 29 November 1999. This file provides an algorithmic description of the program abc2midi which converts an abc file into a midi file. The sources of abc2midi now comprising of 6700 lines of C code are contained in the following five files. parseabc.c is the front end which scans the abc file and invokes the appropriate event handler for each element it encounters (eg. bar lines, notes, chords etc.) It happens to be the front end for other programs such as abc2ps, yaps and abc2abc. store.c contains all the event handlers which are invoked from parseabc. It converts the abc file into an internal representation described later in this file. genmidi.c converts this internal representation in an output midi file. queues.c are some utilities needed by genmidi. midifile.c is a library of public domain routines to read and write midifiles. In order to handle the multiple voices, parts, repeats and accompaniments which occur in abc files, the program performs multiple passes. The midi file stores the different voices, accompaniment and lyrics in individual tracks, so it is necessary to separates these parts prior to writing it to the midi file. If there are repeats in the music, something which appears only once in the abc file may be invoked twice when creating the output midi file. Parseabc.c ---------- The parser has been written so that it should be possible to write your own utility to handle the interpreted abc and link it with the parser code. This is very similar to the way that the midifilelib utilities work. The parser is designed so that it can write out almost exactly what it reads in. This means that in some cases the level of parsing is fairly primitive and it may be necessary to make use of routines in store.c which perform further processing on an item. In the first phase of parsing, the abc file is read and when, say, a note is encountered, the routine event_note() is called. The code for event_note is in the file store.c. Encountering an X in the abc generally causes a routine called event_X() to be called. An internal representation of the tune is built up and when the end of an abc tune is reached, a little bit of processing is done on the internal representation before the routine mywritetrack() is called to actually write out the MIDI file. The main program to abc2midi is contained in this file. Since this main program is used to start other programs such as yaps, it is fairly simple. It calls event_init defined in store.c which obtains all the user parameters (eg. input filename) and then executes the top level procedure, parsefile. The procedure parsefile (filename), opens the specified file (or stdin) and processes it one character at a time. The characters fill a line buffer which is passed to the procedure parseline whenever a linebreak character (eg. linefeed) is encountered. By doing it in this fashion, abc2midi is able to eliminate the extra carriage return which occurs in DOS files. The procedure parseline first checks for blank lines and comments. A blank line signals the end of the tune and the event_blankline is called to initiate the next stage in processing. If it is neither a comment or blank line, the procedure must decide whether the line is a a field line (eg."X:2") or part of a music body (eg. "|:C>DGz|..."). This is decided using the following rule. If the first letter begins with one of the field letter commands and is immediately followed by a ":", then it is treated as a field line and parsefield is called. Otherwise if a K: field line was already encountered (variable inbody set to 1), then it is treated as a line of music. If neither case is satisfied, then the line is intepreted as plain text. The procedure parsefield identifies the particular field line type and invokes the appropriate support function or event handler. If the field command occurs after the first K: line, then only certain field lines are legal. (For example it is illegal to place a T: (title) line after the K: (key signature) line.) The procedure parsemusic is the most complex procedure in the file and recognizes the musical notes, chords, guitar chord names, bar lines, repeats, slurs, ties, graces etc. Appropriate event handlers and support functions are called to complete the tasks of parsing the information. The parsenote function, for example, must check for decorations, accidentals, octave shifts as well as identifying the note and its determining its duration. Unlike the other software modules, parseabc.c contains few global variables. The global variables linenum, inhead, inbody, parsing, slur, parserinchord indicate which lexical component of the abc tune is being processed. store.c ------- This is the most complex module consisting of almost 3000 lines of source code. This module contains all of the event handlers which may be invoked in the file parseabc.c. The main purpose of these handlers are to build up an internal representation of the abc file in the global memory arrays. This is a complete representation which allows the regeneration of the abc file (eg. abc2abc) or the creation of a midi file. The internal representation is used by the genmidi.c software to create the midi file. Global variables The internal representation is stored in global variables defined at the beginning of the file. A description of the most important variables is given here. The music is stored in the four arrays, feature, pitch, num, denom. These arrays contains a list of the lexical components in the order that they have been encountered or created. The array "feature" identifies the type of component which can be one of the following enum features. SINGLE_BAR, DOUBLE_BAR, BAR_REP, REP_BAR, REP1, REP2, BAR1, REP_BAR2, DOUBLE_REP, THICK_THIN, THIN_THICK, PART, TEMPO, TIME, KEY, REST, TUPLE, NOTE, NONOTE, OLDTIE, TEXT, SLUR_ON, SLUR_OFF, TIE, TITLE, CHANNEL, TRANSPOSE, RTRANSPOSE, GRACEON, GRACEOFF, SETGRACE, SETC, GCHORD, GCHORDON, GCHORDOFF, VOICE, CHORDON, CHORDOFF, SLUR_TIE, TNOTE, LT, GT, DYNAMIC, LINENUM, MUSICLINE, MUSICSTOP, WORDLINE, WORDSTOP The array pitch[] mainly stores the pitch of a musical note, represented in the same manner as in a midi file. Thus middle C has a value of 60. The array has other uses, for example in a LINENUM feature it stores the line number relative to the X: reference command where the lexical feature has been detected. The LINENUM feature is useful when reporting warnings or errors in the input file. Duration of the notes are represented as a fraction, where the standard unit is defined by the L: command. If feature[n] is a NOTE, then num[n] and denom[n] contain the numerator and denominator of this fraction. Here is an example of the contents of these arrays for the following small file in the same order that they are stored in these arrays. X: 1 T: sample M: 2/4 L: 1/8 K: G |: C>D[EF]G |C4:| Feature Pitch Num Denom LINENUM 2 0 0 TITLE 0 0 0 LINENUM 3 0 0 LINENUM 4 0 0 LINENUM 5 0 0 DOUBLE_BAR 0 0 0 LINENUM 6 0 0 MUSICLINE 0 0 0 BAR_REP 0 0 0 NOTE 60 2 3 NOTE 62 1 3 CHORDON 0 0 0 NOTE 64 1 2 NOTE 66 1 2 CHORDOFF 0 1 2 NOTE 67 1 2 SINGLE_BAR 0 0 0 NOTE 60 2 1 REP_BAR 0 0 0 MUSIC_STOP 0 0 0 LINENUM 7 0 0 In order to support multivoice abc files in the form V:1 | ...| ...|.... V:2 | ...| ...|.... % V:1 | ...| ...|.... V:2 | ...| ...|.... etc. abc2midi maintains a voicecontext structure for each voice. This allows each voice to define its own key signature, default note length using internal field commands. There is a head voicecontext which is used when no voice field commands are defined in the abc file. The v[] array maintains a set of all voices active in the abc file and voicecount keeps track of the number of voices. Similarly, abcmidi maintains a list of all the part stored in the feature[], pitch[], num[] and denom[] arrays. All text items (eg. title and some other fields) are stored in char atext[][] arrays, so that they can be included in the midi output file. The textual information is repeated in each track of the output midi file. Following the conventions in the midi file, the program uses the quarter note as the standard unit of time instead of the whole note. In contrast, the standard length in the abc file is based on the whole note. For example in L:1/8, the fraction refers to a whole note. In order to convert time units to quarter notes, the numerator of the time specifications for note lengths is multiplied by 4 when it is added to the feature list. Table of contents of procedures in store.c getarg(option, argc, argv) - gets run time arguments. newvoice(n) - creates a new voice context. getvoicecontext() - finds or makes a voice context. clearvoicecontexts() - frees up the memory of the voice context event_init() - called by main program event_text(s) - called when a line of text is encountered. event_reserved(p) - handles reserved character codes H-Z. event_tex(s) - called whenever a TeX command is encountered. event_linebreak() - called whenever a newline character is encountered. event_startmusicline() - called for each new music line. event_endmusicline() - called at the end of each music line. event_eof() - end of abc file encountered. event_fatal_error(s) - reports fatal error. event_error(s) - reports an error. event_warning(s) - reports a potential problem. event_comment(s) - called whenever a comment is encountered. event_specific(package, s) - recognizes %%package s. event_startinline() - beginning of an in-music field command. event_closeinline() - finishes an in-music field command. event_field(k,f) - for R:, T: and any other unprocessed field commands. event_words(p) - processes a w: field command. char_out(list,out,ch) - for building up a parts list in a P: command. read_spec() - converts P:A(AB)3 to P:AABABAB etc. event_part(s) - handles a P: field. char_break() - reports error for improper voice change. event_voice(n,s) - processes a V: field. event_length(n) - recognizes L: field event_blankline - starts finishfile() procedure. event_refno(n) - recognizes X: event_tempo(n, a, b, rel) - recognizes Q: event_timesig(n, m) - recognizes M: event_key(sharps, s, minor, modmap, modmul) - recognizes K: event_graceon() - start of grace notes, "{" encountered. event_graceoff() - end of grace notes, "}" encountered. event_rep1() - handles first repeat indicated by [1. event_rep2() - handles second repeat indicated by [2. event_slur(t) -called when s encountered in abc. event_sluron(t) called when "(" is encountered in abc. event_sluroff(t) called when ")" is encountered in abc. slurtotie() - converts slur into tied notes. event_tie() - handles tie indication "-". event_rest(n,m) - processes rest indication Z or z. event_bar(type) - processes various types of bar lines. event_space() - space character encountered. Ignored here. event_linend(ch,n) - handles line continuation at end of line (eg. \). event_broken(type, mult) - handles >, <, >> etc. in abc. event_tuple(n,q,r) - handles triplets and general tuplets. event_chord() - called whenever + is encountered. marknotestart() - remembers last few notes in voice context. marknoteend() - this is used to process broken rhythms (eg. >). marknote() - called for single note (as opposed to chord). event_chordon() - called whenever [ is encountered. event_chordoff() - called whenever ] is encountered. splitstring(s,sep,handler) - splits string with separator sep. event_instuction(s) - handles !...! event. getchordnumber(s) - searches known chords for chord s. addchordname(s, len, notes) - adds chord name to known chord list. event_gchord(s) - handles guitar chords. event_handle_gchord(s) - handler for guitar chords. event_handle_instruction(s) - handles dynamic indications (eg. !ppp!). event_finger(p) - handles 1,2,...5 in guitar chord field (does nothing). hornp(num,denom) - modifies rhythm to hornpipe. event_note(roll, staccato, updown, accidental, mult, note, octave, n, m) doroll(note,octave,n,m,pitch) - applies roll to note. dotrill(note,octave,n,m,pitch) - applies trill to note. pitchof(note,accidental,mult,octave) -finds MIDI pitch value setmap(sf,map,mult) - converts key signature to accidental map. altermap(v,modmap,modmul) - modifies accidental map. copymap(v) - sets up working accidental map at beginning of bar. addfeature(f,p,n,d) - places feature in internal tables. autoextend(maxnotes) - increase memory limits for feature arrays. textextend(maxstrings, stringarray) - resize array pointers to strings. myputc(c) - workaround for problems with PCC compiler. tiefix() - connect up tied notes. dotie(j,xinchord) - called in preprocessing stage to handle ties. addfrac(xnum,xdenom,a,b) - add a/b to the number of units in bar. applybroken(place, type, n) - adjust length of broken notes. brokenadjust() -adjust length of broken notes. applygrace() - assign lengths to grace notes. dograce() - assign lengths to grace notes. lenmul(n, a, b) - multiply num(n),denom(n) by a/b. zerobar() - start a new count of beats in the bar. delendrep() - remove bogus repeat. placeendrep(j) - patch up missing repeat. placestartrep(j) - patch up missing repeat. fixreps() - find and correct missing repeats in music. startfile() - initialization performed after an event_refno. tempounits(t_num, t_denom) - interprets Q: field. setbeat() - sets default gchord command for time signature. headerprocess() - called after the first K: field in tune. finishfile() - starts next stage of processing when end of tune is encountered. All the functions in this file respond to event calls from parseabc. Once the internal representation of the abc file is completed, the procedure finishfile is called to perform some clean up and create the midi file. An internal representation of the midi file is first created and then it is written onto the designated output file. As finishfile provides the link to the next module, genmidi.c, here is a brief description of what it does. proc finishfile performs several passes through the internal representation to clean up the graces (dograce), the tied notes (tiefix) and any unbalanced repeats. It then calls writetrack(i) for each midi track to create the internal midi representation and finally the midi representation is recorded in the output file. genmidi.c --------- The procedure finishfile described above, creates each midi track by calling the function writetrack which is defined here. To create a track from the internal representation, the program must find all the parts and put them in order with all the repeats. In addition, if it contains karaoke text, this too must be placed in a separate track. Any chordal accompaniment is generated from the guitar chord indications and placed in another track. For multivoice and multipart music, a voice may be missing in a particular part. If the voice is missing, the procedure fillvoice ensures that all voices remain properly aligned when the voice reappears in another part. Here is a simple road map to the important procedures included in this file. dodeferred is here used to handle any dynamic indications (eg. !ppp!) which may be contained in the file. The %%MIDI messages are stored in a atext string which is pointed to by the contents of the pitch[] array. checkbar is called each time a bar line is encountered and reports a warning if the wrong number of beats occur. Transitions between parts are handled by the procedure partbreak. There are a number of procedures for handling karoake text -- karaokestarttrack(), findwline(startline), getword(place,w), write_syllable(place) and checksyllables(). For the first track, the meter, tempo and key signature are recorded using the functions set_meter(n,m), write_meter(n,m), write_keysig(sf,mi). Chordal accompaniment is produced by the procedure dogchords(i). queues.c -------- For each midi note, it is necessary to send a note-on and a note-off instruction. When polyphonic music is played on the same track, keeping track of the time to issue a note-off instruction may get complicated. The procedures in this file are used to maintain a linked list for the notes to be turned off. The notes are put into the list in the order that they are encountered but the order in which to issue note-off commands is maintained by the links. As many as 50 notes playing simultaneously can be handled by the list. Addendum -------- The following section contains clarifications on various components of abc2midi. 29 June 2003 Treatment of octave shifts. The key signature field command K: has provision for shifting a note using either transpose= or octave= subcommands. Both of these functions operate quite differently and deserve some description especially for multivoiced tunes. The octave shift, is performed in event_note in store.c, using the cached value v.octaveshift which is stored in the global voicecontext structure, v. There is a structure for each voice in the abc file. Whenever a new V: command is encountered, event_voice (store.c) is invoked, which swaps the appropriate voices structure into the global v array using the function getvoicecontext(n). If getvoicecontext cannot find a cached structure for that voice, then a new voice structure is created and added to the linked list of voice structures. The v.octaveshift variable is updated by event_octave which is called by event_key (store.c) which is called by parsekey in parseabc.c (Comment: it is not too clear how an octave switch is handled in the middle of a repeat section. i.e. does the old value get restored when repeating.) (Description of transpose shift is in CHANGES July 1 2003.)