]> git.donarmstrong.com Git - lilypond.git/blob - scripts/abc2ly.py
23aa46d3977c9df9c39fd87349449dac56bfbaab
[lilypond.git] / scripts / abc2ly.py
1 #!@PYTHON@
2
3 # once upon a rainy monday afternoon.
4 #
5 #   ...
6 #
7 # (not finished.)
8 # ABC standard v1.6:  http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt
9 #
10 # Enhancements  (Roy R. Rankin)
11 #
12 # Header section moved to top of lilypond file
13 # handle treble, treble-8, alto, and bass clef
14 # Handle voices (V: headers) with clef and part names, multiple voices
15 # Handle w: lyrics with multiple verses
16 # Handle key mode names for minor, major, phrygian, ionian, locrian, aeolian,
17 #     mixolydian, lydian, dorian
18 # Handle part names from V: header
19 # Tuplets handling fixed up
20 # Lines starting with |: not discarded as header lines
21 # Multiple T: and C: header entries handled
22 # Accidental maintained until next bar check
23 # Silent rests supported
24 # articulations fermata, upbow, downbow, ltoe, accent, tenuto supported
25 # Chord strings([-^]"string") can contain a '#'
26 # Header fields enclosed by [] in notes string processed
27 # W: words output after tune as abc2ps does it (they failed before)
28
29 # Enhancements (Laura Conrad)
30 #
31 # Barring now preserved between ABC and lilypond
32 # the default placement for text in abc is above the staff.
33 # %%LY now supported.
34 # \breve and \longa supported.
35 # M:none doesn't crash lily.
36
37 # Limitations
38 #
39 # Multiple tunes in single file not supported
40 # Blank T: header lines should write score and open a new score
41 # Not all header fields supported
42 # ABC line breaks are ignored
43 # Block comments generate error and are ignored
44 # Postscript commands are ignored
45 # lyrics not resynchronized by line breaks (lyrics must fully match notes)
46 # %%LY slyrics can't be directly before a w: line.
47 # ???
48
49
50
51 #TODO:
52 # UNDEF -> None
53  
54   
55   
56 program_name = 'abc2ly'
57 version = '@TOPLEVEL_VERSION@'
58 if version == '@' + 'TOPLEVEL_VERSION' + '@':
59         version = '(unknown version)'           # uGUHGUHGHGUGH
60   
61 import __main__
62 import getopt
63 import sys
64 import re
65 import string
66 import os
67
68
69 UNDEF = 255
70 state = UNDEF
71 strict = 0
72 voice_idx_dict = {}
73 header = {}
74 header['footnotes'] = ''
75 lyrics = []
76 slyrics = []
77 voices = []
78 state_list = []
79 repeat_state = [0] * 8
80 current_voice_idx = -1
81 current_lyric_idx = -1
82 lyric_idx = -1
83 part_names = 0
84 default_len = 8
85 length_specified = 0
86 nobarlines = 0
87 global_key = [0] * 7                    # UGH
88 names = ["One", "Two", "Three"]
89 DIGITS='0123456789'
90 alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"   
91 HSPACE=' \t'
92 midi_specs = ''
93
94
95 def error (msg):
96         sys.stderr.write (msg)
97         if strict:
98                 sys.exit (1)
99         
100         
101 def check_clef(s):
102       if not s:
103               return ''
104       if re.match('-8va', s) or re.match('treble8', s):
105               # treble8 is used by abctab2ps; -8va is used by barfly,
106               # and by my patch to abc2ps. If there's ever a standard
107               # about this we'll support that.
108               s = s[4:]
109               state.base_octave = -1
110               voices_append("\\clef \"G_8\"\n")
111       elif re.match('^treble', s):
112               s = s[6:]
113               if re.match ('^-8', s):
114                       s = s[2:]
115                       state.base_octave = -2
116                       voices_append("\\clef \"G_8\"\n")
117               else:
118                       state.base_octave = 0
119                       voices_append("\\clef treble\n")
120       elif re.match('^alto', s):
121               s = s[4:]
122               state.base_octave = -1
123               voices_append ("\\clef alto\n" )
124       elif re.match('^bass',s ):
125               s = s[4:]
126               state.base_octave = -2
127               voices_append ("\\clef bass\n" )
128       return s
129
130 def select_voice (name, rol):
131         if not voice_idx_dict.has_key (name):
132               state_list.append(Parser_state())
133               voices.append ('')
134               slyrics.append ([])
135               voice_idx_dict[name] = len (voices) -1
136         __main__.current_voice_idx =  voice_idx_dict[name]
137         __main__.state = state_list[current_voice_idx]
138         while rol != '':
139               m = re.match ('^([^ \t=]*)=(.*)$', rol) # find keywork
140               if m:
141                       keyword = m.group(1)
142                       rol = m.group (2)
143                       a = re.match ('^("[^"]*"|[^ \t]*) *(.*)$', rol)
144                       if a:
145                               value = a.group (1)
146                               rol = a.group ( 2)
147                               if keyword == 'clef':
148                                       check_clef(value)
149                               elif keyword == "name":
150                                       value = re.sub ('\\\\','\\\\\\\\', value)
151                                       voices_append ("\\property Staff.instrument = %s\n" % value )
152                                       __main__.part_names = 1
153                               elif keyword == "sname" or keyword == "snm":
154                                       voices_append ("\\property Staff.instr = %s\n" % value )
155
156               else:
157                       break
158
159         return
160
161
162 def dump_header (outf,hdr):
163         outf.write ('\\header {\n')
164         ks = hdr.keys ()
165         ks.sort ()
166         for k in ks:
167                 hdr[k] = re.sub('"', '\\"', hdr[k])             
168                 outf.write ('\t%s = "%s"\n'% (k,hdr[k]))
169         outf.write ('}')
170
171 def dump_lyrics (outf):
172         if (len(lyrics)):
173                 outf.write("\n\\score\n{\n    \\context Lyrics\n    <\n")
174                 for i in range (len (lyrics)):
175                         outf.write ( lyrics [i])
176                         outf.write ("\n")
177                 outf.write("    >\n    \\paper{}\n}\n")
178
179 def dump_default_bar (outf):
180         outf.write ("\n\\property Score.defaultBarType=\"empty\"\n")
181
182
183 def dump_slyrics (outf):
184         ks = voice_idx_dict.keys()
185         ks.sort ()
186         for k in ks:
187                 if re.match('[1-9]', k):
188                         m = alphabet[string.atoi(k)]
189                 else:
190                         m = k
191                 for i in range (len(slyrics[voice_idx_dict[k]])):
192                         l=alphabet[i]
193                         outf.write ("\nwords%sV%s = \\lyrics  {" % (m, l))
194                         outf.write ("\n" + slyrics [voice_idx_dict[k]][i])
195                         outf.write ("\n}")
196
197 def dump_voices (outf):
198         global doing_alternative, in_repeat
199         ks = voice_idx_dict.keys()
200         ks.sort ()
201         for k in ks:
202                 if re.match ('[1-9]', k):
203                         m = alphabet[string.atoi(k)]
204                 else:
205                         m = k
206                 outf.write ("\nvoice%s = \\notes {" % m)
207                 dump_default_bar(outf)
208                 if repeat_state[voice_idx_dict[k]]:
209                         outf.write("\n\\repeat volta 2 {")
210                 outf.write ("\n" + voices [voice_idx_dict[k]])
211                 if not using_old:
212                         if doing_alternative[voice_idx_dict[k]]:
213                                 outf.write("}")
214                         if in_repeat[voice_idx_dict[k]]:
215                                 outf.write("}")
216                 outf.write ("\n}")
217
218 def try_parse_q(a):
219         global midi_specs
220         #assume that Q takes the form "Q:1/4=120"
221         #There are other possibilities, but they are deprecated
222         if string.count(a, '/') == 1:
223                 array=string.split(a,'/')
224                 numerator=array[0]
225                 if numerator != 1:
226                         sys.stderr.write("abc2ly: Warning, unable to translate a Q specification with a numerator of %s: %s\n" % (numerator, a))
227                 array2=string.split(array[1],'=')
228                 denominator=array2[0]
229                 perminute=array2[1]
230                 duration=str(string.atoi(denominator)/string.atoi(numerator))
231                 midi_specs=string.join(["\\tempo", duration, "=", perminute])
232         else:
233                 sys.stderr.write("abc2ly: Warning, unable to parse Q specification: %s\n" % a)
234         
235 def dump_score (outf):
236         outf.write (r"""\score{
237         \notes <
238 """)
239
240         ks  = voice_idx_dict.keys ();
241         ks.sort ()
242         for k in  ks:
243                 if re.match('[1-9]', k):
244                         m = alphabet[string.atoi(k)]
245                 else:
246                         m = k
247                 if k == 'default' and len (voice_idx_dict) > 1:
248                         break
249                 if len ( slyrics [voice_idx_dict[k]] ):
250                         outf.write ("\n        \\addlyrics")
251                 outf.write ("\n\t\\context Staff=\"%s\"\n\t{\n" %k ) 
252                 if k != 'default':
253                         outf.write ("\t    \\voicedefault\n")
254                 outf.write ("\t    \\voice%s " % m)
255                 outf.write ("\n\t}\n")
256                 if len ( slyrics [voice_idx_dict[k]] ):
257                         outf.write ("\n\t\\context Lyrics=\"%s\" \n\t<\t" % k)
258                         if re.match('[1-9]',k):
259                                 m = alphabet[string.atoi(k)]
260                         else:
261                                 m = k
262                         for i in range (len(slyrics[voice_idx_dict[k]])):
263                                 l=alphabet[i]
264                                 outf.write("\n\t  { \\words%sV%s }" % ( m, l) )
265                         outf.write ( "\n\t>\n" )
266         outf.write ("\n    >")
267         outf.write ("\n\t\\paper {\n")
268         if part_names:
269                 outf.write ("\t    \\translator \n\t    {\n")
270                 outf.write ("\t\t\\StaffContext\n")
271 #               outf.write ("\t\t\\consists Staff_margin_engraver\n")
272                 outf.write ("\t    }\n")
273         outf.write ("\t}\n\t\\midi {%s}\n}\n" % midi_specs)
274
275
276
277 def set_default_length (s):
278         global length_specified
279         m =  re.search ('1/([0-9]+)', s)
280         if m:
281                 __main__.default_len = string.atoi ( m.group (1))
282                 length_specified = 1
283
284 def set_default_len_from_time_sig (s):
285         m =  re.search ('([0-9]+)/([0-9]+)', s)
286         if m:
287                 n = string.atoi (m.group (1))
288                 d = string.atoi (m.group (2))
289                 if (n * 1.0 )/(d * 1.0) <  0.75:
290                         __main__.default_len =  16
291                 else:
292                         __main__.default_len = 8
293
294 def gulp_file(f):
295         try:
296                 i = open(f)
297                 i.seek (0, 2)
298                 n = i.tell ()
299                 i.seek (0,0)
300         except:
301                 sys.stderr.write ("can't open file: `%s'\n" % f)
302                 return ''
303         s = i.read (n)
304         if len (s) <= 0:
305                 sys.stderr.write ("gulped empty file: `%s'\n" % f)
306         i.close ()
307         return s
308
309
310 # pitch manipulation. Tuples are (name, alteration).
311 # 0 is (central) C. Alteration -1 is a flat, Alteration +1 is a sharp
312 # pitch in semitones. 
313 def semitone_pitch  (tup):
314         p =0
315
316         t = tup[0]
317         p = p + 12 * (t / 7)
318         t = t % 7
319         
320         if t > 2:
321                 p = p- 1
322                 
323         p = p + t* 2 + tup[1]
324         return p
325
326 def fifth_above_pitch (tup):
327         (n, a)  = (tup[0] + 4, tup[1])
328
329         difference = 7 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
330         a = a + difference
331         
332         return (n,a)
333
334 def sharp_keys ():
335         p = (0,0)
336         l = []
337         k = 0
338         while 1:
339                 l.append (p)
340                 (t,a) = fifth_above_pitch (p)
341                 if semitone_pitch((t,a)) % 12 == 0:
342                         break
343
344                 p = (t % 7, a)
345         return l
346
347 def flat_keys ():
348         p = (0,0)
349         l = []
350         k = 0
351         while 1:
352                 l.append (p)
353                 (t,a) = quart_above_pitch (p)
354                 if semitone_pitch((t,a)) % 12 == 0:
355                         break
356
357                 p = (t % 7, a)
358         return l
359
360 def quart_above_pitch (tup):
361         (n, a)  = (tup[0] + 3, tup[1])
362
363         difference = 5 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
364         a = a + difference
365         
366         return (n,a)
367
368 key_lookup = {  # abc to lilypond key mode names
369         'm'   : 'minor',
370         'min' : 'minor',
371         'maj' : 'major',
372         'major' : 'major',      
373         'phr' : 'phrygian',
374         'ion' : 'ionian',
375         'loc' : 'locrian',
376         'aeo' : 'aeolian',
377         'mix' : 'mixolydian',
378         'mixolydian' : 'mixolydian',    
379         'lyd' : 'lydian',
380         'dor' : 'dorian',
381         'dorian' : 'dorian'     
382 }
383
384 def lily_key (k):
385         k = string.lower (k)
386         key = k[0]
387         k = k[1:]
388         if k and k[0] == '#':
389                 key = key + 'is'
390                 k = k[1:]
391         elif k and k[0] == 'b':
392                 key = key + 'es'
393                 k = k[1:]
394         if not k:
395                 return '%s \\major' % key
396
397         type = k[0:3]
398         if key_lookup.has_key(type):
399                 return("%s \\%s" % ( key, key_lookup[type]))
400         sys.stderr.write("Unknown key type `%s' ignored\n" % type)
401         return key
402
403 def shift_key (note, acc , shift):
404         s = semitone_pitch((note, acc))
405         s = (s + shift + 12) % 12
406         if s <= 4:
407                 n = s / 2
408                 a = s % 2
409         else:
410                 n = (s + 1) / 2
411                 a = (s + 1) % 2
412         if a:
413                 n = n + 1
414                 a = -1
415         return (n,a)
416
417 key_shift = { # semitone shifts for key mode names
418         'm'   : 3,
419         'min' : 3,
420         'minor' : 3,
421         'maj' : 0,
422         'major' : 0,    
423         'phr' : -4,
424         'phrygian' : -4,
425         'ion' : 0,
426         'ionian' : 0,
427         'loc' : 1,
428         'locrian' : 1,  
429         'aeo' : 3,
430         'aeolian' : 3,
431         'mix' : 5,
432         'mixolydian' : 5,       
433         'lyd' : -5,
434         'lydian' : -5,  
435         'dor' : -2,
436         'dorian' :      -2      
437 }
438 def compute_key (k):
439         k = string.lower (k)
440         intkey = (ord (k[0]) - ord('a') + 5) % 7
441         intkeyacc =0
442         k = k[1:]
443         
444         if k and k[0] == 'b':
445                 intkeyacc = -1
446                 k = k[1:]
447         elif  k and k[0] == '#':
448                 intkeyacc = 1
449                 k = k[1:]
450         k = k[0:3]
451         if k and key_shift.has_key(k):
452                 (intkey, intkeyacc) = shift_key(intkey, intkeyacc, key_shift[k])
453         keytup = (intkey, intkeyacc)
454         
455         sharp_key_seq = sharp_keys ()
456         flat_key_seq = flat_keys ()
457
458         accseq = None
459         accsign = 0
460         if keytup in sharp_key_seq:
461                 accsign = 1
462                 key_count = sharp_key_seq.index (keytup)
463                 accseq = map (lambda x: (4*x -1 ) % 7, range (1, key_count + 1))
464
465         elif keytup in flat_key_seq:
466                 accsign = -1
467                 key_count = flat_key_seq.index (keytup)
468                 accseq = map (lambda x: (3*x + 3 ) % 7, range (1, key_count + 1))
469         else:
470                 error ("Huh?")
471                 raise "Huh"
472         
473         key_table = [0] * 7
474         for a in accseq:
475                  key_table[a] = key_table[a] + accsign
476
477         return key_table
478
479 tup_lookup = {
480         '2' : '3/2',
481         '3' : '2/3',
482         '4' : '4/3',
483         '5' : '4/5',
484         '6' : '4/6',
485         '7' : '6/7',
486         '9' : '8/9',
487         }
488
489
490 def try_parse_tuplet_begin (str, state):
491         if re.match ('\([2-9]', str):
492                 dig = str[1]
493                 str = str[2:]
494                 prev_tuplet_state = state.parsing_tuplet
495                 state.parsing_tuplet = string.atoi (dig[0])
496                 if prev_tuplet_state:
497                         voices_append ("}")             
498                 voices_append ("\\times %s {" % tup_lookup[dig])
499         return str
500
501 def  try_parse_group_end (str, state):
502         if str and str[0] in HSPACE:
503                 str = str[1:]
504         return str
505         
506 def header_append (key, a):
507         s = ''
508         if header.has_key (key):
509                 s = header[key] + "\n"
510                 header [key] = s + a
511
512 def wordwrap(a, v):
513         linelen = len (v) - string.rfind(v, '\n')
514         if linelen + len (a) > 80:
515                 v = v + '\n'
516         return v + a + ' '
517
518 def stuff_append (stuff, idx, a):
519         if not stuff:
520                 stuff.append (a)
521         else:
522                 stuff [idx] = wordwrap(a, stuff[idx])
523
524
525
526 def voices_append(a):
527         if current_voice_idx < 0:
528                 select_voice ('default', '')
529         stuff_append (voices, current_voice_idx, a)
530
531 def repeat_prepend():
532         global repeat_state
533         if current_voice_idx < 0:
534                 select_voice ('default', '')
535         if not using_old:
536                 repeat_state[current_voice_idx] = 't'
537
538         
539 def lyrics_append(a):
540         a = re.sub ( '#', '\\#', a)     # latex does not like naked #'s
541         a = re.sub ( '"', '\\"', a)     # latex does not like naked "'s
542         a = '\t{ \\lyrics "' + a + '" }\n'
543         stuff_append (lyrics, current_lyric_idx, a)
544
545 # break lyrics to words and put "'s around words containing numbers and '"'s
546 def fix_lyric(str):
547         ret = ''
548         while str != '':
549                 m = re.match('[ \t]*([^ \t]*)[ \t]*(.*$)', str)
550                 if m:
551                         word = m.group(1)
552                         str = m.group(2)
553                         word = re.sub('"', '\\"', word) # escape "
554                         if re.match('.*[0-9"\(]', word):
555                                 word = re.sub('_', ' ', word) # _ causes probs inside ""
556                                 ret = ret + '\"' + word + '\" '
557                         else:
558                                 ret = ret + word + ' '
559                 else:
560                         return (ret)
561         return (ret)
562
563 def slyrics_append(a):
564         a = re.sub ( '_', ' _ ', a)     # _ to ' _ '
565         a = re.sub ( '-', '- ', a)      # split words with -
566         a = re.sub ( '\\\\- ', '-', a)  # unless \-
567         a = re.sub ( '~', '_', a)       # ~ to space('_')
568         a = re.sub ( '\*', '_ ', a)     # * to to space
569         a = re.sub ( '#', '\\#', a)     # latex does not like naked #'s
570         if re.match('.*[0-9"\(]', a):   # put numbers and " and ( into quoted string
571                 a = fix_lyric(a)
572         a = re.sub ( '$', ' ', a)       # insure space between lines
573         __main__.lyric_idx = lyric_idx + 1
574         if len(slyrics[current_voice_idx]) <= lyric_idx:
575                 slyrics[current_voice_idx].append(a)
576         else:
577                 v = slyrics[current_voice_idx][lyric_idx]
578                 slyrics[current_voice_idx][lyric_idx] = wordwrap(a, slyrics[current_voice_idx][lyric_idx])
579
580
581 def try_parse_header_line (ln, state):
582         global length_specified
583         m = re.match ('^([A-Za-z]): *(.*)$', ln)
584
585         if m:
586                 g =m.group (1)
587                 a = m.group (2)
588                 if g == 'T':    #title
589                         a = re.sub('[ \t]*$','', a)     #strip trailing blanks
590                         if header.has_key('title'):
591                                 if a:
592                                         if len(header['title']):
593                                                 header['title'] = header['title'] + '\\\\\\\\' + a
594                                         else:
595                                                 header['subtitle'] = a
596                         else:
597                                 header['title'] =  a
598                 if g == 'M':    # Meter
599                         if a == 'C':
600                                 if not state.common_time:
601                                         state.common_time = 1
602                                         voices_append ("\\property Staff.TimeSignature \\override #\'style = #'C\n")
603                                 a = '4/4'
604                         if a == 'C|':
605                                 if not state.common_time:
606                                         state.common_time = 1
607                                         voices_append ("\\property Staff.TimeSignature \\override #\'style = #'C\n")
608                                 a = '2/2'
609                         if not length_specified:
610                                 set_default_len_from_time_sig (a)
611                         else:
612                                 length_specified = 0
613                         if not a == 'none':
614                                 voices_append ('\\time %s' % a)
615                         state.next_bar = ''
616                 if g == 'K': # KEY
617                         a = check_clef(a)
618                         if a:
619                                 m = re.match ('^([^ \t]*) *(.*)$', a) # seperate clef info
620                                 if m:
621                                         # there may or may not be a space
622                                         # between the key letter and the mode
623                                         if key_lookup.has_key(m.group(2)[0:3]):
624                                                 key_info = m.group(1) + m.group(2)[0:3]
625                                                 clef_info = m.group(2)[4:]
626                                         else:
627                                                 key_info = m.group(1)
628                                                 clef_info = m.group(2)
629                                         __main__.global_key  = compute_key (key_info)# ugh.
630                                         voices_append ('\\key %s' % lily_key(key_info))
631                                         check_clef(clef_info)
632                                 else:
633                                         __main__.global_key  = compute_key (a)# ugh.
634                                         voices_append ('\\key %s \\major' % lily_key(a))
635                 if g == 'N': # Notes
636                         header ['footnotes'] = header['footnotes'] +  '\\\\\\\\' + a
637                 if g == 'O': # Origin
638                         header ['origin'] = a
639                 if g == 'X': # Reference Number
640                         header ['crossRefNumber'] = a
641                 if g == 'A': #  Area
642                         header ['area'] = a
643                 if g == 'H':    # History
644                         header_append ('history', a)
645                 if g == 'B':    # Book
646                         header ['book'] = a
647                 if g == 'C':    # Composer
648                         if header.has_key('composer'):
649                                 if a:
650                                         header['composer'] = header['composer'] + '\\\\\\\\' + a
651                         else:
652                                 header['composer'] =  a
653                 if g == 'S':
654                         header ['subtitle'] = a
655                 if g == 'L':    # Default note length
656                         set_default_length (ln)
657                 if g == 'V':    # Voice 
658                         voice = re.sub (' .*$', '', a)
659                         rest = re.sub ('^[^ \t]*  *', '', a)
660                         if state.next_bar:
661                                 voices_append(state.next_bar)
662                                 state.next_bar = ''
663                         select_voice (voice, rest)
664                 if g == 'W':    # Words
665                         lyrics_append(a)
666                 if g == 'w':    # vocals
667                         slyrics_append (a)
668                 if g == 'Q':    #tempo
669                         try_parse_q (a)
670                 return ''
671         return ln
672
673 # we use in this order specified accidental, active accidental for bar,
674 # active accidental for key
675 def pitch_to_lilypond_name (name, acc, bar_acc, key):
676         s = ''
677         if acc == UNDEF:
678                 if not nobarlines:
679                         acc = bar_acc
680         if acc == UNDEF:
681                 acc = key
682         if acc == -1:
683                 s = 'es'
684         elif acc == 1:
685                 s =  'is'
686         
687         if name > 4:
688                 name = name -7
689         return(chr (name  + ord('c')) + s)
690
691
692 def octave_to_lilypond_quotes (o):
693         o = o + 2
694         s =''
695         if o < 0:
696                 o = -o
697                 s=','
698         else:
699                 s ='\''
700
701         return s * o
702
703 def parse_num (str):
704         durstr = ''
705         while str and str[0] in DIGITS:
706                 durstr = durstr + str[0]
707                 str = str[1:]
708
709         n = None
710         if durstr:
711                 n  =string.atoi (durstr) 
712         return (str,n)
713
714
715 def duration_to_lilypond_duration  (multiply_tup, defaultlen, dots):
716         base = 1
717         # (num /  den)  / defaultlen < 1/base
718         while base * multiply_tup[0] < multiply_tup[1]:
719                 base = base * 2
720         if base == 1:
721                 if (multiply_tup[0] / multiply_tup[1])  == 2:
722                         base = '\\breve'
723                 if (multiply_tup[0] / multiply_tup[1]) == 3:
724                         base = '\\breve'
725                         dots = 1
726                 if (multiply_tup[0] / multiply_tup[1]) == 4:
727                         base = '\longa'
728         return '%s%s' % ( base, '.'* dots)
729
730 class Parser_state:
731         def __init__ (self):
732                 self.in_acc = {}
733                 self.next_articulation = ''
734                 self.next_bar = ''
735                 self.next_dots = 0
736                 self.next_den = 1
737                 self.parsing_tuplet = 0
738                 self.plus_chord = 0
739                 self.base_octave = 0
740                 self.common_time = 0
741
742
743
744 # return (str, num,den,dots) 
745 def parse_duration (str, parser_state):
746         num = 0
747         den = parser_state.next_den
748         parser_state.next_den = 1
749
750         (str, num) = parse_num (str)
751         if not num:
752                 num = 1
753         if len(str):
754                 if str[0] == '/':
755                         if len(str[0]):
756                                 while str[:1] == '/':
757                                         str= str[1:]
758                                         d = 2
759                                         if str[0] in DIGITS:
760                                                 (str, d) =parse_num (str)
761
762                                         den = den * d
763
764         den = den * default_len
765         
766         current_dots = parser_state.next_dots
767         parser_state.next_dots = 0
768         if re.match ('[ \t]*[<>]', str):
769                 while str[0] in HSPACE:
770                         str = str[1:]
771                 while str[0] == '>':
772                         str = str [1:]
773                         current_dots = current_dots + 1
774                         parser_state.next_den = parser_state.next_den * 2
775                 
776                 while str[0] == '<':
777                         str = str [1:]
778                         den = den * 2
779                         parser_state.next_dots = parser_state.next_dots + 1
780
781
782
783         try_dots = [3, 2, 1]
784         for d in try_dots:
785                 f = 1 << d
786                 multiplier = (2*f-1)
787                 if num % multiplier == 0 and den % f == 0:
788                         num = num / multiplier
789                         den = den / f
790                         current_dots = current_dots + d
791                 
792         return (str, num,den,current_dots)
793
794
795 def try_parse_rest (str, parser_state):
796         if not str or str[0] <> 'z' and str[0] <> 'x':
797                 return str
798
799         __main__.lyric_idx = -1
800
801         if parser_state.next_bar:
802                 voices_append(parser_state.next_bar)
803                 parser_state.next_bar = ''
804
805         if str[0] == 'z':
806                 rest = 'r'
807         else:
808                 rest = 's'
809         str = str[1:]
810
811         (str, num,den,d) = parse_duration (str, parser_state)
812         voices_append ('%s%s' % (rest, duration_to_lilypond_duration ((num,den), default_len, d)))
813         if parser_state.next_articulation:
814                 voices_append (parser_state.next_articulation)
815                 parser_state.next_articulation = ''
816
817         return str
818
819 artic_tbl = {
820         '.'  : '-.',
821          'T' : '^\\trill',
822          'H' : '^\\fermata',
823          'u' : '^\\upbow',
824          'K' : '^\\ltoe',
825          'k' : '^\\accent',
826          'M' : '^\\tenuto',
827          '~' : '^"~" ',
828          'J' : '',              # ignore slide
829          'R' : '',              # ignore roll
830          'v' : '^\\downbow'
831 }
832         
833 def try_parse_articulation (str, state):
834         while str and  artic_tbl.has_key(str[:1]):
835                 state.next_articulation = state.next_articulation + artic_tbl[str[:1]]
836                 if not artic_tbl[str[:1]]:
837                         sys.stderr.write("Warning: ignoring `%s'\n" % str[:1] )
838
839                 str = str[1:]
840
841         
842                 
843         # s7m2 input doesnt care about spaces
844         if re.match('[ \t]*\(', str):
845                 str = string.lstrip (str)
846
847         slur_begin =0
848         while str[:1] =='(' and str[1] not in DIGITS:
849                 slur_begin = slur_begin + 1
850                 state.next_articulation = state.next_articulation + '('
851                 str = str[1:]
852
853         return str
854                 
855 #
856 # remember accidental for rest of bar
857 #
858 def set_bar_acc(note, octave, acc, state):
859         if acc == UNDEF:
860                 return
861         n_oct = note + octave * 7
862         state.in_acc[n_oct] = acc
863
864 # get accidental set in this bar or UNDEF if not set
865 def get_bar_acc(note, octave, state):
866         n_oct = note + octave * 7
867         if state.in_acc.has_key(n_oct):
868                 return(state.in_acc[n_oct])
869         else:
870                 return(UNDEF)
871
872 def clear_bar_acc(state):
873         for k in state.in_acc.keys():
874                 del state.in_acc[k]
875         
876
877                 
878 # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP  !
879 def try_parse_note (str, parser_state):
880         mud = ''
881
882         slur_begin =0
883         if not str:
884                 return str
885
886         articulation =''
887         acc = UNDEF
888         if str[0] in '^=_':
889                 c = str[0]
890                 str = str[1:]
891                 if c == '^':
892                         acc = 1
893                 if c == '=':
894                         acc = 0
895                 if c == '_':
896                         acc = -1
897
898         octave = parser_state.base_octave
899         if str[0] in "ABCDEFG":
900                 str = string.lower (str[0]) + str[1:]
901                 octave = octave - 1
902
903
904         notename = 0
905         if str[0] in "abcdefg":
906                 notename = (ord(str[0]) - ord('a') + 5)%7
907                 str = str[1:]
908         else:
909                 return str              # failed; not a note!
910
911         
912         __main__.lyric_idx = -1
913
914         if parser_state.next_bar:
915                 voices_append(parser_state.next_bar)
916                 parser_state.next_bar = ''
917
918         while str[0] == ',':
919                  octave = octave - 1
920                  str = str[1:]
921         while str[0] == '\'':
922                  octave = octave + 1
923                  str = str[1:]
924
925         (str, num,den,current_dots) = parse_duration (str, parser_state)
926
927
928         if re.match('[ \t]*\)', str):
929                 str = string.lstrip (str)
930         
931         slur_end =0
932         while str[:1] ==')':
933                 slur_end = slur_end + 1
934                 str = str[1:]
935
936         
937         if slur_end:
938                 voices_append ('%s' % ')' *slur_end )
939
940         bar_acc = get_bar_acc(notename, octave, parser_state)
941         pit = pitch_to_lilypond_name(notename, acc, bar_acc, global_key[notename])
942         oct = octave_to_lilypond_quotes (octave)
943         if acc != UNDEF and (acc == global_key[notename] or acc == bar_acc):
944                 mod='!'
945         else:
946                 mod = ''
947         voices_append ("%s%s%s%s" %
948                 (pit, oct, mod,
949                  duration_to_lilypond_duration ((num,den), default_len, current_dots)))
950         
951         set_bar_acc(notename, octave, acc, parser_state)
952         if parser_state.next_articulation:
953                 articulation = articulation + parser_state.next_articulation
954                 parser_state.next_articulation = ''
955
956         voices_append (articulation)
957
958         if parser_state.parsing_tuplet:
959                 parser_state.parsing_tuplet = parser_state.parsing_tuplet - 1
960                 if not parser_state.parsing_tuplet:
961                         voices_append ("}")
962         if slur_begin:
963                 voices_append ('%s' % '(' * slur_begin )
964
965
966         return str
967
968 def junk_space (str):
969         while str and str[0] in '\t\n ':
970                 str = str[1:]
971
972         return str
973
974
975 def try_parse_guitar_chord (str, state):
976         if str[:1] =='"':
977                 str = str[1:]
978                 gc = ''
979                 if str[0] == '_' or (str[0] == '^'):
980                         position = str[0]
981                         str = str[1:]
982                 else:
983                         position = '^'
984                 while str and str[0] != '"':
985                         gc = gc + str[0]
986                         str = str[1:]
987                         
988                 if str:
989                         str = str[1:]
990                 gc = re.sub('#', '\\#', gc)     # escape '#'s
991                 state.next_articulation = ("%c\"%s\"" % (position ,gc)) + state.next_articulation
992         return str
993
994 def try_parse_escape (str):
995         if not str or str [0] != '\\':
996                 return str
997         
998         str = str[1:]
999         if str[:1] =='K':
1000                 key_table = compute_key ()
1001         return str
1002
1003 #
1004 # |] thin-thick double bar line
1005 # || thin-thin double bar line
1006 # [| thick-thin double bar line
1007 # :| left repeat
1008 # |: right repeat
1009 # :: left-right repeat
1010 # |1 volta 1
1011 # |2 volta 2
1012 old_bar_dict = {
1013 '|]' : '|.',
1014 '||' : '||',
1015 '[|' : '||',
1016 ':|' : ':|',
1017 '|:' : '|:',
1018 '::' : ':|:',
1019 '|1' : '|',
1020 '|2' : '|',
1021 ':|2' : ':|',
1022 '|' :  '|'
1023 }
1024 bar_dict = {
1025  '|]' : '\\bar "|."',
1026  '||' : '\\bar "||"',
1027  '[|' : '\\bar "||"',
1028  ':|' : '}',
1029  '|:' : '\\repeat volta 2 {',
1030  '::' : '} \\repeat volta 2 {',
1031  '|1' : '} \\alternative{{',
1032  '|2' : '} {',
1033  ':|2' : '} {',
1034  '|' :  '\\bar "|"'
1035   }
1036
1037
1038 warn_about = ['|:', '::', ':|', '|1', ':|2', '|2']
1039 alternative_opener = ['|1', '|2', ':|2']
1040 repeat_ender = ['::', ':|']
1041 repeat_opener = ['::', '|:']
1042 in_repeat = [''] * 8
1043 doing_alternative = [''] * 8
1044 using_old = ''
1045
1046 def try_parse_bar (str,state):
1047         global in_repeat, doing_alternative, using_old
1048         do_curly = ''
1049         bs = None
1050         if current_voice_idx < 0:
1051                 select_voice ('default', '')
1052         # first try the longer one
1053         for trylen in [3,2,1]:
1054                 if str[:trylen] and bar_dict.has_key (str[:trylen]):
1055                         s = str[:trylen]
1056                         if using_old:
1057                                 bs = "\\bar \"%s\"" % old_bar_dict[s]
1058                         else:
1059                                 bs = "%s" % bar_dict[s]
1060                         str = str[trylen:]
1061                         if s in alternative_opener:
1062                                 if not in_repeat[current_voice_idx]:
1063                                         using_old = 't'
1064                                         bs = "\\bar \"%s\"" % old_bar_dict[s]
1065                                 else:
1066                                         doing_alternative[current_voice_idx] = 't'
1067
1068                         if s in repeat_ender:
1069                                 if not in_repeat[current_voice_idx]:
1070                                         sys.stderr.write("Warning: inserting repeat to beginning of notes.\n")
1071                                         repeat_prepend()
1072                                         in_repeat[current_voice_idx] = ''
1073                                 else:
1074                                         if doing_alternative[current_voice_idx]:
1075                                                 do_curly = 't'
1076                                 if using_old:
1077                                         bs = "\\bar \"%s\"" % old_bar_dict[s]
1078                                 else:
1079                                         bs =  bar_dict[s]
1080                                 doing_alternative[current_voice_idx] = ''
1081                                 in_repeat[current_voice_idx] = ''
1082                         if s in repeat_opener:
1083                                 in_repeat[current_voice_idx] = 't'
1084                                 if using_old:
1085                                         bs = "\\bar \"%s\"" % old_bar_dict[s]
1086                                 else:
1087                                         bs =  bar_dict[s]
1088                         break
1089         if str[:1] == '|':
1090                 state.next_bar = '|\n'
1091                 str = str[1:]
1092                 clear_bar_acc(state)
1093         
1094         if bs <> None or state.next_bar != '':
1095                 if state.parsing_tuplet:
1096                         state.parsing_tuplet =0
1097                         voices_append ('} ')
1098                 
1099         if bs <> None:
1100                 clear_bar_acc(state)
1101                 voices_append (bs)
1102                 if do_curly != '':
1103                         voices_append("} }")
1104                         do_curly = ''
1105         return str
1106
1107 def try_parse_tie (str):
1108         if str[:1] =='-':
1109                 str = str[1:]
1110                 voices_append (' ~ ')
1111         return str
1112
1113 def bracket_escape (str, state):
1114         m = re.match ( '^([^\]]*)] *(.*)$', str)
1115         if m:
1116                 cmd = m.group (1)
1117                 str = m.group (2)
1118                 try_parse_header_line (cmd, state)
1119         return str
1120
1121 def try_parse_chord_delims (str, state):
1122         if str[:1] =='[':
1123                 str = str[1:]
1124                 if re.match('[A-Z]:', str):     # bracket escape
1125                         return bracket_escape(str, state)
1126                 if state.next_bar:
1127                         voices_append(state.next_bar)
1128                         state.next_bar = ''
1129                 voices_append ('<')
1130
1131         if str[:1] == '+':
1132                 str = str[1:]
1133                 if state.plus_chord:
1134                         voices_append ('>')
1135                         state.plus_chord = 0
1136                 else:
1137                         if state.next_bar:
1138                                 voices_append(state.next_bar)
1139                                 state.next_bar = ''
1140                         voices_append ('<')
1141                         state.plus_chord = 1
1142
1143         ch = ''
1144         if str[:1] ==']':
1145                 str = str[1:]
1146                 ch = '>'
1147
1148         end = 0
1149         while str[:1] ==')':
1150                 end = end + 1
1151                 str = str[1:]
1152
1153         
1154         voices_append ("\\spanrequest \\stop \"slur\"" * end)
1155         voices_append (ch)
1156         return str
1157
1158 def try_parse_grace_delims (str, state):
1159         if str[:1] =='{':
1160                 if state.next_bar:
1161                         voices_append(state.next_bar)
1162                         state.next_bar = ''
1163                 str = str[1:]
1164                 voices_append ('\\grace { ')
1165
1166         if str[:1] =='}':
1167                 str = str[1:]
1168                 voices_append ('}')
1169
1170         return str
1171
1172 def try_parse_comment (str):
1173         global nobarlines
1174         if (str[0] == '%'):
1175                 if str[0:5] == '%MIDI':
1176 #the nobarlines option is necessary for an abc to lilypond translator for
1177 #exactly the same reason abc2midi needs it: abc requires the user to enter
1178 #the note that will be printed, and MIDI and lilypond expect entry of the
1179 #pitch that will be played.
1180 #
1181 #In standard 19th century musical notation, the algorithm for translating
1182 #between printed note and pitch involves using the barlines to determine
1183 #the scope of the accidentals.
1184 #
1185 #Since ABC is frequently used for music in styles that do not use this
1186 #convention, such as most music written before 1700, or ethnic music in
1187 #non-western scales, it is necessary to be able to tell a translator that
1188 #the barlines should not affect its interpretation of the pitch.  
1189                         if (string.find(str,'nobarlines') > 0):
1190                                 nobarlines = 1
1191                 elif str[0:3] == '%LY':
1192                         p = string.find(str, 'voices')
1193                         if (p > -1):
1194                                 voices_append(str[p+7:])
1195                                 voices_append("\n")
1196                         p = string.find(str, 'slyrics')
1197                         if (p > -1):
1198                                 slyrics_append(str[p+8:])
1199                         
1200 #write other kinds of appending  if we ever need them.                  
1201         return str
1202
1203 happy_count = 100
1204 def parse_file (fn):
1205         f = open (fn)
1206         ls = f.readlines ()
1207
1208         select_voice('default', '')
1209         lineno = 0
1210         sys.stderr.write ("Line ... ")
1211         sys.stderr.flush ()
1212         __main__.state = state_list[current_voice_idx]
1213         
1214         for ln in ls:
1215                 lineno = lineno + 1
1216
1217                 if not (lineno % happy_count):
1218                         sys.stderr.write ('[%d]'% lineno)
1219                         sys.stderr.flush ()
1220                 m = re.match  ('^([^%]*)%(.*)$',ln)  # add comments to current voice
1221                 if m:
1222                         if m.group(2):
1223                                 try_parse_comment(m.group(2))
1224                                 voices_append ('%% %s\n' % m.group(2))
1225                         ln = m.group (1)
1226
1227                 orig_ln = ln
1228                 
1229                 ln = try_parse_header_line (ln, state)
1230
1231                 # Try nibbling characters off until the line doesn't change.
1232                 prev_ln = ''
1233                 while ln != prev_ln:
1234                         prev_ln = ln
1235                         ln = try_parse_chord_delims (ln, state)
1236                         ln = try_parse_rest (ln, state)
1237                         ln = try_parse_articulation (ln,state)
1238                         ln = try_parse_note  (ln, state)
1239                         ln = try_parse_bar (ln, state)
1240                         ln = try_parse_tie (ln)
1241                         ln = try_parse_escape (ln)
1242                         ln = try_parse_guitar_chord (ln, state)
1243                         ln = try_parse_tuplet_begin (ln, state)
1244                         ln = try_parse_group_end (ln, state)
1245                         ln = try_parse_grace_delims (ln, state)
1246                         ln = junk_space (ln)
1247
1248                 if ln:
1249                         error ("%s: %d: Huh?  Don't understand\n" % (fn, lineno))
1250                         left = orig_ln[0:-len (ln)]
1251                         sys.stderr.write (left + '\n')
1252                         sys.stderr.write (' ' *  len (left) + ln + '\n')        
1253
1254
1255 def identify():
1256         sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
1257
1258 def help ():
1259         print r"""
1260 Convert ABC to lilypond.
1261
1262 Usage: abc2ly [OPTIONS]... ABC-FILE
1263
1264 Options:
1265   -h, --help          this help
1266   -o, --output=FILE   set output filename to FILE
1267   -v, --version       version information
1268   -s, --strict        be strict about succes.
1269   
1270 This program converts ABC music files (see
1271 http://www.gre.ac.uk/~c.walshaw/abc2mtex/abc.txt) To LilyPond input.
1272 """
1273
1274 def print_version ():
1275         print r"""abc2ly (GNU lilypond) %s""" % version
1276
1277
1278
1279 (options, files) = getopt.getopt (sys.argv[1:], 'vo:hs', ['help','version', 'output=', 'strict'])
1280 out_filename = ''
1281
1282 for opt in options:
1283         o = opt[0]
1284         a = opt[1]
1285         if o== '--help' or o == '-h':
1286                 help ()
1287                 sys.exit (0)
1288         elif o == '--version' or o == '-v':
1289                 print_version ()
1290                 sys.exit(0)
1291         elif o == '--strict' or o == '-s':
1292                 strict = 1
1293         elif o == '--output' or o == '-o':
1294                 out_filename = a
1295         else:
1296                 print o
1297                 raise getopt.error
1298
1299 identify()
1300
1301 header['tagline'] = 'Lily was here %s -- automatically converted from ABC' % version
1302 for f in files:
1303         if f == '-':
1304                 f = ''
1305
1306         sys.stderr.write ('Parsing `%s\'...\n' % f)
1307         parse_file (f)
1308
1309         if not out_filename:
1310                 out_filename = os.path.basename (os.path.splitext (f)[0]) + ".ly"
1311         sys.stderr.write ('lilypond output to: `%s\'...' % out_filename)
1312         outf = open (out_filename, 'w')
1313
1314 #       dump_global (outf)
1315         dump_header (outf ,header)
1316         dump_slyrics (outf)
1317         dump_voices (outf)
1318         dump_score (outf)
1319         dump_lyrics (outf)
1320         sys.stderr.write ('\n')
1321