]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/abc2ly.py
release: 1.5.18
[lilypond.git] / scripts / abc2ly.py
index 8cab59b86ab8b7013433e1a42996b7e28d40bab1..6428e0f1054aa4fb6ff2cbc6062da1b4a01c10fd 100644 (file)
@@ -42,6 +42,7 @@
 # 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.
 # ???
 
 
@@ -66,6 +67,7 @@ import os
 
 UNDEF = 255
 state = UNDEF
+strict = 0
 voice_idx_dict = {}
 header = {}
 header['footnotes'] = ''
@@ -84,8 +86,16 @@ 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:
@@ -96,24 +106,24 @@ def check_clef(s):
              # about this we'll support that.
              s = s[4:]
              state.base_octave = -1
-             voices_append("\\clef \"G_8\";\n")
+             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")
+                      voices_append("\\clef \"G_8\"\n")
               else:
                       state.base_octave = 0
-                      voices_append("\\clef treble;\n")
+                      voices_append("\\clef treble\n")
       elif re.match('^alto', s):
               s = s[4:]
               state.base_octave = -1
-              voices_append ("\\clef alto;\n" )
+              voices_append ("\\clef alto\n" )
       elif re.match('^bass',s ):
               s = s[4:]
               state.base_octave = -2
-              voices_append ("\\clef bass;\n" )
+              voices_append ("\\clef bass\n" )
       return s
 
 def select_voice (name, rol):
@@ -153,7 +163,7 @@ def dump_header (outf,hdr):
        ks = hdr.keys ()
        ks.sort ()
        for k in ks:
-               outf.write ('\t%s = "%s";\n'% (k,hdr[k]))
+               outf.write ('\t%s = "%s"\n'% (k,hdr[k]))
        outf.write ('}')
 
 def dump_lyrics (outf):
@@ -172,8 +182,13 @@ 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}")
 
@@ -182,7 +197,11 @@ def dump_voices (outf):
        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 {")
@@ -193,7 +212,24 @@ def dump_voices (outf):
                        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.atof(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 <
@@ -202,28 +238,37 @@ def dump_score (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
                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)
+                       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) )
+                               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)
 
 
 
@@ -251,11 +296,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 empty file: %s\n" % f)
+               sys.stderr.write ("gulped empty file: `%s'\n" % f)
        i.close ()
        return s
 
@@ -350,7 +395,7 @@ def lily_key (k):
        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)
+       sys.stderr.write("Unknown key type `%s' ignored\n" % type)
        return key
 
 def shift_key (note, acc , shift):
@@ -370,14 +415,23 @@ def shift_key (note, acc , shift):
 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)
@@ -411,6 +465,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
@@ -537,18 +592,18 @@ def try_parse_header_line (ln, state):
                        if a == 'C':
                                if not state.common_time:
                                        state.common_time = 1
-                                       voices_append ("\\property Staff.TimeSignature \push #\'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 ("\\property Staff.TimeSignature \push #\'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)
                        else:
                                length_specified = 0
-                       voices_append ('\\time %s;' % a)
+                       voices_append ('\\time %s' % a)
                        state.next_bar = ''
                if g == 'K': # KEY
                        a = check_clef(a)
@@ -564,11 +619,11 @@ def try_parse_header_line (ln, state):
                                                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))
+                                       voices_append ('\\key %s' % lily_key(key_info))
                                        check_clef(clef_info)
                                else:
                                        __main__.global_key  = compute_key (a)# ugh.
-                                       voices_append ('\\key %s \\major;' % lily_key(a))
+                                       voices_append ('\\key %s \\major' % lily_key(a))
                if g == 'N': # Notes
                        header ['footnotes'] = header['footnotes'] +  '\\\\\\\\' + a
                if g == 'O': # Origin
@@ -599,10 +654,11 @@ 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
 
@@ -683,15 +739,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
        
@@ -702,7 +759,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] == '<':
@@ -766,7 +823,7 @@ 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:]
 
@@ -954,16 +1011,16 @@ old_bar_dict = {
 '|' :  '|'
 }
 bar_dict = {
- '|]' : '\\bar "|.";',
- '||' : '\\bar "||";',
- '[|' : '\\bar "||";',
+ '|]' : '\\bar "|."',
+ '||' : '\\bar "||"',
+ '[|' : '\\bar "||"',
  ':|' : '}',
  '|:' : '\\repeat volta 2 {',
  '::' : '} \\repeat volta 2 {',
  '|1' : '} \\alternative{{',
  '|2' : '} {',
  ':|2' : '} {',
- '|' :  '\\bar "|";'
+ '|' :  '\\bar "|"'
   }
 
 
@@ -986,14 +1043,14 @@ def try_parse_bar (str,state):
                if str[:trylen] and bar_dict.has_key (str[:trylen]):
                        s = str[:trylen]
                        if using_old:
-                               bs = "\\bar \"%s\";" % old_bar_dict[s]
+                               bs = "\\bar \"%s\"" % old_bar_dict[s]
                        else:
                                bs = "%s" % bar_dict[s]
                        str = str[trylen:]
                        if s in alternative_opener:
                                if not in_repeat[current_voice_idx]:
                                        using_old = 't'
-                                       bs = "\\bar \"%s\";" % old_bar_dict[s]
+                                       bs = "\\bar \"%s\"" % old_bar_dict[s]
                                else:
                                        doing_alternative[current_voice_idx] = 't'
 
@@ -1006,7 +1063,7 @@ def try_parse_bar (str,state):
                                        if doing_alternative[current_voice_idx]:
                                                do_curly = 't'
                                if using_old:
-                                       bs = "\\bar \"%s\";" % old_bar_dict[s]
+                                       bs = "\\bar \"%s\"" % old_bar_dict[s]
                                else:
                                        bs =  bar_dict[s]
                                doing_alternative[current_voice_idx] = ''
@@ -1014,7 +1071,7 @@ def try_parse_bar (str,state):
                        if s in repeat_opener:
                                in_repeat[current_voice_idx] = 't'
                                if using_old:
-                                       bs = "\\bar \"%s\";" % old_bar_dict[s]
+                                       bs = "\\bar \"%s\"" % old_bar_dict[s]
                                else:
                                        bs =  bar_dict[s]
                        break
@@ -1083,7 +1140,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
 
@@ -1178,8 +1235,7 @@ def parse_file (fn):
                        ln = junk_space (ln)
 
                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')        
@@ -1198,7 +1254,8 @@ Options:
   -h, --help          this help
   -o, --output=FILE   set output filename to FILE
   -v, --version       version information
-
+  -s, --strict        be strict about succes.
+  
 This program converts ABC music files (see
 http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt) To LilyPond input.
 """
@@ -1208,7 +1265,7 @@ def print_version ():
 
 
 
-(options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
+(options, files) = getopt.getopt (sys.argv[1:], 'vo:hs', ['help','version', 'output=', 'strict'])
 out_filename = ''
 
 for opt in options:
@@ -1217,11 +1274,12 @@ 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
        else:
                print o
@@ -1234,12 +1292,12 @@ 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)