]> git.donarmstrong.com Git - lilypond.git/blob - scripts/abc2ly.py
release: 1.1.56
[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 program_name = 'abc-to-ly'
12 version = '@TOPLEVEL_VERSION@'
13 import __main__
14 import getopt
15 import sys
16 import re
17 import string
18 try:
19         import mpz
20 except:
21         sys.stderr.write ("This script needs Python 1.5.1\n")
22         sys.exit (1)
23
24
25 header = {}
26 lyrics = []
27 voices = []
28 global_voice_stuff = []
29 default_len = 4
30 global_key = [0] * 7                    # UGH
31 names = ["One", "Two", "Three"]
32 DIGITS='0123456789'
33 HSPACE=' \t'
34
35 def gcd (a, b):
36         while  a % b:
37                 a,b = b, a % b
38         return b
39         
40 class Rational:
41         def __init__ (self, n, d = 1):
42                 self.num = n
43                 self.den = d
44
45         def simplify (self):
46                 g = gcd (self.num, self.den)
47                 self.num = self.num /  g
48                 self.den = self.den /g
49                 if self.den < 0:
50                         self.den = - self.den
51                         self.num = - self.num
52
53         def __sub__ (self, other):
54                 pass
55         
56
57 def dump_global ():
58         print ("global = \\notes{")
59         for i in global_voice_stuff:
60                 print (i);
61         print ("}")
62
63
64 def dump_header (hdr):
65         print '\\header {'
66         for k in hdr.keys ():
67                 print '%s = "%s";\n'% (k,hdr[k])
68         print '}'
69
70 def dump_lyrics ():
71         for i in range (len (lyrics)):
72                 print ("verse%s = \\lyrics {" % names [i])
73                 print (lyrics [i])
74                 print ("}")
75
76 def dump_voices ():
77         for i in range (len (voices)):
78                 print ("voice%s = \\notes {" % names [i])
79                 print (voices [i])
80                 print ("}")
81         
82 def dump_score ():
83         print ("\\score{")
84         print ("        \\notes<")
85         print ("                \\global")
86         for i in range (len (voices)):
87                 print ("        \\context Staff=%s \\voice%s" %
88                         (names [i], names [i]))
89         for i in range (len (lyrics)):
90                 j = i
91                 if j >= len (voices):
92                         j = len (voices) - 1
93                 print ("        \\context Lyrics=%s \\rhythm \\voice%s \\verse%s" % 
94                         (names [i], names [j], names [i]))
95         print ("    >")
96         dump_header (header)
97         #print "%%%s" % global_voice_stuff, 1
98         print ("}")
99
100 def set_default_length (s):
101         m =  re.search ('1/([0-9]+)', s)
102         if m:
103                 __main__.default_len = string.atoi ( m.group (1))
104
105 def gulp_file(f):
106         try:
107                 i = open(f)
108                 i.seek (0, 2)
109                 n = i.tell ()
110                 i.seek (0,0)
111         except:
112                 sys.stderr.write ("can't open file: %s\n" % f)
113                 return ''
114         s = i.read (n)
115         if len (s) <= 0:
116                 sys.stderr.write ("gulped emty file: %s\n" % f)
117         i.close ()
118         return s
119
120
121 # pitch manipulation. Tuples are (name, alteration).
122 # 0 is (central) C. Alteration -1 is a flat, Alteration +1 is a sharp
123 # pitch in semitones. 
124 def semitone_pitch  (tup):
125         p =0
126
127         t = tup[0]
128         p = p + 12 * (t / 7)
129         t = t % 7
130         
131         if t > 2:
132                 p = p- 1
133                 
134         p = p + t* 2 + tup[1]
135         return p
136
137 def fifth_above_pitch (tup):
138         (n, a)  = (tup[0] + 4, tup[1])
139
140         difference = 7 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
141         a = a + difference
142         
143         return (n,a)
144
145 def sharp_keys ():
146         p = (0,0)
147         l = []
148         k = 0
149         while 1:
150                 l.append (p)
151                 (t,a) = fifth_above_pitch (p)
152                 if semitone_pitch((t,a)) % 12 == 0:
153                         break
154
155                 p = (t % 7, a)
156         return l
157
158 def flat_keys ():
159         p = (0,0)
160         l = []
161         k = 0
162         while 1:
163                 l.append (p)
164                 (t,a) = quart_above_pitch (p)
165                 if semitone_pitch((t,a)) % 12 == 0:
166                         break
167
168                 p = (t % 7, a)
169         return l
170
171 def quart_above_pitch (tup):
172         (n, a)  = (tup[0] + 3, tup[1])
173
174         difference = 5 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
175         a = a + difference
176         
177         return (n,a)
178
179
180 def compute_key (k):
181         k = string.lower (k)
182         intkey = (ord (k[0]) - ord('a') + 5) % 7
183         intkeyacc =0
184         k = k[1:]
185         
186         if k and k[0] == 'b':
187                 intkeyacc = -1
188                 k = k[1:]
189         elif  k and k[0] == '#':
190                 intkeyacc = 1
191                 k = k[1:]
192
193         keytup = (intkey, intkeyacc)
194         
195         sharp_key_seq = sharp_keys ()
196         flat_key_seq = flat_keys ()
197
198         accseq = None
199         accsign = 0
200         if keytup in sharp_key_seq:
201                 accsign = 1
202                 key_count = sharp_key_seq.index (keytup)
203                 accseq = map (lambda x: (4*x -1 ) % 7, range (1, key_count + 1))
204
205         elif keytup in flat_key_seq:
206                 accsign = -1
207                 key_count = flat_key_seq.index (keytup)
208                 accseq = map (lambda x: (3*x + 3 ) % 7, range (1, key_count + 1))
209         else:
210                 raise "Huh"
211         
212         key_table = [0] * 7
213         for a in accseq:
214                  key_table[a] = key_table[a] + accsign
215                 
216
217         return key_table
218
219 tup_lookup = {
220         '3' : '2/3',
221         '4' : '4/3',
222         '5' : '4/5',
223         '6' : '4/6',
224         }
225
226
227 def try_parse_tuplet_begin (str, state):
228         if str and str[0] in DIGITS:
229                 dig = str[0]
230                 str = str[1:]
231                 state.parsing_tuplet = 1
232                 
233                 voices_append ("\\times %s {" % tup_lookup[dig])
234         return str
235
236 def  try_parse_group_end (str, state):
237         if str and str[0] in HSPACE:
238                 str = str[1:]
239                 if state.parsing_tuplet:
240                         state.parsing_tuplet = 0
241                         voices_append ("}")
242         return str
243
244 def header_append (key, a):
245         s = ''
246         if header.has_key (key):
247                 s = header[key] + "\n"
248         header [key] = s + a
249
250 def lyrics_append (a):
251         i = len (lyrics) - 1
252         if i < 0:
253                 i = 0
254         if len (lyrics) <= i:
255                 lyrics.append ('')
256         lyrics [i] = lyrics [i] + a + "\n"
257
258 def voices_append (a):
259         i = len (voices) - 1
260         if i < 0:
261                 i = 0
262         if len (voices) <= i:
263                 voices.append ('')
264         voices [i] = voices [i] + a + "\n"
265
266 def try_parse_header_line (ln):
267         m = re.match ('^(.): *(.*)$', ln)
268
269         if m:
270                 g =m.group (1)
271                 a = m.group (2)
272                 a = re.sub ('"', '\\"', a)
273                 if g == 'T':
274                         header['title'] =  a
275                 if g == 'M':
276                         if a == 'C':
277                                 a = '4/4'
278                         global_voice_stuff.append ('\\time %s;' % a)
279                 if g == 'K':
280                         __main__.global_key  =compute_key (a)# ugh.
281
282                         global_voice_stuff.append ('\\key %s;' % a)
283                 if g == 'O': 
284                         header ['origin'] = a
285                 if g == 'X': 
286                         header ['crossRefNumber'] = a
287                 if g == 'A':
288                         header ['area'] = a
289                 if g == 'H':
290                         header_append ('history', a)
291                 if g == 'B':
292                         header ['book'] = a
293                 if g == 'S':
294                         header ['subtitle'] = a
295                 if g == 'L':
296                         set_default_length (ln)
297                 if g == 'W':
298                         if not len (a):
299                                 lyrics.append ('')
300                         else:
301                                 lyrics_append (a);
302         return m
303
304 def pitch_to_mudela_name (name, acc):
305         s = ''
306         if acc < 0:
307                 s = 'es'
308                 acc = -acc
309         elif acc > 0:
310                 s = 'is'
311
312         if name > 4:
313                 name = name -7
314         return chr (name  + ord('c'))  + s * acc
315
316 def octave_to_mudela_quotes (o):
317         o = o + 2
318         s =''
319         if o < 0:
320                 o = -o
321                 s=','
322         else:
323                 s ='\''
324
325         return s * o
326
327 def parse_num (str):
328         durstr = ''
329         while str and str[0] in DIGITS:
330                 durstr = durstr + str[0]
331                 str = str[1:]
332
333         n = None
334         if durstr:
335                 n  =string.atoi (durstr) 
336         return (str,n)
337
338
339 def duration_to_mudela_duration  (multiply_tup, defaultlen, dots):
340         base = 1
341
342         # (num /  den)  / defaultlen < 1/base
343         while base * multiply_tup[0] < defaultlen * multiply_tup[1]:
344                 base = base * 2
345
346
347         return '%d%s' % ( base, '.'* dots)
348
349 class Parser_state:
350         def __init__ (self):
351                 self.next_dots = 0
352                 self.next_den = 1
353                 self.parsing_tuplet = 0
354
355
356 # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP  !
357 def try_parse_note (str, parser_state):
358         mud = ''
359
360         slur_begin =0
361         if not str:
362                 return str
363         
364         if  str[0] == '(':
365                 slur_begin = 1
366                 str = str[1:]
367
368         acc = 0
369         if str[0] in '^=_':
370                 c = str[0]
371                 str = str[1:]
372                 if c == '^':
373                         acc = 1
374                 if c == '=':
375                         acc = 0
376                 if c == '_':
377                         acc = -1
378
379         octave = 0;
380         if str[0] in "ABCDEFG":
381                 str = string.lower (str[0]) + str[1:]
382                 octave = -1
383
384
385         notename = 0
386         if str[0] in "abcdefg":
387                 notename = (ord(str[0]) - ord('a') + 5)%7
388                 str = str[1:]
389         else:
390                 return str              # failed; not a note!
391
392         while str[0] == ',':
393                  octave = octave - 1
394                  str = str[1:]
395         while str[0] == '\'':
396                  octave = octave + 1
397                  str = str[1:]
398
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         
430         voices_append ("%s%s%s" %
431                 (pitch_to_mudela_name (notename, acc + global_key[notename]),
432                                         octave_to_mudela_quotes (octave),
433                  duration_to_mudela_duration ((num,den), default_len, current_dots)))
434         slur_end =0
435         if str[0] == ')':
436                 slur_begin = 1
437                 str = str[1:]
438
439
440         return str
441
442 def junk_space (str):
443         while str and str[0] in '\t\n ':
444                 str = str[1:]
445
446         return str
447
448
449 def try_parse_guitar_chord (str):
450         if str and str[0] == '"':
451                 str = str[1:]
452                 gc = ''
453                 while str and str[0] != '"':
454                         gc = gc + str[0]
455                         str = str[1:]
456                         
457                 if str:
458                         str = str[1:]
459
460                 sys.stderr.write ("warning: ignoring guitar chord: %s\n" % gc)
461                 
462         return str
463
464 def try_parse_escape (str):
465         if not str or str [0] != '\\':
466                 return str
467         
468         str = str[1:]
469         if str and str[0] == 'K':
470                 key_table = compute_key ()
471
472         return str
473
474 #
475 # |] thin-thick double bar line
476 # || thin-thin double bar line
477 # [| thick-thin double bar line
478 # :| left repeat
479 # |: right repeat
480 # :: left-right repeat
481 #
482
483 def try_parse_bar (str):
484         if str and str[0] == '|':
485                 bs = ''
486                 str = str[1:]
487                 if str:
488                         if  str[0] == ']':
489                                 bs = '|.'
490                         if str[0] == '|':
491                                 bs = '||'
492                         if str[0] == '|:':
493                                 sys.stderr.write ("warning: repeat kludge\n")
494                                 bs = '|:'
495                 if bs:
496                         voices_append ('\\bar "%s";' % bs)
497                         str = str[1:]
498
499         if str and str[:2] == '[|':
500                 sys.stderr.write ("warning: thick-thin bar kludge\n")
501                 voices_append ('\\bar "||";')
502                 str = str[2:]
503
504         if str and str[:2] == ':|':
505                 sys.stderr.write ("warning: repeat kludge\n")
506                 voices_append ('\\bar ":|:";')
507                 str = str[2:]
508
509         if str and str[:2] == '::':
510                 sys.stderr.write ("warning: repeat kludge\n")
511                 voices_append ('\\bar ":|:";')
512                 str = str[2:]
513
514         return str
515         
516
517 def try_parse_chord_delims (str):
518         if str and str[0] == '[':
519                 str = str[1:]
520                 voices_append ('<')
521
522         if str and str[0] == ']':
523                 str = str[1:]
524                 voices_append ('>')
525
526         return str
527
528 # urg, hairy to compute grace note hack using \times{}
529 def try_parse_grace_delims (str):
530         if str and str[0] == '{':
531                 str = str[1:]
532                 voices_append ('\\grace { ')
533
534         if str and str[0] == '}':
535                 str = str[1:]
536                 voices_append ('}')
537
538         return str
539
540 # Try nibbling characters off until the line doesn't change.
541 def try_parse_body_line (ln, state):
542         prev_ln = ''
543         while ln != prev_ln:
544                 prev_ln = ln
545                 ln = try_parse_chord_delims (ln)
546                 ln = try_parse_note  (ln, state)
547                 ln = try_parse_bar (ln)
548                 ln = try_parse_escape (ln)
549                 ln = try_parse_guitar_chord (ln)
550                 ln = try_parse_tuplet_begin (ln, state)
551                 ln = try_parse_group_end (ln, state)
552                 ln = try_parse_grace_delims (ln)
553                 ln = junk_space (ln)
554                 
555         if ln:
556                 sys.stderr.write ("Huh?  Don't understand `%s'\n" % ln)
557         
558
559
560 def parse_file (fn):
561         f = open (fn)
562         ls = f.readlines ()
563
564         head = 1
565         state = Parser_state ()
566         for l in ls:
567                 if re.match ('^[\t ]*(%.*)?$', l):
568                         continue
569                 
570                 if head:
571                         m = try_parse_header_line (l)
572                         if not m:
573                                 head = 0
574
575                 if not head:
576                         m = try_parse_body_line (l,state)
577
578
579 def identify():
580         sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
581
582 def help ():
583         print r"""
584 This is a disfunctional ABC to mudela convertor.  It only gulps input, and
585 says huh when confused.  Go ahead and fix me.
586
587 Usage: abc-2-ly INPUTFILE
588
589 -h, --help   this help.
590 """
591
592
593
594 identify()
595 (options, files) = getopt.getopt (sys.argv[1:], 'h', ['help'])
596
597 for opt in options:
598         o = opt[0]
599         a = opt[1]
600         if o== '--help' or o == '-h':
601                 help ()
602         else:
603                 print o
604                 raise getopt.error
605
606
607 for f in files:
608         if f == '-':
609                 f = ''
610         parse_file (f)
611
612         dump_global ()
613         dump_lyrics ()
614         dump_voices ()
615         dump_score ()
616         
617