]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/abc2ly.py
Changed scripts/* to use sys.argv[0]
[lilypond.git] / scripts / abc2ly.py
index dc08ba9395177ddd6eb721bf428d7ecff70a129e..9a076136470334a348cc35afcb98dad881800f8a 100644 (file)
 
 # Enhancements (Laura Conrad)
 #
-# Beaming now preserved between ABC and lilypond
+# Barring now preserved between ABC and lilypond
+# the default placement for text in abc is above the staff.
+# %%LY now supported.
+# \breve and \longa supported.
+# M:none doesn't crash lily.
+
+# Enhancements (Guy Gascoigne-Piggford)
 #
+# Add support for maintaining ABC's notion of beaming, this is selectable
+# from the command line with a -b or --beam option.
+# Fixd a problem where on cygwin empty lines weren't being correctly identifed
+# and so were complaining, but still generating the correct output.
+
 # Limitations
 #
 # Multiple tunes in single file not supported
 # Block comments generate error and are ignored
 # Postscript commands are ignored
 # lyrics not resynchronized by line breaks (lyrics must fully match notes)
+# %%LY slyrics can't be directly before a w: line.
 # ???
 
 
 
 #TODO:
+#
+# * lilylib
+# * GNU style messages:  warning:FILE:LINE:
+# * l10n
+# 
+# Convert to new chord styles.
+#
 # UNDEF -> None
-  
-  
-program_name = 'abc2ly'
-version = '@TOPLEVEL_VERSION@'
-if version == '@' + 'TOPLEVEL_VERSION' + '@':
-       version = '(unknown version)'           # uGUHGUHGHGUGH
+#
   
+
 import __main__
 import getopt
 import sys
@@ -60,153 +74,228 @@ import re
 import string
 import os
 
+program_name = sys.argv[0]
+
+version = '@TOPLEVEL_VERSION@'
+if version == '@' + 'TOPLEVEL_VERSION' + '@':
+       version = '(unknown version)'           # uGUHGUHGHGUGH  
 
 UNDEF = 255
 state = UNDEF
+strict = 0
+preserve_beams = 0
 voice_idx_dict = {}
 header = {}
+header['footnotes'] = ''
 lyrics = []
 slyrics = []
 voices = []
 state_list = []
+repeat_state = [0] * 8
 current_voice_idx = -1
 current_lyric_idx = -1
 lyric_idx = -1
 part_names = 0
 default_len = 8
 length_specified = 0
+nobarlines = 0
 global_key = [0] * 7                   # UGH
 names = ["One", "Two", "Three"]
 DIGITS='0123456789'
+alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
 HSPACE=' \t'
+midi_specs = ''
+
 
+def error (msg):
+       sys.stderr.write (msg)
+       if strict:
+               sys.exit (1)
+       
        
 def check_clef(s):
-      if not s:
-              return ''
-      if re.match('^treble', s):
-              s = s[6:]
-              if re.match ('^-8', s):
-                      s = s[2:]
-                      state.base_octave = -2
-                      voices_append("\\clef \"G_8\";\n")
-              else:
-                      state.base_octave = 0
-                      voices_append("\\clef treble;\n")
-      elif re.match('^alto', s):
-              s = s[4:]
-              state.base_octave = -1
-              voices_append ("\\clef alto;\n" )
-      elif re.match('^bass',s ):
-              s = s[4:]
-              state.base_octave = -2
-              voices_append ("\\clef bass;\n" )
-      return s
+       if not s:
+               return ''
+       if re.match('-8va', s) or re.match('treble8', s):
+               # treble8 is used by abctab2ps; -8va is used by barfly,
+               # and by my patch to abc2ps. If there's ever a standard
+               # about this we'll support that.
+               s = s[4:]
+               state.base_octave = -1
+               voices_append("\\clef \"G_8\"\n")
+       elif re.match('^treble', s):
+               s = s[6:]
+               if re.match ('^-8', s):
+                       s = s[2:]
+                       state.base_octave = -2
+                       voices_append("\\clef \"G_8\"\n")
+               else:
+                       state.base_octave = 0
+                       voices_append("\\clef treble\n")
+       elif re.match('^alto', s):
+               s = s[4:]
+               state.base_octave = -1
+               voices_append ("\\clef alto\n" )
+       elif re.match('^bass',s ):
+               s = s[4:]
+               state.base_octave = -2
+               voices_append ("\\clef bass\n" )
+               return s
 
 def select_voice (name, rol):
        if not voice_idx_dict.has_key (name):
-             state_list.append(Parser_state())
-             voices.append ('')
-             slyrics.append ([])
-             voice_idx_dict[name] = len (voices) -1
+               state_list.append(Parser_state())
+               voices.append ('')
+               slyrics.append ([])
+               voice_idx_dict[name] = len (voices) -1
        __main__.current_voice_idx =  voice_idx_dict[name]
        __main__.state = state_list[current_voice_idx]
        while rol != '':
-             m = re.match ('^([^ \t=]*)=(.*)$', rol) # find keywork
-             if m:
-                     keyword = m.group(1)
-                     rol = m.group (2)
-                     a = re.match ('^("[^"]*"|[^ \t]*) *(.*)$', rol)
-                     if a:
-                             value = a.group (1)
-                             rol = a.group ( 2)
-                             if keyword == 'clef':
-                                     check_clef(value)
-                             elif keyword == "name":
-                                     value = re.sub ('\\\\','\\\\\\\\', value)
-                                     voices_append ("\\property Staff.instrument = %s\n" % value )
-                                     __main__.part_names = 1
-                             elif keyword == "sname" or keyword == "snm":
-                                     voices_append ("\\property Staff.instr = %s\n" % value )
-
-             else:
-                     break
-
-       return
-
+               m = re.match ('^([^ \t=]*)=(.*)$', rol) # find keywork
+               if m:
+                       keyword = m.group(1)
+                       rol = m.group (2)
+                       a = re.match ('^("[^"]*"|[^ \t]*) *(.*)$', rol)
+                       if a:
+                               value = a.group (1)
+                               rol = a.group ( 2)
+                               if keyword == 'clef':
+                                       check_clef(value)
+                               elif keyword == "name":
+                                       value = re.sub ('\\\\','\\\\\\\\', value)
+                                       ## < 2.2
+                                       ##voices_append ("\\property Staff.instrument = %s\n" % value )
+                                       voices_append ("\\set Staff.instrument = %s\n" % value )
+                                       
+                                       __main__.part_names = 1
+                               elif keyword == "sname" or keyword == "snm":
+                                       ##voices_append ("\\property Staff.instr = %s\n" % value )
+                                       voices_append ("\\set Staff.instr = %s\n" % value )
+               else:
+                       break
 
 def dump_header (outf,hdr):
        outf.write ('\\header {\n')
        ks = hdr.keys ()
        ks.sort ()
        for k in ks:
-               outf.write ('\t%s = "%s";\n'% (k,hdr[k]))
+               hdr[k] = re.sub('"', '\\"', hdr[k])             
+               outf.write ('\t%s = "%s"\n'% (k,hdr[k]))
        outf.write ('}')
 
 def dump_lyrics (outf):
        if (len(lyrics)):
-               outf.write("\n\\score\n{\n    \\context Lyrics\n    <\n")
+               outf.write("\n\\score\n{\n    \\context Lyrics\n    <<\n")
                for i in range (len (lyrics)):
                        outf.write ( lyrics [i])
                        outf.write ("\n")
-               outf.write("    >\n    \\paper{}\n}\n")
+               outf.write("    >>\n    \\paper{}\n}\n")
 
 def dump_default_bar (outf):
-       outf.write ("\n\\property Score.defaultBarType=\"empty\"\n")
+       """
+       Nowadays abc2ly outputs explicits barlines (?)
+       """
+       ## < 2.2
+       ##outf.write ("\n\\property Score.defaultBarType = \"empty\"\n")
+       outf.write ("\n\\set Score.defaultBarType = \"empty\"\n")
 
 
 def dump_slyrics (outf):
        ks = voice_idx_dict.keys()
        ks.sort ()
        for k in ks:
+               if re.match('[1-9]', k):
+                       m = alphabet[string.atoi(k)]
+               else:
+                       m = k
                for i in range (len(slyrics[voice_idx_dict[k]])):
-                       outf.write ("\nwords%sV%d = \\lyrics  {" % (k, i))
+                       l=alphabet[i]
+                       outf.write ("\nwords%sV%s = \\lyrics  {" % (m, l))
                        outf.write ("\n" + slyrics [voice_idx_dict[k]][i])
                        outf.write ("\n}")
 
 def dump_voices (outf):
+       global doing_alternative, in_repeat
        ks = voice_idx_dict.keys()
        ks.sort ()
        for k in ks:
-               outf.write ("\nvoice%s = \\notes {" % k)
+               if re.match ('[1-9]', k):
+                       m = alphabet[string.atoi(k)]
+               else:
+                       m = k
+               outf.write ("\nvoice%s = \\notes {" % m)
                dump_default_bar(outf)
+               if repeat_state[voice_idx_dict[k]]:
+                       outf.write("\n\\repeat volta 2 {")
                outf.write ("\n" + voices [voice_idx_dict[k]])
+               if not using_old:
+                       if doing_alternative[voice_idx_dict[k]]:
+                               outf.write("}")
+                       if in_repeat[voice_idx_dict[k]]:
+                               outf.write("}")
                outf.write ("\n}")
-       
+
+def try_parse_q(a):
+       global midi_specs
+       #assume that Q takes the form "Q:1/4=120"
+       #There are other possibilities, but they are deprecated
+       if string.count(a, '/') == 1:
+               array=string.split(a,'/')
+               numerator=array[0]
+               if numerator != 1:
+                       sys.stderr.write("abc2ly: Warning, unable to translate a Q specification with a numerator of %s: %s\n" % (numerator, a))
+               array2=string.split(array[1],'=')
+               denominator=array2[0]
+               perminute=array2[1]
+               duration=str(string.atoi(denominator)/string.atoi(numerator))
+               midi_specs=string.join(["\\tempo", duration, "=", perminute])
+       else:
+               sys.stderr.write("abc2ly: Warning, unable to parse Q specification: %s\n" % a)
+        
 def dump_score (outf):
        outf.write (r"""\score{
-        \notes <
+        \notes <<
 """)
 
-       ks  = voice_idx_dict.keys ();
+       ks = voice_idx_dict.keys ();
        ks.sort ()
        for k in  ks:
+               if re.match('[1-9]', k):
+                       m = alphabet[string.atoi(k)]
+               else:
+                       m = k
                if k == 'default' and len (voice_idx_dict) > 1:
                        break
                if len ( slyrics [voice_idx_dict[k]] ):
                        outf.write ("\n        \\addlyrics")
                outf.write ("\n\t\\context Staff=\"%s\"\n\t{\n" %k ) 
                if k != 'default':
-                       outf.write ("\t    \\$voicedefault\n")
-               outf.write ("\t    \\$voice%s " % k)
+                       outf.write ("\t    \\voicedefault\n")
+               outf.write ("\t    \\voice%s " % m)
                outf.write ("\n\t}\n")
                if len ( slyrics [voice_idx_dict[k]] ):
-                       outf.write ("\n\t\\context Lyrics=\"%s\" \n\t<\t" % k)
+                       outf.write ("\n\t\\context Lyrics=\"%s\" \n\t<<\t" % k)
+                       if re.match('[1-9]',k):
+                               m = alphabet[string.atoi(k)]
+                       else:
+                               m = k
                        for i in range (len(slyrics[voice_idx_dict[k]])):
-                               outf.write("\n\t  { \\$words%sV%d }" % ( k, i) )
-                       outf.write ( "\n\t>\n" )
-       outf.write ("\n    >")
+                               l=alphabet[i]
+                               outf.write("\n\t  { \\words%sV%s }" % ( m, l) )
+                       outf.write ( "\n\t>>\n" )
+       outf.write ("\n    >>")
        outf.write ("\n\t\\paper {\n")
        if part_names:
                outf.write ("\t    \\translator \n\t    {\n")
                outf.write ("\t\t\\StaffContext\n")
-               outf.write ("\t\t\\consists Staff_margin_engraver;\n")
+#              outf.write ("\t\t\\consists Staff_margin_engraver\n")
                outf.write ("\t    }\n")
-       outf.write ("\t}\n\t\\midi {}\n}\n")
+       outf.write ("\t}\n\t\\midi {%s}\n}\n" % midi_specs)
 
 
 
 def set_default_length (s):
+       global length_specified
        m =  re.search ('1/([0-9]+)', s)
        if m:
                __main__.default_len = string.atoi ( m.group (1))
@@ -229,11 +318,11 @@ def gulp_file(f):
                n = i.tell ()
                i.seek (0,0)
        except:
-               sys.stderr.write ("can't open file: %s\n" % f)
+               sys.stderr.write ("can't open file: `%s'\n" % f)
                return ''
        s = i.read (n)
        if len (s) <= 0:
-               sys.stderr.write ("gulped emty file: %s\n" % f)
+               sys.stderr.write ("gulped empty file: `%s'\n" % f)
        i.close ()
        return s
 
@@ -300,18 +389,24 @@ key_lookup = {    # abc to lilypond key mode names
        'm'   : 'minor',
        'min' : 'minor',
        'maj' : 'major',
+       'major' : 'major',      
        'phr' : 'phrygian',
        'ion' : 'ionian',
        'loc' : 'locrian',
        'aeo' : 'aeolian',
        'mix' : 'mixolydian',
+       'mixolydian' : 'mixolydian',    
        'lyd' : 'lydian',
-       'dor' : 'dorian'
+       'dor' : 'dorian',
+       'dorian' : 'dorian'     
 }
 
 def lily_key (k):
+       orig = "" + k
+       # UGR
        k = string.lower (k)
        key = k[0]
+       #UGH
        k = k[1:]
        if k and k[0] == '#':
                key = key + 'is'
@@ -323,36 +418,48 @@ def lily_key (k):
                return '%s \\major' % key
 
        type = k[0:3]
-       if key_lookup.has_key(type):
-               return("%s \\%s" % ( key, key_lookup[type]))
-       sys.stderr.write("Unknown key type %s ignored\n" % type)
-       return key
-
-def shift_key (note, acc , shift):
-        s = semitone_pitch((note, acc))
-        s = (s + shift + 12) % 12
-        if s <= 4:
-                n = s / 2
-                a = s % 2
-        else:
-                n = (s + 1) / 2
-                a = (s + 1) % 2
-        if a:
-                n = n + 1
-                a = -1
-        return (n,a)
+       if not key_lookup.has_key (type):
+               #ugh, use lilylib, say WARNING:FILE:LINE:
+               sys.stderr.write ("abc2ly:warning:")
+               sys.stderr.write ("ignoring unknown key: `%s'" % orig)
+               sys.stderr.write ('\n')
+               return 0
+       return ("%s \\%s" % ( key, key_lookup[type]))
+
+def shift_key (note, acc, shift):
+       s = semitone_pitch((note, acc))
+       s = (s + shift + 12) % 12
+       if s <= 4:
+               n = s / 2
+               a = s % 2
+       else:
+               n = (s + 1) / 2
+               a = (s + 1) % 2
+       if a:
+               n = n + 1
+               a = -1
+       return (n,a)
 
 key_shift = { # semitone shifts for key mode names
        'm'   : 3,
        'min' : 3,
+       'minor' : 3,
        'maj' : 0,
+       'major' : 0,    
        'phr' : -4,
+       'phrygian' : -4,
        'ion' : 0,
+       'ionian' : 0,
        'loc' : 1,
+       'locrian' : 1,  
        'aeo' : 3,
+       'aeolian' : 3,
        'mix' : 5,
+       'mixolydian' : 5,       
        'lyd' : -5,
-       'dor' : -2
+       'lydian' : -5,  
+       'dor' : -2,
+       'dorian' :      -2      
 }
 def compute_key (k):
        k = string.lower (k)
@@ -386,6 +493,7 @@ def compute_key (k):
                key_count = flat_key_seq.index (keytup)
                accseq = map (lambda x: (3*x + 3 ) % 7, range (1, key_count + 1))
        else:
+               error ("Huh?")
                raise "Huh"
        
        key_table = [0] * 7
@@ -409,21 +517,24 @@ def try_parse_tuplet_begin (str, state):
        if re.match ('\([2-9]', str):
                dig = str[1]
                str = str[2:]
-               state.parsing_tuplet = string.atoi (dig[0])
-               
+               prev_tuplet_state = state.parsing_tuplet
+               state.parsing_tuplet = string.atoi (dig[0])
+               if prev_tuplet_state:
+                       voices_append ("}")             
                voices_append ("\\times %s {" % tup_lookup[dig])
        return str
 
 def  try_parse_group_end (str, state):
        if str and str[0] in HSPACE:
                str = str[1:]
+               close_beam_state(state)
        return str
-
+       
 def header_append (key, a):
        s = ''
        if header.has_key (key):
                s = header[key] + "\n"
-       header [key] = s + a
+               header [key] = s + a
 
 def wordwrap(a, v):
        linelen = len (v) - string.rfind(v, '\n')
@@ -437,14 +548,39 @@ def stuff_append (stuff, idx, a):
        else:
                stuff [idx] = wordwrap(a, stuff[idx])
 
-
+# ignore wordwrap since we are adding to the previous word
+def stuff_append_back(stuff, idx, a):
+       if not stuff:
+               stuff.append (a)
+       else:
+               point = len(stuff[idx])-1
+               while stuff[idx][point] is ' ':
+                       point = point - 1
+               point = point +1
+               stuff[idx] = stuff[idx][:point] + a + stuff[idx][point:]
 
 def voices_append(a):
        if current_voice_idx < 0:
                select_voice ('default', '')
-
        stuff_append (voices, current_voice_idx, a)
 
+# word wrap really makes it hard to bind beams to the end of notes since it
+# pushes out whitespace on every call. The _back functions do an append
+# prior to the last space, effectively tagging whatever they are given
+# onto the last note
+def voices_append_back(a):
+       if current_voice_idx < 0:
+               select_voice ('default', '')
+       stuff_append_back(voices, current_voice_idx, a)
+
+def repeat_prepend():
+       global repeat_state
+       if current_voice_idx < 0:
+               select_voice ('default', '')
+       if not using_old:
+               repeat_state[current_voice_idx] = 't'
+
+       
 def lyrics_append(a):
        a = re.sub ( '#', '\\#', a)     # latex does not like naked #'s
        a = re.sub ( '"', '\\"', a)     # latex does not like naked "'s
@@ -454,14 +590,13 @@ def lyrics_append(a):
 # break lyrics to words and put "'s around words containing numbers and '"'s
 def fix_lyric(str):
        ret = ''
-
        while str != '':
                m = re.match('[ \t]*([^ \t]*)[ \t]*(.*$)', str)
                if m:
                        word = m.group(1)
                        str = m.group(2)
                        word = re.sub('"', '\\"', word) # escape "
-                       if re.match('.*[0-9"]', word):
+                       if re.match('.*[0-9"\(]', word):
                                word = re.sub('_', ' ', word) # _ causes probs inside ""
                                ret = ret + '\"' + word + '\" '
                        else:
@@ -477,7 +612,7 @@ def slyrics_append(a):
        a = re.sub ( '~', '_', a)       # ~ to space('_')
        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 " into quoted string
+       if re.match('.*[0-9"\(]', a):   # put numbers and " and ( into quoted string
                a = fix_lyric(a)
        a = re.sub ( '$', ' ', a)       # insure space between lines
        __main__.lyric_idx = lyric_idx + 1
@@ -489,6 +624,7 @@ def slyrics_append(a):
 
 
 def try_parse_header_line (ln, state):
+       global length_specified
        m = re.match ('^([A-Za-z]): *(.*)$', ln)
 
        if m:
@@ -498,35 +634,55 @@ def try_parse_header_line (ln, state):
                        a = re.sub('[ \t]*$','', a)     #strip trailing blanks
                        if header.has_key('title'):
                                if a:
-                                       header['title'] = header['title'] + '\\\\\\\\' + a
+                                       if len(header['title']):
+                                               header['title'] = header['title'] + '\\\\\\\\' + a
+                                       else:
+                                               header['subtitle'] = a
                        else:
                                header['title'] =  a
                if g == 'M':    # Meter
                        if a == 'C':
                                if not state.common_time:
                                        state.common_time = 1
-                                       voices_append ("\\property Staff.timeSignatureStyle=\"C\"\n")
+                                       voices_append ("\\property Staff.TimeSignature \\override #\'style = #'C\n")
                                a = '4/4'
                        if a == 'C|':
                                if not state.common_time:
                                        state.common_time = 1
-                                       voices_append ("\\property Staff.timeSignatureStyle=\"C\"\n")
+                                       voices_append ("\\property Staff.TimeSignature \\override #\'style = #'C\n")
                                a = '2/2'
                        if not length_specified:
                                set_default_len_from_time_sig (a)
-                       voices_append ('\\time %s;' % a)
+                       else:
+                               length_specified = 0
+                       if not a == 'none':
+                               voices_append ('\\time %s' % a)
                        state.next_bar = ''
                if g == 'K': # KEY
                        a = check_clef(a)
                        if a:
                                m = re.match ('^([^ \t]*) *(.*)$', a) # seperate clef info
                                if m:
-                                       __main__.global_key  =compute_key (m.group(1))# ugh.
-                                       voices_append ('\\key %s;' % lily_key(m.group(1)))
-                                       check_clef(m.group(2))
+                                       # there may or may not be a space
+                                       # between the key letter and the mode
+                                       if key_lookup.has_key(m.group(2)[0:3]):
+                                               key_info = m.group(1) + m.group(2)[0:3]
+                                               clef_info = m.group(2)[4:]
+                                       else:
+                                               key_info = m.group(1)
+                                               clef_info = m.group(2)
+                                       __main__.global_key  = compute_key (key_info)
+                                       k = lily_key (key_info)
+                                       if k:
+                                               voices_append ('\\key %s' % k)
+                                       check_clef(clef_info)
                                else:
-                                       __main__.global_key  =compute_key (a)# ugh.
-                                       voices_append ('\\key %s \\major;' % lily_key(a))
+                                       __main__.global_key  = compute_key (a)
+                                       k = lily_key (a)
+                                       if k:
+                                               voices_append ('\\key %s \\major' % k)
+               if g == 'N': # Notes
+                       header ['footnotes'] = header['footnotes'] +  '\\\\\\\\' + a
                if g == 'O': # Origin
                        header ['origin'] = a
                if g == 'X': # Reference Number
@@ -555,19 +711,21 @@ def try_parse_header_line (ln, state):
                                state.next_bar = ''
                        select_voice (voice, rest)
                if g == 'W':    # Words
-                       lyrics_append(a);
+                       lyrics_append(a)
                if g == 'w':    # vocals
-                       slyrics_append (a);
-
+                       slyrics_append (a)
+               if g == 'Q':    #tempo
+                       try_parse_q (a)
                return ''
        return ln
 
 # we use in this order specified accidental, active accidental for bar,
 # active accidental for key
-def pitch_to_mudela_name (name, acc, bar_acc, key):
+def pitch_to_lilypond_name (name, acc, bar_acc, key):
        s = ''
        if acc == UNDEF:
-               acc = bar_acc
+               if not nobarlines:
+                       acc = bar_acc
        if acc == UNDEF:
                acc = key
        if acc == -1:
@@ -580,7 +738,7 @@ def pitch_to_mudela_name (name, acc, bar_acc, key):
        return(chr (name  + ord('c')) + s)
 
 
-def octave_to_mudela_quotes (o):
+def octave_to_lilypond_quotes (o):
        o = o + 2
        s =''
        if o < 0:
@@ -603,12 +761,20 @@ def parse_num (str):
        return (str,n)
 
 
-def duration_to_mudela_duration  (multiply_tup, defaultlen, dots):
+def duration_to_lilypond_duration  (multiply_tup, defaultlen, dots):
        base = 1
        # (num /  den)  / defaultlen < 1/base
        while base * multiply_tup[0] < multiply_tup[1]:
                base = base * 2
-       return '%d%s' % ( base, '.'* dots)
+       if base == 1:
+               if (multiply_tup[0] / multiply_tup[1])  == 2:
+                       base = '\\breve'
+               if (multiply_tup[0] / multiply_tup[1]) == 3:
+                       base = '\\breve'
+                       dots = 1
+               if (multiply_tup[0] / multiply_tup[1]) == 4:
+                       base = '\longa'
+       return '%s%s' % ( base, '.'* dots)
 
 class Parser_state:
        def __init__ (self):
@@ -621,6 +787,7 @@ class Parser_state:
                self.plus_chord = 0
                self.base_octave = 0
                self.common_time = 0
+               self.parsing_beam = 0
 
 
 
@@ -633,15 +800,16 @@ def parse_duration (str, parser_state):
        (str, num) = parse_num (str)
        if not num:
                num = 1
-       
-       if str[0] == '/':
-               while str[:1] == '/':
-                       str= str[1:]
-                       d = 2
-                       if str[0] in DIGITS:
-                               (str, d) =parse_num (str)
+       if len(str):
+               if str[0] == '/':
+                       if len(str[0]):
+                               while str[:1] == '/':
+                                       str= str[1:]
+                                       d = 2
+                                       if str[0] in DIGITS:
+                                               (str, d) =parse_num (str)
 
-                       den = den * d
+                                       den = den * d
 
        den = den * default_len
        
@@ -652,7 +820,7 @@ def parse_duration (str, parser_state):
                        str = str[1:]
                while str[0] == '>':
                        str = str [1:]
-                       current_dots = current_dots + 1;
+                       current_dots = current_dots + 1
                        parser_state.next_den = parser_state.next_den * 2
                
                while str[0] == '<':
@@ -691,7 +859,7 @@ def try_parse_rest (str, parser_state):
        str = str[1:]
 
        (str, num,den,d) = parse_duration (str, parser_state)
-       voices_append ('%s%s' % (rest, duration_to_mudela_duration ((num,den), default_len, d)))
+       voices_append ('%s%s' % (rest, duration_to_lilypond_duration ((num,den), default_len, d)))
        if parser_state.next_articulation:
                voices_append (parser_state.next_articulation)
                parser_state.next_articulation = ''
@@ -713,11 +881,10 @@ artic_tbl = {
 }
        
 def try_parse_articulation (str, state):
-       
        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]]:
-                       sys.stderr.write("Warning: ignoring %s\n" % str[:1] )
+                       sys.stderr.write("Warning: ignoring `%s'\n" % str[:1] )
 
                str = str[1:]
 
@@ -757,6 +924,12 @@ def clear_bar_acc(state):
                del state.in_acc[k]
        
 
+# if we are parsing a beam, close it off
+def close_beam_state(state):
+       if state.parsing_beam and preserve_beams:
+               state.parsing_beam = 0
+               voices_append_back( ']' )
+
                
 # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP  !
 def try_parse_note (str, parser_state):
@@ -778,7 +951,7 @@ def try_parse_note (str, parser_state):
                if c == '_':
                        acc = -1
 
-        octave = parser_state.base_octave
+       octave = parser_state.base_octave
        if str[0] in "ABCDEFG":
                str = string.lower (str[0]) + str[1:]
                octave = octave - 1
@@ -807,7 +980,6 @@ def try_parse_note (str, parser_state):
 
        (str, num,den,current_dots) = parse_duration (str, parser_state)
 
-
        if re.match('[ \t]*\)', str):
                str = string.lstrip (str)
        
@@ -817,19 +989,16 @@ def try_parse_note (str, parser_state):
                str = str[1:]
 
        
-       if slur_end:
-               voices_append ('%s' % ')' *slur_end )
-
        bar_acc = get_bar_acc(notename, octave, parser_state)
-       pit = pitch_to_mudela_name(notename, acc, bar_acc, global_key[notename])
-       oct = octave_to_mudela_quotes (octave)
+       pit = pitch_to_lilypond_name(notename, acc, bar_acc, global_key[notename])
+       oct = octave_to_lilypond_quotes (octave)
        if acc != UNDEF and (acc == global_key[notename] or acc == bar_acc):
                mod='!'
        else:
                mod = ''
        voices_append ("%s%s%s%s" %
                (pit, oct, mod,
-                duration_to_mudela_duration ((num,den), default_len, current_dots)))
+                duration_to_lilypond_duration ((num,den), default_len, current_dots)))
        
        set_bar_acc(notename, octave, acc, parser_state)
        if parser_state.next_articulation:
@@ -843,14 +1012,23 @@ def try_parse_note (str, parser_state):
                if not parser_state.parsing_tuplet:
                        voices_append ("}")
        if slur_begin:
-               voices_append ('%s' % '(' * slur_begin )
-
-
+               voices_append ('-(' * slur_begin )
+       if slur_end:
+               voices_append ('-)' *slur_end )
+
+       if preserve_beams and \
+          str[0] in '^=_ABCDEFGabcdefg' and \
+          not parser_state.parsing_beam and \
+          not parser_state.parsing_tuplet:
+               parser_state.parsing_beam = 1
+               voices_append_back( '[' )
+               
        return str
 
-def junk_space (str):
-       while str and str[0] in '\t\n ':
+def junk_space (str,state):
+       while str and str[0] in '\t\n\r ':
                str = str[1:]
+               close_beam_state(state)
 
        return str
 
@@ -859,6 +1037,11 @@ def try_parse_guitar_chord (str, state):
        if str[:1] =='"':
                str = str[1:]
                gc = ''
+               if str[0] == '_' or (str[0] == '^'):
+                       position = str[0]
+                       str = str[1:]
+               else:
+                       position = '^'
                while str and str[0] != '"':
                        gc = gc + str[0]
                        str = str[1:]
@@ -866,7 +1049,8 @@ def try_parse_guitar_chord (str, state):
                if str:
                        str = str[1:]
                gc = re.sub('#', '\\#', gc)     # escape '#'s
-               state.next_articulation = ("-\"%s\"" % gc) + state.next_articulation
+               state.next_articulation = ("%c\"%s\"" % (position, gc)) \
+                                         + state.next_articulation
        return str
 
 def try_parse_escape (str):
@@ -887,39 +1071,88 @@ def try_parse_escape (str):
 # :: left-right repeat
 # |1 volta 1
 # |2 volta 2
-bar_dict = {
+old_bar_dict = {
 '|]' : '|.',
 '||' : '||',
 '[|' : '||',
 ':|' : ':|',
 '|:' : '|:',
-'::' : '::',
+'::' : ':|:',
 '|1' : '|',
 '|2' : '|',
 ':|2' : ':|',
 '|' :  '|'
 }
+bar_dict = {
+ '|]' : '\\bar "|."',
+ '||' : '\\bar "||"',
+ '[|' : '\\bar "||"',
+ ':|' : '}',
+ '|:' : '\\repeat volta 2 {',
+ '::' : '} \\repeat volta 2 {',
+ '|1' : '} \\alternative{{',
+ '|2' : '} {',
+ ':|2' : '} {',
+ '|' :  '\\bar "|"'
+  }
 
 
 warn_about = ['|:', '::', ':|', '|1', ':|2', '|2']
+alternative_opener = ['|1', '|2', ':|2']
+repeat_ender = ['::', ':|']
+repeat_opener = ['::', '|:']
+in_repeat = [''] * 8
+doing_alternative = [''] * 8
+using_old = ''
 
 def try_parse_bar (str,state):
+       global in_repeat, doing_alternative, using_old
+       do_curly = ''
        bs = None
-
+       if current_voice_idx < 0:
+               select_voice ('default', '')
        # first try the longer one
        for trylen in [3,2,1]:
                if str[:trylen] and bar_dict.has_key (str[:trylen]):
                        s = str[:trylen]
-                       bs = "\\bar \"%s\";" % bar_dict[s]
-                       if s in warn_about:
-                               sys.stderr.write('Warning kludging for barline `%s\'\n' % s)
+                       if using_old:
+                               bs = "\\bar \"%s\"" % old_bar_dict[s]
+                       else:
+                               bs = "%s" % bar_dict[s]
                        str = str[trylen:]
-                       break
+                       if s in alternative_opener:
+                               if not in_repeat[current_voice_idx]:
+                                       using_old = 't'
+                                       bs = "\\bar \"%s\"" % old_bar_dict[s]
+                               else:
+                                       doing_alternative[current_voice_idx] = 't'
 
+                       if s in repeat_ender:
+                               if not in_repeat[current_voice_idx]:
+                                       sys.stderr.write("Warning: inserting repeat to beginning of notes.\n")
+                                       repeat_prepend()
+                                       in_repeat[current_voice_idx] = ''
+                               else:
+                                       if doing_alternative[current_voice_idx]:
+                                               do_curly = 't'
+                               if using_old:
+                                       bs = "\\bar \"%s\"" % old_bar_dict[s]
+                               else:
+                                       bs =  bar_dict[s]
+                               doing_alternative[current_voice_idx] = ''
+                               in_repeat[current_voice_idx] = ''
+                       if s in repeat_opener:
+                               in_repeat[current_voice_idx] = 't'
+                               if using_old:
+                                       bs = "\\bar \"%s\"" % old_bar_dict[s]
+                               else:
+                                       bs =  bar_dict[s]
+                       break
        if str[:1] == '|':
                state.next_bar = '|\n'
                str = str[1:]
                clear_bar_acc(state)
+               close_beam_state(state)
        
        if bs <> None or state.next_bar != '':
                if state.parsing_tuplet:
@@ -928,8 +1161,11 @@ def try_parse_bar (str,state):
                
        if bs <> None:
                clear_bar_acc(state)
+               close_beam_state(state)
                voices_append (bs)
-
+               if do_curly != '':
+                       voices_append("} }")
+                       do_curly = ''
        return str
 
 def try_parse_tie (str):
@@ -954,18 +1190,18 @@ def try_parse_chord_delims (str, state):
                if state.next_bar:
                        voices_append(state.next_bar)
                        state.next_bar = ''
-               voices_append ('<')
+               voices_append ('<<')
 
        if str[:1] == '+':
                str = str[1:]
                if state.plus_chord:
-                       voices_append ('>')
+                       voices_append ('>>')
                        state.plus_chord = 0
                else:
                        if state.next_bar:
                                voices_append(state.next_bar)
                                state.next_bar = ''
-                       voices_append ('<')
+                       voices_append ('<<')
                        state.plus_chord = 1
 
        ch = ''
@@ -979,7 +1215,7 @@ def try_parse_chord_delims (str, state):
                str = str[1:]
 
        
-       voices_append ("\\spanrequest \\stop \"slur\"" * end);
+       voices_append ("\\spanrequest \\stop \"slur\"" * end)
        voices_append (ch)
        return str
 
@@ -997,13 +1233,46 @@ def try_parse_grace_delims (str, state):
 
        return str
 
+def try_parse_comment (str):
+       global nobarlines
+       if (str[0] == '%'):
+               if str[0:5] == '%MIDI':
+#the nobarlines option is necessary for an abc to lilypond translator for
+#exactly the same reason abc2midi needs it: abc requires the user to enter
+#the note that will be printed, and MIDI and lilypond expect entry of the
+#pitch that will be played.
+#
+#In standard 19th century musical notation, the algorithm for translating
+#between printed note and pitch involves using the barlines to determine
+#the scope of the accidentals.
+#
+#Since ABC is frequently used for music in styles that do not use this
+#convention, such as most music written before 1700, or ethnic music in
+#non-western scales, it is necessary to be able to tell a translator that
+#the barlines should not affect its interpretation of the pitch.  
+                       if (string.find(str,'nobarlines') > 0):
+                               nobarlines = 1
+               elif str[0:3] == '%LY':
+                       p = string.find(str, 'voices')
+                       if (p > -1):
+                               voices_append(str[p+7:])
+                               voices_append("\n")
+                       p = string.find(str, 'slyrics')
+                       if (p > -1):
+                               slyrics_append(str[p+8:])
+                       
+#write other kinds of appending  if we ever need them.                 
+       return str
 
+lineno = 0
 happy_count = 100
 def parse_file (fn):
        f = open (fn)
        ls = f.readlines ()
+       ls = map (lambda x: re.sub ("\r$", '', x), ls)
 
        select_voice('default', '')
+       global lineno
        lineno = 0
        sys.stderr.write ("Line ... ")
        sys.stderr.flush ()
@@ -1018,6 +1287,7 @@ def parse_file (fn):
                m = re.match  ('^([^%]*)%(.*)$',ln)  # add comments to current voice
                if m:
                        if m.group(2):
+                               try_parse_comment(m.group(2))
                                voices_append ('%% %s\n' % m.group(2))
                        ln = m.group (1)
 
@@ -1040,11 +1310,10 @@ def parse_file (fn):
                        ln = try_parse_tuplet_begin (ln, state)
                        ln = try_parse_group_end (ln, state)
                        ln = try_parse_grace_delims (ln, state)
-                       ln = junk_space (ln)
+                       ln = junk_space (ln, state)
 
                if ln:
-                       msg = "%s: %d: Huh?  Don't understand\n" % (fn, lineno)
-                       sys.stderr.write (msg)
+                       error ("%s: %d: Huh?  Don't understand\n" % (fn, lineno))
                        left = orig_ln[0:-len (ln)]
                        sys.stderr.write (left + '\n')
                        sys.stderr.write (' ' *  len (left) + ln + '\n')        
@@ -1055,17 +1324,25 @@ def identify():
 
 def help ():
        print r"""
-Convert ABC to Mudela.
+Convert ABC to lilypond.
 
 Usage: abc2ly [OPTIONS]... ABC-FILE
 
 Options:
-  -h, --help          this help
+  -h, --help          print this help
   -o, --output=FILE   set output filename to FILE
-  -v, --version       version information
-
+  -v, --version       show version information
+  -s, --strict        be strict about succes
+  -b, --beams         preserve ABC's notion of beams
+  
 This program converts ABC music files (see
-http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt) To LilyPond input.
+http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt) to LilyPond input.
+
+
+Report bugs to bug-lilypond@gnu.org.
+
+Written by Han-Wen Nienhuys <hanwen@cs.uu.nl>, Laura Conrad
+<lconrad@laymusic.org>, Roy Rankin <Roy.Rankin@@alcatel.com.au>.
 """
 
 def print_version ():
@@ -1073,7 +1350,7 @@ def print_version ():
 
 
 
-(options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
+(options, files) = getopt.getopt (sys.argv[1:], 'vo:hsb', ['help','version', 'output=', 'strict', 'beams'])
 out_filename = ''
 
 for opt in options:
@@ -1082,12 +1359,15 @@ for opt in options:
        if o== '--help' or o == '-h':
                help ()
                sys.exit (0)
-       if o == '--version' or o == '-v':
+       elif o == '--version' or o == '-v':
                print_version ()
                sys.exit(0)
-               
-       if o == '--output' or o == '-o':
+       elif o == '--strict' or o == '-s':
+               strict = 1
+       elif o == '--output' or o == '-o':
                out_filename = a
+       elif o == '--beams' or o == '-b':
+               preserve_beams = 1
        else:
                print o
                raise getopt.error
@@ -1099,16 +1379,16 @@ for f in files:
        if f == '-':
                f = ''
 
-       sys.stderr.write ('Parsing... [%s]\n' % f)
+       sys.stderr.write ('Parsing `%s\'...\n' % f)
        parse_file (f)
 
        if not out_filename:
                out_filename = os.path.basename (os.path.splitext (f)[0]) + ".ly"
-       sys.stderr.write ('Ly output to: %s...' % out_filename)
+       sys.stderr.write ('lilypond output to: `%s\'...' % out_filename)
        outf = open (out_filename, 'w')
 
 #      dump_global (outf)
-       dump_header (outf ,header)
+       dump_header (outfheader)
        dump_slyrics (outf)
        dump_voices (outf)
        dump_score (outf)