X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fabc2ly.py;h=68200e785e3769859068434d01f4c1af0cc25c4c;hb=24a2d3d698e11727bba043e77b2c6710335f34f0;hp=cc0d03c07ef07e94ba710aad9f4c351d55a5679f;hpb=f1346920f64c571a1475d3ded295b9637560c6bc;p=lilypond.git diff --git a/scripts/abc2ly.py b/scripts/abc2ly.py index cc0d03c07e..68200e785e 100644 --- a/scripts/abc2ly.py +++ b/scripts/abc2ly.py @@ -6,7 +6,7 @@ # # (not finished.) # ABC standard v1.6: http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt -# +# # Enhancements (Roy R. Rankin) # # Header section moved to top of lilypond file @@ -25,31 +25,48 @@ # Chord strings([-^]"string") can contain a '#' # Header fields enclosed by [] in notes string processed # W: words output after tune as abc2ps does it (they failed before) + +# Enhancements (Laura Conrad) # +# Barring now preserved between ABC and lilypond +# the default placement for text in abc is above the staff. +# %%LY now supported. +# \breve and \longa supported. +# M:none doesn't crash lily. + +# Enhancements (Guy Gascoigne-Piggford) +# +# Add support for maintaining ABC's notion of beaming, this is selectable +# from the command line with a -b or --beam option. +# Fixd a problem where on cygwin empty lines weren't being correctly identifed +# and so were complaining, but still generating the correct output. + # Limitations # # Multiple tunes in single file not supported # Blank T: header lines should write score and open a new score # Not all header fields supported -# Beaming not preserved between ABC and lilypond # ABC line breaks are ignored # Block comments generate error and are ignored # Postscript commands are ignored # lyrics not resynchronized by line breaks (lyrics must fully match notes) +# %%LY slyrics can't be directly before a w: line. # ??? #TODO: +# +# * lilylib +# * GNU style messages: warning:FILE:LINE: +# * l10n +# +# Convert to new chord styles. +# # UNDEF -> None - - - -program_name = 'abc2ly' -version = '@TOPLEVEL_VERSION@' -if version == '@' + 'TOPLEVEL_VERSION' + '@': - version = '(unknown version)' # uGUHGUHGHGUGH +# + import __main__ import getopt import sys @@ -57,150 +74,233 @@ import re import string import os +program_name = sys.argv[0] + +version = '@TOPLEVEL_VERSION@' +if version == '@' + 'TOPLEVEL_VERSION' + '@': + version = '(unknown version)' # uGUHGUHGHGUGH UNDEF = 255 state = UNDEF +strict = 0 +preserve_beams = 0 voice_idx_dict = {} header = {} +header['footnotes'] = '' lyrics = [] slyrics = [] voices = [] state_list = [] +repeat_state = [0] * 8 current_voice_idx = -1 current_lyric_idx = -1 lyric_idx = -1 part_names = 0 default_len = 8 +length_specified = 0 +nobarlines = 0 global_key = [0] * 7 # UGH names = ["One", "Two", "Three"] DIGITS='0123456789' HSPACE=' \t' +midi_specs = '' + +def error (msg): + sys.stderr.write (msg) + if strict: + sys.exit (1) + + +def alphabet (i): + return chr (i + ord('A')) def check_clef(s): - if not s: - return '' - if re.match('^treble', s): - s = s[6:] - if re.match ('^-8', s): - s = s[2:] - state.base_octave = -2 - voices_append("\\clef \"G_8\";\n") - else: - state.base_octave = 0 - voices_append("\\clef treble;\n") - elif re.match('^alto', s): - s = s[4:] - state.base_octave = -1 - voices_append ("\\clef alto;\n" ) - elif re.match('^bass',s ): - s = s[4:] - state.base_octave = -2 - voices_append ("\\clef bass;\n" ) - return s + if not s: + return '' + if re.match('-8va', s) or re.match('treble8', s): + # treble8 is used by abctab2ps; -8va is used by barfly, + # and by my patch to abc2ps. If there's ever a standard + # about this we'll support that. + s = s[4:] + state.base_octave = -1 + voices_append("\\clef \"G_8\"\n") + elif re.match('^treble', s): + s = s[6:] + if re.match ('^-8', s): + s = s[2:] + state.base_octave = -2 + voices_append("\\clef \"G_8\"\n") + else: + state.base_octave = 0 + voices_append("\\clef treble\n") + elif re.match('^alto', s): + s = s[4:] + state.base_octave = -1 + voices_append ("\\clef alto\n" ) + elif re.match('^bass',s ): + s = s[4:] + state.base_octave = -2 + voices_append ("\\clef bass\n" ) + return s def select_voice (name, rol): if not voice_idx_dict.has_key (name): - state_list.append(Parser_state()) - voices.append ('') - slyrics.append ([]) - voice_idx_dict[name] = len (voices) -1 + state_list.append(Parser_state()) + voices.append ('') + slyrics.append ([]) + voice_idx_dict[name] = len (voices) -1 __main__.current_voice_idx = voice_idx_dict[name] __main__.state = state_list[current_voice_idx] while rol != '': - m = re.match ('^([^ \t=]*)=(.*)$', rol) # find keywork - if m: - keyword = m.group(1) - rol = m.group (2) - a = re.match ('^("[^"]*"|[^ \t]*) *(.*)$', rol) - if a: - value = a.group (1) - rol = a.group ( 2) - if keyword == 'clef': - check_clef(value) - elif keyword == "name": - value = re.sub ('\\\\','\\\\\\\\', value) - voices_append ("\\property Staff.instrument = %s\n" % value ) - __main__.part_names = 1 - elif keyword == "sname" or keyword == "snm": - voices_append ("\\property Staff.instr = %s\n" % value ) - - else: - break - - return - + m = re.match ('^([^ \t=]*)=(.*)$', rol) # find keywork + if m: + keyword = m.group(1) + rol = m.group (2) + a = re.match ('^("[^"]*"|[^ \t]*) *(.*)$', rol) + if a: + value = a.group (1) + rol = a.group ( 2) + if keyword == 'clef': + check_clef(value) + elif keyword == "name": + value = re.sub ('\\\\','\\\\\\\\', value) + ## < 2.2 + voices_append ("\\set Staff.instrument = %s\n" % value ) + + __main__.part_names = 1 + elif keyword == "sname" or keyword == "snm": + voices_append ("\\set Staff.instr = %s\n" % value ) + else: + break def dump_header (outf,hdr): outf.write ('\\header {\n') ks = hdr.keys () ks.sort () for k in ks: - outf.write ('\t%s = "%s";\n'% (k,hdr[k])) + hdr[k] = re.sub('"', '\\"', hdr[k]) + outf.write ('\t%s = "%s"\n'% (k,hdr[k])) outf.write ('}') def dump_lyrics (outf): if (len(lyrics)): - outf.write("\n\\score\n{\n \\context Lyrics\n <\n") + outf.write("\n\\score\n{\n \\lyrics\n <<\n") for i in range (len (lyrics)): outf.write ( lyrics [i]) outf.write ("\n") - outf.write(" >\n \\paper{}\n}\n") + outf.write(" >>\n \\layout{}\n}\n") + +def dump_default_bar (outf): + """ + Nowadays abc2ly outputs explicits barlines (?) + """ + ## < 2.2 + outf.write ("\n\\set Score.defaultBarType = \"empty\"\n") + def dump_slyrics (outf): ks = voice_idx_dict.keys() ks.sort () for k in ks: + if re.match('[1-9]', k): + m = chr(string.atoi(k) + 'A') + else: + m = k for i in range (len(slyrics[voice_idx_dict[k]])): - outf.write ("\nwords%sV%d = \\lyrics {" % (k, i)) + l= alphabet(i) + outf.write ("\nwords%sV%s = \lyricmode {" % (m, l)) outf.write ("\n" + slyrics [voice_idx_dict[k]][i]) outf.write ("\n}") def dump_voices (outf): + global doing_alternative, in_repeat ks = voice_idx_dict.keys() ks.sort () for k in ks: - outf.write ("\nvoice%s = \\notes {" % k) + if re.match ('[1-9]', k): + m = chr(string.atoi(k) + 'A') + else: + m = k + outf.write ("\nvoice%s = {" % m) + dump_default_bar(outf) + if repeat_state[voice_idx_dict[k]]: + outf.write("\n\\repeat volta 2 {") outf.write ("\n" + voices [voice_idx_dict[k]]) + if not using_old: + if doing_alternative[voice_idx_dict[k]]: + outf.write("}") + if in_repeat[voice_idx_dict[k]]: + outf.write("}") outf.write ("\n}") - + +def try_parse_q(a): + global midi_specs + #assume that Q takes the form "Q:1/4=120" + #There are other possibilities, but they are deprecated + if string.count(a, '/') == 1: + array=string.split(a,'/') + numerator=array[0] + if numerator != 1: + sys.stderr.write("abc2ly: Warning, unable to translate a Q specification with a numerator of %s: %s\n" % (numerator, a)) + array2=string.split(array[1],'=') + denominator=array2[0] + perminute=array2[1] + duration=str(string.atoi(denominator)/string.atoi(numerator)) + midi_specs=string.join(["\\tempo", duration, "=", perminute]) + else: + sys.stderr.write("abc2ly: Warning, unable to parse Q specification: %s\n" % a) + def dump_score (outf): - outf.write (r"""\score{ - \notes < + outf.write (r""" + +\score{ + << """) - ks = voice_idx_dict.keys (); + ks = voice_idx_dict.keys (); ks.sort () for k in ks: + if re.match('[1-9]', k): + m = alphabet (string.atoi(k)) + else: + m = k if k == 'default' and len (voice_idx_dict) > 1: break - if len ( slyrics [voice_idx_dict[k]] ): - outf.write ("\n \\addlyrics") outf.write ("\n\t\\context Staff=\"%s\"\n\t{\n" %k ) if k != 'default': - outf.write ("\t \\$voicedefault\n") - outf.write ("\t \\$voice%s " % k) + outf.write ("\t \\voicedefault\n") + outf.write ("\t \\voice%s " % m) outf.write ("\n\t}\n") - if len ( slyrics [voice_idx_dict[k]] ): - outf.write ("\n\t\\context Lyrics=\"%s\" \n\t<\t" % k) - for i in range (len(slyrics[voice_idx_dict[k]])): - outf.write("\n\t { \\$words%sV%d }" % ( k, i) ) - outf.write ( "\n\t>\n" ) - outf.write ("\n >") - outf.write ("\n\t\\paper {\n") + + l = ord( 'A' ) + for lyrics in slyrics [voice_idx_dict[k]]: + outf.write ("\n\t\\addlyrics { \n") + if re.match('[1-9]',k): + m = alphabet (string.atoi(k)) + else: + m = k + + outf.write ( " \\words%sV%s } " % ( m, chr (l)) ) + l += 1 + + outf.write ("\n >>") + outf.write ("\n\t\\layout {\n") if part_names: outf.write ("\t \\translator \n\t {\n") outf.write ("\t\t\\StaffContext\n") - outf.write ("\t\t\\consists Staff_margin_engraver;\n") +# outf.write ("\t\t\\consists Staff_margin_engraver\n") outf.write ("\t }\n") - outf.write ("\t}\n\t\\midi {}\n}\n") + outf.write ("\t}\n\t\\midi {%s}\n}\n" % midi_specs) def set_default_length (s): + global length_specified m = re.search ('1/([0-9]+)', s) if m: __main__.default_len = string.atoi ( m.group (1)) + length_specified = 1 def set_default_len_from_time_sig (s): m = re.search ('([0-9]+)/([0-9]+)', s) @@ -219,11 +319,11 @@ def gulp_file(f): n = i.tell () i.seek (0,0) except: - sys.stderr.write ("can't open file: %s\n" % f) + sys.stderr.write ("can't open file: `%s'\n" % f) return '' s = i.read (n) if len (s) <= 0: - sys.stderr.write ("gulped emty file: %s\n" % f) + sys.stderr.write ("gulped empty file: `%s'\n" % f) i.close () return s @@ -290,18 +390,24 @@ key_lookup = { # abc to lilypond key mode names 'm' : 'minor', 'min' : 'minor', 'maj' : 'major', + 'major' : 'major', 'phr' : 'phrygian', 'ion' : 'ionian', 'loc' : 'locrian', 'aeo' : 'aeolian', 'mix' : 'mixolydian', + 'mixolydian' : 'mixolydian', 'lyd' : 'lydian', - 'dor' : 'dorian' + 'dor' : 'dorian', + 'dorian' : 'dorian' } def lily_key (k): + orig = "" + k + # UGR k = string.lower (k) key = k[0] + #UGH k = k[1:] if k and k[0] == '#': key = key + 'is' @@ -313,36 +419,48 @@ def lily_key (k): return '%s \\major' % key type = k[0:3] - if key_lookup.has_key(type): - return("%s \\%s" % ( key, key_lookup[type])) - sys.stderr.write("Unknown key type %s ignored\n" % type) - return key - -def shift_key (note, acc , shift): - s = semitone_pitch((note, acc)) - s = (s + shift + 12) % 12 - if s <= 4: - n = s / 2 - a = s % 2 - else: - n = (s + 1) / 2 - a = (s + 1) % 2 - if a: - n = n + 1 - a = -1 - return (n,a) + if not key_lookup.has_key (type): + #ugh, use lilylib, say WARNING:FILE:LINE: + sys.stderr.write ("abc2ly:warning:") + sys.stderr.write ("ignoring unknown key: `%s'" % orig) + sys.stderr.write ('\n') + return 0 + return ("%s \\%s" % ( key, key_lookup[type])) + +def shift_key (note, acc, shift): + s = semitone_pitch((note, acc)) + s = (s + shift + 12) % 12 + if s <= 4: + n = s / 2 + a = s % 2 + else: + n = (s + 1) / 2 + a = (s + 1) % 2 + if a: + n = n + 1 + a = -1 + return (n,a) key_shift = { # semitone shifts for key mode names 'm' : 3, 'min' : 3, + 'minor' : 3, 'maj' : 0, + 'major' : 0, 'phr' : -4, + 'phrygian' : -4, 'ion' : 0, + 'ionian' : 0, 'loc' : 1, + 'locrian' : 1, 'aeo' : 3, + 'aeolian' : 3, 'mix' : 5, + 'mixolydian' : 5, 'lyd' : -5, - 'dor' : -2 + 'lydian' : -5, + 'dor' : -2, + 'dorian' : -2 } def compute_key (k): k = string.lower (k) @@ -376,6 +494,7 @@ def compute_key (k): key_count = flat_key_seq.index (keytup) accseq = map (lambda x: (3*x + 3 ) % 7, range (1, key_count + 1)) else: + error ("Huh?") raise "Huh" key_table = [0] * 7 @@ -399,21 +518,24 @@ def try_parse_tuplet_begin (str, state): if re.match ('\([2-9]', str): dig = str[1] str = str[2:] - state.parsing_tuplet = string.atoi (dig[0]) - + prev_tuplet_state = state.parsing_tuplet + state.parsing_tuplet = string.atoi (dig[0]) + if prev_tuplet_state: + voices_append ("}") voices_append ("\\times %s {" % tup_lookup[dig]) return str def try_parse_group_end (str, state): if str and str[0] in HSPACE: str = str[1:] + close_beam_state(state) return str - + def header_append (key, a): s = '' if header.has_key (key): s = header[key] + "\n" - header [key] = s + a + header [key] = s + a def wordwrap(a, v): linelen = len (v) - string.rfind(v, '\n') @@ -427,31 +549,55 @@ def stuff_append (stuff, idx, a): else: stuff [idx] = wordwrap(a, stuff[idx]) - +# ignore wordwrap since we are adding to the previous word +def stuff_append_back(stuff, idx, a): + if not stuff: + stuff.append (a) + else: + point = len(stuff[idx])-1 + while stuff[idx][point] is ' ': + point = point - 1 + point = point +1 + stuff[idx] = stuff[idx][:point] + a + stuff[idx][point:] def voices_append(a): if current_voice_idx < 0: select_voice ('default', '') - stuff_append (voices, current_voice_idx, a) +# word wrap really makes it hard to bind beams to the end of notes since it +# pushes out whitespace on every call. The _back functions do an append +# prior to the last space, effectively tagging whatever they are given +# onto the last note +def voices_append_back(a): + if current_voice_idx < 0: + select_voice ('default', '') + stuff_append_back(voices, current_voice_idx, a) + +def repeat_prepend(): + global repeat_state + if current_voice_idx < 0: + select_voice ('default', '') + if not using_old: + repeat_state[current_voice_idx] = 't' + + def lyrics_append(a): - a = re.sub ( '#', '\\#', a) # latex does not like naked #'s - a = re.sub ( '"', '\\"', a) # latex does not like naked "'s - a = '\t{ \\lyrics "' + a + '" }\n' + a = re.sub ('#', '\\#', a) # latex does not like naked #'s + a = re.sub ('"', '\\"', a) # latex does not like naked "'s + a = '\t{ "' + a + '" }\n' stuff_append (lyrics, current_lyric_idx, a) # break lyrics to words and put "'s around words containing numbers and '"'s def fix_lyric(str): ret = '' - while str != '': m = re.match('[ \t]*([^ \t]*)[ \t]*(.*$)', str) if m: word = m.group(1) str = m.group(2) word = re.sub('"', '\\"', word) # escape " - if re.match('.*[0-9"]', word): + if re.match('.*[0-9"\(]', word): word = re.sub('_', ' ', word) # _ causes probs inside "" ret = ret + '\"' + word + '\" ' else: @@ -467,7 +613,7 @@ def slyrics_append(a): a = re.sub ( '~', '_', a) # ~ to space('_') a = re.sub ( '\*', '_ ', a) # * to to space a = re.sub ( '#', '\\#', a) # latex does not like naked #'s - if re.match('.*[0-9"]', a): # put numbers and " into quoted string + if re.match('.*[0-9"\(]', a): # put numbers and " and ( into quoted string a = fix_lyric(a) a = re.sub ( '$', ' ', a) # insure space between lines __main__.lyric_idx = lyric_idx + 1 @@ -479,6 +625,7 @@ def slyrics_append(a): def try_parse_header_line (ln, state): + global length_specified m = re.match ('^([A-Za-z]): *(.*)$', ln) if m: @@ -488,35 +635,55 @@ def try_parse_header_line (ln, state): a = re.sub('[ \t]*$','', a) #strip trailing blanks if header.has_key('title'): if a: - header['title'] = header['title'] + '\\\\\\\\' + a + if len(header['title']): + header['title'] = header['title'] + '\\\\\\\\' + a + else: + header['subtitle'] = a else: header['title'] = a if g == 'M': # Meter if a == 'C': if not state.common_time: state.common_time = 1 - voices_append ("\\property Staff.timeSignatureStyle=\"C\"\n") + voices_append (" \\override Staff.TimeSignature #\'style = #'C\n") a = '4/4' if a == 'C|': if not state.common_time: state.common_time = 1 - voices_append ("\\property Staff.timeSignatureStyle=\"C\"\n") + voices_append ("\\override Staff.TimeSignature #\'style = #'C\n") a = '2/2' -# global_voice_stuff.append ('\\time %s;' % a) - set_default_len_from_time_sig (a) - voices_append ('\\time %s;' % a) + if not length_specified: + set_default_len_from_time_sig (a) + else: + length_specified = 0 + if not a == 'none': + voices_append ('\\time %s' % a) state.next_bar = '' if g == 'K': # KEY a = check_clef(a) if a: m = re.match ('^([^ \t]*) *(.*)$', a) # seperate clef info if m: - __main__.global_key =compute_key (m.group(1))# ugh. - voices_append ('\\key %s;' % lily_key(m.group(1))) - check_clef(m.group(2)) + # there may or may not be a space + # between the key letter and the mode + if key_lookup.has_key(m.group(2)[0:3]): + key_info = m.group(1) + m.group(2)[0:3] + clef_info = m.group(2)[4:] + else: + key_info = m.group(1) + clef_info = m.group(2) + __main__.global_key = compute_key (key_info) + k = lily_key (key_info) + if k: + voices_append ('\\key %s' % k) + check_clef(clef_info) else: - __main__.global_key =compute_key (a)# ugh. - voices_append ('\\key %s \\major;' % lily_key(a)) + __main__.global_key = compute_key (a) + k = lily_key (a) + if k: + voices_append ('\\key %s \\major' % k) + if g == 'N': # Notes + header ['footnotes'] = header['footnotes'] + '\\\\\\\\' + a if g == 'O': # Origin header ['origin'] = a if g == 'X': # Reference Number @@ -545,19 +712,21 @@ def try_parse_header_line (ln, state): state.next_bar = '' select_voice (voice, rest) if g == 'W': # Words - lyrics_append(a); + lyrics_append(a) if g == 'w': # vocals - slyrics_append (a); - + slyrics_append (a) + if g == 'Q': #tempo + try_parse_q (a) return '' return ln # we use in this order specified accidental, active accidental for bar, # active accidental for key -def pitch_to_mudela_name (name, acc, bar_acc, key): +def pitch_to_lilypond_name (name, acc, bar_acc, key): s = '' if acc == UNDEF: - acc = bar_acc + if not nobarlines: + acc = bar_acc if acc == UNDEF: acc = key if acc == -1: @@ -570,7 +739,7 @@ def pitch_to_mudela_name (name, acc, bar_acc, key): return(chr (name + ord('c')) + s) -def octave_to_mudela_quotes (o): +def octave_to_lilypond_quotes (o): o = o + 2 s ='' if o < 0: @@ -593,15 +762,20 @@ def parse_num (str): return (str,n) -def duration_to_mudela_duration (multiply_tup, defaultlen, dots): +def duration_to_lilypond_duration (multiply_tup, defaultlen, dots): base = 1 - # (num / den) / defaultlen < 1/base while base * multiply_tup[0] < multiply_tup[1]: base = base * 2 - - - return '%d%s' % ( base, '.'* dots) + if base == 1: + if (multiply_tup[0] / multiply_tup[1]) == 2: + base = '\\breve' + if (multiply_tup[0] / multiply_tup[1]) == 3: + base = '\\breve' + dots = 1 + if (multiply_tup[0] / multiply_tup[1]) == 4: + base = '\longa' + return '%s%s' % ( base, '.'* dots) class Parser_state: def __init__ (self): @@ -614,6 +788,7 @@ class Parser_state: self.plus_chord = 0 self.base_octave = 0 self.common_time = 0 + self.parsing_beam = 0 @@ -626,15 +801,16 @@ def parse_duration (str, parser_state): (str, num) = parse_num (str) if not num: num = 1 - - if str[0] == '/': - while str[:1] == '/': - str= str[1:] - d = 2 - if str[0] in DIGITS: - (str, d) =parse_num (str) + if len(str): + if str[0] == '/': + if len(str[0]): + while str[:1] == '/': + str= str[1:] + d = 2 + if str[0] in DIGITS: + (str, d) =parse_num (str) - den = den * d + den = den * d den = den * default_len @@ -645,7 +821,7 @@ def parse_duration (str, parser_state): str = str[1:] while str[0] == '>': str = str [1:] - current_dots = current_dots + 1; + current_dots = current_dots + 1 parser_state.next_den = parser_state.next_den * 2 while str[0] == '<': @@ -684,7 +860,7 @@ def try_parse_rest (str, parser_state): str = str[1:] (str, num,den,d) = parse_duration (str, parser_state) - voices_append ('%s%s' % (rest, duration_to_mudela_duration ((num,den), default_len, d))) + voices_append ('%s%s' % (rest, duration_to_lilypond_duration ((num,den), default_len, d))) if parser_state.next_articulation: voices_append (parser_state.next_articulation) parser_state.next_articulation = '' @@ -706,11 +882,10 @@ artic_tbl = { } def try_parse_articulation (str, state): - while str and artic_tbl.has_key(str[:1]): state.next_articulation = state.next_articulation + artic_tbl[str[:1]] if not artic_tbl[str[:1]]: - sys.stderr.write("Warning: ignoring %s\n" % str[:1] ) + sys.stderr.write("Warning: ignoring `%s'\n" % str[:1] ) str = str[1:] @@ -750,6 +925,12 @@ def clear_bar_acc(state): del state.in_acc[k] +# if we are parsing a beam, close it off +def close_beam_state(state): + if state.parsing_beam and preserve_beams: + state.parsing_beam = 0 + voices_append_back( ']' ) + # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP ! def try_parse_note (str, parser_state): @@ -771,7 +952,7 @@ def try_parse_note (str, parser_state): if c == '_': acc = -1 - octave = parser_state.base_octave + octave = parser_state.base_octave if str[0] in "ABCDEFG": str = string.lower (str[0]) + str[1:] octave = octave - 1 @@ -800,7 +981,6 @@ def try_parse_note (str, parser_state): (str, num,den,current_dots) = parse_duration (str, parser_state) - if re.match('[ \t]*\)', str): str = string.lstrip (str) @@ -810,19 +990,16 @@ def try_parse_note (str, parser_state): str = str[1:] - if slur_end: - voices_append ('%s' % ')' *slur_end ) - bar_acc = get_bar_acc(notename, octave, parser_state) - pit = pitch_to_mudela_name(notename, acc, bar_acc, global_key[notename]) - oct = octave_to_mudela_quotes (octave) + pit = pitch_to_lilypond_name(notename, acc, bar_acc, global_key[notename]) + oct = octave_to_lilypond_quotes (octave) if acc != UNDEF and (acc == global_key[notename] or acc == bar_acc): mod='!' else: mod = '' voices_append ("%s%s%s%s" % (pit, oct, mod, - duration_to_mudela_duration ((num,den), default_len, current_dots))) + duration_to_lilypond_duration ((num,den), default_len, current_dots))) set_bar_acc(notename, octave, acc, parser_state) if parser_state.next_articulation: @@ -836,14 +1013,23 @@ def try_parse_note (str, parser_state): if not parser_state.parsing_tuplet: voices_append ("}") if slur_begin: - voices_append ('%s' % '(' * slur_begin ) - - + voices_append ('-(' * slur_begin ) + if slur_end: + voices_append ('-)' *slur_end ) + + if preserve_beams and \ + str[0] in '^=_ABCDEFGabcdefg' and \ + not parser_state.parsing_beam and \ + not parser_state.parsing_tuplet: + parser_state.parsing_beam = 1 + voices_append_back( '[' ) + return str -def junk_space (str): - while str and str[0] in '\t\n ': +def junk_space (str,state): + while str and str[0] in '\t\n\r ': str = str[1:] + close_beam_state(state) return str @@ -852,6 +1038,11 @@ def try_parse_guitar_chord (str, state): if str[:1] =='"': str = str[1:] gc = '' + if str[0] == '_' or (str[0] == '^'): + position = str[0] + str = str[1:] + else: + position = '^' while str and str[0] != '"': gc = gc + str[0] str = str[1:] @@ -859,7 +1050,8 @@ def try_parse_guitar_chord (str, state): if str: str = str[1:] gc = re.sub('#', '\\#', gc) # escape '#'s - state.next_articulation = ("-\"%s\"" % gc) + state.next_articulation + state.next_articulation = ("%c\"%s\"" % (position, gc)) \ + + state.next_articulation return str def try_parse_escape (str): @@ -880,38 +1072,88 @@ def try_parse_escape (str): # :: left-right repeat # |1 volta 1 # |2 volta 2 -bar_dict = { +old_bar_dict = { '|]' : '|.', '||' : '||', '[|' : '||', ':|' : ':|', '|:' : '|:', -'::' : '::', +'::' : ':|:', '|1' : '|', '|2' : '|', -':|2' : ':|' +':|2' : ':|', +'|' : '|' } +bar_dict = { + '|]' : '\\bar "|."', + '||' : '\\bar "||"', + '[|' : '\\bar "||"', + ':|' : '}', + '|:' : '\\repeat volta 2 {', + '::' : '} \\repeat volta 2 {', + '|1' : '} \\alternative{{', + '|2' : '} {', + ':|2' : '} {', + '|' : '\\bar "|"' + } warn_about = ['|:', '::', ':|', '|1', ':|2', '|2'] +alternative_opener = ['|1', '|2', ':|2'] +repeat_ender = ['::', ':|'] +repeat_opener = ['::', '|:'] +in_repeat = [''] * 8 +doing_alternative = [''] * 8 +using_old = '' def try_parse_bar (str,state): + global in_repeat, doing_alternative, using_old + do_curly = '' bs = None - + if current_voice_idx < 0: + select_voice ('default', '') # first try the longer one - for trylen in [3,2]: + for trylen in [3,2,1]: if str[:trylen] and bar_dict.has_key (str[:trylen]): s = str[:trylen] - bs = "\\bar \"%s\";" % bar_dict[s] - if s in warn_about: - sys.stderr.write('Warning kludging for barline `%s\'\n' % s) + if using_old: + bs = "\\bar \"%s\"" % old_bar_dict[s] + else: + bs = "%s" % bar_dict[s] str = str[trylen:] - break + if s in alternative_opener: + if not in_repeat[current_voice_idx]: + using_old = 't' + bs = "\\bar \"%s\"" % old_bar_dict[s] + else: + doing_alternative[current_voice_idx] = 't' + if s in repeat_ender: + if not in_repeat[current_voice_idx]: + sys.stderr.write("Warning: inserting repeat to beginning of notes.\n") + repeat_prepend() + in_repeat[current_voice_idx] = '' + else: + if doing_alternative[current_voice_idx]: + do_curly = 't' + if using_old: + bs = "\\bar \"%s\"" % old_bar_dict[s] + else: + bs = bar_dict[s] + doing_alternative[current_voice_idx] = '' + in_repeat[current_voice_idx] = '' + if s in repeat_opener: + in_repeat[current_voice_idx] = 't' + if using_old: + bs = "\\bar \"%s\"" % old_bar_dict[s] + else: + bs = bar_dict[s] + break if str[:1] == '|': state.next_bar = '|\n' str = str[1:] clear_bar_acc(state) + close_beam_state(state) if bs <> None or state.next_bar != '': if state.parsing_tuplet: @@ -920,8 +1162,11 @@ def try_parse_bar (str,state): if bs <> None: clear_bar_acc(state) + close_beam_state(state) voices_append (bs) - + if do_curly != '': + voices_append("} }") + do_curly = '' return str def try_parse_tie (str): @@ -946,18 +1191,18 @@ def try_parse_chord_delims (str, state): if state.next_bar: voices_append(state.next_bar) state.next_bar = '' - voices_append ('<') + voices_append ('<<') if str[:1] == '+': str = str[1:] if state.plus_chord: - voices_append ('>') + voices_append ('>>') state.plus_chord = 0 else: if state.next_bar: voices_append(state.next_bar) state.next_bar = '' - voices_append ('<') + voices_append ('<<') state.plus_chord = 1 ch = '' @@ -971,7 +1216,7 @@ def try_parse_chord_delims (str, state): str = str[1:] - voices_append ("\\spanrequest \\stop \"slur\"" * end); + voices_append ("\\spanrequest \\stop \"slur\"" * end) voices_append (ch) return str @@ -989,13 +1234,46 @@ def try_parse_grace_delims (str, state): return str +def try_parse_comment (str): + global nobarlines + if (str[0] == '%'): + if str[0:5] == '%MIDI': +#the nobarlines option is necessary for an abc to lilypond translator for +#exactly the same reason abc2midi needs it: abc requires the user to enter +#the note that will be printed, and MIDI and lilypond expect entry of the +#pitch that will be played. +# +#In standard 19th century musical notation, the algorithm for translating +#between printed note and pitch involves using the barlines to determine +#the scope of the accidentals. +# +#Since ABC is frequently used for music in styles that do not use this +#convention, such as most music written before 1700, or ethnic music in +#non-western scales, it is necessary to be able to tell a translator that +#the barlines should not affect its interpretation of the pitch. + if (string.find(str,'nobarlines') > 0): + nobarlines = 1 + elif str[0:3] == '%LY': + p = string.find(str, 'voices') + if (p > -1): + voices_append(str[p+7:]) + voices_append("\n") + p = string.find(str, 'slyrics') + if (p > -1): + slyrics_append(str[p+8:]) + +#write other kinds of appending if we ever need them. + return str +lineno = 0 happy_count = 100 def parse_file (fn): f = open (fn) ls = f.readlines () + ls = map (lambda x: re.sub ("\r$", '', x), ls) select_voice('default', '') + global lineno lineno = 0 sys.stderr.write ("Line ... ") sys.stderr.flush () @@ -1010,6 +1288,7 @@ def parse_file (fn): m = re.match ('^([^%]*)%(.*)$',ln) # add comments to current voice if m: if m.group(2): + try_parse_comment(m.group(2)) voices_append ('%% %s\n' % m.group(2)) ln = m.group (1) @@ -1032,11 +1311,10 @@ def parse_file (fn): ln = try_parse_tuplet_begin (ln, state) ln = try_parse_group_end (ln, state) ln = try_parse_grace_delims (ln, state) - ln = junk_space (ln) + ln = junk_space (ln, state) if ln: - msg = "%s: %d: Huh? Don't understand\n" % (fn, lineno) - sys.stderr.write (msg) + error ("%s: %d: Huh? Don't understand\n" % (fn, lineno)) left = orig_ln[0:-len (ln)] sys.stderr.write (left + '\n') sys.stderr.write (' ' * len (left) + ln + '\n') @@ -1047,17 +1325,25 @@ def identify(): def help (): print r""" -Convert ABC to Mudela. +Convert ABC to lilypond. Usage: abc2ly [OPTIONS]... ABC-FILE Options: - -h, --help this help + -h, --help print this help -o, --output=FILE set output filename to FILE - -v, --version version information - + -v, --version show version information + -s, --strict be strict about succes + -b, --beams preserve ABC's notion of beams + This program converts ABC music files (see -http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt) To LilyPond input. +http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt) to LilyPond input. + + +Report bugs to bug-lilypond@gnu.org. + +Written by Han-Wen Nienhuys , Laura Conrad +, Roy Rankin . """ def print_version (): @@ -1065,7 +1351,7 @@ def print_version (): -(options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output=']) +(options, files) = getopt.getopt (sys.argv[1:], 'vo:hsb', ['help','version', 'output=', 'strict', 'beams']) out_filename = '' for opt in options: @@ -1074,12 +1360,15 @@ for opt in options: if o== '--help' or o == '-h': help () sys.exit (0) - if o == '--version' or o == '-v': + elif o == '--version' or o == '-v': print_version () sys.exit(0) - - if o == '--output' or o == '-o': + elif o == '--strict' or o == '-s': + strict = 1 + elif o == '--output' or o == '-o': out_filename = a + elif o == '--beams' or o == '-b': + preserve_beams = 1 else: print o raise getopt.error @@ -1091,16 +1380,18 @@ for f in files: if f == '-': f = '' - sys.stderr.write ('Parsing... [%s]\n' % f) + sys.stderr.write ('Parsing `%s\'...\n' % f) parse_file (f) if not out_filename: out_filename = os.path.basename (os.path.splitext (f)[0]) + ".ly" - sys.stderr.write ('Ly output to: %s...' % out_filename) + sys.stderr.write ('lilypond output to: `%s\'...' % out_filename) outf = open (out_filename, 'w') + outf.write ('\\version "2.3.25"\n') + # dump_global (outf) - dump_header (outf ,header) + dump_header (outf, header) dump_slyrics (outf) dump_voices (outf) dump_score (outf)