[prev in list] [next in list] [prev in thread] [next in thread] 

List:       lilypond-devel
Subject:    Fwd: abc2ly Patch
From:       Reinhold Kainhofer <reinhold () kainhofer ! com>
Date:       2012-08-09 15:42:25
Message-ID: 5023DA61.50600 () kainhofer ! com
[Download RAW message or body]

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.rempke@tu-clausthal.de>
To: Reinhold Kainhofer <reinhold@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 =C4nderung d=FCrfte die Behandlung von Akkorden sein, die n=
un
als \chordmode und nicht mehr als hochgestellte Anmerkung verarbeitet
werden. Au=DFerdem wird die Taktz=E4hlung wieder lilypond =FCberlassen un=
d
Auftakte versucht zu erkennen. Tats=E4chlich sind auch ein paar =C4nderun=
gen
drin, die zumindest diskussionw=FCrdig 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 =C4nderungen nur auf meiner Platte verk=FCmmern
versuch ich sie doch noch zu teilen -- hab aber zugegeben grad nicht den
Nerv mich mit dem Repository/Ticketsystem zu besch=E4ftigen 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=F6s drum. Der Patch ist gegen das Skript einer
Lilypond 2.14.2-Installation erstellt worden.

Danke und viele Gr=FC=DFe aus dem Harz,
Arne




["abc2ly.patch" (text/x-patch)]

--- /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')



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic