Arne has sent me a patch for abc2ly, which improves e.g. the handling of chords (previously converted as annotation, now as chordmode)...
I haven't looked at or tested this patch. Cheers, Reinhold -------- Original Message -------- Subject: abc2ly Patch Date: Thu, 09 Aug 2012 16:47:52 +0200 From: Arne Rempke <arne.rem...@tu-clausthal.de> To: Reinhold Kainhofer <reinh...@kainhofer.com> Hallo, ich schlage mich grade mit der Konvertierung von abc-Daten nach lilypond herum und habe dazu das Skript abc2ly von lilypond etwas erweitert. Wesentlichste Änderung dürfte die Behandlung von Akkorden sein, die nun als \chordmode und nicht mehr als hochgestellte Anmerkung verarbeitet werden. Außerdem wird die Taktzählung wieder lilypond überlassen und Auftakte versucht zu erkennen. Tatsächlich sind auch ein paar Änderungen drin, die zumindest diskussionwürdig sind (z.B. Silbenmatching Lyrics/Melodie bei Slur/Tie-Konstruktionen), aber die bei meinen Daten so die besseren Ergebnisse erzielt haben. Ich dachte mir, bevor die Änderungen nur auf meiner Platte verkümmern versuch ich sie doch noch zu teilen -- hab aber zugegeben grad nicht den Nerv mich mit dem Repository/Ticketsystem zu beschäftigen und schick daher einfach mal nen Patch an einen mir geeignet erscheinenden Entwickler. Evtl. kann man das (oder Teile) ja verwenden, ansonsten bin ich da auch nicht bös drum. Der Patch ist gegen das Skript einer Lilypond 2.14.2-Installation erstellt worden. Danke und viele Grüße aus dem Harz, Arne
--- /usr/bin/abc2ly 2011-10-22 15:48:20.000000000 +0200 +++ abc2ly2 2012-08-09 15:50:38.138219097 +0200 @@ -91,6 +91,8 @@ import sys import re import os +from fractions import Fraction +import math program_name = sys.argv[0] @@ -132,11 +134,15 @@ header['footnotes'] = '' lyrics = [] slyrics = [] +slyrics_num_syl = [] voices = [] +chords = [''] state_list = [] repeat_state = [0] * 8 current_voice_idx = -1 current_lyric_idx = -1 +chord_length = 0 +chord_name = '' lyric_idx = -1 part_names = 0 default_len = 8 @@ -148,7 +154,6 @@ HSPACE=' \t' midi_specs = '' - def error (msg): sys.stderr.write (msg) if global_options.strict: @@ -205,6 +210,7 @@ state_list.append(Parser_state()) voices.append ('') slyrics.append ([]) + slyrics_num_syl.append([]) voice_idx_dict[name] = len (voices) -1 __main__.current_voice_idx = voice_idx_dict[name] __main__.state = state_list[current_voice_idx] @@ -241,18 +247,18 @@ def dump_lyrics (outf): if (len(lyrics)): - outf.write("\n\\score\n{\n \\lyrics\n <<\n") + outf.write("\n\\markup { \\column {\n") for i in range (len (lyrics)): - outf.write ( lyrics [i]) - outf.write ("\n") - outf.write(" >>\n \\layout{}\n}\n") + l = re.sub('^[ \t]*\}\}', '', lyrics [i]) + outf.write ( l + "}}\n") + outf.write ( "} }\n") def dump_default_bar (outf): """ Nowadays abc2ly outputs explicits barlines (?) """ ## < 2.2 - outf.write ("\n\\set Score.defaultBarType = \"empty\"\n") +# outf.write ("\n\\set Score.defaultBarType = \"empty\"\n") def dump_slyrics (outf): @@ -280,9 +286,11 @@ m = k outf.write ("\nvoice%s = {" % m) dump_default_bar(outf) + if state_list[voice_idx_dict[k]].partial: + outf.write("\n \\partial %s" % state_list[voice_idx_dict[k]].partial) if repeat_state[voice_idx_dict[k]]: outf.write("\n\\repeat volta 2 {") - outf.write ("\n" + voices [voice_idx_dict[k]]) + outf.write (voices [voice_idx_dict[k]]) if not using_old: if doing_alternative[voice_idx_dict[k]]: outf.write("}") @@ -290,6 +298,14 @@ outf.write("}") outf.write ("\n}") +def dump_chords (outf): + chord_flush() + if (len(chords[0])): + outf.write ("\nharmonies = \\chordmode {") + outf.write ("\n" + chords[0]) + outf.write ("\n}") + + def try_parse_q(a): #assume that Q takes the form "Q:'opt. description' 1/4=120" #There are other possibilities, but they are deprecated @@ -314,6 +330,7 @@ ks = voice_idx_dict.keys (); ks.sort () + outf.write ("\n\t\\context ChordNames\n\t{\n\t\t\\set chordChanges = ##t\n\t\t\\germanChords \\harmonies\n\t}\n" ) for k in ks: if re.match('[1-9]', k): m = alphabet (int (k)) @@ -596,6 +613,8 @@ if not stuff: stuff.append (a) else: + if (a == '\n' and stuff[idx] and stuff[idx][-2] == a): + return stuff [idx] = wordwrap(a, stuff[idx]) # ignore wordwrap since we are adding to the previous word @@ -610,6 +629,8 @@ stuff[idx] = stuff[idx][:point] + a + stuff[idx][point:] def voices_append(a): + if (not a): + return if current_voice_idx < 0: select_voice ('default', '') stuff_append (voices, current_voice_idx, a) @@ -619,6 +640,8 @@ # prior to the last space, effectively tagging whatever they are given # onto the last note def voices_append_back(a): + if (not a): + return if current_voice_idx < 0: select_voice ('default', '') stuff_append_back(voices, current_voice_idx, a) @@ -632,9 +655,15 @@ def lyrics_append(a): + if (a == ''): + return a = re.sub ('#', '\\#', a) # latex does not like naked #'s a = re.sub ('"', '\\"', a) # latex does not like naked "'s - a = '\t{ "' + a + '" }\n' + m = re.match('^([^ ]*[0-9"\(][^ ]*) +(.*)$', a) + if m: + a = m.group(2) + stuff_append (lyrics, current_lyric_idx, '}}\\hspace #0.1\n\\line{ \\bold "%s" \column {' % m.group(1)) + a = '\t "' + a + '" \n' stuff_append (lyrics, current_lyric_idx, a) # break lyrics to words and put "'s around words containing numbers and '"'s @@ -646,20 +675,31 @@ word = m.group(1) str = m.group(2) word = re.sub('"', '\\"', word) # escape " - if re.match('.*[0-9"\(]', word): - word = re.sub('_', ' ', word) # _ causes probs inside "" - ret = ret + '\"' + word + '\" ' + m = re.match('^([^_]*[0-9"\(][^_]*)_+(.*)$', word) + if m: + ret = ret + ('\\set stanza = \"%s\"\n %s' % (m.group(1), m.group(2))) + ' ' else: ret = ret + word + ' ' else: return (ret) return (ret) +def count_syl(s): + s = re.sub('\\set stanza = \"[^\"]+\"', '', s) + s = re.sub(' -- ', ' ', s) + s = s.strip(" \t\n") + if (s == ''): + return 0 + a = re.split('[ \t\n]+', s) + return len(a) + def slyrics_append(a): a = re.sub ( '_', ' _ ', a) # _ to ' _ ' - a = re.sub ( '([^-])-([^-])', '\\1- \\2', a) # split words with "-" unless was originally "--" + a = re.sub ( '--', '-', a) # slurs and ties are handled by lilypond + a = re.sub ( '([^-])-([^-])', '\\1 -- \\2', a) # split words with "-" unless was originally "--" a = re.sub ( '\\\\- ', '-', a) # unless \- a = re.sub ( '~', '_', a) # ~ to space('_') + a = re.sub ( ' -- \*', ' -- ', a) # syllables are slured 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 " and ( into quoted string @@ -667,19 +707,87 @@ a = re.sub ( '$', ' ', a) # insure space between lines __main__.lyric_idx = lyric_idx + 1 if len(slyrics[current_voice_idx]) <= lyric_idx: - slyrics[current_voice_idx].append(a) + ns = __main__.state.line_num_syl + slyrics_num_syl[current_voice_idx].append(ns + count_syl(a)) + slyrics[current_voice_idx].append(" _" * ns + ("\n" * min(1,ns)) + ' ' + a) else: - v = slyrics[current_voice_idx][lyric_idx] - slyrics[current_voice_idx][lyric_idx] = wordwrap(a, slyrics[current_voice_idx][lyric_idx]) + l = count_syl(a) + ns = __main__.state.line_num_syl - slyrics_num_syl[current_voice_idx][lyric_idx] + slyrics_num_syl[current_voice_idx][lyric_idx] += ns + l + slyrics[current_voice_idx][lyric_idx] = wordwrap(" _" * ns + ("\n" * min(1,ns)) + ' ' + a, slyrics[current_voice_idx][lyric_idx]) + +def chord_append(a): + chord_flush() + __main__.chord_name = a + +def chord_add_length(num,denum,dots): + n = num + for i in range(dots): + num = num * 2 + n + denum = denum * 2 + a = Fraction(num,denum) + b = Fraction(__main__.chord_length) + __main__.chord_length = a + b + +def conv_chordname(pitch, acc): + c = pitch.lower() + if (acc): + a = re.sub('#', 'is', acc) + a = re.sub('b', 'es', a) + c = c + a + c = re.sub('ees', 'es', c) + return c + +def chord_flush(): + if (__main__.chord_length): + n = 'R' + b = '' + if (__main__.chord_name): + m = re.match('^([A-G])([b#]*)([^b#/][^/]*)?(/([A-G])([b#]*))?$', __main__.chord_name) + if (m): + n = conv_chordname(m.group(1), m.group(2)) + if (m.group(3)): + b = ':' + m.group(3) + if (m.group(4)): + b = '/' + conv_chordname(m.group(5), m.group(6)) + else: + sys.stderr.write("Warning: unknown chord: %s\n" % __main__.chord_name) + l = __main__.chord_length + add = '' + lens = duration_to_lilypond_duration_2(l.numerator, l.denominator) + for k in lens: + stuff_append(chords, 0, '%s%s%s%s' % (add, n, k, b)) + add = '~ ' + __main__.chord_length = 0 + __main__.chord_name = '' +def partial_add_length(num,denum,dots): + if (__main__.state.partial_count < 0): + return + n = num + for i in range(dots): + num = num * 2 + n + denum = denum * 2 + a = Fraction(num,denum) + b = Fraction(__main__.state.partial_count) + __main__.state.partial_count = a + b + +def check_partial(): + if (__main__.state.partial_count < 0): + return + if (__main__.state.partial_count > 0 and __main__.state.time_sig and Fraction(__main__.state.time_sig) != __main__.state.partial_count): + lens = duration_to_lilypond_duration_2(__main__.state.partial_count.numerator, __main__.state.partial_count.denominator) + __main__.state.partial = '%s' % lens[0] + __main__.state.partial_count = -1 def try_parse_header_line (ln, state): global length_specified - m = re.match ('^([A-Za-z]): *(.*)$', ln) + m = re.match ('^([A-Za-z]): *([^%]*)(%.*)?$', ln) if m: g =m.group (1) a = m.group (2) + a = a.strip(' \t\n') if g == 'T': #title a = re.sub('[ \t]*$','', a) #strip trailing blanks if header.has_key('title'): @@ -710,6 +818,7 @@ length_specified = 0 if not a == 'none': voices_append ('\\time %s' % a) + state.time_sig = a state.next_bar = '' if g == 'K': # KEY a = check_clef(a) @@ -744,7 +853,7 @@ if g == 'X': # Reference Number header ['crossRefNumber'] = a if g == 'A': # Area - header ['area'] = a + header ['poet'] = a if g == 'H': # History header_append ('history', a) if g == 'B': # Book @@ -832,6 +941,29 @@ base = '\\longa' return '%s%s' % ( base, '.'* dots) +def duration_to_lilypond_duration_2 (num, denom): + l = Fraction(num,denom) + lens = [] + while l > 0: + base = 1 + while base * l < 1: + base = base * 2 + m = 1 + if base == 1: + if (math.floor(l) > 1): + m = math.floor(l) + dots = 0 + f = 1 + while base * f * m * l > 1 and base * 2 * m * l < 1: + dots += 1 + f = (2 * (1 << dots) -1) + if (m > 1): + lens.append('%d%s*%d' % ( base, '.'* dots, m)) + else: + lens.append('%d%s' % ( base, '.'* dots)) + l -= f * m * 1.0 / base + return lens + class Parser_state: def __init__ (self): self.in_acc = {} @@ -844,6 +976,12 @@ self.base_octave = 0 self.common_time = 0 self.parsing_beam = 0 + self.num_syl = 0 + self.line_num_syl = 0 + self.is_slurring = 0 + self.partial_count = 0 + self.partial = 0 + self.time_sig = '' @@ -862,7 +1000,7 @@ while str[:1] == '/': str= str[1:] d = 2 - if str[0] in DIGITS: + if len(str) > 0 and str[0] in DIGITS: (str, d) =parse_num (str) den = den * d @@ -916,6 +1054,8 @@ (str, num,den,d) = parse_duration (str, parser_state) voices_append ('%s%s' % (rest, duration_to_lilypond_duration ((num,den), default_len, d))) + chord_add_length(num,den,d) + partial_add_length(num,den,d) if parser_state.next_articulation: voices_append (parser_state.next_articulation) parser_state.next_articulation = '' @@ -939,6 +1079,14 @@ } def try_parse_articulation (str, state): + while str[:2] =='.(' and str[2] not in DIGITS: + state.is_slurring = 1 + state.next_articulation = state.next_articulation + '(' + voices_append('\slurDashed') + str = str[2:] + if str[:1] in "',": + str = str[1:] + 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]]: @@ -952,11 +1100,13 @@ if re.match('[ \t]*\(', str): str = str.lstrip () - slur_begin =0 while str[:1] =='(' and str[1] not in DIGITS: - slur_begin = slur_begin + 1 + state.is_slurring = 1 state.next_articulation = state.next_articulation + '(' + voices_append('\slurSolid') str = str[1:] + if str[:1] in "',": + str = str[1:] return str @@ -992,7 +1142,6 @@ def try_parse_note (str, parser_state): mud = '' - slur_begin =0 if not str: return str @@ -1056,6 +1205,10 @@ voices_append ("%s%s%s%s" % (pit, oct, mod, duration_to_lilypond_duration ((num,den), default_len, current_dots))) + chord_add_length(num,den,current_dots) + partial_add_length(num,den,current_dots) + if (state.is_slurring == 0): + parser_state.num_syl += 1 set_bar_acc(notename, octave, acc, parser_state) if parser_state.next_articulation: @@ -1064,10 +1217,10 @@ voices_append (articulation) - if slur_begin: - voices_append ('-(' * slur_begin ) if slur_end: - voices_append ('-)' *slur_end ) + voices_append (')' *slur_end ) + parser_state.num_syl += 1 + state.is_slurring = 0 if parser_state.parsing_tuplet: parser_state.parsing_tuplet = parser_state.parsing_tuplet - 1 @@ -1084,6 +1237,10 @@ return str def junk_space (str,state): + while len(str) > 1 and str[0:2] == 'y0': + str = str[2:] + while str and str[0] == 'y': + str = str[1:] while str and str[0] in '\t\n\r ': str = str[1:] close_beam_state(state) @@ -1106,9 +1263,10 @@ if str: str = str[1:] - gc = re.sub('#', '\\#', gc) # escape '#'s - state.next_articulation = ("%c\"%s\"" % (position, gc)) \ - + state.next_articulation +# gc = re.sub('#', '\\#', gc) # escape '#'s +# state.next_articulation = ("%c\"%s\"" % (position, gc)) \ +# + state.next_articulation + chord_append(gc) return str def try_parse_escape (str): @@ -1149,14 +1307,18 @@ '|:' : '\\repeat volta 2 {', '::' : '} \\repeat volta 2 {', '|1' : '} \\alternative{{', +'[1' : '} \\alternative{{', +'|[1' : '} \\alternative{{', '|2' : '} {', +'[2' : '} {', ':|2' : '} {', -'|' : '\\bar "|"' +':|[2' : '} {', +'|' : '|' } warn_about = ['|:', '::', ':|', '|1', ':|2', '|2'] -alternative_opener = ['|1', '|2', ':|2'] +alternative_opener = ['|1', '[1', '|[1', '|2', ':|2', '[2', ':|[2'] repeat_ender = ['::', ':|'] repeat_opener = ['::', '|:'] in_repeat = [''] * 8 @@ -1170,7 +1332,7 @@ if current_voice_idx < 0: select_voice ('default', '') # first try the longer one - for trylen in [3,2,1]: + for trylen in [4,3,2,1]: if str[:trylen] and bar_dict.has_key (str[:trylen]): s = str[:trylen] if using_old: @@ -1179,9 +1341,13 @@ bs = "%s" % bar_dict[s] str = str[trylen:] if s in alternative_opener: + doing_alternative[current_voice_idx] = 't' if not in_repeat[current_voice_idx]: - using_old = 't' - bs = "\\bar \"%s\"" % old_bar_dict[s] + sys.stderr.write("Warning: inserting repeat to beginning of notes.\n") + repeat_prepend() + in_repeat[current_voice_idx] = 't' +# using_old = 't' +# bs = "\\bar \"%s\"" % old_bar_dict[s] else: doing_alternative[current_voice_idx] = 't' @@ -1224,12 +1390,14 @@ if do_curly != '': voices_append("} ") do_curly = '' + check_partial() return str -def try_parse_tie (str): +def try_parse_tie (str, state): if str[:1] =='-': str = str[1:] voices_append (' ~ ') + state.num_syl -= 1 return str def bracket_escape (str, state): @@ -1291,6 +1459,60 @@ return str +def try_parse_line_break (str, state): + if str[:1] == '$': + str = str[1:] + voices_append ('\\break\n') + return str + +annot_dict = { +'!fine!' : 'Fine', +'!D.C.!' : 'D.C.', +'!D.S.!' : 'D.S.', +'!dsalcoda!' : 'D.S.al Coda', +'!coda!' : 'Coda' +} + +def try_parse_annotations (str, state): + for trylen in [10,6]: + if str[:trylen] and annot_dict.has_key (str[:trylen]): + s = str[:trylen] + str = str[trylen:] + voices_append ('^"%s"' % annot_dict[s]) + break + return str + +dynamic_dict = { +'!ppp!' : '\\ppp', +'!pp!' : '\\pp', +'!p!' : '\\p', +'!mp!' : '\\mp', +'!mf!' : '\\mf', +'!f!' : '\\f', +'!ff!' : '\\ff', +'!fff!' : '\\fff', +'!crescendo(!' : '\\<', +'!crescendo)!' : '\\!', +'!diminuendo(!' : '\\>', +'!diminuendo)!' : '\\!', +'!breath!' : '\\breathe', +'!fermata!' : '^\\fermata', +'!invertedfermata!' : '_\\fermata', +'!1!' : '^"1"', +'!2!' : '^"2"', +'!3!' : '^"3"', +'!4!' : '^"4"', +} + +def try_parse_dynamics (str, state): + for trylen in [17,13,12,9,8,5,4,3]: + if str[:trylen] and dynamic_dict.has_key (str[:trylen]): + s = str[:trylen] + str = str[trylen:] + state.next_articulation = state.next_articulation + dynamic_dict[s] + break + return str + def try_parse_comment (str): global nobarlines if (str[0] == '%'): @@ -1352,6 +1574,8 @@ orig_ln = ln ln = try_parse_header_line (ln, state) + if (ln): + state.line_num_syl = state.num_syl # Try nibbling characters off until the line doesn't change. prev_ln = '' @@ -1362,13 +1586,17 @@ ln = try_parse_articulation (ln,state) ln = try_parse_note (ln, state) ln = try_parse_bar (ln, state) - ln = try_parse_tie (ln) + ln = try_parse_tie (ln, state) ln = try_parse_escape (ln) ln = try_parse_guitar_chord (ln, state) ln = try_parse_tuplet_begin (ln, state) ln = try_parse_group_end (ln, state) ln = try_parse_grace_delims (ln, state) + ln = try_parse_line_break (ln, state) + ln = try_parse_annotations (ln, state) + ln = try_parse_dynamics (ln, state) ln = junk_space (ln, state) + voices_append ('\n') if ln: error ("%s: %d: Huh? Don't understand\n" % (fn, lineno)) @@ -1438,12 +1666,13 @@ # don't substitute @VERSION@. We want this to reflect # the last version that was verified to work. - outf.write ('\\version "2.7.40"\n') + outf.write ('\\version "2.14.2"\n') # dump_global (outf) dump_header (outf, header) dump_slyrics (outf) dump_voices (outf) + dump_chords (outf) dump_score (outf) dump_lyrics (outf) sys.stderr.write ('\n')
_______________________________________________ lilypond-devel mailing list lilypond-devel@gnu.org https://lists.gnu.org/mailman/listinfo/lilypond-devel