* Documentation/user/changing-defaults.itely (Text encoding): node
[lilypond.git] / scripts / mup2ly.py
blobe6e1050bafdcc5bbcbee39cecaad14af6e4404ee
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', _ ("print this help")),
76 ('FILE', 'o', 'output', _ ("write output to FILE")),
77 ('', 'E', 'pre-process', _ ("only pre-process")),
78 ('', 'V', 'verbose', _ ("be 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--2004')
131 sys.stdout.write ('\n')
132 sys.stdout.write (' Han-Wen Nienhuys')
133 sys.stdout.write (' Jan Nieuwenhuizen')
134 sys.stdout.write ('\n\n')
135 sys.stdout.write (_ ("Distributed under terms of the GNU General Public License."))
136 sys.stdout.write (_ ("It comes with NO WARRANTY."))
137 sys.stdout.write ('\n')
139 def progress (s):
140 errorport.write (s + '\n')
142 def warning (s):
143 progress (_ ("warning: ") + s)
145 def user_error (s, e=1):
146 errorport.write (program_name + ":" + _ ("error: ") + s + '\n')
147 sys.exit (e)
149 def error (s):
150 '''Report the error S. Exit by raising an exception. Please
151 do not abuse by trying to catch this error. If you do not want
152 a stack trace, write to the output directly.
154 RETURN VALUE
156 None
160 progress (_ ("error: ") + s)
161 raise _ ("Exiting ... ")
163 def getopt_args (opts):
164 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
165 short = ''
166 long = []
167 for o in opts:
168 if o[1]:
169 short = short + o[1]
170 if o[0]:
171 short = short + ':'
172 if o[2]:
173 l = o[2]
174 if o[0]:
175 l = l + '='
176 long.append (l)
177 return (short, long)
179 def option_help_str (o):
180 '''Transform one option description (4-tuple ) into neatly formatted string'''
181 sh = ' '
182 if o[1]:
183 sh = '-%s' % o[1]
185 sep = ' '
186 if o[1] and o[2]:
187 sep = ', '
189 long = ''
190 if o[2]:
191 long= '--%s' % o[2]
193 arg = ''
194 if o[0]:
195 if o[2]:
196 arg = '='
197 arg = arg + o[0]
198 return ' ' + sh + sep + long + arg
201 def options_help_str (opts):
202 '''Convert a list of options into a neatly formatted string'''
203 w = 0
204 strs =[]
205 helps = []
207 for o in opts:
208 s = option_help_str (o)
209 strs.append ((s, o[3]))
210 if len (s) > w:
211 w = len (s)
213 str = ''
214 for s in strs:
215 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
216 return str
218 def help ():
219 ls = [(_ ("Usage: %s [OPTIONS]... FILE") % program_name),
220 ('\n\n'),
221 (help_summary),
222 ('\n\n'),
223 (_ ("Options:")),
224 ('\n'),
225 (options_help_str (option_definitions)),
226 ('\n\n'),
227 (_ ("Report bugs to %s.") % 'bug-lilypond@gnu.org'),
228 ('\n')]
229 map (sys.stdout.write, ls)
231 def setup_temp ():
233 Create a temporary directory, and return its name.
235 global temp_dir
236 if not keep_temp_dir_p:
237 temp_dir = tempfile.mktemp (program_name)
238 try:
239 os.mkdir (temp_dir, 0777)
240 except OSError:
241 pass
243 return temp_dir
246 def system (cmd, ignore_error = 0, quiet =0):
247 """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
249 RETURN VALUE
251 Exit status of CMD
254 if verbose_p:
255 progress (_ ("Invoking `%s\'") % cmd)
257 st = os.system (cmd)
258 if st:
259 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
260 msg = name + ': ' + _ ("command exited with value %d") % st
261 if ignore_error:
262 if not quiet:
263 warning (msg + ' ' + _ ("(ignored)") + ' ')
264 else:
265 error (msg)
267 return st
270 def cleanup_temp ():
271 if not keep_temp_dir_p:
272 if verbose_p:
273 progress (_ ("Cleaning %s...") % temp_dir)
274 shutil.rmtree (temp_dir)
277 def strip_extension (f, ext):
278 (p, e) = os.path.splitext (f)
279 if e == ext:
280 e = ''
281 return p + e
284 def cp_to_dir (pattern, dir):
285 "Copy files matching re PATTERN from cwd to DIR"
286 # Duh. Python style portable: cp *.EXT OUTDIR
287 # system ('cp *.%s %s' % (ext, outdir), 1)
288 files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.'))
289 map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files)
292 # Python < 1.5.2 compatibility
294 # On most platforms, this is equivalent to
295 #`normpath(join(os.getcwd()), PATH)'. *Added in Python version 1.5.2*
296 if os.path.__dict__.has_key ('abspath'):
297 abspath = os.path.abspath
298 else:
299 def abspath (path):
300 return os.path.normpath (os.path.join (os.getcwd (), path))
302 if os.__dict__.has_key ('makedirs'):
303 makedirs = os.makedirs
304 else:
305 def makedirs (dir, mode=0777):
306 system ('mkdir -p %s' % dir)
309 def mkdir_p (dir, mode=0777):
310 if not os.path.isdir (dir):
311 makedirs (dir, mode)
314 # if set, LILYPONDPREFIX must take prevalence
315 # if datadir is not set, we're doing a build and LILYPONDPREFIX
316 datadir = '@local_lilypond_datadir@'
318 if os.environ.has_key ('LILYPONDPREFIX') :
319 datadir = os.environ['LILYPONDPREFIX']
320 else:
321 datadir = '@local_lilypond_datadir@'
324 while datadir[-1] == os.sep:
325 datadir= datadir[:-1]
327 sys.path.insert (0, os.path.join (datadir, 'python'))
329 ################################################################
330 # END Library
333 output = 0
336 # PMX cut and paste
339 def encodeint (i):
340 return chr (i + ord ('A'))
343 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
345 def pitch_to_lily_string (tup):
346 (o,n,a) = tup
348 nm = chr((n + 2) % 7 + ord ('a'))
349 nm = nm + actab[a]
350 if o > 0:
351 nm = nm + "'" * o
352 elif o < 0:
353 nm = nm + "," * -o
354 return nm
356 def gcd (a,b):
357 if b == 0:
358 return a
359 c = a
360 while c:
361 c = a % b
362 a = b
363 b = c
364 return a
366 def rat_simplify (r):
367 (n,d) = r
368 if d < 0:
369 d = -d
370 n = -n
371 if n == 0:
372 return (0,1)
373 else:
374 g = gcd (n, d)
375 return (n/g, d/g)
377 def rat_multiply (a,b):
378 (x,y) = a
379 (p,q) = b
381 return rat_simplify ((x*p, y*q))
383 def rat_divide (a,b):
384 (p,q) = b
385 return rat_multiply (a, (q,p))
387 tuplet_table = {
388 2: 3,
389 3: 2,
390 5: 4
394 def rat_add (a,b):
395 (x,y) = a
396 (p,q) = b
398 return rat_simplify ((x*q + p*y, y*q))
400 def rat_neg (a):
401 (p,q) = a
402 return (-p,q)
405 def rat_larger (a,b):
406 return rat_subtract (a, b )[0] > 0
408 def rat_subtract (a,b ):
409 return rat_add (a, rat_neg (b))
411 def rat_to_duration (frac):
412 log = 1
413 d = (1,1)
414 while rat_larger (d, frac):
415 d = rat_multiply (d, (1,2))
416 log = log << 1
418 frac = rat_subtract (frac, d)
419 dots = 0
420 if frac == rat_multiply (d, (1,2)):
421 dots = 1
422 elif frac == rat_multiply (d, (3,4)):
423 dots = 2
424 return (log, dots)
427 class Barcheck :
428 def __init__ (self):
429 pass
430 def dump (self):
431 return '|\n'
434 class Meter :
435 def __init__ (self,nums):
436 self.nums = nums
437 def dump (self):
438 return ' %{ FIXME: meter change %} '
440 class Beam:
441 def __init__ (self, ch):
442 self.char = ch
443 def dump (self):
444 return self.char
446 class Slur:
447 def __init__ (self,id):
448 self.id = id
449 self.start_chord = None
450 self.end_chord = None
452 def calculate (self):
453 s =self.start_chord
454 e= self.end_chord
456 if e and s:
457 s.note_suffix = s.note_suffix + '-('
458 e.note_prefix = e.note_suffix + "-)"
459 else:
460 sys.stderr.write ("\nOrphaned slur")
462 class Voice:
463 def __init__ (self, n):
464 self.number = n
465 self.entries = []
466 self.chords = []
467 self.staff = None
468 self.current_slurs = []
469 self.slurs = []
471 def toggle_slur (self, id):
473 for s in self.current_slurs:
474 if s.id == id:
475 self.current_slurs.remove (s)
476 s.end_chord = self.chords[-1]
477 return
478 s = Slur (id)
479 s.start_chord = self.chords[-1]
480 self.current_slurs.append (s)
481 self.slurs.append (s)
483 def last_chord (self):
484 if len (self.chords):
485 return self.chords[-1]
486 else:
487 ch = Chord ()
488 ch.basic_duration = 4
489 return ch
491 def add_chord (self, ch):
492 self.chords.append (ch)
493 self.entries.append (ch)
495 def add_nonchord (self, nch):
496 self.entries.append (nch)
498 def idstring (self):
499 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
501 def dump (self):
502 str = ''
503 #if not self.entries:
504 # #return '\n'
505 # #ugh ugh
506 # return '\n%s = {}\n\n' % self.idstring ()
507 ln = ' '
508 one_two = ("One", "Two")
509 if self.staff.voices [1 - self.number].entries:
510 ln = ln + '\\voice%s\n ' % one_two[self.number]
511 for e in self.entries:
512 next = e.dump ()
513 if next[-1] == '\n':
514 str = str + ln + next + ' '
515 ln = ' '
516 continue
518 if len (ln) +len (next) > 72:
519 str = str+ ln + '\n'
520 ln = ' '
521 ln = ln + next + ' '
524 str = str + ln
525 id = self.idstring ()
527 str = '''%s = \\context Voice = %s \\notes {
531 '''% (id, id, str)
532 return str
534 def calculate_graces (self):
535 lastgr = 0
536 lastc = None
537 for c in self.chords:
538 if c.grace and not lastgr:
539 c.chord_prefix = c.chord_prefix + '\\grace { '
540 elif not c.grace and lastgr:
541 lastc.chord_suffix = lastc.chord_suffix + ' } '
542 lastgr = c.grace
543 lastc = c
545 def calculate (self):
546 self.calculate_graces ()
547 for s in self.slurs:
548 s.calculate ()
550 class Clef:
551 def __init__ (self, cl):
552 self.type = cl
554 def dump (self):
555 return '\\clef %s' % self.type
557 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
558 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
560 class Key:
561 def __init__ (self, sharps, flats):
562 self.flats = flats
563 self.sharps = sharps
565 def dump (self):
566 if self.sharps and self.flats:
567 k = '\\keysignature %s ' % 'TODO'
568 elif self.sharps:
569 k = '\\notes\\key %s \major' % key_sharps[self.sharps]
570 elif self.flats:
571 k = '\\notes\\key %s \major' % key_flats[self.flats]
572 return k
574 class Time:
575 def __init__ (self, frac):
576 self.frac = frac
578 def dump (self):
579 return '\\time %d/%d' % (self.frac[0], self.frac[1])
582 clef_table = {
583 'b':'bass' ,
584 'r':'baritone',
585 'n':'tenor',
586 'a':'alto',
587 'm':'mezzosoprano',
588 's':'soprano',
589 't':'treble',
590 'f':'frenchviolin',
593 class Staff:
594 def __init__ (self, n):
595 # ugh
596 self.voices = (Voice (0), Voice (1))
598 self.clef = None
599 self.time = None
600 self.key = None
601 self.instrument = 0
602 self.number = n
604 i = 0
605 for v in self.voices:
606 v.staff = self
607 v.number = i
608 i = i+1
610 #def set_clef (self, letter):
611 # clstr = clef_table[letter]
612 # self.voices[0].add_nonchord (Clef (clstr))
614 def calculate (self):
615 for v in self.voices:
616 v.calculate ()
618 def idstring (self):
619 return 'staff%s' % encodeint (self.number)
621 def dump (self):
622 str = ''
624 refs = ''
625 for v in self.voices:
626 if v.entries:
627 # urg
628 if v == self.voices[0]:
629 if self.clef:
630 refs = refs + self.clef.dump ()
631 if self.time:
632 refs = refs + self.time.dump ()
633 if self.key:
634 refs = refs + self.key.dump ()
635 if refs:
636 refs = '\n ' + refs
637 str = str + v.dump()
638 refs = refs + '\n \\' + v.idstring ()
639 str = str + '''
640 %s = \context Staff = %s <<%s
643 ''' % (self.idstring (), self.idstring (), refs)
644 return str
646 class Tuplet:
647 def __init__ (self, number, base, dots):
648 self.chords = []
649 self.number = number
650 self.replaces = tuplet_table[number]
651 self.base = base
652 self.dots = dots
654 length = (1,base)
655 if dots == 1:
656 length = rat_multiply (length, (3,2))
657 elif dots == 2:
658 length = rat_multiply (length, (7,4))
660 length = rat_multiply (length, (1,self.replaces))
662 (nb,nd) =rat_to_duration (length)
664 self.note_base = nb
665 self.note_dots = nd
667 def add_chord (self, ch):
668 ch.dots = self.note_dots
669 ch.basic_duration = self.note_base
670 self.chords.append (ch)
672 if len (self.chords) == 1:
673 ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
674 elif len (self.chords) == self.number:
675 ch.chord_suffix = ' }'
677 class Chord:
678 def __init__ (self):
679 self.pitches = []
680 self.multimeasure = 0
681 self.dots = 0
682 self.basic_duration = 0
683 self.scripts = []
684 self.grace = 0
685 self.chord_prefix = ''
686 self.chord_suffix = ''
687 self.note_prefix = ''
688 self.note_suffix = ''
690 # maybe use import copy?
691 def copy (self):
692 ch = Chord ()
693 #for i in self.pitches:
694 # ch.pitches.append (i)
695 ch.pitches = self.pitches[:]
696 ch.multimeasure = self.multimeasure
697 ch.dots = self.dots
698 ch.basic_duration = self.basic_duration
699 #for i in self.scripts:
700 # ch.scripts.append (i)
701 ch.scripts = self.scripts[:]
702 ch.grace = self.grace
704 ch.chord_prefix = self.chord_prefix
705 ch.chord_suffix = self.chord_suffix
706 ch.note_prefix = self.note_prefix
707 ch.note_suffix = self.note_suffix
708 return ch
711 def dump (self):
712 str = ''
714 sd = ''
715 if self.basic_duration == 0.5:
716 sd = '\\breve'
717 else:
718 sd = '%d' % self.basic_duration
719 sd = sd + '.' * self.dots
720 for p in self.pitches:
721 if str:
722 str = str + ' '
723 str = str + pitch_to_lily_string (p)
726 str = self.note_prefix +str + self.note_suffix
728 if len (self.pitches) > 1:
729 str = '<%s>' % str
730 elif self.multimeasure:
731 str = 'R'
732 elif len (self.pitches) == 0:
733 str = 'r'
735 str = str + sd
736 for s in self.scripts:
737 str = str + '-' + s
739 str = self.chord_prefix + str + self.chord_suffix
741 return str
743 SPACE=' \t\n'
744 DIGITS ='0123456789'
745 basicdur_table = {
746 9: 0.5,
747 0: 0 ,
748 2: 2 ,
749 4: 4 ,
750 8: 8 ,
751 1: 16,
752 3: 32,
753 6: 64
757 ornament_table = {
758 't': '\\prall',
759 'm': '\\mordent',
760 'x': '"x"',
761 '+': '+',
762 'u': '"pizz"',
763 'p': '|',
764 '(': '"paren"',
765 ')': '"paren"',
766 'g': '"segno"',
767 '.': '.',
768 'fd': '\\fermata',
769 'f': '\\fermata',
770 '_': '-',
771 'T': '\\trill',
772 '>': '>',
773 '^': '^',
776 # http://www.arkkra.com/doc/uguide/contexts.html
778 contexts = (
779 'header',
780 'footer',
781 'header2',
782 'footer2',
783 'score',
784 'staff',
785 'voice',
786 'grids',
787 'music',
790 class Parser:
791 def __init__ (self, lines):
792 self.parse_function = self.parse_context_music
793 self.staffs = []
794 self.current_voices = []
795 self.forced_duration = None
796 self.last_name = 0
797 self.last_oct = 0
798 self.tuplets_expected = 0
799 self.tuplets = []
800 self.clef = None
801 self.time = None
802 self.key = None
804 self.parse (lines)
806 def parse_compound_location (self, line):
807 colon = string.index (line, ':')
808 s = line[:colon]
809 debug (s)
810 line = line[colon + 1:]
811 debug (line)
812 self.current_voices = []
813 ##self.current_staffs = []
814 map (self.parse_location, string.split (s, '&'))
815 return line
817 def parse_location (self, line):
818 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
820 def range_list_to_idxs (s):
822 # duh
823 def flatten (l):
824 f = []
825 for i in l:
826 for j in i:
827 f.append (j)
828 return f
830 def range_to_list (s):
831 if string.find (s, '-') >= 0:
832 debug ('s: ' + s)
833 l = map (string.lstrip,
834 string.split (s, '-'))
835 r = range (string.atoi (l[0]) - 1,
836 string.atoi (l[1]))
837 else:
838 r = (string.atoi (s) - 1,)
839 return r
841 ranges = string.split (s, ',')
842 l = flatten (map (range_to_list, ranges))
843 l.sort ()
844 return l
846 staff_idxs = range_list_to_idxs (m.group (1))
847 if m.group (2):
848 voice_idxs = range_list_to_idxs (m.group (2))
849 else:
850 voice_idxs = [0]
851 for s in staff_idxs:
852 while s > len (self.staffs) - 1:
853 self.staffs.append (Staff (s))
854 for v in voice_idxs:
855 self.current_voices.append (self.staffs[s].voices[v])
857 def parse_note (self, line):
858 # FIXME: 1?
859 oct = 1
860 name = (ord (line[0]) - ord ('a') + 5) % 7
861 # FIXME: does key play any role in this?
862 alteration = 0
863 debug ('NOTE: ' + `line`)
864 line = string.lstrip (line[1:])
865 while line:
866 if len (line) > 1 and line[:2] == '//':
867 line = 0
868 break
869 elif line[0] == '#':
870 alteration = alteration + 1
871 elif line[0] == '&':
872 alteration = alteration - 1
873 elif line[0] == '+':
874 oct = oct + 1
875 elif line[0] == '-':
876 oct = oct - 1
877 else:
878 skipping (line[0])
879 line = string.lstrip (line[1:])
880 return (oct, name, alteration)
882 def parse_chord (self, line):
883 debug ('CHORD: ' + line)
884 line = string.lstrip (line)
885 ch = Chord ()
886 if not line:
887 #ch = self.current_voices[0].last_chord ()
888 ch = self.last_chord.copy ()
889 else:
890 m = re.match ('^([0-9]+)([.]*)', line)
891 if m:
892 ch.basic_duration = string.atoi (m.group (1))
893 line = line[len (m.group (1)):]
894 if m.group (2):
895 ch.dots = len (m.group (2))
896 line = line[len (m.group (2)):]
897 else:
898 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
899 ch.basic_duration = self.last_chord.basic_duration
901 line = string.lstrip (line)
902 if len (line) > 1 and line[:2] == '//':
903 line = 0
904 #ugh
905 if not line:
906 debug ('nline: ' + line)
907 #ch = self.current_voices[0].last_chord ()
908 n = self.last_chord.copy ()
909 n.basic_duration = ch.basic_duration
910 n.dots = ch.dots
911 ch = n
912 debug ('ch.pitsen:' + `ch.pitches`)
913 debug ('ch.dur:' + `ch.basic_duration`)
914 else:
915 debug ('eline: ' + line)
917 while line:
918 if len (line) > 1 and line[:2] == '//':
919 line = 0
920 break
921 elif line[:1] == 'mr':
922 ch.multimeasure = 1
923 line = line[2:]
924 elif line[:1] == 'ms':
925 ch.multimeasure = 1
926 line = line[2:]
927 elif line[0] in 'rs':
928 line = line[1:]
929 pass
930 elif line[0] in 'abcdefg':
931 m = re.match ('([a-g][-#&+]*)', line)
932 l = len (m.group (1))
933 pitch = self.parse_note (line[:l])
934 debug ('PITCH: ' + `pitch`)
935 ch.pitches.append (pitch)
936 line = line[l:]
937 break
938 else:
939 skipping (line[0])
940 line = line[1:]
941 line = string.lstrip (line)
942 debug ('CUR-VOICES: ' + `self.current_voices`)
943 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
944 self.last_chord = ch
946 def parse_lyrics_location (self, line):
947 line = line.lstrip (line)
948 addition = 0
949 m = re.match ('^(between[ \t]+)', line)
950 if m:
951 line = line[len (m.group (1)):]
952 addition = 0.5
953 else:
954 m = re.match ('^(above [ \t]+)', line)
955 if m:
956 line = line[len (m.group (1)):]
957 addition = -0.5
958 else:
959 addlyrics = 1
961 def parse_voice (self, line):
962 line = string.lstrip (line)
963 # `;' is not a separator, chords end with ';'
964 chords = string.split (line, ';')[:-1]
965 # mup resets default duration and pitch each bar
966 self.last_chord = Chord ()
967 self.last_chord.basic_duration = 4
968 map (self.parse_chord, chords)
970 def init_context_header (self, line):
971 self.parse_function = self.parse_context_header
973 def parse_context_header (self, line):
974 debug ('header: ' + line)
975 skipping (line)
977 def init_context_footer (self, line):
978 self.parse_function = self.parse_context_footer
980 def parse_context_footer (self, line):
981 debug ('footer: ' + line)
982 skipping (line)
984 def init_context_header2 (self, line):
985 self.parse_function = self.parse_context_header2
987 def parse_context_header2 (self, line):
988 debug ('header2: ' + line)
989 skipping (line)
991 def init_context_footer2 (self, line):
992 self.parse_function = self.parse_context_footer2
994 def parse_context_footer2 (self, line):
995 debug ('footer2: ' + line)
996 skipping (line)
998 def init_context_score (self, line):
999 self.parse_function = self.parse_context_score
1001 def parse_context_score (self, line):
1002 debug ('score: ' + line)
1003 line = string.lstrip (line)
1004 # ugh: these (and lots more) should also be parsed in
1005 # context staff. we should have a class Staff_properties
1006 # and parse/set all those.
1007 m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line)
1008 if m:
1009 line = line[len (m.group (1)):]
1010 self.time = Time ((string.atoi (m.group (2)),
1011 string.atoi (m.group (3))))
1013 m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line)
1014 if m:
1015 line = line[len (m.group (1)):]
1016 n = string.atoi (m.group (2))
1017 if m.group (3) == '#':
1018 self.key = Key (n, 0)
1019 else:
1020 self.key = Key (0, n)
1021 skipping (line)
1023 def init_context_staff (self, line):
1024 self.parse_function = self.parse_context_staff
1026 def parse_context_staff (self, line):
1027 debug ('staff: ' + line)
1028 skipping (line)
1030 def init_context_voice (self, line):
1031 self.parse_function = self.parse_context_voice
1033 def parse_context_voice (self, line):
1034 debug ('voice: ' + line)
1035 skipping (line)
1037 def init_context_grids (self, line):
1038 self.parse_function = self.parse_context_grids
1040 def parse_context_grids (self, line):
1041 debug ('grids: ' + line)
1042 skipping (line)
1044 def init_context_music (self, line):
1045 self.parse_function = self.parse_context_music
1047 def parse_context_music (self, line):
1048 debug ('music: ' + line)
1049 line = string.lstrip (line)
1050 if line and line[0] in '0123456789':
1051 line = self.parse_compound_location (line)
1052 self.parse_voice (line)
1053 else:
1054 m = re.match ('^(TODOlyrics[ \t]+)', line)
1055 if m:
1056 line = line[len (m.group (1)):]
1057 self.parse_lyrics_location (line[7:])
1058 self.parse_lyrics (line)
1059 else:
1060 skipping (line)
1062 def parse (self, lines):
1063 # shortcut: set to official mup maximum (duh)
1064 # self.set_staffs (40)
1065 for line in lines:
1066 debug ('LINE: ' + `line`)
1067 m = re.match ('^([a-z]+2?)', line)
1069 if m:
1070 word = m.group (1)
1071 if word in contexts:
1072 eval ('self.init_context_%s (line)' % word)
1073 continue
1074 else:
1075 warning (_ ("no such context: %s") % word)
1076 skipping (line)
1077 else:
1078 debug ('FUNC: ' + `self.parse_function`)
1079 self.parse_function (line)
1081 for c in self.staffs:
1082 # hmm
1083 if not c.clef and self.clef:
1084 c.clef = self.clef
1085 if not c.time and self.time:
1086 c.time = self.time
1087 if not c.key and self.key:
1088 c.key = self.key
1089 c.calculate ()
1091 def dump (self):
1092 str = ''
1094 refs = ''
1095 for s in self.staffs:
1096 str = str + s.dump ()
1097 refs = refs + '\n \\' + s.idstring ()
1099 str = str + '''
1101 \score {
1102 <<%s
1104 \paper {}
1105 \midi {}
1107 ''' % refs
1108 return str
1111 class Pre_processor:
1112 def __init__ (self, raw_lines):
1113 self.lines = []
1114 self.active = [1]
1115 self.process_function = self.process_line
1116 self.macro_name = ''
1117 self.macro_body = ''
1118 self.process (raw_lines)
1120 def process_line (self, line):
1121 global macros
1122 m = re.match ('^([ \t]*([a-zA-Z]+))', line)
1123 s = line
1124 if m:
1125 word = m.group (2)
1126 debug ('MACRO?: ' + `word`)
1127 if word in pre_processor_commands:
1128 line = line[len (m.group (1)):]
1129 eval ('self.process_macro_%s (line)' % word)
1130 s = ''
1131 else:
1132 if macros.has_key (word):
1133 s = macros[word] + line[len (m.group (1)):]
1134 if not self.active [-1]:
1135 s = ''
1136 return s
1138 def process_macro_body (self, line):
1139 global macros
1140 # dig this: mup allows ifdefs inside macro bodies
1141 s = self.process_line (line)
1142 m = re.match ('(.*[^\\\\])(@)(.*)', s)
1143 if m:
1144 self.macro_body = self.macro_body + '\n' + m.group (1)
1145 macros[self.macro_name] = self.macro_body
1146 debug ('MACROS: ' + `macros`)
1147 # don't do nested multi-line defines
1148 self.process_function = self.process_line
1149 if m.group (3):
1150 s = m.group (3)
1151 else:
1152 s = ''
1153 else:
1154 self.macro_body = self.macro_body + '\n' + s
1155 s = ''
1156 return s
1158 # duh: mup is strictly line-based, except for `define',
1159 # which is `@' terminated and may span several lines
1160 def process_macro_define (self, line):
1161 global macros
1162 # don't define new macros in unactive areas
1163 if not self.active[-1]:
1164 return
1165 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line)
1166 n = m.group (1)
1167 if m.group (5):
1168 if m.group (2):
1169 e = m.group (2)
1170 else:
1171 e = ''
1172 macros[n] = e
1173 debug ('MACROS: ' + `macros`)
1174 else:
1175 # To support nested multi-line define's
1176 # process_function and macro_name, macro_body
1177 # should become lists (stacks)
1178 # The mup manual is undetermined on this
1179 # and I haven't seen examples doing it.
1181 # don't do nested multi-line define's
1182 if m.group (2):
1183 self.macro_body = m.group (2)
1184 else:
1185 self.macro_body = ''
1186 self.macro_name = n
1187 self.process_function = self.process_macro_body
1189 def process_macro_ifdef (self, line):
1190 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1191 if m:
1193 active = self.active[-1] and macros.has_key (m.group (1))
1194 debug ('ACTIVE: %d' % active)
1195 self.active.append (active)
1197 def process_macro_ifndef (self, line):
1198 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1199 if m:
1200 active = self.active[-1] and not macros.has_key (m.group (1))
1201 self.active.append (active)
1203 def process_macro_else (self, line):
1204 debug ('ELSE')
1205 self.active[-1] = not self.active[-1]
1207 def process_macro_endif (self, line):
1208 self.active = self.active[:-1]
1210 def process (self, raw_lines):
1211 s = ''
1212 for line in raw_lines:
1213 ls = string.split (self.process_function (line), '\n')
1214 for i in ls:
1215 if i:
1216 s = s + string.rstrip (i)
1217 if s and s[-1] == '\\':
1218 s = string.rstrip (s[:-1])
1219 elif s:
1220 self.lines.append (s)
1221 s = ''
1224 debug_p = 0
1225 only_pre_process_p = 0
1226 def debug (s):
1227 if debug_p:
1228 progress ('DEBUG: ' + s)
1230 def skipping (s):
1231 if verbose_p or debug_p:
1232 progress ('SKIPPING: ' + s)
1234 (sh, long) = getopt_args (__main__.option_definitions)
1235 try:
1236 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1237 except:
1238 help ()
1239 sys.exit (2)
1241 macros = {}
1242 pre_processor_commands = (
1243 'define',
1244 'else',
1245 'endif',
1246 'ifdef',
1247 'ifndef',
1250 for opt in options:
1251 o = opt[0]
1252 a = opt[1]
1253 if 0:
1254 pass
1255 elif o== '--debug' or o == '-d':
1256 debug_p = 1
1257 elif o== '--define' or o == '-D':
1258 if string.find (a, '=') >= 0:
1259 (n, e) = string.split (a, '=')
1260 else:
1261 n = a
1262 e = ''
1263 macros[n] = e
1264 elif o== '--pre-process' or o == '-E':
1265 only_pre_process_p = 1
1266 elif o== '--help' or o == '-h':
1267 help ()
1268 sys.exit (0)
1269 elif o== '--verbose' or o == '-V':
1270 verbose_p = 1
1271 elif o == '--version' or o == '-v':
1272 identify ()
1273 sys.exit (0)
1274 elif o == '--output' or o == '-o':
1275 output = a
1276 else:
1277 print o
1278 raise getopt.error
1280 # writes to stdout for help2man
1281 # don't call
1282 # identify ()
1283 # sys.stdout.flush ()
1285 # handy emacs testing
1286 # if not files:
1287 # files = ['template.mup']
1289 if not files:
1290 files = ['-']
1292 for f in files:
1293 h = None
1294 if f == '-':
1295 h = sys.stdin
1296 elif f and not os.path.isfile (f):
1297 f = strip_extension (f, '.mup') + '.mup'
1298 h = open (f)
1299 progress ( _("Processing `%s'..." % f))
1300 raw_lines = h.readlines ()
1301 p = Pre_processor (raw_lines)
1302 if only_pre_process_p:
1303 if not output:
1304 output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f))
1305 else:
1306 e = Parser (p.lines)
1307 if not output:
1308 output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
1309 if output == f:
1310 output = os.path.basename (f + '.ly')
1312 if f == '-':
1313 output = '-'
1314 out_h = sys.stdout
1315 else:
1316 out_h = open (output, 'w')
1318 progress (_ ("Writing `%s'...") % output)
1320 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1321 if only_pre_process_p:
1322 # duh
1323 ly = string.join (p.lines, '\n')
1324 else:
1325 ly = tag + '\n\n' + e.dump ()
1327 out_h.write (ly)
1328 out_h.close ()
1329 if debug_p:
1330 print (ly)