lilypond-1.3.146
[lilypond.git] / scripts / mup2ly.py
blob0bb9cbf01b8f55073015ffb8876de4f92dea9334
1 #!@PYTHON@
2 # mup2ly.py -- mup input converter
3 #
4 # source file of the GNU LilyPond music typesetter
6 # (c) 2001
8 '''
9 TODO:
10 LOTS: we get all notes out now, rest after 1.4
12 * lyrics (partly done)
13 * bars
14 * slurs,ties
15 * staff settings
16 * tuplets
17 * grace
18 * ornaments
19 * midi settings
20 * titling
21 * chords entry mode
22 * repeats, percent repeats
24 '''
26 import os
27 import fnmatch
28 import stat
29 import string
30 import re
31 import getopt
32 import sys
33 import __main__
34 import operator
35 import tempfile
37 # let's not yet clutter lily's po with this mup converter junk
38 def _ (s):
39 return s
41 #sys.path.append ('@datadir@/python')
42 #import gettext
43 #gettext.bindtextdomain ('lilypond', '@localedir@')
44 #gettext.textdomain('lilypond')
45 #_ = gettext.gettext
50 program_name = 'mup2ly'
51 help_summary = _("Convert mup to ly")
52 output = 0
54 # lily_py.py -- options and stuff
56 # source file of the GNU LilyPond music typesetter
58 # BEGIN Library for these?
59 # cut-n-paste from ly2dvi
61 program_version = '@TOPLEVEL_VERSION@'
62 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
63 program_version = '1.3.142'
66 original_dir = os.getcwd ()
67 temp_dir = '%s.dir' % program_name
68 keep_temp_dir_p = 0
69 verbose_p = 0
71 def identify ():
72 sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
74 def warranty ():
75 identify ()
76 sys.stdout.write ('\n')
77 sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001'))
78 sys.stdout.write ('\n')
79 sys.stdout.write (' Han-Wen Nienhuys')
80 sys.stdout.write (' Jan Nieuwenhuizen')
81 sys.stdout.write ('\n')
82 sys.stdout.write (_ (r'''
83 Distributed under terms of the GNU General Public License. It comes with
84 absolutely NO WARRANTY.'''))
85 sys.stdout.write ('\n')
87 def progress (s):
88 if s[-1] != '\n':
89 s = s + '\n'
90 sys.stderr.write (s)
92 def warning (s):
93 sys.stderr.write (_ ("warning: ") + s)
94 sys.stderr.write ('\n')
97 def error (s):
98 sys.stderr.write (_ ("error: ") + s)
99 sys.stderr.write ('\n')
100 raise _ ("Exiting ... ")
102 def getopt_args (opts):
103 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
104 short = ''
105 long = []
106 for o in opts:
107 if o[1]:
108 short = short + o[1]
109 if o[0]:
110 short = short + ':'
111 if o[2]:
112 l = o[2]
113 if o[0]:
114 l = l + '='
115 long.append (l)
116 return (short, long)
118 def option_help_str (o):
119 '''Transform one option description (4-tuple ) into neatly formatted string'''
120 sh = ' '
121 if o[1]:
122 sh = '-%s' % o[1]
124 sep = ' '
125 if o[1] and o[2]:
126 sep = ','
128 long = ''
129 if o[2]:
130 long= '--%s' % o[2]
132 arg = ''
133 if o[0]:
134 if o[2]:
135 arg = '='
136 arg = arg + o[0]
137 return ' ' + sh + sep + long + arg
140 def options_help_str (opts):
141 '''Convert a list of options into a neatly formatted string'''
142 w = 0
143 strs =[]
144 helps = []
146 for o in opts:
147 s = option_help_str (o)
148 strs.append ((s, o[3]))
149 if len (s) > w:
150 w = len (s)
152 str = ''
153 for s in strs:
154 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
155 return str
157 def help ():
158 sys.stdout.write (_ ("Usage: %s [OPTION]... FILE") % program_name)
159 sys.stdout.write ('\n\n')
160 sys.stdout.write (help_summary)
161 sys.stdout.write ('\n\n')
162 sys.stdout.write (_ ("Options:"))
163 sys.stdout.write ('\n')
164 sys.stdout.write (options_help_str (option_definitions))
165 sys.stdout.write ('\n')
166 warning (_ ("%s is far from completed. Not all constructs are recognised.") % program_name)
167 sys.stdout.write ('\n')
168 sys.stdout.write (_ ("Report bugs to %s") % 'bug-gnu-music@gnu.org')
169 sys.stdout.write ('\n')
170 sys.exit (0)
173 def setup_temp ():
174 global temp_dir
175 if not keep_temp_dir_p:
176 temp_dir = tempfile.mktemp (program_name)
177 try:
178 os.mkdir (temp_dir, 0777)
179 except OSError:
180 pass
183 def system (cmd, ignore_error = 0):
184 if verbose_p:
185 progress (_ ("Invoking `%s\'") % cmd)
186 st = os.system (cmd)
187 if st:
188 msg = ( _ ("error: ") + _ ("command exited with value %d") % st)
189 if ignore_error:
190 sys.stderr.write (msg + ' ' + _ ("(ignored)") + ' ')
191 else:
192 error (msg)
194 return st
197 def cleanup_temp ():
198 if not keep_temp_dir_p:
199 if verbose_p:
200 progress (_ ('Cleaning up `%s\'') % temp_dir)
201 system ('rm -rf %s' % temp_dir)
204 def set_setting (dict, key, val):
205 try:
206 val = string.atof (val)
207 except ValueError:
208 #warning (_ ("invalid value: %s") % `val`)
209 pass
211 try:
212 dict[key].append (val)
213 except KeyError:
214 warning (_ ("no such setting: %s") % `key`)
215 dict[key] = [val]
217 def strip_extension (f, ext):
218 (p, e) = os.path.splitext (f)
219 if e == ext:
220 e = ''
221 return p + e
223 # END Library
227 # PMX cut and paste
230 def encodeint (i):
231 return chr (i + ord ('A'))
234 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
236 def pitch_to_lily_string (tup):
237 (o,n,a) = tup
239 nm = chr((n + 2) % 7 + ord ('a'))
240 nm = nm + actab[a]
241 if o > 0:
242 nm = nm + "'" * o
243 elif o < 0:
244 nm = nm + "," * -o
245 return nm
247 def gcd (a,b):
248 if b == 0:
249 return a
250 c = a
251 while c:
252 c = a % b
253 a = b
254 b = c
255 return a
257 def rat_simplify (r):
258 (n,d) = r
259 if d < 0:
260 d = -d
261 n = -n
262 if n == 0:
263 return (0,1)
264 else:
265 g = gcd (n, d)
266 return (n/g, d/g)
268 def rat_multiply (a,b):
269 (x,y) = a
270 (p,q) = b
272 return rat_simplify ((x*p, y*q))
274 def rat_divide (a,b):
275 (p,q) = b
276 return rat_multiply (a, (q,p))
278 tuplet_table = {
279 2: 3,
280 3: 2,
281 5: 4
285 def rat_add (a,b):
286 (x,y) = a
287 (p,q) = b
289 return rat_simplify ((x*q + p*y, y*q))
291 def rat_neg (a):
292 (p,q) = a
293 return (-p,q)
296 def rat_larger (a,b):
297 return rat_subtract (a, b )[0] > 0
299 def rat_subtract (a,b ):
300 return rat_add (a, rat_neg (b))
302 def rat_to_duration (frac):
303 log = 1
304 d = (1,1)
305 while rat_larger (d, frac):
306 d = rat_multiply (d, (1,2))
307 log = log << 1
309 frac = rat_subtract (frac, d)
310 dots = 0
311 if frac == rat_multiply (d, (1,2)):
312 dots = 1
313 elif frac == rat_multiply (d, (3,4)):
314 dots = 2
315 return (log, dots)
318 class Barcheck :
319 def __init__ (self):
320 pass
321 def dump (self):
322 return '|\n'
325 class Meter :
326 def __init__ (self,nums):
327 self.nums = nums
328 def dump (self):
329 return ' %{ FIXME: meter change %} '
331 class Beam:
332 def __init__ (self, ch):
333 self.char = ch
334 def dump (self):
335 return self.char
337 class Slur:
338 def __init__ (self,id):
339 self.id = id
340 self.start_chord = None
341 self.end_chord = None
343 def calculate (self):
344 s =self.start_chord
345 e= self.end_chord
347 if e and s:
348 s.note_suffix = s.note_suffix + '('
349 e.note_prefix = ')' + e.note_prefix
350 else:
351 sys.stderr.write ("\nOrphaned slur")
353 class Voice:
354 def __init__ (self, n):
355 self.number = n
356 self.entries = []
357 self.chords = []
358 self.staff = None
359 self.current_slurs = []
360 self.slurs = []
362 def toggle_slur (self, id):
364 for s in self.current_slurs:
365 if s.id == id:
366 self.current_slurs.remove (s)
367 s.end_chord = self.chords[-1]
368 return
369 s = Slur (id)
370 s.start_chord = self.chords[-1]
371 self.current_slurs.append (s)
372 self.slurs.append (s)
374 def last_chord (self):
375 if len (self.chords):
376 return self.chords[-1]
377 else:
378 ch = Chord ()
379 ch.basic_duration = 4
380 return ch
382 def add_chord (self, ch):
383 self.chords.append (ch)
384 self.entries.append (ch)
386 def add_nonchord (self, nch):
387 self.entries.append (nch)
389 def idstring (self):
390 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
392 def dump (self):
393 str = ''
394 #if not self.entries:
395 # #return '\n'
396 # #ugh ugh
397 # return '\n%s = {}\n\n' % self.idstring ()
398 ln = ' '
399 one_two = ("One", "Two")
400 if self.staff.voices [1 - self.number].entries:
401 ln = ln + '\\voice%s\n ' % one_two[self.number]
402 for e in self.entries:
403 next = e.dump ()
404 if next[-1] == '\n':
405 str = str + ln + next + ' '
406 ln = ' '
407 continue
409 if len (ln) +len (next) > 72:
410 str = str+ ln + '\n'
411 ln = ' '
412 ln = ln + next + ' '
415 str = str + ln
416 id = self.idstring ()
418 str = '''%s = \\context Voice = %s \\notes {
422 '''% (id, id, str)
423 return str
425 def calculate_graces (self):
426 lastgr = 0
427 lastc = None
428 for c in self.chords:
429 if c.grace and not lastgr:
430 c.chord_prefix = c.chord_prefix + '\\grace { '
431 elif not c.grace and lastgr:
432 lastc.chord_suffix = lastc.chord_suffix + ' } '
433 lastgr = c.grace
434 lastc = c
436 def calculate (self):
437 self.calculate_graces ()
438 for s in self.slurs:
439 s.calculate ()
441 class Clef:
442 def __init__ (self, cl):
443 self.type = cl
445 def dump (self):
446 return '\\clef %s' % self.type
448 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
449 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
451 class Key:
452 def __init__ (self, sharps, flats):
453 self.flats = flats
454 self.sharps = sharps
456 def dump (self):
457 if self.sharps and self.flats:
458 k = '\\keysignature %s ' % 'TODO'
459 elif self.sharps:
460 k = '\\notes\\key %s \major' % key_sharps[self.sharps]
461 elif self.flats:
462 k = '\\notes\\key %s \major' % key_flats[self.flats]
463 return k
465 class Time:
466 def __init__ (self, frac):
467 self.frac = frac
469 def dump (self):
470 return '\\time %d/%d' % (self.frac[0], self.frac[1])
473 clef_table = {
474 'b':'bass' ,
475 'r':'baritone',
476 'n':'tenor',
477 'a':'alto',
478 'm':'mezzosoprano',
479 's':'soprano',
480 't':'treble',
481 'f':'frenchviolin',
484 class Staff:
485 def __init__ (self, n):
486 # ugh
487 self.voices = (Voice (0), Voice (1))
489 self.clef = None
490 self.time = None
491 self.key = None
492 self.instrument = 0
493 self.number = n
495 i = 0
496 for v in self.voices:
497 v.staff = self
498 v.number = i
499 i = i+1
501 #def set_clef (self, letter):
502 # clstr = clef_table[letter]
503 # self.voices[0].add_nonchord (Clef (clstr))
505 def calculate (self):
506 for v in self.voices:
507 v.calculate ()
509 def idstring (self):
510 return 'staff%s' % encodeint (self.number)
512 def dump (self):
513 str = ''
515 refs = ''
516 for v in self.voices:
517 if v.entries:
518 # urg
519 if v == self.voices[0]:
520 if self.clef:
521 refs = refs + self.clef.dump ()
522 if self.time:
523 refs = refs + self.time.dump ()
524 if self.key:
525 refs = refs + self.key.dump ()
526 if refs:
527 refs = '\n ' + refs
528 str = str + v.dump()
529 refs = refs + '\n \\' + v.idstring ()
530 str = str + '''
531 %s = \context Staff = %s <%s
534 ''' % (self.idstring (), self.idstring (), refs)
535 return str
537 class Tuplet:
538 def __init__ (self, number, base, dots):
539 self.chords = []
540 self.number = number
541 self.replaces = tuplet_table[number]
542 self.base = base
543 self.dots = dots
545 length = (1,base)
546 if dots == 1:
547 length = rat_multiply (length, (3,2))
548 elif dots == 2:
549 length = rat_multiply (length, (7,4))
551 length = rat_multiply (length, (1,self.replaces))
553 (nb,nd) =rat_to_duration (length)
555 self.note_base = nb
556 self.note_dots = nd
558 def add_chord (self, ch):
559 ch.dots = self.note_dots
560 ch.basic_duration = self.note_base
561 self.chords.append (ch)
563 if len (self.chords) == 1:
564 ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
565 elif len (self.chords) == self.number:
566 ch.chord_suffix = ' }'
568 class Chord:
569 def __init__ (self):
570 self.pitches = []
571 self.multimeasure = 0
572 self.dots = 0
573 self.basic_duration = 0
574 self.scripts = []
575 self.grace = 0
576 self.chord_prefix = ''
577 self.chord_suffix = ''
578 self.note_prefix = ''
579 self.note_suffix = ''
581 # maybe use import copy?
582 def copy (self):
583 ch = Chord ()
584 #for i in self.pitches:
585 # ch.pitches.append (i)
586 ch.pitches = self.pitches[:]
587 ch.multimeasure = self.multimeasure
588 ch.dots = self.dots
589 ch.basic_duration = self.basic_duration
590 #for i in self.scripts:
591 # ch.scripts.append (i)
592 ch.scripts = self.scripts[:]
593 ch.grace = self.grace
595 ch.chord_prefix = self.chord_prefix
596 ch.chord_suffix = self.chord_suffix
597 ch.note_prefix = self.note_prefix
598 ch.note_suffix = self.note_suffix
599 return ch
602 def dump (self):
603 str = ''
605 sd = ''
606 if self.basic_duration == 0.5:
607 sd = '\\breve'
608 else:
609 sd = '%d' % self.basic_duration
610 sd = sd + '.' * self.dots
611 for p in self.pitches:
612 if str:
613 str = str + ' '
614 str = str + pitch_to_lily_string (p) + sd
616 for s in self.scripts:
617 str = str + '-' + s
619 str = self.note_prefix +str + self.note_suffix
621 if len (self.pitches) > 1:
622 str = '<%s>' % str
623 elif self.multimeasure:
624 str = 'R' + sd
625 elif len (self.pitches) == 0:
626 str = 'r' + sd
628 str = self.chord_prefix + str + self.chord_suffix
630 return str
632 SPACE=' \t\n'
633 DIGITS ='0123456789'
634 basicdur_table = {
635 9: 0.5,
636 0: 0 ,
637 2: 2 ,
638 4: 4 ,
639 8: 8 ,
640 1: 16,
641 3: 32,
642 6: 64
646 ornament_table = {
647 't': '\\prall',
648 'm': '\\mordent',
649 'x': '"x"',
650 '+': '+',
651 'u': '"pizz"',
652 'p': '|',
653 '(': '"paren"',
654 ')': '"paren"',
655 'g': '"segno"',
656 '.': '.',
657 'fd': '\\fermata',
658 'f': '\\fermata',
659 '_': '-',
660 'T': '\\trill',
661 '>': '>',
662 '^': '^',
665 # http://www.arkkra.com/doc/uguide/contexts.html
667 contexts = (
668 'header',
669 'footer',
670 'header2',
671 'footer2',
672 'score',
673 'staff',
674 'voice',
675 'grids',
676 'music',
679 class Parser:
680 def __init__ (self, lines):
681 self.parse_function = self.parse_context_music
682 self.staffs = []
683 self.current_voices = []
684 self.forced_duration = None
685 self.last_name = 0
686 self.last_oct = 0
687 self.tuplets_expected = 0
688 self.tuplets = []
689 self.clef = None
690 self.time = None
691 self.key = None
693 self.parse (lines)
695 def parse_compound_location (self, line):
696 colon = string.index (line, ':')
697 s = line[:colon]
698 debug (s)
699 line = line[colon + 1:]
700 debug (line)
701 self.current_voices = []
702 ##self.current_staffs = []
703 map (self.parse_location, string.split (s, '&'))
704 return line
706 def parse_location (self, line):
707 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
709 def range_list_to_idxs (s):
711 # duh
712 def flatten (l):
713 f = []
714 for i in l:
715 for j in i:
716 f.append (j)
717 return f
719 def range_to_list (s):
720 if string.find (s, '-') >= 0:
721 debug ('s: ' + s)
722 l = map (string.lstrip,
723 string.split (s, '-'))
724 r = range (string.atoi (l[0]) - 1,
725 string.atoi (l[1]))
726 else:
727 r = (string.atoi (s) - 1,)
728 return r
730 ranges = string.split (s, ',')
731 l = flatten (map (range_to_list, ranges))
732 l.sort ()
733 return l
735 staff_idxs = range_list_to_idxs (m.group (1))
736 if m.group (2):
737 voice_idxs = range_list_to_idxs (m.group (2))
738 else:
739 voice_idxs = [0]
740 for s in staff_idxs:
741 while s > len (self.staffs) - 1:
742 self.staffs.append (Staff (s))
743 for v in voice_idxs:
744 self.current_voices.append (self.staffs[s].voices[v])
746 def parse_note (self, line):
747 # FIXME: 1?
748 oct = 1
749 name = (ord (line[0]) - ord ('a') + 5) % 7
750 # FIXME: does key play any role in this?
751 alteration = 0
752 debug ('NOTE: ' + `line`)
753 line = string.lstrip (line[1:])
754 while line:
755 if len (line) > 1 and line[:2] == '//':
756 line = 0
757 break
758 elif line[0] == '#':
759 alteration = alteration + 1
760 elif line[0] == '&':
761 alteration = alteration - 1
762 elif line[0] == '+':
763 oct = oct + 1
764 elif line[0] == '-':
765 oct = oct - 1
766 else:
767 skipping (line[0])
768 line = string.lstrip (line[1:])
769 return (oct, name, alteration)
771 def parse_chord (self, line):
772 debug ('CHORD: ' + line)
773 line = string.lstrip (line)
774 ch = Chord ()
775 if not line:
776 #ch = self.current_voices[0].last_chord ()
777 ch = self.last_chord.copy ()
778 else:
779 m = re.match ('^([0-9]+)([.]*)', line)
780 if m:
781 ch.basic_duration = string.atoi (m.group (1))
782 line = line[len (m.group (1)):]
783 if m.group (2):
784 ch.dots = len (m.group (2))
785 line = line[len (m.group (2)):]
786 else:
787 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
788 ch.basic_duration = self.last_chord.basic_duration
790 line = string.lstrip (line)
791 if len (line) > 1 and line[:2] == '//':
792 line = 0
793 #ugh
794 if not line:
795 debug ('nline: ' + line)
796 #ch = self.current_voices[0].last_chord ()
797 n = self.last_chord.copy ()
798 n.basic_duration = ch.basic_duration
799 n.dots = ch.dots
800 ch = n
801 debug ('ch.pitsen:' + `ch.pitches`)
802 debug ('ch.dur:' + `ch.basic_duration`)
803 else:
804 debug ('eline: ' + line)
806 while line:
807 if len (line) > 1 and line[:2] == '//':
808 line = 0
809 break
810 elif line[:1] == 'mr':
811 ch.multimeasure = 1
812 line = line[2:]
813 elif line[:1] == 'ms':
814 ch.multimeasure = 1
815 line = line[2:]
816 elif line[0] in 'rs':
817 line = line[1:]
818 pass
819 elif line[0] in 'abcdefg':
820 m = re.match ('([a-g][-#&+]*)', line)
821 l = len (m.group (1))
822 pitch = self.parse_note (line[:l])
823 debug ('PITCH: ' + `pitch`)
824 ch.pitches.append (pitch)
825 line = line[l:]
826 break
827 else:
828 skipping (line[0])
829 line = line[1:]
830 line = string.lstrip (line)
831 debug ('CUR-VOICES: ' + `self.current_voices`)
832 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
833 self.last_chord = ch
835 def parse_lyrics_location (self, line):
836 line = line.lstrip (line)
837 addition = 0
838 m = re.match ('^(between[ \t]+)', line)
839 if m:
840 line = line[len (m.group (1)):]
841 addition = 0.5
842 else:
843 m = re.match ('^(above [ \t]+)', line)
844 if m:
845 line = line[len (m.group (1)):]
846 addition = -0.5
847 else:
848 addlyrics = 1
850 def parse_voice (self, line):
851 line = string.lstrip (line)
852 # `;' is not a separator, chords end with ';'
853 chords = string.split (line, ';')[:-1]
854 # mup resets default duration and pitch each bar
855 self.last_chord = Chord ()
856 self.last_chord.basic_duration = 4
857 map (self.parse_chord, chords)
859 def init_context_header (self, line):
860 self.parse_function = self.parse_context_header
862 def parse_context_header (self, line):
863 debug ('header: ' + line)
864 skipping (line)
866 def init_context_footer (self, line):
867 self.parse_function = self.parse_context_footer
869 def parse_context_footer (self, line):
870 debug ('footer: ' + line)
871 skipping (line)
873 def init_context_header2 (self, line):
874 self.parse_function = self.parse_context_header2
876 def parse_context_header2 (self, line):
877 debug ('header2: ' + line)
878 skipping (line)
880 def init_context_footer2 (self, line):
881 self.parse_function = self.parse_context_footer2
883 def parse_context_footer2 (self, line):
884 debug ('footer2: ' + line)
885 skipping (line)
887 def init_context_score (self, line):
888 self.parse_function = self.parse_context_score
890 def parse_context_score (self, line):
891 debug ('score: ' + line)
892 line = string.lstrip (line)
893 # ugh: these (and lots more) should also be parsed in
894 # context staff. we should have a class Staff_properties
895 # and parse/set all those.
896 m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line)
897 if m:
898 line = line[len (m.group (1)):]
899 self.time = Time ((string.atoi (m.group (2)),
900 string.atoi (m.group (3))))
902 m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line)
903 if m:
904 line = line[len (m.group (1)):]
905 n = string.atoi (m.group (2))
906 if m.group (3) == '#':
907 self.key = Key (n, 0)
908 else:
909 self.key = Key (0, n)
910 skipping (line)
912 def init_context_staff (self, line):
913 self.parse_function = self.parse_context_staff
915 def parse_context_staff (self, line):
916 debug ('staff: ' + line)
917 skipping (line)
919 def init_context_voice (self, line):
920 self.parse_function = self.parse_context_voice
922 def parse_context_voice (self, line):
923 debug ('voice: ' + line)
924 skipping (line)
926 def init_context_grids (self, line):
927 self.parse_function = self.parse_context_grids
929 def parse_context_grids (self, line):
930 debug ('grids: ' + line)
931 skipping (line)
933 def init_context_music (self, line):
934 self.parse_function = self.parse_context_music
936 def parse_context_music (self, line):
937 debug ('music: ' + line)
938 line = string.lstrip (line)
939 if line and line[0] in '0123456789':
940 line = self.parse_compound_location (line)
941 self.parse_voice (line)
942 else:
943 m = re.match ('^(TODOlyrics[ \t]+)', line)
944 if m:
945 line = line[len (m.group (1)):]
946 self.parse_lyrics_location (line[7:])
947 self.parse_lyrics (line)
948 else:
949 skipping (line)
951 def parse (self, lines):
952 # shortcut: set to official mup maximum (duh)
953 # self.set_staffs (40)
954 for line in lines:
955 debug ('LINE: ' + `line`)
956 m = re.match ('^([a-z]+2?)', line)
958 if m:
959 word = m.group (1)
960 if word in contexts:
961 eval ('self.init_context_%s (line)' % word)
962 continue
963 else:
964 warning (_ ("no such context: %s") % word)
965 skipping (line)
966 else:
967 debug ('FUNC: ' + `self.parse_function`)
968 self.parse_function (line)
970 for c in self.staffs:
971 # hmm
972 if not c.clef and self.clef:
973 c.clef = self.clef
974 if not c.time and self.time:
975 c.time = self.time
976 if not c.key and self.key:
977 c.key = self.key
978 c.calculate ()
980 def dump (self):
981 str = ''
983 refs = ''
984 for s in self.staffs:
985 str = str + s.dump ()
986 refs = refs + '\n \\' + s.idstring ()
988 str = str + '''
990 \score {
993 \paper {}
994 \midi {}
996 ''' % refs
997 return str
1000 class Pre_processor:
1001 def __init__ (self, raw_lines):
1002 self.lines = []
1003 self.active = [1]
1004 self.process_function = self.process_line
1005 self.macro_name = ''
1006 self.macro_body = ''
1007 self.process (raw_lines)
1009 def process_line (self, line):
1010 global macros
1011 m = re.match ('^([ \t]*([a-zA-Z]+))', line)
1012 s = line
1013 if m:
1014 word = m.group (2)
1015 debug ('MACRO?: ' + `word`)
1016 if word in pre_processor_commands:
1017 line = line[len (m.group (1)):]
1018 eval ('self.process_macro_%s (line)' % word)
1019 s = ''
1020 else:
1021 if macros.has_key (word):
1022 s = macros[word] + line[len (m.group (1)):]
1023 if not self.active [-1]:
1024 s = ''
1025 return s
1027 def process_macro_body (self, line):
1028 global macros
1029 # dig this: mup allows ifdefs inside macro bodies
1030 s = self.process_line (line)
1031 m = re.match ('(.*[^\\\\])(@)(.*)', s)
1032 if m:
1033 self.macro_body = self.macro_body + '\n' + m.group (1)
1034 macros[self.macro_name] = self.macro_body
1035 debug ('MACROS: ' + `macros`)
1036 # don't do nested multi-line defines
1037 self.process_function = self.process_line
1038 if m.group (3):
1039 s = m.group (3)
1040 else:
1041 s = ''
1042 else:
1043 self.macro_body = self.macro_body + '\n' + s
1044 s = ''
1045 return s
1047 # duh: mup is strictly line-based, except for `define',
1048 # which is `@' terminated and may span several lines
1049 def process_macro_define (self, line):
1050 global macros
1051 # don't define new macros in unactive areas
1052 if not self.active[-1]:
1053 return
1054 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line)
1055 n = m.group (1)
1056 if m.group (5):
1057 if m.group (2):
1058 e = m.group (2)
1059 else:
1060 e = ''
1061 macros[n] = e
1062 debug ('MACROS: ' + `macros`)
1063 else:
1064 # To support nested multi-line define's
1065 # process_function and macro_name, macro_body
1066 # should become lists (stacks)
1067 # The mup manual is undetermined on this
1068 # and I haven't seen examples doing it.
1070 # don't do nested multi-line define's
1071 if m.group (2):
1072 self.macro_body = m.group (2)
1073 else:
1074 self.macro_body = ''
1075 self.macro_name = n
1076 self.process_function = self.process_macro_body
1078 def process_macro_ifdef (self, line):
1079 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1080 if m:
1082 active = self.active[-1] and macros.has_key (m.group (1))
1083 debug ('ACTIVE: %d' % active)
1084 self.active.append (active)
1086 def process_macro_ifndef (self, line):
1087 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1088 if m:
1089 active = self.active[-1] and not macros.has_key (m.group (1))
1090 self.active.append (active)
1092 def process_macro_else (self, line):
1093 debug ('ELSE')
1094 self.active[-1] = not self.active[-1]
1096 def process_macro_endif (self, line):
1097 self.active = self.active[:-1]
1099 def process (self, raw_lines):
1100 s = ''
1101 for line in raw_lines:
1102 ls = string.split (self.process_function (line), '\n')
1103 for i in ls:
1104 if i:
1105 s = s + string.rstrip (i)
1106 if s and s[-1] == '\\':
1107 s = string.rstrip (s[:-1])
1108 elif s:
1109 self.lines.append (s)
1110 s = ''
1114 option_definitions = [
1115 ('', 'd', 'debug', _ ("debug")),
1116 ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")),
1117 ('', 'h', 'help', _ ("this help")),
1118 ('FILE', 'o', 'output', _ ("write output to FILE")),
1119 ('', 'E', 'pre-process', _ ("only pre-process")),
1120 ('', 'V', 'verbose', _ ("verbose")),
1121 ('', 'v', 'version', _ ("print version number")),
1122 ('', 'w', 'warranty', _ ("show warranty and copyright")),
1125 debug_p = 0
1126 only_pre_process_p = 0
1127 def debug (s):
1128 if debug_p:
1129 progress ('DEBUG: ' + s)
1131 def skipping (s):
1132 if verbose_p or debug_p:
1133 progress ('SKIPPING: ' + s)
1135 (sh, long) = getopt_args (__main__.option_definitions)
1136 try:
1137 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1138 except:
1139 help ()
1140 sys.exit (2)
1142 macros = {}
1143 pre_processor_commands = (
1144 'define',
1145 'else',
1146 'endif',
1147 'ifdef',
1148 'ifndef',
1151 for opt in options:
1152 o = opt[0]
1153 a = opt[1]
1154 if 0:
1155 pass
1156 elif o== '--debug' or o == '-d':
1157 debug_p = 1
1158 elif o== '--define' or o == '-D':
1159 if string.find (a, '=') >= 0:
1160 (n, e) = string.split (a, '=')
1161 else:
1162 n = a
1163 e = ''
1164 macros[n] = e
1165 elif o== '--pre-process' or o == '-E':
1166 only_pre_process_p = 1
1167 elif o== '--help' or o == '-h':
1168 help ()
1169 sys.exit (0)
1170 elif o== '--verbose' or o == '-V':
1171 verbose_p = 1
1172 elif o == '--version' or o == '-v':
1173 identify ()
1174 sys.exit (0)
1175 elif o == '--output' or o == '-o':
1176 output = a
1177 else:
1178 print o
1179 raise getopt.error
1181 # writes to stdout for help2man
1182 # don't call
1183 # identify ()
1184 # sys.stdout.flush ()
1186 # handy emacs testing
1187 if not files:
1188 files = ['template.mup']
1190 for f in files:
1191 if f == '-':
1192 f = ''
1194 if f and not os.path.isfile (f):
1195 f = strip_extension (f, '.mup') + '.mup'
1196 progress ( _("Processing %s..." % f))
1197 raw_lines = open (f).readlines ()
1198 p = Pre_processor (raw_lines)
1199 if only_pre_process_p:
1200 if not output:
1201 output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f))
1202 else:
1203 e = Parser (p.lines)
1204 if not output:
1205 output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
1206 if output == f:
1207 output = os.path.basename (f + '.ly')
1209 progress (_ ("Writing %s...") % output)
1211 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1212 if only_pre_process_p:
1213 # duh
1214 ly = string.join (p.lines, '\n')
1215 else:
1216 ly = tag + '\n\n' + e.dump ()
1218 o = open (output, 'w')
1219 o.write (ly)
1220 o.close ()
1221 if debug_p:
1222 print (ly)