]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/etf2ly.py
release: 1.5.29
[lilypond.git] / scripts / etf2ly.py
index 7a99aeeb6d602aded24d0b6ab631c7f34cc3536a..4219f1d8df054b35bdfa51bf31e58c7effe756ef 100644 (file)
@@ -1,28 +1,30 @@
 #!@PYTHON@
 
-# info taken from
+# info mostly taken from looking at files. See also
+# http://lilypond.org/wiki/?EnigmaTransportFormat
 
-#  * convertmusic (see sourceforge)
-#  * Margaret Cahill's thesis
-#  * http://www.codamusic.com/coda/Fin2000_pdk_download.asp (you have
-#    to register, but you don't need to have bought a code product
-
-#
 # This supports
-
 #
 #  * notes
 #  * rests
 #  * ties
 #  * slurs
+#  * lyrics
+#  * articulation
+#  * grace notes
+#  * tuplets
 #
 
 # todo:
 #  * slur/stem directions
-#  * articulation
 #  * voices (2nd half of frame?)
-#  * lyrics
+#  * more intelligent lyrics
 #  * beams (better use autobeam?)
+#  * more robust: try entertainer.etf (freenote)
+#  * dynamics
+#  * empty measures (eg. twopt03.etf from freenote)
+#
+
 
 program_name = 'etf2ly'
 version = '@TOPLEVEL_VERSION@'
@@ -36,13 +38,17 @@ import re
 import string
 import os
 
-
-
-finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble8vb', 'bass8vb', 'baritone']
-
+finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble_8', 'bass_8', 'baritone']
 
 def lily_clef (fin):
-       return finale_clefs[fin]
+       try:
+               return finale_clefs[fin]
+       except IndexError:
+               sys.stderr.write ( '\nHuh? Found clef number %d\n' % fin)
+
+       return 'treble'
+       
+       
 
 def gulp_file(f):
        return open (f).read ()
@@ -52,6 +58,7 @@ distances = [0, 2, 4, 5, 7, 9, 11, 12]
 def semitones (name, acc):
        return (name / 7 ) * 12 + distances[name % 7] + acc
 
+# represent pitches as (notename, alteration), relative to C-major scale
 def transpose(orig, delta):
        (oname, oacc) = orig
        (dname, dacc) = delta
@@ -89,6 +96,36 @@ def find_scale (transposition):
        trscale = map(lambda x, k=transposition: transpose(x, k), cscale)
 
        return trscale
+def EDU_to_duration (edu):
+       log = 1
+       d = 4096
+       while d > edu:
+               d = d >> 1
+               log = log << 1
+
+       edu = edu - d
+       dots = 0
+       if edu == d /2:
+               dots = 1
+       elif edu == d*3/4:
+               dots = 2
+       return (log, dots)      
+
+def rat_to_lily_duration (rat):
+       (n,d) = rat
+
+       basedur = 1
+       while d and  d % 2 == 0:
+               basedur = basedur << 1
+               d = d >> 1
+
+       str = 's%d' % basedur
+       if n <> 1:
+               str = str + '*%d' % n
+       if d <> 1:
+               str = str + '/%d' % d
+
+       return str
 
 def gcd (a,b):
        if b == 0:
@@ -128,6 +165,8 @@ def rat_neg (a):
        (p,q) = a
        return (-p,q)
 
+
+
 def rat_subtract (a,b ):
        return rat_add (a, rat_neg (b))
 
@@ -135,33 +174,70 @@ def lily_notename (tuple2):
        (n, a) = tuple2
        nn = chr ((n+ 2)%7 + ord ('a'))
 
-       if a == -1:
-               nn = nn + 'es'
-       elif a == -2:
-               nn = nn + 'eses'
-       elif a == 1:
-               nn = nn + 'is'
-       elif a == 2:
-               nn = nn + 'isis'
+       return nn + {-2:'eses', -1:'es', 0:'', 1:'is', 2:'isis'}[a]
 
-       return nn
 
-class Slur:
+class Tuplet:
        def __init__ (self, number):
-               self.number = number
+               self.start_note = number
                self.finale = []
 
+       def append_finale (self, fin):
+               self.finale.append (fin)
+
+       def factor (self):
+               n = self.finale[0][2]*self.finale[0][3]
+               d = self.finale[0][0]*self.finale[0][1]
+               return rat_simplify( (n, d))
+       
+       def dump_start (self):
+               return '\\times %d/%d { ' % self.factor ()
+       
+       def dump_end (self):
+               return ' }'
+
+       def calculate (self, chords):
+               edu_left = self.finale[0][0] * self.finale[0][1]
+
+               startch = chords[self.start_note]
+               c = startch
+               while c and edu_left:
+                       c.tuplet = self
+                       if c == startch:
+                               c.chord_prefix = self.dump_start () + c.chord_prefix 
+
+                       if not c.grace:
+                               edu_left = edu_left - c.EDU_duration ()
+                       if edu_left == 0:
+                               c.chord_suffix = c.chord_suffix+ self.dump_end ()
+                       c = c.next
+
+               if edu_left:
+                       sys.stderr.write ("\nHuh? Tuplet starting at entry %d was too short." % self.start_note)
+               
+class Slur:
+       def __init__ (self, number, params):
+               self.number = number
+               self.finale = params
+
        def append_entry (self, finale_e):
                self.finale.append (finale_e)
 
        def calculate (self, chords):
-               startnote = self.finale[0][5]
-               endnote = self.finale[3][2]
-
-               cs = chords[startnote]
-               cs.suffix = '(' + cs.suffix 
-               ce = chords[endnote]
-               ce.prefix = ce.prefix + ')'
+               startnote = self.finale[5]
+               endnote = self.finale[3*6 + 2]
+               try:
+                       cs = chords[startnote]
+                       ce = chords[endnote]
+
+                       if not cs or not ce:
+                               raise IndexError
+                       
+                       cs.note_suffix = '(' + cs.note_suffix 
+                       ce.note_prefix = ce.note_prefix + ')'
+               except IndexError:
+                       sys.stderr.write ("""\nHuh? Slur no %d between (%d,%d), with %d notes""" % (self.number,  startnote, endnote, len (chords)))
+                                        
                
 class Global_measure:
        def __init__ (self, number):
@@ -169,13 +245,19 @@ class Global_measure:
                self.number = number
                self.keysignature = None
                self.scale = None
-
+               self.force_break = 0
+               
+               self.repeats = []
                self.finale = []
 
+       def __str__ (self):
+               return `self.finale `
+       
        def set_timesig (self, finale):
                (beats, fdur) = finale
                (log, dots) = EDU_to_duration (fdur)
-               assert dots == 0
+               if dots <> 0:
+                       sys.stderr.write ("\nHuh? Beat duration has a dot? (EDU Duration = %d)" % fdur) 
                self.timesig = (beats, log)
 
        def length (self):
@@ -186,29 +268,132 @@ class Global_measure:
                self.keysignature = k
                self.scale = find_scale (k)
 
+       def set_flags (self,flag1, flag2):
+               
+               # flag1 isn't all that interesting.
+               if flag2 & 0x8000:
+                       self.force_break = 1
+                       
+               if flag2 & 0x0008:
+                       self.repeats.append ('start')
+               if flag2 & 0x0004:
+                       self.repeats.append ('stop')
+                       
+               if flag2 & 0x0002:
+                       if flag2 & 0x0004:
+                               self.repeats.append ('bracket')
+
+articulation_dict ={
+       94: '^',
+       109: '\\prall',
+       84: '\\turn',
+       62: '\\mordent',
+       85: '\\fermata',
+       46: '.',
+#      3: '>',
+#      18: '\arpeggio' ,
+}
+
+class Articulation_def:
+       def __init__ (self, n, a, b):
+               self.finale_glyph = a & 0xff
+               self.number = n
+
+       def dump (self):
+               try:
+                       return articulation_dict[self.finale_glyph]
+               except KeyError:
+                       sys.stderr.write ("\nUnknown articulation no. %d" % self.finale_glyph)
+                       sys.stderr.write ("\nPlease add an entry to articulation_dict in the Python source")                    
+                       return None
+       
+class Articulation:
+       def __init__ (self, a,b, finale):
+               self.definition = finale[0]
+               self.notenumber = b
+               
+       def calculate (self, chords, defs):
+               c = chords[self.notenumber]
+
+               adef = defs[self.definition]
+               lystr =adef.dump()
+               if lystr == None:
+                       lystr = '"art"'
+                       sys.stderr.write ("\nThis happened on note %d" % self.notenumber)
+
+               c.note_suffix = '-' + lystr
+
+class Syllable:
+       def __init__ (self, a,b , finale):
+               self.chordnum = b
+               self.syllable = finale[1]
+               self.verse = finale[0]
+       def calculate (self, chords, lyrics):
+               self.chord = chords[self.chordnum]
+
+class Verse:
+       def __init__ (self, number, body):
+               self.body = body
+               self.number = number
+               self.split_syllables ()
+       def split_syllables (self):
+               ss = re.split ('(-| +)', self.body)
+
+               sep = 0
+               syls = [None]
+               for s in ss:
+                       if sep:
+                               septor = re.sub (" +", "", s)
+                               septor = re.sub ("-", " -- ", septor) 
+                               syls[-1] = syls[-1] + septor
+                       else:
+                               syls.append (s)
+                       
+                       sep = not sep 
 
+               self.syllables = syls
 
+       def dump (self):
+               str = ''
+               line = ''
+               for s in self.syllables[1:]:
+                       line = line + ' ' + s
+                       if len (line) > 72:
+                               str = str + ' ' * 4 + line + '\n'
+                               line = ''
+                       
+               str = """\nverse%s = \\lyrics {\n %s}\n""" %  (encodeint (self.number - 1) ,str)
+               return str
+
+       
 class Measure:
        def __init__(self, no):
                self.number = no
-               self.frames = []
+               self.frames = [0] * 4
                self.flags = 0
                self.clef = 0
                self.finale = []
                self.global_measure = None
+               self.staff = None
+               self.valid = 1
                
-       def add_finale_entry (self, entry):
-               self.finale.append (entry)
 
+       def valid (self):
+               return self.valid
        def calculate (self):
-               f0  = self.finale[0]
-               f1 = self.finale[1]
-               
-               self.clef = string.atoi (f0[0])
-               self.flags = string.atoi (f0[1])
-               fs = map (string.atoi, list (f0[2:]) + [f1[0]])
+               fs = []
+
+               if len (self.finale) < 2:
+                       fs = self.finale[0]
+
+                       self.clef = fs[1]
+                       self.frames = [fs[0]]
+               else:
+                       fs = self.finale
+                       self.clef = fs[0]
+                       self.flags = fs[1]
+                       self.frames = fs[2:]
 
-               self.frames = fs
 
 class Frame:
        def __init__ (self, finale):
@@ -223,38 +408,64 @@ class Frame:
        def set_measure (self, m):
                self.measure = m
 
+       def calculate (self):
+
+               # do grace notes.
+               lastch = None
+               for c in self.chords:
+                       if c.grace and (lastch == None or (not lastch.grace)):
+                               c.chord_prefix = r'\grace {' + c.chord_prefix
+                       elif not c.grace and lastch and lastch.grace:
+                               lastch.chord_suffix = lastch.chord_suffix + ' } '
+
+                       lastch = c
+                       
+
+               
        def dump (self):
-               str = ''
+               str = '%% FR(%d)\n' % self.number
                left = self.measure.global_measure.length ()
+
+               
+               ln = ''
                for c in self.chords:
-                       str = str + c.ly_string () + ' '
+                       add = c.ly_string () + ' '
+                       if len (ln) + len(add) > 72:
+                               str = str + ln + '\n'
+                               ln = ''
+                       ln = ln + add
                        left = rat_subtract (left, c.length ())
+
+               str = str + ln 
+               
                if left[0] < 0:
-                       print self.number
-                       print self.start, self.end
-                       print left
-                       raise 'bla'
+                       sys.stderr.write ("""\nHuh? Going backwards in frame no %d, start/end (%d,%d)""" % (self.number, self.start, self.end))
+                       left = (0,1)
                if left[0]:
-                       str = str + 's*%d/%d' % left
-                       
-               str = str + '\n'
+                       str = str + rat_to_lily_duration (left)
+
+               str = str + '  | \n'
                return str
                
+def encodeint (i):
+       return chr ( i  + ord ('A'))
+
 class Staff:
        def __init__ (self, number):
                self.number = number
                self.measures = []
 
        def get_measure (self, no):
-               if len (self.measures) <= no:
-                       self.measures = self.measures + [None]* (1 + no - len (self.measures))
+               fill_list_to (self.measures, no)
 
                if self.measures[no] == None:
-                       self.measures [no] = Measure (no)
+                       m = Measure (no)
+                       self.measures [no] =m
+                       m.staff = self
 
                return self.measures[no]
        def staffid (self):
-               return 'staff%s'% chr (self.number - 1 +ord ('A'))
+               return 'staff' + encodeint (self.number - 1)
        def layerid (self, l):
                return self.staffid() +  'layer%s' % chr (l -1 + ord ('A'))
        
@@ -265,25 +476,54 @@ class Staff:
                last_clef = None
                gap = (0,1)
                for m in self.measures[1:]:
+                       if not m or not m.valid:
+                               continue # ugh.
+                       
                        g = m.global_measure
                        e = ''
-                       if last_key <> g.keysignature:
-                               e = e + "\\key %s \\major; " % lily_notename (g.keysignature)
-                               last_key = g.keysignature
-                       if last_time <> g.timesig :
-                               e = e + "\\time %d/%d; " % g.timesig
-                               last_time = g.timesig
+                       
+                       if g:
+                               if last_key <> g.keysignature:
+                                       e = e + "\\key %s \\major " % lily_notename (g.keysignature)
+                                       last_key = g.keysignature
+                               if last_time <> g.timesig :
+                                       e = e + "\\time %d/%d " % g.timesig
+                                       last_time = g.timesig
+
+                               if 'start' in g.repeats:
+                                       e = e + ' \\bar "|:" ' 
+
+
+                               # we don't attempt voltas since they fail easily.
+                               if 0 : # and g.repeat_bar == '|:' or g.repeat_bar == ':|:' or g.bracket:
+                                       strs = []
+                                       if g.repeat_bar == '|:' or g.repeat_bar == ':|:' or g.bracket == 'end':
+                                               strs.append ('#f')
+
+                                       
+                                       if g.bracket == 'start':
+                                               strs.append ('"0."')
+
+                                       str = string.join (map (lambda x: '(volta %s)' % x, strs))
+                                       
+                                       e = e + ' \\property Score.repeatCommands =  #\'(%s) ' % str
+
+                               if g.force_break:
+                                       e = e + ' \\break '  
+                       
                        if last_clef <> m.clef :
-                               e = e + '\\clef %s;' % lily_clef (m.clef)
+                               e = e + '\\clef "%s"' % lily_clef (m.clef)
                                last_clef = m.clef
                        if e:
                                if gap <> (0,1):
-                                       k = k +' s1*%d/%d \n ' % gap
+                                       k = k +' ' + rat_to_lily_duration (gap) + '\n'
                                gap = (0,1)
                                k = k + e
-                       
-                       gap = rat_add (gap, g.length ())
-
+                               
+                       if g:
+                               gap = rat_add (gap, g.length ())
+                               if 'stop' in g.repeats:
+                                       k = k + ' \\bar ":|" '
                                
                k = '%sglobal = \\notes  { %s }\n\n ' % (self.staffid (), k)
                return k
@@ -299,16 +539,29 @@ class Staff:
                        first_frame = None
                        gap = (0,1)
                        for m in self.measures[1:]:
-                               fr = m.frames[x]
+                               if not m or not m.valid:
+                                       sys.stderr.write ("Skipping non-existant or invalid measure\n")
+                                       continue
+
+                               fr = None
+                               try:
+                                       fr = m.frames[x]
+                               except IndexError:
+                                       sys.stderr.write ("Skipping nonexistent frame %d\n" % x)
+                                       laystr = laystr + "%% non existent frame %d (skipped) \n" % x
                                if fr:
                                        first_frame = fr
                                        if gap <> (0,1):
-                                               laystr = laystr +'} s1*%d/%d {\n ' % gap
+                                               laystr = laystr +'} %s {\n ' % rat_to_lily_duration (gap)
                                                gap = (0,1)
                                        laystr = laystr + fr.dump ()
                                else:
-                                       gap = rat_add (gap, m.global_measure.length ())
-
+                                       if m.global_measure :
+                                               gap = rat_add (gap, m.global_measure.length ())
+                                       else:
+                                               sys.stderr.write ( \
+                                                       "No global measure for staff %d measure %d\n"
+                                                       % (self.number, m.number))
                        if first_frame:
                                l = self.layerid (x)
                                laystr = '%s =  \\notes { { %s } }\n\n' % (l, laystr)
@@ -326,65 +579,83 @@ class Staff:
                return str
 
                                
-def EDU_to_duration (edu):
-       log = 1
-       d = 4096
-       while d > edu:
-               d = d >> 1
-               log = log << 1
 
-       edu = edu - d
-       dots = 0
-       if edu == d /2:
-               dots = 1
-       elif edu == d*3/4:
-               dots = 2
-       return (log, dots)      
+def ziplist (l):
+       if len (l) < 2:
+               return []
+       else:
+               return [(l[0], l[1])] + ziplist (l[2:])
+
 
 class Chord:
-       def __init__ (self, finale_entry):
+       def __init__ (self, number, contents):
                self.pitches = []
                self.frame = None
-               self.finale = finale_entry
+               self.finale = contents[:7]
+
+               self.notelist = ziplist (contents[7:])
                self.duration  = None
                self.next = None
                self.prev = None
-               self.prefix= ''
-               self.suffix = ''
+               self.number = number
+               self.note_prefix= ''
+               self.note_suffix = ''
+               self.chord_suffix = ''
+               self.chord_prefix = ''
+               self.tuplet = None
+               self.grace = 0
+               
        def measure (self):
                if not self.frame:
                        return None
                return self.frame.measure
 
        def length (self):
+               if self.grace:
+                       return (0,1)
+               
                l = (1, self.duration[0])
 
                d = 1 << self.duration[1]
 
                dotfact = rat_subtract ((2,1), (1,d))
-               return rat_multiply (dotfact, l)
+               mylen =  rat_multiply (dotfact, l)
+
+               if self.tuplet:
+                       mylen = rat_multiply (mylen, self.tuplet.factor())
+               return mylen
                
-       def number (self):
-               return self.finale[0][0]
+
+       def EDU_duration (self):
+               return self.finale[2]
        def set_duration (self):
-               ((no, prev, next, dur, pos, entryflag, extended, follow),
-                notelist) = self.finale
-               self.duration = EDU_to_duration(dur)
-       def find_realpitch (self):
+               self.duration = EDU_to_duration(self.EDU_duration ())
+               
+       def calculate (self):
+               self.find_realpitch ()
+               self.set_duration ()
+
+               flag = self.finale[4]
+               if Chord.GRACE_MASK & flag:
+                       self.grace = 1
                
-               ((no, prev, next, dur, pos, entryflag, extended, follow), notelist) = self.finale
+       
+       def find_realpitch (self):
 
                meas = self.measure ()
                tiestart = 0
                if not meas or not meas.global_measure  :
-                       print 'note %d not in measure' % self.number ()
+                       sys.stderr.write ('note %d not in measure\n' % self.number)
                elif not meas.global_measure.scale:
-                       print  'note %d: no scale in this measure.' % self.number ()
+                       sys.stderr.write ('note %d: no scale in this measure.' % self.number)
                else:
-                       for p in notelist:
+                       
+                       for p in self.notelist:
                                (pitch, flag) = p
-                               
+
+
                                nib1 = pitch & 0x0f
+                               
                                if nib1 > 8:
                                        nib1 = -(nib1 - 8)
                                rest = pitch / 16
@@ -396,16 +667,19 @@ class Chord:
                                self.pitches.append ((sn, acc))
                                tiestart =  tiestart or (flag & Chord.TIE_START_MASK)
                if tiestart :
-                       self.suffix = self.suffix + ' ~ '
+                       self.chord_suffix = self.chord_suffix + ' ~ '
                
        REST_MASK = 0x40000000L
        TIE_START_MASK = 0x40000000L
+       GRACE_MASK = 0x00800000L
+       
        def ly_string (self):
                s = ''
 
                rest = ''
 
-               if not (self.finale[0][5] & Chord.REST_MASK):
+
+               if not (self.finale[4] & Chord.REST_MASK):
                        rest = 'r'
                
                for p in self.pitches:
@@ -427,39 +701,151 @@ class Chord:
                                nn = rest
                                
                        s = s + '%s%d%s' % (nn, self.duration[0], '.'* self.duration[1])
-                       
+
+               if not self.pitches:
+                       s  = 'r%d%s' % (self.duration[0] , '.'* self.duration[1])
+               s = self.note_prefix + s + self.note_suffix
                if len (self.pitches) > 1:
                        s = '<%s>' % s
-               elif not self.pitches:
-                       s  = 'r%d%s' % (self.duration[0] , '.'* self.duration[1])
-
-               s = self.prefix + s + self.suffix
+               
+               s = self.chord_prefix + s + self.chord_suffix
                return s
 
-GFre = re.compile(r"""^\^GF\(([0-9-]+),([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
-BCre = re.compile (r"""^\^BC\(([0-9-]+)\) ([0-9-]+) .*$""")
-eEre = re.compile(r"""^\^eE\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) \$([0-9A-Fa-f]+) ([0-9-]+) ([0-9-]+)""")
-FRre = re.compile (r"""^\^FR\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
-MSre = re.compile (r"""^\^MS\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
-note_re = re.compile (r"""^ +([0-9-]+) \$([A-Fa-f0-9]+)""")
-Sxre  = re.compile (r"""^\^Sx\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
+
+def fill_list_to (list, no):
+       """
+Add None to LIST until it contains entry number NO.
+       """
+       while len (list) <= no:
+               list.extend ([None] * (no - len(list) + 1))
+       return list
+
+def read_finale_value (str):
+       """
+Pry off one value from STR. The value may be $hex, decimal, or "string".
+Return: (value, rest-of-STR)
+       """
+       while str and str[0] in ' \t\n':
+               str = str[1:]
+
+       if not str:
+               return (None,str)
+       
+       if str[0] == '$':
+               str = str [1:]
+
+               hex = ''
+               while str and str[0] in '0123456789ABCDEF':
+                       hex = hex  + str[0]
+                       str = str[1:]
+
+               
+               return (string.atol (hex, 16), str)
+       elif str[0] == '"':
+               str = str[1:]
+               s = ''
+               while str and str[0] <> '"':
+                       s = s + str[0]
+                       str = str[1:]
+
+               return (s,str)
+       elif str[0] in '-0123456789':
+               dec = ''
+               while str and str[0] in '-0123456789':
+                       dec = dec  + str[0]
+                       str = str[1:]
+                       
+               return (string.atoi (dec), str)
+       else:
+               sys.stderr.write ("Can't convert `%s'\n" % str)
+               return (None, str)
+
+
+
+       
+def parse_etf_file (fn, tag_dict):
+
+       """ Read FN, putting ETF info into
+       a giant dictionary.  The keys of TAG_DICT indicate which tags
+       to put into the dict.
+       """
+       
+       sys.stderr.write ('parsing ... ' )
+       f = open (fn)
+       
+       gulp = re.sub ('[\n\r]+', '\n',  f.read ())
+       ls = string.split (gulp, '\n^')
+
+       etf_file_dict = {}
+       for k in tag_dict.keys (): 
+               etf_file_dict[k] = {}
+
+       last_tag = None
+       last_numbers = None
+
+
+       for l in  ls:
+               m = re.match ('^([a-zA-Z0-9&]+)\(([^)]+)\)', l)
+               if m and tag_dict.has_key (m.group (1)):
+                       tag = m.group (1)
+
+                       indices = tuple (map (string.atoi, string.split (m.group (2), ',')))
+                       content = l[m.end (2)+1:]
+
+
+                       tdict = etf_file_dict[tag]
+                       if not tdict.has_key (indices):
+                               tdict[indices] = []
+
+
+                       parsed = []
+
+                       if tag == 'verse' or tag == 'block':
+                               m2 = re.match ('(.*)\^end', content)
+                               if m2:
+                                       parsed = [m2.group (1)]
+                       else:
+                               while content:
+                                       (v, content) = read_finale_value (content)
+                                       if v <> None:
+                                               parsed.append (v)
+
+                       tdict [indices].extend (parsed)
+
+                       last_indices = indices
+                       last_tag = tag
+
+                       continue
+
+# let's not do this: this really confuses when eE happens to be before  a ^text.
+#              if last_tag and last_indices:
+#                      etf_file_dict[last_tag][last_indices].append (l)
+                       
+       sys.stderr.write ('\n') 
+       return etf_file_dict
+
+       
+
+
 
 class Etf_file:
        def __init__ (self, name):
                self.measures = [None]
-               self.entries = [None]
                self.chords = [None]
                self.frames = [None]
+               self.tuplets = [None]
                self.staffs = [None]
                self.slurs = [None]
+               self.articulations = [None]
+               self.syllables = [None]
+               self.verses = [None]
+               self.articulation_defs = [None]
 
                ## do it
                self.parse (name)
 
        def get_global_measure (self, no):
-               if len (self.measures) <= no:
-                       self.measures = self.measures + [None]* (1 + no - len (self.measures))
-
+               fill_list_to (self.measures, no)
                if self.measures[no] == None:
                        self.measures [no] = Global_measure (no)
 
@@ -467,115 +853,124 @@ class Etf_file:
 
                
        def get_staff(self,staffno):
-               if len (self.staffs) <= staffno:
-                       self.staffs = self.staffs + [None] * (1 + staffno - len (self.staffs))
-
+               fill_list_to (self.staffs, staffno)
                if self.staffs[staffno] == None:
                        self.staffs[staffno] = Staff (staffno)
 
                return self.staffs[staffno]
 
        # staff-spec
-       def try_IS (self, l):
+       def try_IS (self, indices, contents):
                pass
 
-       def try_BC (self, l):
-               m =  BCre.match  (l)
-               if m:
-                       bn = string.atoi (m.group (1))
-                       where = string.atoi (m.group (2)) / 1024.0
-               return m
-
-       def try_eE (self, l):
-               m = eEre.match (l)
-               if m:
-                       tup = m.groups()
-                       (no, prev, next, dur, pos, entryflag, extended, follow) = tup
-                       (no, prev, next, dur, pos,extended, follow) \
-                         = tuple (map (string.atoi, [no,prev,next,dur,pos,extended,follow]))
-
-                       entryflag = string.atol (entryflag,16)
-                       assert (no==len (self.entries))
-                       current_entry = ((no, prev, next, dur, pos, entryflag, extended, follow), [])
-                       self.entries.append (current_entry)
-               return m
-
-       def try_Sx(self,l):
-               m = Sxre.match (l)
-               if m:
-                       slurno = string.atoi (m.group (1))
-
-                       if len (self.slurs) == slurno:
-                               self.slurs.append (Slur (slurno))
-
-                       params = list (m.groups ()[1:])
-                       params = map (string.atoi, params)
-                       self.slurs[-1].append_entry (params)
-
-               return m        
-       def try_GF(self, l):
-               m = GFre.match (l)
-               if m:
-                       (staffno,measno) = m.groups ()[0:2]
-                       s = string.atoi (staffno)
-                       me = string.atoi (measno)
-                       
-                       entry = m.groups () [2:]
-                       st = self.get_staff (s)
-                       meas = st.get_measure (me)
-                       meas.add_finale_entry (entry)
+       def try_BC (self, indices, contents):
+               bn = indices[0]
+               where = contents[0] / 1024.0
+       def try_TP(self,  indices, contents):
+               (nil, num) = indices
+
+               if self.tuplets[-1] == None or num <> self.tuplets[-1].start_note:
+                       self.tuplets.append (Tuplet (num))
+
+               self.tuplets[-1].append_finale (contents)
+
+       def try_IM (self, indices, contents):
+               (a,b) = indices
+               fin = contents
+               self.articulations.append (Articulation (a,b,fin))
+       def try_verse (self, indices, contents):
+               a = indices[0]
+               body = contents[0]
+
+               body = re.sub (r"""\^[a-z]+\([^)]+\)""", "", body)
+               body = re.sub ("\^[a-z]+", "", body)
+               self.verses.append (Verse (a, body))
+       def try_ve (self,indices, contents):
+               (a,b) = indices
+               self.syllables.append (Syllable (a,b,contents))
+
+       def try_eE (self,indices, contents):
+               no = indices[0]
+               (prev, next, dur, pos, entryflag, extended, follow) = contents[:7]
+
+               fill_list_to (self.chords, no)
+               self.chords[no]  =Chord (no, contents)
+
+       def try_Sx(self,indices, contents):
+               slurno = indices[0]
+               fill_list_to (self.slurs, slurno)
+               self.slurs[slurno] = Slur(slurno, contents)
+
+       def try_IX (self, indices, contents):
+               n = indices[0]
+               a = contents[0]
+               b = contents[1]
+
+               ix= None
+               try:
+                       ix = self.articulation_defs[n]
+               except IndexError:
+                       ix = Articulation_def (n,a,b)
+                       self.articulation_defs.append (Articulation_def (n, a, b))
+
+       def try_GF(self, indices, contents):
+               (staffno,measno) = indices
+
+               st = self.get_staff (staffno)
+               meas = st.get_measure (measno)
+               meas.finale = contents
                
-       # frame  ?
-       def try_FR(self, l):
-               m = FRre.match (l)
-               if m:
-                       (frameno, startnote, endnote, foo, bar) = m.groups ()
-                       (frameno, startnote, endnote)  = tuple (map (string.atoi, [frameno, startnote, endnote]))
-                       self.frames.append (Frame ((frameno, startnote, endnote)))
-                       
-               return m
-       def try_MS (self, l):
-               m = MSre.match (l)
-               if m:
-                       measno = string.atoi (m.group (1))
-                       keynum = string.atoi (m.group (3))
-                       meas =self. get_global_measure (measno)
-                       meas.set_keysig (keynum)
-
-                       beats = string.atoi (m.group (4))
-                       beatlen = string.atoi (m.group (5))
-                       meas.set_timesig ((beats, beatlen))
-                                               
-               return m
-
-       def try_note (self, l):
-               m = note_re.match (l)
-               if m:
-                       (pitch, flag) = m.groups ()
-                       pitch = string.atoi (pitch)
-                       flag = string.atol (flag,16)
-                       self.entries[-1][1].append ((pitch,flag))
-
-       def parse (self, name):
-               sys.stderr.write ('parsing ...')
-               sys.stderr.flush ()
+       def try_FR(self, indices, contents):
+               frameno = indices [0]
                
-               ls = open (name).readlines ()
-               for l in ls:
-                       m = None
-                       if not m: 
-                               m = self.try_MS (l)
-                       if not m: 
-                               m = self.try_FR (l)
-                       if not m: 
-                               m = self.try_GF (l)
-                       if not m: 
-                               m = self.try_note (l)
-                       if not m: 
-                               m = self.try_eE (l)
-                       if not m:
-                               m = self.try_Sx (l)
+               startnote = contents[0]
+               endnote = contents[1]
 
+               fill_list_to (self.frames, frameno)
+       
+               self.frames[frameno] = Frame ((frameno, startnote, endnote))
+       
+       def try_MS (self, indices, contents):
+               measno = indices[0]
+               keynum = contents[1]
+               meas =self. get_global_measure (measno)
+               meas.set_keysig (keynum)
+
+               beats = contents[2]
+               beatlen = contents[3]
+               meas.set_timesig ((beats, beatlen))
+
+               meas_flag1 = contents[4]
+               meas_flag2 = contents[5]
+
+               meas.set_flags (meas_flag1, meas_flag2);
+
+
+       routine_dict = {
+               'MS': try_MS,
+               'FR': try_FR,
+               'GF': try_GF,
+               'IX': try_IX,
+               'Sx' : try_Sx,
+               'eE' : try_eE,
+               'verse' : try_verse,
+               've' : try_ve,
+               'IM' : try_IM,
+               'TP' : try_TP,
+               'BC' : try_BC,
+               'IS' : try_IS,
+               }
+       
+       def parse (self, etf_dict):
+               sys.stderr.write ('reconstructing ...')
+               sys.stderr.flush ()
+
+               for (tag,routine) in Etf_file.routine_dict.items ():
+                       ks = etf_dict[tag].keys ()
+                       ks.sort ()
+                       for k in ks:
+                               routine (self, k, etf_dict[tag][k])
+                       
                sys.stderr.write ('processing ...')
                sys.stderr.flush ()
 
@@ -586,13 +981,23 @@ class Etf_file:
                                continue
                        mno = 1
                        for m in st.measures[1:]:
-                               m.global_measure = self.measures[mno]
+                               if not m:
+                                       continue
+                               
                                m.calculate()
-
+                               try:
+                                       m.global_measure = self.measures[mno]
+                               except IndexError:
+                                       sys.stderr.write ("Non-existent global measure %d" % mno)
+                                       continue
+                               
                                frame_obj_list = [None]
                                for frno in m.frames:
-                                       fr = self.frames[frno]
-                                       frame_obj_list.append (fr)
+                                       try:
+                                               fr = self.frames[frno]
+                                               frame_obj_list.append (fr)
+                                       except IndexError:
+                                               sys.stderr.write ("\nNon-existent frame %d"  % frno)
 
                                m.frames = frame_obj_list
                                for fr in frame_obj_list[1:]:
@@ -607,17 +1012,36 @@ class Etf_file:
                                mno = mno + 1
 
                for c in self.chords[1:]:
-                       c.find_realpitch ()
-                       c.set_duration ()
+                       if c:
+                               c.calculate()
+
+               for f in self.frames[1:]:
+                       if f:
+                               f.calculate ()
                        
-               for s in self.slurs [1:]:
-                       s.calculate (self.chords)
+               for t in self.tuplets[1:]:
+                       t.calculate (self.chords)
+                       
+               for s in self.slurs[1:]:
+                       if s:
+                               s.calculate (self.chords)
+                       
+               for s in self.articulations[1:]:
+                       s.calculate (self.chords, self.articulation_defs)
                        
        def get_thread (self, startno, endno):
 
                thread = []
-               c = self.chords[startno]
-               while c and c.number () <> endno:
+
+               c = None
+               try:
+                       c = self.chords[startno]
+               except IndexError:
+                       sys.stderr.write ("Huh? Frame has invalid bounds (%d,%d)\n" % (startno, endno))
+                       return []
+
+               
+               while c and c.number <> endno:
                        thread.append (c)
                        c = c.next
 
@@ -634,53 +1058,63 @@ class Etf_file:
                                str = str + '\n\n' + s.dump () 
                                staffs.append ('\\' + s.staffid ())
 
+
+               # should use \addlyrics ?
+
+               for v in self.verses[1:]:
+                       str = str + v.dump()
+
+               if len (self.verses) > 1:
+                       sys.stderr.write ("\nLyrics found; edit to use \\addlyrics to couple to a staff\n")
+                       
                if staffs:
                        str = str + '\\score { < %s > } ' % string.join (staffs)
-
-               
+                       
                return str
 
 
        def __str__ (self):
-               return self.dump ()
+               return 'ETF FILE %s %s' % (self.measures,  self.entries)
        
        def unthread_entries (self):
-               self.chords = [None]
-               for e in self.entries[1:]:
-                       self.chords.append (Chord (e))
-
                for e in self.chords[1:]:
-                       e.prev = self.chords[e.finale[0][1]]
-                       e.next = self.chords[e.finale[0][2]]
-
-
-        
-
+                       if not e:
+                               continue
 
+                       e.prev = self.chords[e.finale[0]]
+                       e.next = self.chords[e.finale[1]]
 
 def identify():
        sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
 
 def help ():
-       print r"""
-Convert ETF to LilyPond.
+       sys.stdout.write("""Usage: etf2ly [OPTION]... ETF-FILE
 
-Usage: etf2ly [OPTION]... ETF-FILE
+Convert ETF to LilyPond.
 
 Options:
-  -h, --help          this help
-  -o, --output=FILE   set output filename to FILE
-  -v, --version       version information
+  -h,--help          this help
+  -o,--output=FILE   set output filename to FILE
+  -v,--version       version information
 
 Enigma Transport Format is a format used by Coda Music Technology's
 Finale product. This program will convert a subset of ETF to a
 ready-to-use lilypond file.
 
+Report bugs to bug-lilypond@gnu.org
 
-"""
+Written by  Han-Wen Nienhuys <hanwen@cs.uu.nl>
+""")
 
 def print_version ():
-       print r"""etf2ly (GNU lilypond) %s""" % version
+       sys.stdout.write (r"""etf2ly (GNU lilypond) %s
+
+This is free software.  It is covered by the GNU General Public License,
+and you are welcome to change it and/or distribute copies of it under
+certain conditions.  Invoke as `midi2ly --warranty' for more information.
+
+Copyright (c) 2000 by Han-Wen Nienhuys <hanwen@cs.uu.nl>
+""" % version)
 
 
 
@@ -705,15 +1139,17 @@ for opt in options:
 
 identify()
 
-# header['tagline'] = 'Lily was here %s -- automatically converted from ABC' % version
+e = None
 for f in files:
        if f == '-':
                f = ''
 
        sys.stderr.write ('Processing `%s\'\n' % f)
-       e = Etf_file(f)
+
+       dict = parse_etf_file (f, Etf_file.routine_dict)
+       e = Etf_file(dict)
        if not out_filename:
-               out_filename = os.path.basename (re.sub ('.etf$', '.ly', f))
+               out_filename = os.path.basename (re.sub ('(?i).etf$', '.ly', f))
                
        if out_filename == f:
                out_filename = os.path.basename (f + '.ly')