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

Reply via email to