]> git.donarmstrong.com Git - lilypond.git/blob - scripts/abc-2-ly.py
release: 1.1.46
[lilypond.git] / scripts / abc-2-ly.py
1 #!@PYTHON@
2
3 # once upon a rainy monday afternoon.
4 #
5 #   ...
6 #
7 # (not finished.)
8
9
10 program_name = 'abc-to-ly'
11 version = '0.1'
12 import __main__
13 import getopt
14 import sys
15 import re
16 import string
17 import mpz
18
19
20 header = {}
21 global_voice_stuff = []
22 default_len = 4
23 global_key = [0] * 7                    # UGH
24 DIGITS='0123456789'
25
26 def gcd (a, b):
27         while  a % b:
28                 a,b = b, a % b
29         return b
30         
31 class Rational:
32         def __init__ (self, n, d = 1):
33                 self.num = n
34                 self.den = d
35
36         def simplify (self):
37                 g = gcd (self.num, self.den)
38                 self.num = self.num /  g
39                 self.den = self.den /g
40                 if self.den < 0:
41                         self.den = - self.den
42                         self.num = - self.num
43
44         def __sub__ (self, other):
45                 pass
46         
47
48
49 def dump_header (hdr):
50         print '\\header {'
51         for k in hdr.keys ():
52                 print '%s = "%s";\n'% (k,hdr[k])
53         print '};'
54
55 def set_default_length (s):
56         m =  re.search ('1/([0-9]+)', s)
57         if m:
58                 __main__.default_len = string.atoi ( m.group (1))
59
60 def gulp_file(f):
61         try:
62                 i = open(f)
63                 i.seek (0, 2)
64                 n = i.tell ()
65                 i.seek (0,0)
66         except:
67                 print 'can\'t open file: ' + f + '\n'
68                 return ''
69         s = i.read (n)
70         if len (s) <= 0:
71                 print 'gulped empty file: ' + f + '\n'
72         i.close ()
73         return s
74
75
76 # pitch manipulation. Tuples are (name, alteration).
77 # 0 is (central) C. Alteration -1 is a flat, Alteration +1 is a sharp
78 # pitch in semitones. 
79 def semitone_pitch  (tup):
80         p =0
81
82         t = tup[0]
83         p = p + 12 * (t / 7)
84         t = t % 7
85         
86         if t > 2:
87                 p = p- 1
88                 
89         p = p + t* 2 + tup[1]
90         return p
91
92 def fifth_above_pitch (tup):
93         (n, a)  = (tup[0] + 4, tup[1])
94
95         difference = 7 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
96         a = a + difference
97         
98         return (n,a)
99
100 def sharp_keys ():
101         p = (0,0)
102         l = []
103         k = 0
104         while 1:
105                 l.append (p)
106                 (t,a) = fifth_above_pitch (p)
107                 if semitone_pitch((t,a)) % 12 == 0:
108                         break
109
110                 p = (t % 7, a)
111         return l
112
113 def flat_keys ():
114         p = (0,0)
115         l = []
116         k = 0
117         while 1:
118                 l.append (p)
119                 (t,a) = quart_above_pitch (p)
120                 if semitone_pitch((t,a)) % 12 == 0:
121                         break
122
123                 p = (t % 7, a)
124         return l
125
126 def quart_above_pitch (tup):
127         (n, a)  = (tup[0] + 3, tup[1])
128
129         difference = 5 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
130         a = a + difference
131         
132         return (n,a)
133
134
135 def compute_key (k):
136         k = string.lower (k)
137         intkey = (ord (k[0]) - ord('a') + 5) % 7
138         intkeyacc =0
139         k = k[1:]
140         
141         if k and k[0] == 'b':
142                 intkeyacc = -1
143                 k = k[1:]
144         elif  k and k[0] == '#':
145                 intkeyacc = 1
146                 k = k[1:]
147
148         keytup = (intkey, intkeyacc)
149         
150         sharp_key_seq = sharp_keys ()
151         flat_key_seq = flat_keys ()
152
153         accseq = None
154         accsign = 0
155         if keytup in sharp_key_seq:
156                 accsign = 1
157                 key_count = sharp_key_seq.index (keytup)
158                 accseq = map (lambda x: (4*x -1 ) % 7, range (1, key_count + 1))
159
160         elif keytup in flat_key_seq:
161                 accsign = -1
162                 key_count = flat_key_seq.index (keytup)
163                 accseq = map (lambda x: (3*x + 3 ) % 7, range (1, key_count + 1))
164         else:
165                 raise "Huh"
166         
167         key_table = [0] * 7
168         for a in accseq:
169                  key_table[a] = key_table[a] + accsign
170                 
171
172         return key_table
173
174 def try_parse_header_line (ln):
175         m = re.match ('^(.): *(.*)$', ln)
176
177         if m:
178                 g =m.group (1)
179                 a = m.group (2)
180                 if g == 'T':
181                         header['title'] =  a
182                 if g == 'M':
183                         global_voice_stuff.append ('\\time %s;' % a)
184                 if g == 'K':
185                         __main__.global_key  =compute_key (a)# ugh.
186
187                         global_voice_stuff.append ('\\key %s;' % a)
188                 if g == 'O': 
189                         header ['origin'] = a
190                 if g == 'X': 
191                         header ['crossRefNumber'] = a
192
193                 if g == 'A':
194                         header ['area'] = a
195                 if g == 'H':
196                         header ['history'] = a
197                 if g == 'B':
198                         header ['book'] = a
199                 if g == 'S':
200                         header ['subtitle'] = a
201                 if g == 'L':
202                         set_default_length (ln)
203         
204
205         return m
206
207 def pitch_to_mudela_name (name, acc):
208         s = ''
209         if acc < 0:
210                 s = 'es'
211                 acc = -acc
212         elif acc > 0:
213                 s = 'is'
214
215         if name > 4:
216                 name = name -7
217         return chr (name  + ord('c'))  + s * acc
218
219 def octave_to_mudela_quotes (o):
220         s =''
221         if o < 0:
222                 o = -o
223                 s=','
224         else:
225                 s ='\''
226
227         return s * o
228
229 def parse_num (str):
230         durstr = ''
231         while str[0] in DIGITS:
232                 durstr = durstr + str[0]
233                 str = str[1:]
234
235         n = None
236         if durstr:
237                 n  =string.atoi (durstr) 
238         return (str,n)
239
240
241 def duration_to_mudela_duration  (multiply_tup, defaultlen, dots):
242         base = 1
243
244         # (num /  den)  / defaultlen < 1/base
245         while base * multiply_tup[0] < defaultlen * multiply_tup[1]:
246                 base = base * 2
247
248
249         return '%d%s' % ( base, '.'* dots)
250
251 class Parser_state:
252         def __init__ (self):
253                 self.next_dots = 0
254                 self.next_den = 1
255
256
257 # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP  !
258 def try_parse_note (str, parser_state):
259         mud = ''
260
261         slur_begin =0
262         if str[0] == '(':
263                 slur_begin = 1
264                 str = str[1:]
265
266         acc = 0
267         if str[0] in '^=_':
268                 c = str[0]
269                 str = str[1:]
270                 if c == '^':
271                         acc = 1
272                 if c == '=':
273                         acc = 0
274                 if c == '_':
275                         acc = -1
276
277         octave = 0;
278         if str[0] in "ABCDEFG":
279                 str = string.lower (str[0]) + str[1:]
280                 octave = -1
281
282
283         notename = 0
284         if str[0] in "abcdefg":
285                 notename = (ord(str[0]) - ord('a') + 5)%7
286                 str = str[1:]
287         else:
288                 return str              # failed; not a note!
289
290         while str[0] == ',':
291                  octave = octave - 1
292                  str = str[1:]
293         while str[0] == '\'':
294                  octave = octave + 1
295                  str = str[1:]
296
297         num = 0
298         den = parser_state.next_den
299         parser_state.next_den = 1
300
301         (str, num) = parse_num (str)
302         if not num:
303                 num = 1
304         
305         if str[0] == '/':
306                 while str[0] == '/':
307                         str= str[1:]
308                         d = 2
309                         if str[0] in DIGITS:
310                                 (str, d) =parse_num (str)
311
312                         den = den * d
313
314         current_dots = parser_state.next_dots
315         parser_state.next_dots = 0
316         while str[0] == '>':
317                 str = str [1:]
318                 current_dots = current_dots + 1;
319                 parser_state.next_den = parser_state.next_den * 2
320         
321         while str[0] == '<':
322                 str = str [1:]
323                 den = den * 2
324                 parser_state.next_dots = parser_state.next_dots + 1
325         
326                 
327         
328         print '%s%s%s' %  (pitch_to_mudela_name(notename, acc + global_key[notename]) , octave_to_mudela_quotes (octave),
329                            duration_to_mudela_duration ((num,den), default_len, current_dots))
330
331         slur_end =0
332         if str[0] == ')':
333                 slur_begin = 1
334                 str = str[1:]
335
336
337         return str
338
339 def junk_space (str):
340         while str and str[0] in '\t\n ':
341                 str = str[1:]
342
343         return str
344
345 def try_parse_escape (str):
346         if str [0] != '\\':
347                 return str
348         
349         str = str[1:]
350         if str[0] == 'K':
351                 compute_key ()
352
353         return str
354
355
356 def try_parse_bar (str):
357         if str[0] == '|':
358                 str = str[1:]
359         return str
360         
361
362
363 def try_parse_body_line (ln, state):
364         prev_ln = ''
365         while ln and  ln != prev_ln:
366                 prev_ln = ln
367                 ln = try_parse_note  (ln, state)
368                 ln = try_parse_bar (ln)
369                 ln = junk_space (ln)
370                 ln = try_parse_escape (ln)
371         if ln:
372                 print 'Huh %s' % ln
373                 
374
375
376         
377
378
379 def parse_file (fn):
380         f = open (fn)
381         ls = f.readlines ()
382
383         head = 1
384         state = Parser_state ()
385         for l in ls:
386                 if re.match ('^[\t ]*(%.*)?$', l):
387                         continue
388                 
389                 if head:
390                         m = try_parse_header_line (l)
391                         if not m:
392                                 head = 0
393
394                 if not head:
395                         m = try_parse_body_line (l,state)
396
397
398 def identify():
399         print '%s %s' % (program_name, version)
400
401 def help ():
402         print r"""
403 This is a disfunctional ABC to mudela convertor.  It only gulps input, and
404 says huh when confused.  Does not do chords.  Go ahead and fix me.
405
406 -h, --help   this help.
407 """
408
409
410
411 identify()
412 (options, files) = getopt.getopt (sys.argv[1:], 'h', ['help'])
413
414 for opt in options:
415         o = opt[0]
416         a = opt[1]
417         if o== '--help' or o == '-h':
418                 help ()
419         else:
420                 print o
421                 raise getopt.error
422
423
424 for f in files:
425         if f == '-':
426                 f = ''
427         
428         parse_file (f)
429         dump_header (header)
430         print global_voice_stuff, 1
431