[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