X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fabc2ly.py;h=68200e785e3769859068434d01f4c1af0cc25c4c;hb=24a2d3d698e11727bba043e77b2c6710335f34f0;hp=534c6a8b7fd7d4ca14ad96dd6f86fec31e85a685;hpb=47a9c0b80ab41b3a7252d5973944384ed6a4a70b;p=lilypond.git diff --git a/scripts/abc2ly.py b/scripts/abc2ly.py index 534c6a8b7f..68200e785e 100644 --- a/scripts/abc2ly.py +++ b/scripts/abc2ly.py @@ -34,6 +34,13 @@ # \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 @@ -50,17 +57,16 @@ #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 @@ -68,10 +74,16 @@ 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'] = '' @@ -90,7 +102,6 @@ nobarlines = 0 global_key = [0] * 7 # UGH names = ["One", "Two", "Three"] DIGITS='0123456789' -alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ" HSPACE=' \t' midi_specs = '' @@ -100,67 +111,68 @@ def error (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('-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 + 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 ("\\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 - - 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') @@ -173,17 +185,18 @@ def dump_header (outf,hdr): 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 (?) """ - outf.write ("\n\\set Score.defaultBarType=\"empty\"\n") + ## < 2.2 + outf.write ("\n\\set Score.defaultBarType = \"empty\"\n") def dump_slyrics (outf): @@ -191,12 +204,12 @@ def dump_slyrics (outf): ks.sort () for k in ks: if re.match('[1-9]', k): - m = alphabet[string.atoi(k)] + m = chr(string.atoi(k) + 'A') else: m = k for i in range (len(slyrics[voice_idx_dict[k]])): - l=alphabet[i] - outf.write ("\nwords%sV%s = \\lyrics {" % (m, l)) + l= alphabet(i) + outf.write ("\nwords%sV%s = \lyricmode {" % (m, l)) outf.write ("\n" + slyrics [voice_idx_dict[k]][i]) outf.write ("\n}") @@ -206,10 +219,10 @@ def dump_voices (outf): ks.sort () for k in ks: if re.match ('[1-9]', k): - m = alphabet[string.atoi(k)] + m = chr(string.atoi(k) + 'A') else: m = k - outf.write ("\nvoice%s = \\notes {" % m) + outf.write ("\nvoice%s = {" % m) dump_default_bar(outf) if repeat_state[voice_idx_dict[k]]: outf.write("\n\\repeat volta 2 {") @@ -239,38 +252,40 @@ def try_parse_q(a): 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)] + 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 " % m) outf.write ("\n\t}\n") - if len ( slyrics [voice_idx_dict[k]] ): - outf.write ("\n\t\\context Lyrics=\"%s\" \n\t<<\t" % k) + + 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)] + m = alphabet (string.atoi(k)) else: m = k - for i in range (len(slyrics[voice_idx_dict[k]])): - l=alphabet[i] - outf.write("\n\t { \\words%sV%s }" % ( m, l) ) - outf.write ( "\n\t>>\n" ) + + outf.write ( " \\words%sV%s } " % ( m, chr (l)) ) + l += 1 + outf.write ("\n >>") - outf.write ("\n\t\\paper {\n") + outf.write ("\n\t\\layout {\n") if part_names: outf.write ("\t \\translator \n\t {\n") outf.write ("\t\t\\StaffContext\n") @@ -388,8 +403,11 @@ key_lookup = { # abc to lilypond key mode names } 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' @@ -401,24 +419,27 @@ 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 "" - -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, @@ -507,6 +528,7 @@ def try_parse_tuplet_begin (str, state): 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): @@ -527,13 +549,31 @@ 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: @@ -543,9 +583,9 @@ def repeat_prepend(): 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 @@ -605,7 +645,7 @@ def try_parse_header_line (ln, state): if a == 'C': if not state.common_time: state.common_time = 1 - voices_append ("\\override Staff.TimeSignature #\'style = #'C\n") + voices_append (" \\override Staff.TimeSignature #\'style = #'C\n") a = '4/4' if a == 'C|': if not state.common_time: @@ -632,12 +672,16 @@ def try_parse_header_line (ln, state): else: key_info = m.group(1) clef_info = m.group(2) - __main__.global_key = compute_key (key_info)# ugh. - voices_append ('\\key %s' % lily_key(key_info)) + __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 @@ -744,6 +788,7 @@ class Parser_state: self.plus_chord = 0 self.base_octave = 0 self.common_time = 0 + self.parsing_beam = 0 @@ -880,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): @@ -901,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 @@ -966,13 +1017,19 @@ def try_parse_note (str, parser_state): 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 @@ -993,7 +1050,8 @@ def try_parse_guitar_chord (str, state): if str: str = str[1:] gc = re.sub('#', '\\#', gc) # escape '#'s - state.next_articulation = ("%c\"%s\"" % (position ,gc)) + state.next_articulation + state.next_articulation = ("%c\"%s\"" % (position, gc)) \ + + state.next_articulation return str def try_parse_escape (str): @@ -1095,6 +1153,7 @@ def try_parse_bar (str,state): 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: @@ -1103,6 +1162,7 @@ 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("} }") @@ -1131,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 = '' @@ -1205,6 +1265,7 @@ def try_parse_comment (str): #write other kinds of appending if we ever need them. return str +lineno = 0 happy_count = 100 def parse_file (fn): f = open (fn) @@ -1212,6 +1273,7 @@ def parse_file (fn): ls = map (lambda x: re.sub ("\r$", '', x), ls) select_voice('default', '') + global lineno lineno = 0 sys.stderr.write ("Line ... ") sys.stderr.flush () @@ -1249,7 +1311,7 @@ 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: error ("%s: %d: Huh? Don't understand\n" % (fn, lineno)) @@ -1272,6 +1334,7 @@ Options: -o, --output=FILE set output filename to FILE -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. @@ -1288,7 +1351,7 @@ def print_version (): -(options, files) = getopt.getopt (sys.argv[1:], 'vo:hs', ['help','version', 'output=', 'strict']) +(options, files) = getopt.getopt (sys.argv[1:], 'vo:hsb', ['help','version', 'output=', 'strict', 'beams']) out_filename = '' for opt in options: @@ -1304,6 +1367,8 @@ for opt in options: 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 @@ -1323,8 +1388,10 @@ for f in files: 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)