]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/abc2ly.py
Changed scripts/* to use sys.argv[0]
[lilypond.git] / scripts / abc2ly.py
index 534c6a8b7fd7d4ca14ad96dd6f86fec31e85a685..9a076136470334a348cc35afcb98dad881800f8a 100644 (file)
 # \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
 
 #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
@@ -68,10 +74,16 @@ 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'] = ''
@@ -102,65 +114,65 @@ def error (msg):
        
        
 def check_clef(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
+       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 ("\\set Staff.instrument = %s\n" % value )
-                                     __main__.part_names = 1
-                             elif keyword == "sname" or keyword == "snm":
-                                     voices_append ("\\set 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')
@@ -183,7 +195,9 @@ def dump_default_bar (outf):
        """
        Nowadays abc2ly outputs explicits barlines (?)
        """
-       outf.write ("\n\\set Score.defaultBarType=\"empty\"\n")
+       ## < 2.2
+       ##outf.write ("\n\\property Score.defaultBarType = \"empty\"\n")
+       outf.write ("\n\\set Score.defaultBarType = \"empty\"\n")
 
 
 def dump_slyrics (outf):
@@ -243,7 +257,7 @@ def dump_score (outf):
         \notes <<
 """)
 
-       ks  = voice_idx_dict.keys ();
+       ks = voice_idx_dict.keys ();
        ks.sort ()
        for k in  ks:
                if re.match('[1-9]', k):
@@ -388,8 +402,11 @@ key_lookup = {     # abc to lilypond key mode names
 }
 
 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'
@@ -401,24 +418,27 @@ 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 ""
-
-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,
@@ -507,6 +527,7 @@ def try_parse_tuplet_begin (str, state):
 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):
@@ -527,13 +548,31 @@ 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:
@@ -605,12 +644,12 @@ def try_parse_header_line (ln, state):
                        if a == 'C':
                                if not state.common_time:
                                        state.common_time = 1
-                                       voices_append ("\\override Staff.TimeSignature #\'style = #'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 ("\\override Staff.TimeSignature #\'style = #'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)
@@ -632,12 +671,16 @@ def try_parse_header_line (ln, state):
                                        else:
                                                key_info = m.group(1)
                                                clef_info = m.group(2)
-                                       __main__.global_key  = compute_key (key_info)# ugh.
-                                       voices_append ('\\key %s' % lily_key(key_info))
+                                       __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
@@ -744,6 +787,7 @@ class Parser_state:
                self.plus_chord = 0
                self.base_octave = 0
                self.common_time = 0
+               self.parsing_beam = 0
 
 
 
@@ -880,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):
@@ -901,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
@@ -966,13 +1016,19 @@ def try_parse_note (str, parser_state):
        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
 
@@ -993,7 +1049,8 @@ def try_parse_guitar_chord (str, state):
                if str:
                        str = str[1:]
                gc = re.sub('#', '\\#', gc)     # escape '#'s
-               state.next_articulation = ("%c\"%s\"" % (position ,gc)) + state.next_articulation
+               state.next_articulation = ("%c\"%s\"" % (position, gc)) \
+                                         + state.next_articulation
        return str
 
 def try_parse_escape (str):
@@ -1095,6 +1152,7 @@ def try_parse_bar (str,state):
                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:
@@ -1103,6 +1161,7 @@ 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("} }")
@@ -1131,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 = ''
@@ -1205,6 +1264,7 @@ def try_parse_comment (str):
 #write other kinds of appending  if we ever need them.                 
        return str
 
+lineno = 0
 happy_count = 100
 def parse_file (fn):
        f = open (fn)
@@ -1212,6 +1272,7 @@ def parse_file (fn):
        ls = map (lambda x: re.sub ("\r$", '', x), ls)
 
        select_voice('default', '')
+       global lineno
        lineno = 0
        sys.stderr.write ("Line ... ")
        sys.stderr.flush ()
@@ -1249,7 +1310,7 @@ 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:
                        error ("%s: %d: Huh?  Don't understand\n" % (fn, lineno))
@@ -1272,6 +1333,7 @@ Options:
   -o, --output=FILE   set output filename to FILE
   -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.
@@ -1288,7 +1350,7 @@ def print_version ():
 
 
 
-(options, files) = getopt.getopt (sys.argv[1:], 'vo:hs', ['help','version', 'output=', 'strict'])
+(options, files) = getopt.getopt (sys.argv[1:], 'vo:hsb', ['help','version', 'output=', 'strict', 'beams'])
 out_filename = ''
 
 for opt in options:
@@ -1304,6 +1366,8 @@ for opt in options:
                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
@@ -1324,7 +1388,7 @@ for f in files:
        outf = open (out_filename, 'w')
 
 #      dump_global (outf)
-       dump_header (outf ,header)
+       dump_header (outfheader)
        dump_slyrics (outf)
        dump_voices (outf)
        dump_score (outf)