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