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