]> git.donarmstrong.com Git - lilypond.git/blob - scripts/abc2ly.py
release: 1.1.62
[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 r"""\score{
84         \notes<
85            \global"""
86
87         for i in range (len (voices)):
88                 print ("        \\context Staff=%s \\voice%s" %
89                         (names [i], names [i]))
90         for i in range (len (lyrics)):
91                 j = i
92                 if j >= len (voices):
93                         j = len (voices) - 1
94                 print ("        \\context Lyrics=%s \\addlyrics \\voice%s \\verse%s" % 
95                         (names [i], names [j], names [i]))
96         print ("    >")
97         dump_header (header)
98         #print "%%%s" % global_voice_stuff, 1
99         print ("}")
100
101 def set_default_length (s):
102         m =  re.search ('1/([0-9]+)', s)
103         if m:
104                 __main__.default_len = string.atoi ( m.group (1))
105
106 def gulp_file(f):
107         try:
108                 i = open(f)
109                 i.seek (0, 2)
110                 n = i.tell ()
111                 i.seek (0,0)
112         except:
113                 sys.stderr.write ("can't open file: %s\n" % f)
114                 return ''
115         s = i.read (n)
116         if len (s) <= 0:
117                 sys.stderr.write ("gulped emty file: %s\n" % f)
118         i.close ()
119         return s
120
121
122 # pitch manipulation. Tuples are (name, alteration).
123 # 0 is (central) C. Alteration -1 is a flat, Alteration +1 is a sharp
124 # pitch in semitones. 
125 def semitone_pitch  (tup):
126         p =0
127
128         t = tup[0]
129         p = p + 12 * (t / 7)
130         t = t % 7
131         
132         if t > 2:
133                 p = p- 1
134                 
135         p = p + t* 2 + tup[1]
136         return p
137
138 def fifth_above_pitch (tup):
139         (n, a)  = (tup[0] + 4, tup[1])
140
141         difference = 7 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
142         a = a + difference
143         
144         return (n,a)
145
146 def sharp_keys ():
147         p = (0,0)
148         l = []
149         k = 0
150         while 1:
151                 l.append (p)
152                 (t,a) = fifth_above_pitch (p)
153                 if semitone_pitch((t,a)) % 12 == 0:
154                         break
155
156                 p = (t % 7, a)
157         return l
158
159 def flat_keys ():
160         p = (0,0)
161         l = []
162         k = 0
163         while 1:
164                 l.append (p)
165                 (t,a) = quart_above_pitch (p)
166                 if semitone_pitch((t,a)) % 12 == 0:
167                         break
168
169                 p = (t % 7, a)
170         return l
171
172 def quart_above_pitch (tup):
173         (n, a)  = (tup[0] + 3, tup[1])
174
175         difference = 5 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
176         a = a + difference
177         
178         return (n,a)
179
180
181 def compute_key (k):
182         k = string.lower (k)
183         intkey = (ord (k[0]) - ord('a') + 5) % 7
184         intkeyacc =0
185         k = k[1:]
186         
187         if k and k[0] == 'b':
188                 intkeyacc = -1
189                 k = k[1:]
190         elif  k and k[0] == '#':
191                 intkeyacc = 1
192                 k = k[1:]
193
194         keytup = (intkey, intkeyacc)
195         
196         sharp_key_seq = sharp_keys ()
197         flat_key_seq = flat_keys ()
198
199         accseq = None
200         accsign = 0
201         if keytup in sharp_key_seq:
202                 accsign = 1
203                 key_count = sharp_key_seq.index (keytup)
204                 accseq = map (lambda x: (4*x -1 ) % 7, range (1, key_count + 1))
205
206         elif keytup in flat_key_seq:
207                 accsign = -1
208                 key_count = flat_key_seq.index (keytup)
209                 accseq = map (lambda x: (3*x + 3 ) % 7, range (1, key_count + 1))
210         else:
211                 raise "Huh"
212         
213         key_table = [0] * 7
214         for a in accseq:
215                  key_table[a] = key_table[a] + accsign
216                 
217
218         return key_table
219
220 tup_lookup = {
221         '3' : '2/3',
222         '4' : '4/3',
223         '5' : '4/5',
224         '6' : '4/6',
225         }
226
227
228 def try_parse_tuplet_begin (str, state):
229         if str and str[0] in DIGITS:
230                 dig = str[0]
231                 str = str[1:]
232                 state.parsing_tuplet = 1
233                 
234                 voices_append ("\\times %s {" % tup_lookup[dig])
235         return str
236
237 def  try_parse_group_end (str, state):
238         if str and str[0] in HSPACE:
239                 str = str[1:]
240                 if state.parsing_tuplet:
241                         state.parsing_tuplet = 0
242                         voices_append ("}")
243         return str
244
245 def header_append (key, a):
246         s = ''
247         if header.has_key (key):
248                 s = header[key] + "\n"
249         header [key] = s + a
250
251 def lyrics_append (a):
252         i = len (lyrics) - 1
253         if i < 0:
254                 i = 0
255         if len (lyrics) <= i:
256                 lyrics.append ('')
257         lyrics [i] = lyrics [i] + a + "\n"
258
259 def voices_append (a):
260         i = len (voices) - 1
261         if i < 0:
262                 i = 0
263         if len (voices) <= i:
264                 voices.append ('')
265         voices [i] = voices [i] + a + "\n"
266
267 def try_parse_header_line (ln):
268         m = re.match ('^(.): *(.*)$', ln)
269
270         if m:
271                 g =m.group (1)
272                 a = m.group (2)
273                 a = re.sub ('"', '\\"', a)
274                 if g == 'T':
275                         header['title'] =  a
276                 if g == 'M':
277                         if a == 'C':
278                                 a = '4/4'
279                         global_voice_stuff.append ('\\time %s;' % a)
280                 if g == 'K':
281                         __main__.global_key  =compute_key (a)# ugh.
282
283                         global_voice_stuff.append ('\\key %s;' % a)
284                 if g == 'O': 
285                         header ['origin'] = a
286                 if g == 'X': 
287                         header ['crossRefNumber'] = a
288                 if g == 'A':
289                         header ['area'] = a
290                 if g == 'H':
291                         header_append ('history', a)
292                 if g == 'B':
293                         header ['book'] = a
294                 if g == 'S':
295                         header ['subtitle'] = a
296                 if g == 'L':
297                         set_default_length (ln)
298                 if g == 'W':
299                         if not len (a):
300                                 lyrics.append ('')
301                         else:
302                                 lyrics_append (a);
303         return m
304
305 def pitch_to_mudela_name (name, acc):
306         s = ''
307         if acc < 0:
308                 s = 'es'
309                 acc = -acc
310         elif acc > 0:
311                 s = 'is'
312
313         if name > 4:
314                 name = name -7
315         return chr (name  + ord('c'))  + s * acc
316
317 def octave_to_mudela_quotes (o):
318         o = o + 2
319         s =''
320         if o < 0:
321                 o = -o
322                 s=','
323         else:
324                 s ='\''
325
326         return s * o
327
328 def parse_num (str):
329         durstr = ''
330         while str and str[0] in DIGITS:
331                 durstr = durstr + str[0]
332                 str = str[1:]
333
334         n = None
335         if durstr:
336                 n  =string.atoi (durstr) 
337         return (str,n)
338
339
340 def duration_to_mudela_duration  (multiply_tup, defaultlen, dots):
341         base = 1
342
343         # (num /  den)  / defaultlen < 1/base
344         while base * multiply_tup[0] < defaultlen * multiply_tup[1]:
345                 base = base * 2
346
347
348         return '%d%s' % ( base, '.'* dots)
349
350 class Parser_state:
351         def __init__ (self):
352                 self.next_dots = 0
353                 self.next_den = 1
354                 self.parsing_tuplet = 0
355
356
357 # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP  !
358 def try_parse_note (str, parser_state):
359         mud = ''
360
361         slur_begin =0
362         if not str:
363                 return str
364         
365         if  str[0] == '(':
366                 slur_begin = 1
367                 str = str[1:]
368
369         acc = 0
370         if str[0] in '^=_':
371                 c = str[0]
372                 str = str[1:]
373                 if c == '^':
374                         acc = 1
375                 if c == '=':
376                         acc = 0
377                 if c == '_':
378                         acc = -1
379
380         octave = 0;
381         if str[0] in "ABCDEFG":
382                 str = string.lower (str[0]) + str[1:]
383                 octave = -1
384
385
386         notename = 0
387         if str[0] in "abcdefg":
388                 notename = (ord(str[0]) - ord('a') + 5)%7
389                 str = str[1:]
390         else:
391                 return str              # failed; not a note!
392
393         while str[0] == ',':
394                  octave = octave - 1
395                  str = str[1:]
396         while str[0] == '\'':
397                  octave = octave + 1
398                  str = str[1:]
399
400         num = 0
401         den = parser_state.next_den
402         parser_state.next_den = 1
403
404         (str, num) = parse_num (str)
405         if not num:
406                 num = 1
407         
408         if str[0] == '/':
409                 while str[0] == '/':
410                         str= str[1:]
411                         d = 2
412                         if str[0] in DIGITS:
413                                 (str, d) =parse_num (str)
414
415                         den = den * d
416
417         current_dots = parser_state.next_dots
418         parser_state.next_dots = 0
419         while str[0] == '>':
420                 str = str [1:]
421                 current_dots = current_dots + 1;
422                 parser_state.next_den = parser_state.next_den * 2
423         
424         while str[0] == '<':
425                 str = str [1:]
426                 den = den * 2
427                 parser_state.next_dots = parser_state.next_dots + 1
428         
429                 
430         
431         voices_append ("%s%s%s" %
432                 (pitch_to_mudela_name (notename, acc + global_key[notename]),
433                                         octave_to_mudela_quotes (octave),
434                  duration_to_mudela_duration ((num,den), default_len, current_dots)))
435         slur_end =0
436         if str[0] == ')':
437                 slur_begin = 1
438                 str = str[1:]
439
440
441         return str
442
443 def junk_space (str):
444         while str and str[0] in '\t\n ':
445                 str = str[1:]
446
447         return str
448
449
450 def try_parse_guitar_chord (str):
451         if str and str[0] == '"':
452                 str = str[1:]
453                 gc = ''
454                 while str and str[0] != '"':
455                         gc = gc + str[0]
456                         str = str[1:]
457                         
458                 if str:
459                         str = str[1:]
460
461                 sys.stderr.write ("warning: ignoring guitar chord: %s\n" % gc)
462                 
463         return str
464
465 def try_parse_escape (str):
466         if not str or str [0] != '\\':
467                 return str
468         
469         str = str[1:]
470         if str and str[0] == 'K':
471                 key_table = compute_key ()
472
473         return str
474
475 #
476 # |] thin-thick double bar line
477 # || thin-thin double bar line
478 # [| thick-thin double bar line
479 # :| left repeat
480 # |: right repeat
481 # :: left-right repeat
482 #
483
484 def try_parse_bar (str):
485         if str and str[0] == '|':
486                 bs = ''
487                 str = str[1:]
488                 if str:
489                         if  str[0] == ']':
490                                 bs = '|.'
491                         if str[0] == '|':
492                                 bs = '||'
493                         if str[0] == '|:':
494                                 sys.stderr.write ("warning: repeat kludge\n")
495                                 bs = '|:'
496                 if bs:
497                         voices_append ('\\bar "%s";' % bs)
498                         str = str[1:]
499
500         if str and str[:2] == '[|':
501                 sys.stderr.write ("warning: thick-thin bar kludge\n")
502                 voices_append ('\\bar "||";')
503                 str = str[2:]
504
505         if str and str[:2] == ':|':
506                 sys.stderr.write ("warning: repeat kludge\n")
507                 voices_append ('\\bar ":|:";')
508                 str = str[2:]
509
510         if str and str[:2] == '::':
511                 sys.stderr.write ("warning: repeat kludge\n")
512                 voices_append ('\\bar ":|:";')
513                 str = str[2:]
514
515         return str
516         
517
518 def try_parse_chord_delims (str):
519         if str and str[0] == '[':
520                 str = str[1:]
521                 voices_append ('<')
522
523         if str and str[0] == ']':
524                 str = str[1:]
525                 voices_append ('>')
526
527         return str
528
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