]> git.donarmstrong.com Git - lilypond.git/blob - scripts/abc2ly.py
bddb9116c7b0bc587d4e71601a048b46afa943cc
[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
11
12 program_name = 'abc2ly'
13 version = '@TOPLEVEL_VERSION@'
14 import __main__
15 import getopt
16 import sys
17 import re
18 import string
19 try:
20         import mpz
21 except:
22         sys.stderr.write ("This script needs Python 1.5.1\n")
23         sys.exit (1)
24
25
26 voice_idx_dict = {}
27
28
29 header = {}
30 lyrics = []
31 voices = []
32 current_voice_idx = -1
33 current_lyric_idx = -1
34
35 def select_voice (name):
36         if not voice_idx_dict.has_key (name):
37                 voices.append ('')              
38                 voice_idx_dict[name] = len (voices) -1
39         __main__.current_voice_idx =  voice_idx_dict[name]
40         
41 #       assert 0
42 # current_voice_idx >= 0
43
44 default_len = 8
45 global_key = [0] * 7                    # UGH
46 names = ["One", "Two", "Three"]
47 DIGITS='0123456789'
48 HSPACE=' \t'
49
50 def gcd (a, b):
51         while  a % b:
52                 a,b = b, a % b
53         return b
54         
55 class Rational:
56         def __init__ (self, n, d = 1):
57                 self.num = n
58                 self.den = d
59
60         def simplify (self):
61                 g = gcd (self.num, self.den)
62                 self.num = self.num /  g
63                 self.den = self.den /g
64                 if self.den < 0:
65                         self.den = - self.den
66                         self.num = - self.num
67
68         def __sub__ (self, other):
69                 pass
70         
71
72
73 def dump_header (outf,hdr):
74         outf.write ('\\header {')
75         ks = hdr.keys ()
76         ks.sort ()
77         for k in ks:
78                 outf.write ('\n%s = "%s";\n'% (k,hdr[k]))
79         outf.write ('}')
80
81 def dump_lyrics (outf):
82         for i in range (len (lyrics)):
83                 outf.write ("\nverse%s = \\lyrics {" % names [i])
84                 outf.write ("\n" + lyrics [i])
85                 outf.write ("\n}")
86
87 def dump_voices (outf):
88         ks = voice_idx_dict.keys()
89         ks.sort ()
90         for k in ks:
91                 outf.write ("\nvoice%s = \\notes {" % k)
92                 outf.write ("\n" + voices [voice_idx_dict[k]])
93                 outf.write ("\n}")
94         
95 def dump_score (outf):
96         outf.write (r"""\score{
97         \notes <
98 """)
99
100         ks  = voice_idx_dict.keys ();
101         ks.sort ()
102         for k in  ks:
103                 outf.write ("\n        \\context Staff=\"%s\" \\$voice%s " % (k,k))# ugh
104         for i in range (len (lyrics)):
105                 j = i
106                 if j >= len (voices):
107                         j = len (voices) - 1
108                 outf.write ("\n        \\context Lyrics=\"%s\" \\addlyrics \\$voice%s \\$verse%s " % 
109                         (names [i], names [j], names [i]))
110         outf.write ("\n    >")
111         dump_header (outf ,header)
112         outf.write (r"""
113 \paper {}
114 \midi {}
115 }""")
116
117 def set_default_length (s):
118         m =  re.search ('1/([0-9]+)', s)
119         if m:
120                 __main__.default_len = string.atoi ( m.group (1))
121
122 def set_default_len_from_time_sig (s):
123         m =  re.search ('([0-9]+)/([0-9]+)', s)
124         if m:
125                 n = string.atoi (m.group (1))
126                 d = string.atoi (m.group (2))
127                 if (n * 1.0 )/(d * 1.0) <  0.75:
128                         default_len =  16
129                 else:
130                         default_len = 8
131
132 def gulp_file(f):
133         try:
134                 i = open(f)
135                 i.seek (0, 2)
136                 n = i.tell ()
137                 i.seek (0,0)
138         except:
139                 sys.stderr.write ("can't open file: %s\n" % f)
140                 return ''
141         s = i.read (n)
142         if len (s) <= 0:
143                 sys.stderr.write ("gulped emty file: %s\n" % f)
144         i.close ()
145         return s
146
147
148 # pitch manipulation. Tuples are (name, alteration).
149 # 0 is (central) C. Alteration -1 is a flat, Alteration +1 is a sharp
150 # pitch in semitones. 
151 def semitone_pitch  (tup):
152         p =0
153
154         t = tup[0]
155         p = p + 12 * (t / 7)
156         t = t % 7
157         
158         if t > 2:
159                 p = p- 1
160                 
161         p = p + t* 2 + tup[1]
162         return p
163
164 def fifth_above_pitch (tup):
165         (n, a)  = (tup[0] + 4, tup[1])
166
167         difference = 7 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
168         a = a + difference
169         
170         return (n,a)
171
172 def sharp_keys ():
173         p = (0,0)
174         l = []
175         k = 0
176         while 1:
177                 l.append (p)
178                 (t,a) = fifth_above_pitch (p)
179                 if semitone_pitch((t,a)) % 12 == 0:
180                         break
181
182                 p = (t % 7, a)
183         return l
184
185 def flat_keys ():
186         p = (0,0)
187         l = []
188         k = 0
189         while 1:
190                 l.append (p)
191                 (t,a) = quart_above_pitch (p)
192                 if semitone_pitch((t,a)) % 12 == 0:
193                         break
194
195                 p = (t % 7, a)
196         return l
197
198 def quart_above_pitch (tup):
199         (n, a)  = (tup[0] + 3, tup[1])
200
201         difference = 5 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
202         a = a + difference
203         
204         return (n,a)
205
206
207 def compute_key (k):
208         k = string.lower (k)
209         intkey = (ord (k[0]) - ord('a') + 5) % 7
210         intkeyacc =0
211         k = k[1:]
212         
213         if k and k[0] == 'b':
214                 intkeyacc = -1
215                 k = k[1:]
216         elif  k and k[0] == '#':
217                 intkeyacc = 1
218                 k = k[1:]
219
220         keytup = (intkey, intkeyacc)
221         
222         sharp_key_seq = sharp_keys ()
223         flat_key_seq = flat_keys ()
224
225         accseq = None
226         accsign = 0
227         if keytup in sharp_key_seq:
228                 accsign = 1
229                 key_count = sharp_key_seq.index (keytup)
230                 accseq = map (lambda x: (4*x -1 ) % 7, range (1, key_count + 1))
231
232         elif keytup in flat_key_seq:
233                 accsign = -1
234                 key_count = flat_key_seq.index (keytup)
235                 accseq = map (lambda x: (3*x + 3 ) % 7, range (1, key_count + 1))
236         else:
237                 raise "Huh"
238         
239         key_table = [0] * 7
240         for a in accseq:
241                  key_table[a] = key_table[a] + accsign
242                 
243
244         return key_table
245
246 tup_lookup = {
247         '2' : '3/2',
248         '3' : '2/3',
249         '4' : '4/3',
250         '5' : '4/5',
251         '6' : '4/6',
252         '7' : '6/7',
253         '9' : '8/9',
254         }
255
256
257 def try_parse_tuplet_begin (str, state):
258         if re.match ('\([0-9]', str):
259                 dig = str[1]
260                 str = str[2:]
261                 state.parsing_tuplet = 1
262                 
263                 voices_append ("\\times %s {" % tup_lookup[dig])
264         return str
265
266 def  try_parse_group_end (str, state):
267         if str and str[0] in HSPACE:
268                 str = str[1:]
269                 if state.parsing_tuplet:
270                         state.parsing_tuplet = 0
271                         voices_append ("}")
272         return str
273
274 def header_append (key, a):
275         s = ''
276         if header.has_key (key):
277                 s = header[key] + "\n"
278         header [key] = s + a
279
280 def stuff_append (stuff, idx, a):
281         if not stuff:
282                 stuff.append ('')
283
284         v = stuff[idx]
285
286         #wordwrap
287         linelen = len (v) - string.rfind(v, '\n')
288         if linelen + len (a) > 80:
289                 v = v + '\n'
290         v = v + a + ' '
291         stuff [idx] = v
292
293
294
295 def voices_append(a):
296         if current_voice_idx < 0:
297                 select_voice ('default')
298
299         stuff_append (voices, current_voice_idx, a)
300
301 def lyrics_append(a):
302         stuff_append (lyrics, current_lyric_idx, a)
303
304
305 def try_parse_header_line (ln):
306         m = re.match ('^(.): *(.*)$', ln)
307
308         if m:
309                 g =m.group (1)
310                 a = m.group (2)
311                 a = re.sub ('"', '\\"', a)
312                 if g == 'T':
313                         header['title'] =  a
314                 if g == 'M':
315                         if a == 'C':
316                                 a = '4/4'
317 #                       global_voice_stuff.append ('\\time %s;' % a)
318                         set_default_len_from_time_sig (a)
319                         voices_append ('\\time %s;' % a)
320                 if g == 'K':
321                         __main__.global_key  =compute_key (a)# ugh.
322                         voices_append ('\\key %s;' % a)
323
324                 if g == 'O': 
325                         header ['origin'] = a
326                 if g == 'X': 
327                         header ['crossRefNumber'] = a
328                 if g == 'A':
329                         header ['area'] = a
330                 if g == 'H':
331                         header_append ('history', a)
332                 if g == 'B':
333                         header ['book'] = a
334                 if g == 'C':
335                         header ['composer'] = a
336                 if g == 'S':
337                         header ['subtitle'] = a
338                 if g == 'L':
339                         set_default_length (ln)
340                 if g == 'V':
341                         a = re.sub (' .*$', '', a)
342                         select_voice (a)
343                 if g == 'W':
344                         if not len (a):
345                                 lyrics.append ('')
346                         else:
347                                 lyrics_append (a);
348
349                 return ''
350         return ln
351
352 def pitch_to_mudela_name (name, acc):
353         s = ''
354         if acc < 0:
355                 s = 'es'
356                 acc = -acc
357         elif acc > 0:
358                 s = 'is'
359
360         if name > 4:
361                 name = name -7
362         return chr (name  + ord('c'))  + s * acc
363
364 def octave_to_mudela_quotes (o):
365         o = o + 2
366         s =''
367         if o < 0:
368                 o = -o
369                 s=','
370         else:
371                 s ='\''
372
373         return s * o
374
375 def parse_num (str):
376         durstr = ''
377         while str and str[0] in DIGITS:
378                 durstr = durstr + str[0]
379                 str = str[1:]
380
381         n = None
382         if durstr:
383                 n  =string.atoi (durstr) 
384         return (str,n)
385
386
387 def duration_to_mudela_duration  (multiply_tup, defaultlen, dots):
388         base = 1
389
390         # (num /  den)  / defaultlen < 1/base
391         while base * multiply_tup[0] < multiply_tup[1]:
392                 base = base * 2
393
394
395         return '%d%s' % ( base, '.'* dots)
396
397 class Parser_state:
398         def __init__ (self):
399                 self.next_articulation = ''
400                 self.next_dots = 0
401                 self.next_den = 1
402                 self.parsing_tuplet = 0
403
404
405
406 # return (str, num,den,dots) 
407 def parse_duration (str, parser_state):
408         num = 0
409         den = parser_state.next_den
410         parser_state.next_den = 1
411
412         (str, num) = parse_num (str)
413         if not num:
414                 num = 1
415         
416         if str[0] == '/':
417                 while str[:1] == '/':
418                         str= str[1:]
419                         d = 2
420                         if str[0] in DIGITS:
421                                 (str, d) =parse_num (str)
422
423                         den = den * d
424
425         den = den * default_len
426         
427         current_dots = parser_state.next_dots
428         parser_state.next_dots = 0
429         while str[0] == '>':
430                 str = str [1:]
431                 current_dots = current_dots + 1;
432                 parser_state.next_den = parser_state.next_den * 2
433         
434         while str[0] == '<':
435                 str = str [1:]
436                 den = den * 2
437                 parser_state.next_dots = parser_state.next_dots + 1
438
439
440
441         try_dots = [3, 2, 1]
442         for d in try_dots:
443                 f = 1 << d
444                 multiplier = (2*f-1)
445                 if num % multiplier == 0 and den % f == 0:
446                         num = num / multiplier
447                         den = den / f
448                         current_dots = current_dots + d
449                 
450         return (str, num,den,current_dots)
451
452
453 def try_parse_rest (str, parser_state):
454         if not str or str[0] <> 'z':
455                 return str
456
457         str = str[1:]
458
459         (str, num,den,d) = parse_duration (str, parser_state)
460         voices_append ('r%s' % duration_to_mudela_duration ((num,den), default_len, d))
461
462         return str
463
464 def try_parse_articulation (str, state):
465         
466         if str[:1] =='.':
467                 state.next_articulation = state.next_articulation + '-.'
468                 str = str[1:]
469
470         if str[:1] =='~':
471                 state.next_articulation = state.next_articulation + '-\\trill'
472                 str = str[1:]
473                 
474         # s7m2 input doesnt care about spaces
475         if re.match('[ \t]*\(', str):
476                 str = string.lstrip (str)
477
478         slur_begin =0
479         while str[:1] =='(' and str[1] not in DIGITS:
480                 slur_begin = slur_begin + 1
481                 state.next_articulation = state.next_articulation + '('
482                 str = str[1:]
483
484         return str
485                 
486 # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP  !
487 def try_parse_note (str, parser_state):
488         mud = ''
489
490         slur_begin =0
491         if not str:
492                 return str
493
494         articulation =''
495         acc = 0
496         if str[0] in '^=_':
497                 c = str[0]
498                 str = str[1:]
499                 if c == '^':
500                         acc = 1
501                 if c == '=':
502                         acc = 0
503                 if c == '_':
504                         acc = -1
505
506         octave = 0;
507         if str[0] in "ABCDEFG":
508                 str = string.lower (str[0]) + str[1:]
509                 octave = -1
510
511
512         notename = 0
513         if str[0] in "abcdefg":
514                 notename = (ord(str[0]) - ord('a') + 5)%7
515                 str = str[1:]
516         else:
517                 return str              # failed; not a note!
518
519         while str[0] == ',':
520                  octave = octave - 1
521                  str = str[1:]
522         while str[0] == '\'':
523                  octave = octave + 1
524                  str = str[1:]
525
526         (str, num,den,current_dots) = parse_duration (str, parser_state)
527
528
529         if re.match('[ \t]*\)', str):
530                 str = string.lstrip (str)
531         
532         slur_end =0
533         while str[:1] ==')':
534                 slur_end = slur_end + 1
535                 str = str[1:]
536
537         
538         if slur_end:
539                 voices_append ('%s' % ')' *slur_end )
540         voices_append ("%s%s%s" %
541                 (pitch_to_mudela_name (notename, acc + global_key[notename]),
542                                         octave_to_mudela_quotes (octave),
543                  duration_to_mudela_duration ((num,den), default_len, current_dots)))
544         if parser_state.next_articulation:
545                 articulation = articulation + parser_state.next_articulation
546                 parser_state.next_articulation = ''
547
548         voices_append (articulation)
549         if slur_begin:
550                 voices_append ('%s' % '(' * slur_begin )
551
552
553         return str
554
555 def junk_space (str):
556         while str and str[0] in '\t\n ':
557                 str = str[1:]
558
559         return str
560
561
562 def try_parse_guitar_chord (str, state):
563         if str[:1] =='"':
564                 str = str[1:]
565                 gc = ''
566                 while str and str[0] != '"':
567                         gc = gc + str[0]
568                         str = str[1:]
569                         
570                 if str:
571                         str = str[1:]
572
573                 state.next_articulation = "-\"%s\"" % gc
574         return str
575
576 def try_parse_escape (str):
577         if not str or str [0] != '\\':
578                 return str
579         
580         str = str[1:]
581         if str[:1] =='K':
582                 key_table = compute_key ()
583
584         return str
585
586 #
587 # |] thin-thick double bar line
588 # || thin-thin double bar line
589 # [| thick-thin double bar line
590 # :| left repeat
591 # |: right repeat
592 # :: left-right repeat
593 # |1 volta 1
594 # |2 volta 2
595 bar_dict = {
596 '|]' : '|.',
597 '||' : '||',
598 '[|' : '||',
599 ':|' : ':|',
600 '|:' : '|:',
601 '::' : '::',
602 '|1' : '|',
603 '|2' : '|',
604 ':|2' : ':|'
605 }
606
607
608 warn_about = ['|:', '::', ':|', '|1', ':|2', '|2']
609
610 def try_parse_bar (str,state):
611         bs = None
612
613         # first try the longer one
614         for trylen in [3,2]:
615                 if str[:trylen] and bar_dict.has_key (str[:trylen]):
616                         s = str[:trylen]
617                         bs = "\\bar \"%s\";" % bar_dict[s]
618                         if s in warn_about:
619                                 sys.stderr.write('Warning kludging for barline `%s\'\n' % s)
620                         str = str[trylen:]
621                         break
622
623         if str[:1] == '|':
624                 bs = '|\n'
625                 str = str[1:]
626         
627         if bs <> None:
628                 if state.parsing_tuplet:
629                         state.parsing_tuplet =0
630                         voices_append ('} ')
631                 
632                 voices_append (bs)
633
634         return str
635
636 def try_parse_tie (str):
637         if str[:1] =='-':
638                 str = str[1:]
639                 voices_append (' ~ ')
640         return str
641
642 def try_parse_chord_delims (str):
643         if str[:1] =='[':
644                 str = str[1:]
645                 voices_append ('<')
646
647         ch = ''
648         if str[:1] ==']':
649                 str = str[1:]
650                 ch = '>'
651
652         end = 0
653         while str[:1] ==')':
654                 end = end + 1
655                 str = str[1:]
656
657         
658         voices_append ("\\spanrequest \\stop \"slur\"" * end);
659         voices_append (ch)
660         return str
661
662 def try_parse_grace_delims (str):
663         if str[:1] =='{':
664                 str = str[1:]
665                 voices_append ('\\grace { ')
666
667         if str[:1] =='}':
668                 str = str[1:]
669                 voices_append ('}')
670
671         return str
672
673
674 happy_count = 100
675 def parse_file (fn):
676         f = open (fn)
677         ls = f.readlines ()
678
679         state = Parser_state ()
680         lineno = 0
681         sys.stderr.write ("Parsing line ... ")
682         sys.stderr.flush ()
683         
684         for ln in ls:
685                 lineno = lineno + 1
686
687                 if not (lineno % happy_count):
688                         sys.stderr.write ('[%d]'% lineno)
689                         sys.stderr.flush ()
690                 if re.match ('^[\t ]*(%.*)?$', ln):
691                         continue
692                 m = re.match  ('^(.*?)%(.*)$',ln)
693                 if m:
694                         voices_append ('%% %s\n' % m.group(2))
695                         ln = m.group (1)
696
697                 orig_ln = ln
698                 
699                 ln = try_parse_header_line (ln)
700
701                 # Try nibbling characters off until the line doesn't change.
702                 prev_ln = ''
703                 while ln != prev_ln:
704                         prev_ln = ln
705                         ln = try_parse_chord_delims (ln)
706                         ln = try_parse_rest (ln, state)
707                         ln = try_parse_articulation (ln,state)
708                         ln = try_parse_note  (ln, state)
709                         ln = try_parse_bar (ln, state)
710                         ln = try_parse_tie (ln)
711                         ln = try_parse_escape (ln)
712                         ln = try_parse_guitar_chord (ln, state)
713                         ln = try_parse_tuplet_begin (ln, state)
714                         ln = try_parse_group_end (ln, state)
715                         ln = try_parse_grace_delims (ln)
716                         ln = junk_space (ln)
717
718                 if ln:
719                         msg = "%s: %d: Huh?  Don't understand\n" % (fn, lineno)
720                         sys.stderr.write (msg)
721                         left = orig_ln[0:-len (ln)]
722                         sys.stderr.write (left + '\n')
723                         sys.stderr.write (' ' *  len (left) + ln + '\n')        
724
725
726 def identify():
727         sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
728
729 def help ():
730         print r"""
731 This is an ABC to mudela convertor.
732
733 Usage: abc2ly INPUTFILE
734
735 -h, --help   this help.
736 -o, --output set output filename
737 """
738
739
740
741 identify()
742 (options, files) = getopt.getopt (sys.argv[1:], 'o:h', ['help', 'output='])
743 out_filename = ''
744
745 for opt in options:
746         o = opt[0]
747         a = opt[1]
748         if o== '--help' or o == '-h':
749                 help ()
750         if o == '--output' or o == '-o':
751                 out_filename = a
752         else:
753                 print o
754                 raise getopt.error
755
756
757 header['tagline'] = 'Lily was here %s -- Directly converted from ABC' % version
758 for f in files:
759         if f == '-':
760                 f = ''
761
762         parse_file (f)
763
764         outf = None
765         if out_filename:
766                 outf = open (out_filename, 'w')
767         else:
768                 outf = sys.stdout
769
770
771 #       dump_global (outf)
772         dump_lyrics (outf)
773         dump_voices (outf)
774         dump_score (outf)
775         
776