#!/usr/bin/python

# This file is part of LilyPond, the GNU music typesetter.
#
# LilyPond is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# LilyPond is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.

# info mostly taken from looking at files. See also
# http://lilypond.org/wiki/?EnigmaTransportFormat

# This supports
#
#  * notes
#  * rests
#  * ties
#  * slurs
#  * lyrics
#  * articulation
#  * grace notes
#  * tuplets
#

# todo:
#  * slur/stem directions
#  * voices (2nd half of frame?)
#  * more intelligent lyrics
#  * beams (better use autobeam?)
#  * more robust: try entertainer.etf (freenote)
#  * dynamics
#  * empty measures (eg. twopt03.etf from freenote)
#


import __main__
import getopt
import sys
import re
import os

program_name = sys.argv[0]

authors = ('Jan Nieuwenhuizen <janneke@gnu.org>',
           'Han-Wen Nienhuys <hanwen@xs4all.nl>')

version = '2.18.2'
if version == '@' + 'TOPLEVEL_VERSION' + '@':
    version = '(unknown version)'           # uGUHGUHGHGUGH

"""

This generic code used for all python scripts.

The quotes are to ensure that the source .py file can still be
run as a python script, but does not include any sys.path handling.
Otherwise, the lilypond-book calls inside the build
might modify installed .pyc files.

"""

for d in ['/usr/share/lilypond/2.18.2',
	  '/usr/lib/lilypond/2.18.2']:
    sys.path.insert (0, os.path.join (d, 'python'))

# dynamic relocation, for GUB binaries.
bindir = os.path.abspath (os.path.dirname (sys.argv[0]))
for p in ['share', 'lib']:
    datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % p)
    sys.path.insert (0, datadir)
"""
"""

################################################################

import lilylib as ly
_ = ly._
 
finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble_8', 'bass_8', 'baritone']

def lily_clef (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 ()

# notename 0 == central C
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
    
    old_pitch =semitones (oname, oacc)
    delta_pitch = semitones (dname, dacc)
    nname = (oname + dname) 
    nacc = oacc
    new_pitch = semitones (nname, nacc) 

    nacc = nacc - (new_pitch - old_pitch - delta_pitch)

    return (nname, nacc)



def interpret_finale_key_sig (finale_id):
    """
find the transposition of C-major scale that belongs here.

we are not going to insert the correct major/minor, we only want to
have the correct number of accidentals
"""

    p = (0,0)

    
    bank_number = finale_id >> 8
    accidental_bits = finale_id & 0xff

    if 0 <= accidental_bits < 7:
        while accidental_bits > 0:
            p = transpose (p, (4,0)) # a fifth up
            accidental_bits = accidental_bits - 1
    elif 248 < accidental_bits <= 255:
        while accidental_bits < 256:
            p = transpose (p, (3,0))
            accidental_bits = accidental_bits + 1

    if bank_number == 1:
        # minor scale
        p = transpose (p, (5, 0))
    p  = (p[0] % 7, p[1])

    return KeySignature (p, bank_number)

# should cache this.
def find_scale (keysig):
    cscale = map (lambda x: (x,0), range (0,7))
#        print "cscale: ", cscale
    ascale = map (lambda x: (x,0), range (-2,5))
#        print "ascale: ", ascale
    transposition = keysig.pitch
    if keysig.sig_type == 1:
        transposition = transpose(transposition, (2, -1))
        transposition = (transposition[0] % 7, transposition[1])
        trscale = map(lambda x, k=transposition: transpose(x, k), ascale)
    else:
        trscale = map(lambda x, k=transposition: transpose(x, k), cscale)
#        print "trscale: ", trscale
    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 rational_to_lily_skip (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:
        return a
    c = a
    while c: 
        c = a % b
        a = b
        b = c
    return a
    

def rat_simplify (r):
    (n,d) = r
    if d < 0:
        d = -d
        n = -n
    if n == 0:
        return (0,1)
    else:
        g = gcd (n, d)
        return (n/g, d/g)
    
def rat_multiply (a,b):
    (x,y) = a
    (p,q) = b

    return rat_simplify ((x*p, y*q))

def rat_add (a,b):
    (x,y) = a
    (p,q) = b

    return rat_simplify ((x*q + p*y, y*q))

def rat_neg (a):
    (p,q) = a
    return (-p,q)



def rat_subtract (a,b ):
    return rat_add (a, rat_neg (b))

def lily_notename (tuple2):
    (n, a) = tuple2
    nn = chr ((n+ 2)%7 + ord ('a'))

    return nn + {-2:'eses', -1:'es', 0:'', 1:'is', 2:'isis'}[a]


class Tuplet:
    def __init__ (self, 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[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_suffix = ce.note_suffix + '-)'
            
        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):
        self.timesig = ''
        self.number = number
        self.key_signature = 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)

        if dots == 1:
            beats = beats * 3
            log = log * 2
            dots = 0

        if dots <> 0:
            sys.stderr.write ("\nHuh? Beat duration has  dots? (EDU Duration = %d)" % fdur) 
        self.timesig = (beats, log)

    def length (self):
        return self.timesig
    
    def set_key_sig (self, finale):
        k = interpret_finale_key_sig (finale)
        self.key_signature = 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 = \\lyricmode {\n %s }\n""" %  (encodeint (self.number - 1) ,str)
        return str

class KeySignature:
    def __init__(self, pitch, sig_type = 0):
        self.pitch = pitch
        self.sig_type = sig_type
    
    def signature_type (self):
        if self.sig_type == 1:
            return "\\minor"
        else:
            # really only for 0, but we only know about 0 and 1
            return "\\major"
    
    def equal (self, other):
        if other and other.pitch == self.pitch and other.sig_type == self.sig_type:
            return 1
        else:
            return 0
    

class Measure:
    def __init__(self, no):
        self.number = no
        self.frames = [0] * 4
        self.flags = 0
        self.clef = 0
        self.finale = []
        self.global_measure = None
        self.staff = None
        self.valid = 1
        

    def valid (self):
        return self.valid
    def calculate (self):
        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:]


class Frame:
    def __init__ (self, finale):
        self.measure = None
        self.finale = finale
        (number, start, end ) = finale
        self.number = number
        self.start = start
        self.end = end
        self.chords  = []

    def set_measure (self, m):
        self.measure = m

    def calculate (self):

        # do grace notes.
        lastch = None
        in_grace = 0
        for c in self.chords:
            if c.grace and (lastch == None or (not lastch.grace)):
                c.chord_prefix = r'\grace {' + c.chord_prefix
                in_grace = 1
            elif not c.grace and lastch and lastch.grace:
                lastch.chord_suffix = lastch.chord_suffix + ' } '
                in_grace = 0
                
            lastch = c

        if lastch and in_grace:
            lastch.chord_suffix += '}' 

        
    def dump (self):
        str = '%% FR(%d)\n' % self.number
        left = self.measure.global_measure.length ()

        
        ln = ''
        for c in self.chords:
            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:
            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 + rational_to_lily_skip (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):
        fill_list_to (self.measures, no)

        if self.measures[no] == None:
            m = Measure (no)
            self.measures [no] =m
            m.staff = self

        return self.measures[no]
    def staffid (self):
        return 'staff' + encodeint (self.number - 1)
    def layerid (self, l):
        return self.staffid() +  'layer%s' % chr (l -1 + ord ('A'))
    
    def dump_time_key_sigs (self):
        k  = ''
        last_key = None
        last_time = None
        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 g:
                if g.key_signature and not g.key_signature.equal(last_key):
                    pitch= g.key_signature.pitch
                    e = e + "\\key %s %s " % (lily_notename (pitch),
                                 g.key_signature.signature_type())
                    
                    last_key = g.key_signature
                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 = ' '.join (['(volta %s)' % x for x in strs])
                    
                    e = e + ' \\set Score.repeatCommands =  #\'(%s) ' % str

                if g.force_break:
                    e = e + ' \\break '  
            
            if last_clef <> m.clef :
                e = e + '\\clef "%s"' % lily_clef (m.clef)
                last_clef = m.clef
            if e:
                if gap <> (0,1):
                    k = k +' ' + rational_to_lily_skip (gap) + '\n'
                gap = (0,1)
                k = k + e
                
            if g:
                gap = rat_add (gap, g.length ())
                if 'stop' in g.repeats:
                    k = k + ' \\bar ":|." '
                
        k = '%sglobal = { %s }\n\n ' % (self.staffid (), k)
        return k
    
    def dump (self):
        str = ''


        layerids = []
        for x in range (1,5): # 4 layers.
            laystr =  ''
            last_frame = None
            first_frame = None
            gap = (0,1)
            for m in self.measures[1:]:
                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 +'} %s {\n ' % rational_to_lily_skip (gap)
                        gap = (0,1)
                    laystr = laystr + fr.dump ()
                else:
                    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 = { {  %s } }\n\n' % (l, laystr)
                str = str  + laystr
                layerids.append (l)

        str = str +  self.dump_time_key_sigs ()                
        stafdef = '\\%sglobal' % self.staffid ()
        for i in layerids:
            stafdef = stafdef + ' \\' + i
            

        str = str + '%s = \\context Staff = %s <<\n %s\n >>\n' % \
           (self.staffid (), self.staffid (), stafdef)
        return str

                

def ziplist (l):
    if len (l) < 2:
        return []
    else:
        return [(l[0], l[1])] + ziplist (l[2:])


class Chord:
    def __init__ (self, number, contents):
        self.pitches = []
        self.frame = None
        self.finale = contents[:7]

        self.notelist = ziplist (contents[7:])
        self.duration  = None
        self.next = None
        self.prev = None
        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))
        mylen =  rat_multiply (dotfact, l)

        if self.tuplet:
            mylen = rat_multiply (mylen, self.tuplet.factor())
        return mylen
        

    def EDU_duration (self):
        return self.finale[2]
    def set_duration (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
        
    
    def find_realpitch (self):

        meas = self.measure ()
        tiestart = 0
        if not meas or not meas.global_measure  :
            sys.stderr.write ('note %d not in measure\n' % self.number)
        elif not meas.global_measure.scale:
            sys.stderr.write ('note %d: no scale in this measure.' % self.number)
        else:
            
            for p in self.notelist:
                (pitch, flag) = p


                nib1 = pitch & 0x0f
                
                if nib1 > 8:
                    nib1 = -(nib1 - 8)
                rest = pitch / 16

                scale =  meas.global_measure.scale 
                (sn, sa) =scale[rest % 7]
                sn = sn + (rest - (rest%7)) + 7
                acc = sa + nib1
                self.pitches.append ((sn, acc))
                tiestart =  tiestart or (flag & Chord.TIE_START_MASK)
        if tiestart :
            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[4] & Chord.REST_MASK):
            rest = 'r'
        
        for p in self.pitches:
            (n,a) =  p
            o = n/ 7
            n = n % 7

            nn = lily_notename ((n,a))

            if o < 0:
                nn = nn + (',' * -o)
            elif o > 0:
                nn = nn + ('\'' * o)
                
            if s:
                s = s + ' '

            if rest:
                nn = rest
                
            s = s + nn 

        if not self.pitches:
            s  = 'r'
        if len (self.pitches) > 1:
            s = '<%s>' % s

        s = s + '%d%s' % (self.duration[0], '.'* self.duration[1])
        s = self.note_prefix + s + self.note_suffix
        
        s = self.chord_prefix + s + self.chord_suffix

        return s


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 (long (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 (int (dec), str)
    else:
        sys.stderr.write ("cannot 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 = gulp.split ('\n^')

    etf_file_dict = {}
    for k in tag_dict:
        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 ([int (s) for s in m.group (2).split (',')])
            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.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):
        fill_list_to (self.measures, no)
        if self.measures[no] == None:
            self.measures [no] = Global_measure (no)

        return self.measures[no]

        
    def get_staff(self,staffno):
        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, indices, contents):
        pass

    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
        
    def try_FR(self, indices, contents):
        frameno = indices [0]
        
        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_key_sig (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 ()

        self.unthread_entries ()

        for st in self.staffs[1:]:
            if not st:
                continue
            mno = 1
            for m in st.measures[1:]:
                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:
                    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:]:
                    if not fr:
                        continue
                    
                    fr.set_measure (m)
                    
                    fr.chords = self.get_thread (fr.start, fr.end)
                    for c in fr.chords:
                        c.frame = fr
                mno = mno + 1

        for c in self.chords[1:]:
            if c:
                c.calculate()

        for f in self.frames[1:]:
            if f:
                f.calculate ()
            
        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 = 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:
            d = c # hack to avoid problem with build/scripts/grand-replace.py
            thread.append (d)
            c = c.next

        if c: 
            d = c # hack to avoid problem with build/scripts/grand-replace.py
            thread.append (d)
        
        return thread

    def dump (self):
        str = ''
        staffs = []
        for s in self.staffs[1:]:
            if s:
                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 += '\\version "2.3.25"\n'
            str = str + '<<\n  %s\n>> } ' % ' '.join (staffs)
            
        return str


    def __str__ (self):
        return 'ETF FILE %s %s' % (self.measures,  self.entries)
    
    def unthread_entries (self):
        for e in self.chords[1:]:
            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 warranty ():
    identify ()
    sys.stdout.write ('''
%s

  %s

%s
%s
''' % ( _ ('Copyright (c) %s by') % '2001--2012',
        '\n  '.join (authors),
        _ ('Distributed under terms of the GNU General Public License.'),
        _ ('It comes with NO WARRANTY.')))

def get_option_parser ():
    p = ly.get_option_parser (usage=_ ("%s [OPTION]... ETF-FILE") % 'etf2ly',
                 description=_ ("""Enigma Transport Format is a format used by Coda Music Technology's
Finale product.  etf2ly converts a subset of ETF to a ready-to-use LilyPond file.
"""),
                 add_help_option=False)
    p.add_option("-h", "--help",
                 action="help",
                 help=_ ("show this help and exit"))
    p.version = "etf2ly (LilyPond) 2.18.2"
    p.add_option("--version",
                 action="version",
                 help=_ ("show version number and exit"))
    p.add_option ('-o', '--output', help=_ ("write output to FILE"),
           metavar=_("FILE"),
           action='store')
    p.add_option ('-w', '--warranty', help=_ ("show warranty and copyright"),
           action='store_true',
           ),

    p.add_option_group ('',
                        description=(
            _ ('Report bugs via %s')
            % 'http://post.gmane.org/post.php'
            '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
    return p

def do_options ():
    opt_parser = get_option_parser()
    (options,args) = opt_parser.parse_args ()
    if options.warranty:
        warranty ()
        sys.exit (0)

    return (options,args)

(options, files) = do_options()
identify()

out_filename = options.output

e = None
for f in files:
    if f == '-':
        f = ''

    sys.stderr.write ('Processing `%s\'\n' % 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 ('(?i).etf$', '.ly', f))
        
    if out_filename == f:
        out_filename = os.path.basename (f + '.ly')
        
    sys.stderr.write ('Writing `%s\'' % out_filename)
    ly = e.dump()

    fo = open (out_filename, 'w')
    fo.write ('%% lily was here -- automatically converted by etf2ly from %s\n' % f)
    fo.write(ly)
    fo.close ()
    
