*** empty log message ***
[lilypond.git] / scripts / mup2ly.py
blob2630c4931d9ab2b9b19fb0dd40f22907e161968e
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
38 # if set, LILYPONDPREFIX must take prevalence
39 # if datadir is not set, we're doing a build and LILYPONDPREFIX
40 datadir = '@local_lilypond_datadir@'
41 if os.environ.has_key ('LILYPONDPREFIX') \
42 or '@local_lilypond_datadir@' == '@' + 'local_lilypond_datadir' + '@':
43 datadir = os.environ['LILYPONDPREFIX']
44 else:
45 datadir = '@local_lilypond_datadir@'
47 sys.path.append (os.path.join (datadir, 'python'))
48 sys.path.append (os.path.join (datadir, 'python/out'))
50 program_name = 'mup2ly'
51 program_version = '@TOPLEVEL_VERSION@'
52 original_dir = os.getcwd ()
53 temp_dir = os.path.join (original_dir, '%s.dir' % program_name)
54 errorport = sys.stderr
55 keep_temp_dir_p = 0
56 verbose_p = 0
58 localedir = '@localedir@'
59 try:
60 import gettext
61 gettext.bindtextdomain ('lilypond', localedir)
62 gettext.textdomain ('lilypond')
63 _ = gettext.gettext
64 except:
65 def _ (s):
66 return s
69 program_name = 'mup2ly'
70 help_summary = _ ("Convert mup to LilyPond source")
72 option_definitions = [
73 ('', 'd', 'debug', _ ("debug")),
74 ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")),
75 ('', 'h', 'help', _ ("this help")),
76 ('FILE', 'o', 'output', _ ("write output to FILE")),
77 ('', 'E', 'pre-process', _ ("only pre-process")),
78 ('', 'V', 'verbose', _ ("verbose")),
79 ('', 'v', 'version', _ ("print version number")),
80 ('', 'w', 'warranty', _ ("show warranty and copyright")),
84 ################################################################
85 # lilylib.py -- options and stuff
87 # source file of the GNU LilyPond music typesetter
89 # Handle bug in Python 1.6-2.1
91 # there are recursion limits for some patterns in Python 1.6 til 2.1.
92 # fix this by importing pre instead. Fix by Mats.
94 # todo: should check Python version first.
95 try:
96 import pre
97 re = pre
98 del pre
99 except ImportError:
100 import re
102 # Attempt to fix problems with limited stack size set by Python!
103 # Sets unlimited stack size. Note that the resource module only
104 # is available on UNIX.
105 try:
106 import resource
107 resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
108 except:
109 pass
111 try:
112 import gettext
113 gettext.bindtextdomain ('lilypond', localedir)
114 gettext.textdomain ('lilypond')
115 _ = gettext.gettext
116 except:
117 def _ (s):
118 return s
120 program_version = '@TOPLEVEL_VERSION@'
121 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
122 program_version = '1.5.54'
124 def identify ():
125 sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
127 def warranty ():
128 identify ()
129 sys.stdout.write ('\n')
130 sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2002'))
131 sys.stdout.write ('\n')
132 sys.stdout.write (' Han-Wen Nienhuys')
133 sys.stdout.write (' Jan Nieuwenhuizen')
134 sys.stdout.write ('\n')
135 sys.stdout.write (_ (r'''
136 Distributed under terms of the GNU General Public License. It comes with
137 NO WARRANTY.'''))
138 sys.stdout.write ('\n')
140 def progress (s):
141 errorport.write (s + '\n')
143 def warning (s):
144 progress (_ ("warning: ") + s)
146 def user_error (s, e=1):
147 errorport.write (program_name + ":" + _ ("error: ") + s + '\n')
148 sys.exit (e)
150 def error (s):
151 '''Report the error S. Exit by raising an exception. Please
152 do not abuse by trying to catch this error. If you do not want
153 a stack trace, write to the output directly.
155 RETURN VALUE
157 None
161 progress (_ ("error: ") + s)
162 raise _ ("Exiting ... ")
164 def getopt_args (opts):
165 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
166 short = ''
167 long = []
168 for o in opts:
169 if o[1]:
170 short = short + o[1]
171 if o[0]:
172 short = short + ':'
173 if o[2]:
174 l = o[2]
175 if o[0]:
176 l = l + '='
177 long.append (l)
178 return (short, long)
180 def option_help_str (o):
181 '''Transform one option description (4-tuple ) into neatly formatted string'''
182 sh = ' '
183 if o[1]:
184 sh = '-%s' % o[1]
186 sep = ' '
187 if o[1] and o[2]:
188 sep = ','
190 long = ''
191 if o[2]:
192 long= '--%s' % o[2]
194 arg = ''
195 if o[0]:
196 if o[2]:
197 arg = '='
198 arg = arg + o[0]
199 return ' ' + sh + sep + long + arg
202 def options_help_str (opts):
203 '''Convert a list of options into a neatly formatted string'''
204 w = 0
205 strs =[]
206 helps = []
208 for o in opts:
209 s = option_help_str (o)
210 strs.append ((s, o[3]))
211 if len (s) > w:
212 w = len (s)
214 str = ''
215 for s in strs:
216 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
217 return str
219 def help ():
220 ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name),
221 ('\n\n'),
222 (help_summary),
223 ('\n\n'),
224 (_ ("Options:")),
225 ('\n'),
226 (options_help_str (option_definitions)),
227 ('\n\n'),
228 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
229 ('\n')]
230 map (sys.stdout.write, ls)
232 def setup_temp ():
234 Create a temporary directory, and return its name.
236 global temp_dir
237 if not keep_temp_dir_p:
238 temp_dir = tempfile.mktemp (program_name)
239 try:
240 os.mkdir (temp_dir, 0777)
241 except OSError:
242 pass
244 return temp_dir
247 def system (cmd, ignore_error = 0, quiet =0):
248 """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
250 RETURN VALUE
252 Exit status of CMD
255 if verbose_p:
256 progress (_ ("Invoking `%s\'") % cmd)
258 st = os.system (cmd)
259 if st:
260 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
261 msg = name + ': ' + _ ("command exited with value %d") % st
262 if ignore_error:
263 if not quiet:
264 warning (msg + ' ' + _ ("(ignored)") + ' ')
265 else:
266 error (msg)
268 return st
271 def cleanup_temp ():
272 if not keep_temp_dir_p:
273 if verbose_p:
274 progress (_ ("Cleaning %s...") % temp_dir)
275 shutil.rmtree (temp_dir)
278 def strip_extension (f, ext):
279 (p, e) = os.path.splitext (f)
280 if e == ext:
281 e = ''
282 return p + e
285 def cp_to_dir (pattern, dir):
286 "Copy files matching re PATTERN from cwd to DIR"
287 # Duh. Python style portable: cp *.EXT OUTDIR
288 # system ('cp *.%s %s' % (ext, outdir), 1)
289 files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.'))
290 map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files)
293 # Python < 1.5.2 compatibility
295 # On most platforms, this is equivalent to
296 #`normpath(join(os.getcwd()), PATH)'. *Added in Python version 1.5.2*
297 if os.path.__dict__.has_key ('abspath'):
298 abspath = os.path.abspath
299 else:
300 def abspath (path):
301 return os.path.normpath (os.path.join (os.getcwd (), path))
303 if os.__dict__.has_key ('makedirs'):
304 makedirs = os.makedirs
305 else:
306 def makedirs (dir, mode=0777):
307 system ('mkdir -p %s' % dir)
310 def mkdir_p (dir, mode=0777):
311 if not os.path.isdir (dir):
312 makedirs (dir, mode)
315 # if set, LILYPONDPREFIX must take prevalence
316 # if datadir is not set, we're doing a build and LILYPONDPREFIX
317 datadir = '@local_lilypond_datadir@'
319 if os.environ.has_key ('LILYPONDPREFIX') :
320 datadir = os.environ['LILYPONDPREFIX']
321 else:
322 datadir = '@local_lilypond_datadir@'
325 while datadir[-1] == os.sep:
326 datadir= datadir[:-1]
328 sys.path.insert (0, os.path.join (datadir, 'python'))
330 ################################################################
331 # END Library
334 output = 0
337 # PMX cut and paste
340 def encodeint (i):
341 return chr (i + ord ('A'))
344 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
346 def pitch_to_lily_string (tup):
347 (o,n,a) = tup
349 nm = chr((n + 2) % 7 + ord ('a'))
350 nm = nm + actab[a]
351 if o > 0:
352 nm = nm + "'" * o
353 elif o < 0:
354 nm = nm + "," * -o
355 return nm
357 def gcd (a,b):
358 if b == 0:
359 return a
360 c = a
361 while c:
362 c = a % b
363 a = b
364 b = c
365 return a
367 def rat_simplify (r):
368 (n,d) = r
369 if d < 0:
370 d = -d
371 n = -n
372 if n == 0:
373 return (0,1)
374 else:
375 g = gcd (n, d)
376 return (n/g, d/g)
378 def rat_multiply (a,b):
379 (x,y) = a
380 (p,q) = b
382 return rat_simplify ((x*p, y*q))
384 def rat_divide (a,b):
385 (p,q) = b
386 return rat_multiply (a, (q,p))
388 tuplet_table = {
389 2: 3,
390 3: 2,
391 5: 4
395 def rat_add (a,b):
396 (x,y) = a
397 (p,q) = b
399 return rat_simplify ((x*q + p*y, y*q))
401 def rat_neg (a):
402 (p,q) = a
403 return (-p,q)
406 def rat_larger (a,b):
407 return rat_subtract (a, b )[0] > 0
409 def rat_subtract (a,b ):
410 return rat_add (a, rat_neg (b))
412 def rat_to_duration (frac):
413 log = 1
414 d = (1,1)
415 while rat_larger (d, frac):
416 d = rat_multiply (d, (1,2))
417 log = log << 1
419 frac = rat_subtract (frac, d)
420 dots = 0
421 if frac == rat_multiply (d, (1,2)):
422 dots = 1
423 elif frac == rat_multiply (d, (3,4)):
424 dots = 2
425 return (log, dots)
428 class Barcheck :
429 def __init__ (self):
430 pass
431 def dump (self):
432 return '|\n'
435 class Meter :
436 def __init__ (self,nums):
437 self.nums = nums
438 def dump (self):
439 return ' %{ FIXME: meter change %} '
441 class Beam:
442 def __init__ (self, ch):
443 self.char = ch
444 def dump (self):
445 return self.char
447 class Slur:
448 def __init__ (self,id):
449 self.id = id
450 self.start_chord = None
451 self.end_chord = None
453 def calculate (self):
454 s =self.start_chord
455 e= self.end_chord
457 if e and s:
458 s.note_suffix = s.note_suffix + '-('
459 e.note_prefix = e.note_suffix + "-)"
460 else:
461 sys.stderr.write ("\nOrphaned slur")
463 class Voice:
464 def __init__ (self, n):
465 self.number = n
466 self.entries = []
467 self.chords = []
468 self.staff = None
469 self.current_slurs = []
470 self.slurs = []
472 def toggle_slur (self, id):
474 for s in self.current_slurs:
475 if s.id == id:
476 self.current_slurs.remove (s)
477 s.end_chord = self.chords[-1]
478 return
479 s = Slur (id)
480 s.start_chord = self.chords[-1]
481 self.current_slurs.append (s)
482 self.slurs.append (s)
484 def last_chord (self):
485 if len (self.chords):
486 return self.chords[-1]
487 else:
488 ch = Chord ()
489 ch.basic_duration = 4
490 return ch
492 def add_chord (self, ch):
493 self.chords.append (ch)
494 self.entries.append (ch)
496 def add_nonchord (self, nch):
497 self.entries.append (nch)
499 def idstring (self):
500 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
502 def dump (self):
503 str = ''
504 #if not self.entries:
505 # #return '\n'
506 # #ugh ugh
507 # return '\n%s = {}\n\n' % self.idstring ()
508 ln = ' '
509 one_two = ("One", "Two")
510 if self.staff.voices [1 - self.number].entries:
511 ln = ln + '\\voice%s\n ' % one_two[self.number]
512 for e in self.entries:
513 next = e.dump ()
514 if next[-1] == '\n':
515 str = str + ln + next + ' '
516 ln = ' '
517 continue
519 if len (ln) +len (next) > 72:
520 str = str+ ln + '\n'
521 ln = ' '
522 ln = ln + next + ' '
525 str = str + ln
526 id = self.idstring ()
528 str = '''%s = \\context Voice = %s \\notes {
532 '''% (id, id, str)
533 return str
535 def calculate_graces (self):
536 lastgr = 0
537 lastc = None
538 for c in self.chords:
539 if c.grace and not lastgr:
540 c.chord_prefix = c.chord_prefix + '\\grace { '
541 elif not c.grace and lastgr:
542 lastc.chord_suffix = lastc.chord_suffix + ' } '
543 lastgr = c.grace
544 lastc = c
546 def calculate (self):
547 self.calculate_graces ()
548 for s in self.slurs:
549 s.calculate ()
551 class Clef:
552 def __init__ (self, cl):
553 self.type = cl
555 def dump (self):
556 return '\\clef %s' % self.type
558 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
559 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
561 class Key:
562 def __init__ (self, sharps, flats):
563 self.flats = flats
564 self.sharps = sharps
566 def dump (self):
567 if self.sharps and self.flats:
568 k = '\\keysignature %s ' % 'TODO'
569 elif self.sharps:
570 k = '\\notes\\key %s \major' % key_sharps[self.sharps]
571 elif self.flats:
572 k = '\\notes\\key %s \major' % key_flats[self.flats]
573 return k
575 class Time:
576 def __init__ (self, frac):
577 self.frac = frac
579 def dump (self):
580 return '\\time %d/%d' % (self.frac[0], self.frac[1])
583 clef_table = {
584 'b':'bass' ,
585 'r':'baritone',
586 'n':'tenor',
587 'a':'alto',
588 'm':'mezzosoprano',
589 's':'soprano',
590 't':'treble',
591 'f':'frenchviolin',
594 class Staff:
595 def __init__ (self, n):
596 # ugh
597 self.voices = (Voice (0), Voice (1))
599 self.clef = None
600 self.time = None
601 self.key = None
602 self.instrument = 0
603 self.number = n
605 i = 0
606 for v in self.voices:
607 v.staff = self
608 v.number = i
609 i = i+1
611 #def set_clef (self, letter):
612 # clstr = clef_table[letter]
613 # self.voices[0].add_nonchord (Clef (clstr))
615 def calculate (self):
616 for v in self.voices:
617 v.calculate ()
619 def idstring (self):
620 return 'staff%s' % encodeint (self.number)
622 def dump (self):
623 str = ''
625 refs = ''
626 for v in self.voices:
627 if v.entries:
628 # urg
629 if v == self.voices[0]:
630 if self.clef:
631 refs = refs + self.clef.dump ()
632 if self.time:
633 refs = refs + self.time.dump ()
634 if self.key:
635 refs = refs + self.key.dump ()
636 if refs:
637 refs = '\n ' + refs
638 str = str + v.dump()
639 refs = refs + '\n \\' + v.idstring ()
640 str = str + '''
641 %s = \context Staff = %s <%s
644 ''' % (self.idstring (), self.idstring (), refs)
645 return str
647 class Tuplet:
648 def __init__ (self, number, base, dots):
649 self.chords = []
650 self.number = number
651 self.replaces = tuplet_table[number]
652 self.base = base
653 self.dots = dots
655 length = (1,base)
656 if dots == 1:
657 length = rat_multiply (length, (3,2))
658 elif dots == 2:
659 length = rat_multiply (length, (7,4))
661 length = rat_multiply (length, (1,self.replaces))
663 (nb,nd) =rat_to_duration (length)
665 self.note_base = nb
666 self.note_dots = nd
668 def add_chord (self, ch):
669 ch.dots = self.note_dots
670 ch.basic_duration = self.note_base
671 self.chords.append (ch)
673 if len (self.chords) == 1:
674 ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
675 elif len (self.chords) == self.number:
676 ch.chord_suffix = ' }'
678 class Chord:
679 def __init__ (self):
680 self.pitches = []
681 self.multimeasure = 0
682 self.dots = 0
683 self.basic_duration = 0
684 self.scripts = []
685 self.grace = 0
686 self.chord_prefix = ''
687 self.chord_suffix = ''
688 self.note_prefix = ''
689 self.note_suffix = ''
691 # maybe use import copy?
692 def copy (self):
693 ch = Chord ()
694 #for i in self.pitches:
695 # ch.pitches.append (i)
696 ch.pitches = self.pitches[:]
697 ch.multimeasure = self.multimeasure
698 ch.dots = self.dots
699 ch.basic_duration = self.basic_duration
700 #for i in self.scripts:
701 # ch.scripts.append (i)
702 ch.scripts = self.scripts[:]
703 ch.grace = self.grace
705 ch.chord_prefix = self.chord_prefix
706 ch.chord_suffix = self.chord_suffix
707 ch.note_prefix = self.note_prefix
708 ch.note_suffix = self.note_suffix
709 return ch
712 def dump (self):
713 str = ''
715 sd = ''
716 if self.basic_duration == 0.5:
717 sd = '\\breve'
718 else:
719 sd = '%d' % self.basic_duration
720 sd = sd + '.' * self.dots
721 for p in self.pitches:
722 if str:
723 str = str + ' '
724 str = str + pitch_to_lily_string (p)
727 str = self.note_prefix +str + self.note_suffix
729 if len (self.pitches) > 1:
730 str = '<<%s>>' % str
731 elif self.multimeasure:
732 str = 'R'
733 elif len (self.pitches) == 0:
734 str = 'r'
736 str = str + sd
737 for s in self.scripts:
738 str = str + '-' + s
740 str = self.chord_prefix + str + self.chord_suffix
742 return str
744 SPACE=' \t\n'
745 DIGITS ='0123456789'
746 basicdur_table = {
747 9: 0.5,
748 0: 0 ,
749 2: 2 ,
750 4: 4 ,
751 8: 8 ,
752 1: 16,
753 3: 32,
754 6: 64
758 ornament_table = {
759 't': '\\prall',
760 'm': '\\mordent',
761 'x': '"x"',
762 '+': '+',
763 'u': '"pizz"',
764 'p': '|',
765 '(': '"paren"',
766 ')': '"paren"',
767 'g': '"segno"',
768 '.': '.',
769 'fd': '\\fermata',
770 'f': '\\fermata',
771 '_': '-',
772 'T': '\\trill',
773 '>': '>',
774 '^': '^',
777 # http://www.arkkra.com/doc/uguide/contexts.html
779 contexts = (
780 'header',
781 'footer',
782 'header2',
783 'footer2',
784 'score',
785 'staff',
786 'voice',
787 'grids',
788 'music',
791 class Parser:
792 def __init__ (self, lines):
793 self.parse_function = self.parse_context_music
794 self.staffs = []
795 self.current_voices = []
796 self.forced_duration = None
797 self.last_name = 0
798 self.last_oct = 0
799 self.tuplets_expected = 0
800 self.tuplets = []
801 self.clef = None
802 self.time = None
803 self.key = None
805 self.parse (lines)
807 def parse_compound_location (self, line):
808 colon = string.index (line, ':')
809 s = line[:colon]
810 debug (s)
811 line = line[colon + 1:]
812 debug (line)
813 self.current_voices = []
814 ##self.current_staffs = []
815 map (self.parse_location, string.split (s, '&'))
816 return line
818 def parse_location (self, line):
819 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
821 def range_list_to_idxs (s):
823 # duh
824 def flatten (l):
825 f = []
826 for i in l:
827 for j in i:
828 f.append (j)
829 return f
831 def range_to_list (s):
832 if string.find (s, '-') >= 0:
833 debug ('s: ' + s)
834 l = map (string.lstrip,
835 string.split (s, '-'))
836 r = range (string.atoi (l[0]) - 1,
837 string.atoi (l[1]))
838 else:
839 r = (string.atoi (s) - 1,)
840 return r
842 ranges = string.split (s, ',')
843 l = flatten (map (range_to_list, ranges))
844 l.sort ()
845 return l
847 staff_idxs = range_list_to_idxs (m.group (1))
848 if m.group (2):
849 voice_idxs = range_list_to_idxs (m.group (2))
850 else:
851 voice_idxs = [0]
852 for s in staff_idxs:
853 while s > len (self.staffs) - 1:
854 self.staffs.append (Staff (s))
855 for v in voice_idxs:
856 self.current_voices.append (self.staffs[s].voices[v])
858 def parse_note (self, line):
859 # FIXME: 1?
860 oct = 1
861 name = (ord (line[0]) - ord ('a') + 5) % 7
862 # FIXME: does key play any role in this?
863 alteration = 0
864 debug ('NOTE: ' + `line`)
865 line = string.lstrip (line[1:])
866 while line:
867 if len (line) > 1 and line[:2] == '//':
868 line = 0
869 break
870 elif line[0] == '#':
871 alteration = alteration + 1
872 elif line[0] == '&':
873 alteration = alteration - 1
874 elif line[0] == '+':
875 oct = oct + 1
876 elif line[0] == '-':
877 oct = oct - 1
878 else:
879 skipping (line[0])
880 line = string.lstrip (line[1:])
881 return (oct, name, alteration)
883 def parse_chord (self, line):
884 debug ('CHORD: ' + line)
885 line = string.lstrip (line)
886 ch = Chord ()
887 if not line:
888 #ch = self.current_voices[0].last_chord ()
889 ch = self.last_chord.copy ()
890 else:
891 m = re.match ('^([0-9]+)([.]*)', line)
892 if m:
893 ch.basic_duration = string.atoi (m.group (1))
894 line = line[len (m.group (1)):]
895 if m.group (2):
896 ch.dots = len (m.group (2))
897 line = line[len (m.group (2)):]
898 else:
899 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
900 ch.basic_duration = self.last_chord.basic_duration
902 line = string.lstrip (line)
903 if len (line) > 1 and line[:2] == '//':
904 line = 0
905 #ugh
906 if not line:
907 debug ('nline: ' + line)
908 #ch = self.current_voices[0].last_chord ()
909 n = self.last_chord.copy ()
910 n.basic_duration = ch.basic_duration
911 n.dots = ch.dots
912 ch = n
913 debug ('ch.pitsen:' + `ch.pitches`)
914 debug ('ch.dur:' + `ch.basic_duration`)
915 else:
916 debug ('eline: ' + line)
918 while line:
919 if len (line) > 1 and line[:2] == '//':
920 line = 0
921 break
922 elif line[:1] == 'mr':
923 ch.multimeasure = 1
924 line = line[2:]
925 elif line[:1] == 'ms':
926 ch.multimeasure = 1
927 line = line[2:]
928 elif line[0] in 'rs':
929 line = line[1:]
930 pass
931 elif line[0] in 'abcdefg':
932 m = re.match ('([a-g][-#&+]*)', line)
933 l = len (m.group (1))
934 pitch = self.parse_note (line[:l])
935 debug ('PITCH: ' + `pitch`)
936 ch.pitches.append (pitch)
937 line = line[l:]
938 break
939 else:
940 skipping (line[0])
941 line = line[1:]
942 line = string.lstrip (line)
943 debug ('CUR-VOICES: ' + `self.current_voices`)
944 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
945 self.last_chord = ch
947 def parse_lyrics_location (self, line):
948 line = line.lstrip (line)
949 addition = 0
950 m = re.match ('^(between[ \t]+)', line)
951 if m:
952 line = line[len (m.group (1)):]
953 addition = 0.5
954 else:
955 m = re.match ('^(above [ \t]+)', line)
956 if m:
957 line = line[len (m.group (1)):]
958 addition = -0.5
959 else:
960 addlyrics = 1
962 def parse_voice (self, line):
963 line = string.lstrip (line)
964 # `;' is not a separator, chords end with ';'
965 chords = string.split (line, ';')[:-1]
966 # mup resets default duration and pitch each bar
967 self.last_chord = Chord ()
968 self.last_chord.basic_duration = 4
969 map (self.parse_chord, chords)
971 def init_context_header (self, line):
972 self.parse_function = self.parse_context_header
974 def parse_context_header (self, line):
975 debug ('header: ' + line)
976 skipping (line)
978 def init_context_footer (self, line):
979 self.parse_function = self.parse_context_footer
981 def parse_context_footer (self, line):
982 debug ('footer: ' + line)
983 skipping (line)
985 def init_context_header2 (self, line):
986 self.parse_function = self.parse_context_header2
988 def parse_context_header2 (self, line):
989 debug ('header2: ' + line)
990 skipping (line)
992 def init_context_footer2 (self, line):
993 self.parse_function = self.parse_context_footer2
995 def parse_context_footer2 (self, line):
996 debug ('footer2: ' + line)
997 skipping (line)
999 def init_context_score (self, line):
1000 self.parse_function = self.parse_context_score
1002 def parse_context_score (self, line):
1003 debug ('score: ' + line)
1004 line = string.lstrip (line)
1005 # ugh: these (and lots more) should also be parsed in
1006 # context staff. we should have a class Staff_properties
1007 # and parse/set all those.
1008 m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line)
1009 if m:
1010 line = line[len (m.group (1)):]
1011 self.time = Time ((string.atoi (m.group (2)),
1012 string.atoi (m.group (3))))
1014 m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line)
1015 if m:
1016 line = line[len (m.group (1)):]
1017 n = string.atoi (m.group (2))
1018 if m.group (3) == '#':
1019 self.key = Key (n, 0)
1020 else:
1021 self.key = Key (0, n)
1022 skipping (line)
1024 def init_context_staff (self, line):
1025 self.parse_function = self.parse_context_staff
1027 def parse_context_staff (self, line):
1028 debug ('staff: ' + line)
1029 skipping (line)
1031 def init_context_voice (self, line):
1032 self.parse_function = self.parse_context_voice
1034 def parse_context_voice (self, line):
1035 debug ('voice: ' + line)
1036 skipping (line)
1038 def init_context_grids (self, line):
1039 self.parse_function = self.parse_context_grids
1041 def parse_context_grids (self, line):
1042 debug ('grids: ' + line)
1043 skipping (line)
1045 def init_context_music (self, line):
1046 self.parse_function = self.parse_context_music
1048 def parse_context_music (self, line):
1049 debug ('music: ' + line)
1050 line = string.lstrip (line)
1051 if line and line[0] in '0123456789':
1052 line = self.parse_compound_location (line)
1053 self.parse_voice (line)
1054 else:
1055 m = re.match ('^(TODOlyrics[ \t]+)', line)
1056 if m:
1057 line = line[len (m.group (1)):]
1058 self.parse_lyrics_location (line[7:])
1059 self.parse_lyrics (line)
1060 else:
1061 skipping (line)
1063 def parse (self, lines):
1064 # shortcut: set to official mup maximum (duh)
1065 # self.set_staffs (40)
1066 for line in lines:
1067 debug ('LINE: ' + `line`)
1068 m = re.match ('^([a-z]+2?)', line)
1070 if m:
1071 word = m.group (1)
1072 if word in contexts:
1073 eval ('self.init_context_%s (line)' % word)
1074 continue
1075 else:
1076 warning (_ ("no such context: %s") % word)
1077 skipping (line)
1078 else:
1079 debug ('FUNC: ' + `self.parse_function`)
1080 self.parse_function (line)
1082 for c in self.staffs:
1083 # hmm
1084 if not c.clef and self.clef:
1085 c.clef = self.clef
1086 if not c.time and self.time:
1087 c.time = self.time
1088 if not c.key and self.key:
1089 c.key = self.key
1090 c.calculate ()
1092 def dump (self):
1093 str = ''
1095 refs = ''
1096 for s in self.staffs:
1097 str = str + s.dump ()
1098 refs = refs + '\n \\' + s.idstring ()
1100 str = str + '''
1102 \score {
1105 \paper {}
1106 \midi {}
1108 ''' % refs
1109 return str
1112 class Pre_processor:
1113 def __init__ (self, raw_lines):
1114 self.lines = []
1115 self.active = [1]
1116 self.process_function = self.process_line
1117 self.macro_name = ''
1118 self.macro_body = ''
1119 self.process (raw_lines)
1121 def process_line (self, line):
1122 global macros
1123 m = re.match ('^([ \t]*([a-zA-Z]+))', line)
1124 s = line
1125 if m:
1126 word = m.group (2)
1127 debug ('MACRO?: ' + `word`)
1128 if word in pre_processor_commands:
1129 line = line[len (m.group (1)):]
1130 eval ('self.process_macro_%s (line)' % word)
1131 s = ''
1132 else:
1133 if macros.has_key (word):
1134 s = macros[word] + line[len (m.group (1)):]
1135 if not self.active [-1]:
1136 s = ''
1137 return s
1139 def process_macro_body (self, line):
1140 global macros
1141 # dig this: mup allows ifdefs inside macro bodies
1142 s = self.process_line (line)
1143 m = re.match ('(.*[^\\\\])(@)(.*)', s)
1144 if m:
1145 self.macro_body = self.macro_body + '\n' + m.group (1)
1146 macros[self.macro_name] = self.macro_body
1147 debug ('MACROS: ' + `macros`)
1148 # don't do nested multi-line defines
1149 self.process_function = self.process_line
1150 if m.group (3):
1151 s = m.group (3)
1152 else:
1153 s = ''
1154 else:
1155 self.macro_body = self.macro_body + '\n' + s
1156 s = ''
1157 return s
1159 # duh: mup is strictly line-based, except for `define',
1160 # which is `@' terminated and may span several lines
1161 def process_macro_define (self, line):
1162 global macros
1163 # don't define new macros in unactive areas
1164 if not self.active[-1]:
1165 return
1166 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line)
1167 n = m.group (1)
1168 if m.group (5):
1169 if m.group (2):
1170 e = m.group (2)
1171 else:
1172 e = ''
1173 macros[n] = e
1174 debug ('MACROS: ' + `macros`)
1175 else:
1176 # To support nested multi-line define's
1177 # process_function and macro_name, macro_body
1178 # should become lists (stacks)
1179 # The mup manual is undetermined on this
1180 # and I haven't seen examples doing it.
1182 # don't do nested multi-line define's
1183 if m.group (2):
1184 self.macro_body = m.group (2)
1185 else:
1186 self.macro_body = ''
1187 self.macro_name = n
1188 self.process_function = self.process_macro_body
1190 def process_macro_ifdef (self, line):
1191 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1192 if m:
1194 active = self.active[-1] and macros.has_key (m.group (1))
1195 debug ('ACTIVE: %d' % active)
1196 self.active.append (active)
1198 def process_macro_ifndef (self, line):
1199 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1200 if m:
1201 active = self.active[-1] and not macros.has_key (m.group (1))
1202 self.active.append (active)
1204 def process_macro_else (self, line):
1205 debug ('ELSE')
1206 self.active[-1] = not self.active[-1]
1208 def process_macro_endif (self, line):
1209 self.active = self.active[:-1]
1211 def process (self, raw_lines):
1212 s = ''
1213 for line in raw_lines:
1214 ls = string.split (self.process_function (line), '\n')
1215 for i in ls:
1216 if i:
1217 s = s + string.rstrip (i)
1218 if s and s[-1] == '\\':
1219 s = string.rstrip (s[:-1])
1220 elif s:
1221 self.lines.append (s)
1222 s = ''
1225 debug_p = 0
1226 only_pre_process_p = 0
1227 def debug (s):
1228 if debug_p:
1229 progress ('DEBUG: ' + s)
1231 def skipping (s):
1232 if verbose_p or debug_p:
1233 progress ('SKIPPING: ' + s)
1235 (sh, long) = getopt_args (__main__.option_definitions)
1236 try:
1237 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1238 except:
1239 help ()
1240 sys.exit (2)
1242 macros = {}
1243 pre_processor_commands = (
1244 'define',
1245 'else',
1246 'endif',
1247 'ifdef',
1248 'ifndef',
1251 for opt in options:
1252 o = opt[0]
1253 a = opt[1]
1254 if 0:
1255 pass
1256 elif o== '--debug' or o == '-d':
1257 debug_p = 1
1258 elif o== '--define' or o == '-D':
1259 if string.find (a, '=') >= 0:
1260 (n, e) = string.split (a, '=')
1261 else:
1262 n = a
1263 e = ''
1264 macros[n] = e
1265 elif o== '--pre-process' or o == '-E':
1266 only_pre_process_p = 1
1267 elif o== '--help' or o == '-h':
1268 help ()
1269 sys.exit (0)
1270 elif o== '--verbose' or o == '-V':
1271 verbose_p = 1
1272 elif o == '--version' or o == '-v':
1273 identify ()
1274 sys.exit (0)
1275 elif o == '--output' or o == '-o':
1276 output = a
1277 else:
1278 print o
1279 raise getopt.error
1281 # writes to stdout for help2man
1282 # don't call
1283 # identify ()
1284 # sys.stdout.flush ()
1286 # handy emacs testing
1287 # if not files:
1288 # files = ['template.mup']
1290 if not files:
1291 files = ['-']
1293 for f in files:
1295 if f == '-':
1296 h = sys.stdin
1297 elif f and not os.path.isfile (f):
1298 f = strip_extension (f, '.mup') + '.mup'
1299 h = open (f)
1300 progress ( _("Processing `%s'..." % f))
1301 raw_lines = h.readlines ()
1302 p = Pre_processor (raw_lines)
1303 if only_pre_process_p:
1304 if not output:
1305 output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f))
1306 else:
1307 e = Parser (p.lines)
1308 if not output:
1309 output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
1310 if output == f:
1311 output = os.path.basename (f + '.ly')
1313 if f == '-':
1314 output = '-'
1315 out_h = sys.stdout
1316 else:
1317 out_h = open (output, 'w')
1319 progress (_ ("Writing `%s'...") % output)
1321 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1322 if only_pre_process_p:
1323 # duh
1324 ly = string.join (p.lines, '\n')
1325 else:
1326 ly = tag + '\n\n' + e.dump ()
1328 out_h.write (ly)
1329 out_h.close ()
1330 if debug_p:
1331 print (ly)