lilypond-1.1.46
[lilypond.git] / scripts / abc-2-ly.py
blobb818d04351f6e306471b7c192011d2a52e5a6aaf
1 #!@PYTHON@
3 # once upon a rainy monday afternoon.
5 # ...
7 # (not finished.)
8 #
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
20 header = {}
21 global_voice_stuff = []
22 default_len = 4
23 global_key = [0] * 7 # UGH
24 DIGITS='0123456789'
26 def gcd (a, b):
27 while a % b:
28 a,b = b, a % b
29 return b
31 class Rational:
32 def __init__ (self, n, d = 1):
33 self.num = n
34 self.den = d
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
44 def __sub__ (self, other):
45 pass
49 def dump_header (hdr):
50 print '\\header {'
51 for k in hdr.keys ():
52 print '%s = "%s";\n'% (k,hdr[k])
53 print '};'
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))
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
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
82 t = tup[0]
83 p = p + 12 * (t / 7)
84 t = t % 7
86 if t > 2:
87 p = p- 1
89 p = p + t* 2 + tup[1]
90 return p
92 def fifth_above_pitch (tup):
93 (n, a) = (tup[0] + 4, tup[1])
95 difference = 7 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
96 a = a + difference
98 return (n,a)
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
110 p = (t % 7, a)
111 return l
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
123 p = (t % 7, a)
124 return l
126 def quart_above_pitch (tup):
127 (n, a) = (tup[0] + 3, tup[1])
129 difference = 5 - (semitone_pitch ((n,a)) - semitone_pitch (tup))
130 a = a + difference
132 return (n,a)
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:]
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:]
148 keytup = (intkey, intkeyacc)
150 sharp_key_seq = sharp_keys ()
151 flat_key_seq = flat_keys ()
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))
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"
167 key_table = [0] * 7
168 for a in accseq:
169 key_table[a] = key_table[a] + accsign
172 return key_table
174 def try_parse_header_line (ln):
175 m = re.match ('^(.): *(.*)$', ln)
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.
187 global_voice_stuff.append ('\\key %s;' % a)
188 if g == 'O':
189 header ['origin'] = a
190 if g == 'X':
191 header ['crossRefNumber'] = a
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)
205 return m
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'
215 if name > 4:
216 name = name -7
217 return chr (name + ord('c')) + s * acc
219 def octave_to_mudela_quotes (o):
220 s =''
221 if o < 0:
222 o = -o
223 s=','
224 else:
225 s ='\''
227 return s * o
229 def parse_num (str):
230 durstr = ''
231 while str[0] in DIGITS:
232 durstr = durstr + str[0]
233 str = str[1:]
235 n = None
236 if durstr:
237 n =string.atoi (durstr)
238 return (str,n)
241 def duration_to_mudela_duration (multiply_tup, defaultlen, dots):
242 base = 1
244 # (num / den) / defaultlen < 1/base
245 while base * multiply_tup[0] < defaultlen * multiply_tup[1]:
246 base = base * 2
249 return '%d%s' % ( base, '.'* dots)
251 class Parser_state:
252 def __init__ (self):
253 self.next_dots = 0
254 self.next_den = 1
257 # WAT IS ABC EEN ONTZETTENDE PROGRAMMEERPOEP !
258 def try_parse_note (str, parser_state):
259 mud = ''
261 slur_begin =0
262 if str[0] == '(':
263 slur_begin = 1
264 str = str[1:]
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
277 octave = 0;
278 if str[0] in "ABCDEFG":
279 str = string.lower (str[0]) + str[1:]
280 octave = -1
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!
290 while str[0] == ',':
291 octave = octave - 1
292 str = str[1:]
293 while str[0] == '\'':
294 octave = octave + 1
295 str = str[1:]
297 num = 0
298 den = parser_state.next_den
299 parser_state.next_den = 1
301 (str, num) = parse_num (str)
302 if not num:
303 num = 1
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)
312 den = den * d
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
321 while str[0] == '<':
322 str = str [1:]
323 den = den * 2
324 parser_state.next_dots = parser_state.next_dots + 1
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))
331 slur_end =0
332 if str[0] == ')':
333 slur_begin = 1
334 str = str[1:]
337 return str
339 def junk_space (str):
340 while str and str[0] in '\t\n ':
341 str = str[1:]
343 return str
345 def try_parse_escape (str):
346 if str [0] != '\\':
347 return str
349 str = str[1:]
350 if str[0] == 'K':
351 compute_key ()
353 return str
356 def try_parse_bar (str):
357 if str[0] == '|':
358 str = str[1:]
359 return str
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
379 def parse_file (fn):
380 f = open (fn)
381 ls = f.readlines ()
383 head = 1
384 state = Parser_state ()
385 for l in ls:
386 if re.match ('^[\t ]*(%.*)?$', l):
387 continue
389 if head:
390 m = try_parse_header_line (l)
391 if not m:
392 head = 0
394 if not head:
395 m = try_parse_body_line (l,state)
398 def identify():
399 print '%s %s' % (program_name, version)
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.
406 -h, --help this help.
411 identify()
412 (options, files) = getopt.getopt (sys.argv[1:], 'h', ['help'])
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
424 for f in files:
425 if f == '-':
426 f = ''
428 parse_file (f)
429 dump_header (header)
430 print global_voice_stuff, 1