2 # mup2ly.py -- mup input converter
4 # source file of the GNU LilyPond music typesetter
10 LOTS: we get all notes out now, rest after 1.4
12 * lyrics (partly done)
22 * repeats, percent repeats
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']
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
60 gettext.bindtextdomain ('lilypond', '@localedir@')
61 gettext.textdomain ('lilypond')
68 program_name = 'mup2ly'
69 help_summary = _ ("Convert mup to LilyPond source")
71 option_definitions = [
72 ('', 'd', 'debug', _ ("debug")),
73 ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")),
74 ('', 'h', 'help', _ ("this help")),
75 ('FILE', 'o', 'output', _ ("write output to FILE")),
76 ('', 'E', 'pre-process', _ ("only pre-process")),
77 ('', 'V', 'verbose', _ ("verbose")),
78 ('', 'v', 'version', _ ("print version number")),
79 ('', 'w', 'warranty', _ ("show warranty and copyright")),
83 ################################################################
84 # lilylib.py -- options and stuff
86 # source file of the GNU LilyPond music typesetter
88 # Handle bug in Python 1.6-2.1
90 # there are recursion limits for some patterns in Python 1.6 til 2.1.
91 # fix this by importing pre instead. Fix by Mats.
93 # todo: should check Python version first.
101 # Attempt to fix problems with limited stack size set by Python!
102 # Sets unlimited stack size. Note that the resource module only
103 # is available on UNIX.
106 resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
112 gettext.bindtextdomain ('lilypond', localedir)
113 gettext.textdomain ('lilypond')
119 program_version = '@TOPLEVEL_VERSION@'
120 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
121 program_version = '1.5.54'
124 sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
128 sys.stdout.write ('\n')
129 sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2002'))
130 sys.stdout.write ('\n')
131 sys.stdout.write (' Han-Wen Nienhuys')
132 sys.stdout.write (' Jan Nieuwenhuizen')
133 sys.stdout.write ('\n')
134 sys.stdout.write (_ (r'''
135 Distributed under terms of the GNU General Public License. It comes with
137 sys.stdout.write ('\n')
140 errorport.write (s + '\n')
143 progress (_ ("warning: ") + s)
145 def user_error (s, e=1):
146 errorport.write (program_name + ":" + _ ("error: ") + s + '\n')
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.
160 progress (_ ("error: ") + s)
161 raise _ ("Exiting ... ")
163 def getopt_args (opts):
164 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
179 def option_help_str (o):
180 '''Transform one option description (4-tuple ) into neatly formatted string'''
198 return ' ' + sh + sep + long + arg
201 def options_help_str (opts):
202 '''Convert a list of options into a neatly formatted string'''
208 s = option_help_str (o)
209 strs.append ((s, o[3]))
215 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
219 ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name),
225 (options_help_str (option_definitions)),
227 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
229 map (sys.stdout.write, ls)
233 Create a temporary directory, and return its name.
236 if not keep_temp_dir_p:
237 temp_dir = tempfile.mktemp (program_name)
239 os.mkdir (temp_dir, 0777)
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.
255 progress (_ ("Invoking `%s\'") % cmd)
259 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
260 msg = name + ': ' + _ ("command exited with value %d") % st
263 warning (msg + ' ' + _ ("(ignored)") + ' ')
271 if not keep_temp_dir_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)
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
300 return os.path.normpath (os.path.join (os.getcwd (), path))
302 if os.__dict__.has_key ('makedirs'):
303 makedirs = os.makedirs
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):
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']
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 ################################################################
340 return chr (i + ord ('A'))
343 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
345 def pitch_to_lily_string (tup):
348 nm = chr((n + 2) % 7 + ord ('a'))
366 def rat_simplify (r):
377 def rat_multiply (a,b):
381 return rat_simplify ((x*p, y*q))
383 def rat_divide (a,b):
385 return rat_multiply (a, (q,p))
398 return rat_simplify ((x*q + p*y, y*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):
414 while rat_larger (d, frac):
415 d = rat_multiply (d, (1,2))
418 frac = rat_subtract (frac, d)
420 if frac == rat_multiply (d, (1,2)):
422 elif frac == rat_multiply (d, (3,4)):
435 def __init__ (self,nums):
438 return ' %{ FIXME: meter change %} '
441 def __init__ (self, ch):
447 def __init__ (self,id):
449 self.start_chord = None
450 self.end_chord = None
452 def calculate (self):
457 s.note_suffix = s.note_suffix + '('
458 e.note_prefix = ')' + e.note_prefix
460 sys.stderr.write ("\nOrphaned slur")
463 def __init__ (self, n):
468 self.current_slurs = []
471 def toggle_slur (self, id):
473 for s in self.current_slurs:
475 self.current_slurs.remove (s)
476 s.end_chord = self.chords[-1]
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]
488 ch.basic_duration = 4
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)
499 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
503 #if not self.entries:
506 # return '\n%s = {}\n\n' % self.idstring ()
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:
514 str = str + ln + next + ' '
518 if len (ln) +len (next) > 72:
525 id = self.idstring ()
527 str = '''%s = \\context Voice = %s \\notes {
534 def calculate_graces (self):
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 + ' } '
545 def calculate (self):
546 self.calculate_graces ()
551 def __init__ (self, cl):
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')
561 def __init__ (self, sharps, flats):
566 if self.sharps and self.flats:
567 k = '\\keysignature %s ' % 'TODO'
569 k = '\\notes\\key %s \major' % key_sharps[self.sharps]
571 k = '\\notes\\key %s \major' % key_flats[self.flats]
575 def __init__ (self, frac):
579 return '\\time %d/%d' % (self.frac[0], self.frac[1])
594 def __init__ (self, n):
596 self.voices = (Voice (0), Voice (1))
605 for v in self.voices:
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:
619 return 'staff%s' % encodeint (self.number)
625 for v in self.voices:
628 if v == self.voices[0]:
630 refs = refs + self.clef.dump ()
632 refs = refs + self.time.dump ()
634 refs = refs + self.key.dump ()
638 refs = refs + '\n \\' + v.idstring ()
640 %s = \context Staff = %s <%s
643 ''' % (self.idstring (), self.idstring (), refs)
647 def __init__ (self, number, base, dots):
650 self.replaces = tuplet_table[number]
656 length = rat_multiply (length, (3,2))
658 length = rat_multiply (length, (7,4))
660 length = rat_multiply (length, (1,self.replaces))
662 (nb,nd) =rat_to_duration (length)
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 = ' }'
680 self.multimeasure = 0
682 self.basic_duration = 0
685 self.chord_prefix = ''
686 self.chord_suffix = ''
687 self.note_prefix = ''
688 self.note_suffix = ''
690 # maybe use import copy?
693 #for i in self.pitches:
694 # ch.pitches.append (i)
695 ch.pitches = self.pitches[:]
696 ch.multimeasure = self.multimeasure
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
715 if self.basic_duration == 0.5:
718 sd = '%d' % self.basic_duration
719 sd = sd + '.' * self.dots
720 for p in self.pitches:
723 str = str + pitch_to_lily_string (p) + sd
725 for s in self.scripts:
728 str = self.note_prefix +str + self.note_suffix
730 if len (self.pitches) > 1:
732 elif self.multimeasure:
734 elif len (self.pitches) == 0:
737 str = self.chord_prefix + str + self.chord_suffix
774 # http://www.arkkra.com/doc/uguide/contexts.html
789 def __init__ (self, lines):
790 self.parse_function = self.parse_context_music
792 self.current_voices = []
793 self.forced_duration = None
796 self.tuplets_expected = 0
804 def parse_compound_location (self, line):
805 colon = string.index (line, ':')
808 line = line[colon + 1:]
810 self.current_voices = []
811 ##self.current_staffs = []
812 map (self.parse_location, string.split (s, '&'))
815 def parse_location (self, line):
816 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
818 def range_list_to_idxs (s):
828 def range_to_list (s):
829 if string.find (s, '-') >= 0:
831 l = map (string.lstrip,
832 string.split (s, '-'))
833 r = range (string.atoi (l[0]) - 1,
836 r = (string.atoi (s) - 1,)
839 ranges = string.split (s, ',')
840 l = flatten (map (range_to_list, ranges))
844 staff_idxs = range_list_to_idxs (m.group (1))
846 voice_idxs = range_list_to_idxs (m.group (2))
850 while s > len (self.staffs) - 1:
851 self.staffs.append (Staff (s))
853 self.current_voices.append (self.staffs[s].voices[v])
855 def parse_note (self, line):
858 name = (ord (line[0]) - ord ('a') + 5) % 7
859 # FIXME: does key play any role in this?
861 debug ('NOTE: ' + `line`)
862 line = string.lstrip (line[1:])
864 if len (line) > 1 and line[:2] == '//':
868 alteration = alteration + 1
870 alteration = alteration - 1
877 line = string.lstrip (line[1:])
878 return (oct, name, alteration)
880 def parse_chord (self, line):
881 debug ('CHORD: ' + line)
882 line = string.lstrip (line)
885 #ch = self.current_voices[0].last_chord ()
886 ch = self.last_chord.copy ()
888 m = re.match ('^([0-9]+)([.]*)', line)
890 ch.basic_duration = string.atoi (m.group (1))
891 line = line[len (m.group (1)):]
893 ch.dots = len (m.group (2))
894 line = line[len (m.group (2)):]
896 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
897 ch.basic_duration = self.last_chord.basic_duration
899 line = string.lstrip (line)
900 if len (line) > 1 and line[:2] == '//':
904 debug ('nline: ' + line)
905 #ch = self.current_voices[0].last_chord ()
906 n = self.last_chord.copy ()
907 n.basic_duration = ch.basic_duration
910 debug ('ch.pitsen:' + `ch.pitches`)
911 debug ('ch.dur:' + `ch.basic_duration`)
913 debug ('eline: ' + line)
916 if len (line) > 1 and line[:2] == '//':
919 elif line[:1] == 'mr':
922 elif line[:1] == 'ms':
925 elif line[0] in 'rs':
928 elif line[0] in 'abcdefg':
929 m = re.match ('([a-g][-#&+]*)', line)
930 l = len (m.group (1))
931 pitch = self.parse_note (line[:l])
932 debug ('PITCH: ' + `pitch`)
933 ch.pitches.append (pitch)
939 line = string.lstrip (line)
940 debug ('CUR-VOICES: ' + `self.current_voices`)
941 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
944 def parse_lyrics_location (self, line):
945 line = line.lstrip (line)
947 m = re.match ('^(between[ \t]+)', line)
949 line = line[len (m.group (1)):]
952 m = re.match ('^(above [ \t]+)', line)
954 line = line[len (m.group (1)):]
959 def parse_voice (self, line):
960 line = string.lstrip (line)
961 # `;' is not a separator, chords end with ';'
962 chords = string.split (line, ';')[:-1]
963 # mup resets default duration and pitch each bar
964 self.last_chord = Chord ()
965 self.last_chord.basic_duration = 4
966 map (self.parse_chord, chords)
968 def init_context_header (self, line):
969 self.parse_function = self.parse_context_header
971 def parse_context_header (self, line):
972 debug ('header: ' + line)
975 def init_context_footer (self, line):
976 self.parse_function = self.parse_context_footer
978 def parse_context_footer (self, line):
979 debug ('footer: ' + line)
982 def init_context_header2 (self, line):
983 self.parse_function = self.parse_context_header2
985 def parse_context_header2 (self, line):
986 debug ('header2: ' + line)
989 def init_context_footer2 (self, line):
990 self.parse_function = self.parse_context_footer2
992 def parse_context_footer2 (self, line):
993 debug ('footer2: ' + line)
996 def init_context_score (self, line):
997 self.parse_function = self.parse_context_score
999 def parse_context_score (self, line):
1000 debug ('score: ' + line)
1001 line = string.lstrip (line)
1002 # ugh: these (and lots more) should also be parsed in
1003 # context staff. we should have a class Staff_properties
1004 # and parse/set all those.
1005 m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line)
1007 line = line[len (m.group (1)):]
1008 self.time = Time ((string.atoi (m.group (2)),
1009 string.atoi (m.group (3))))
1011 m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line)
1013 line = line[len (m.group (1)):]
1014 n = string.atoi (m.group (2))
1015 if m.group (3) == '#':
1016 self.key = Key (n, 0)
1018 self.key = Key (0, n)
1021 def init_context_staff (self, line):
1022 self.parse_function = self.parse_context_staff
1024 def parse_context_staff (self, line):
1025 debug ('staff: ' + line)
1028 def init_context_voice (self, line):
1029 self.parse_function = self.parse_context_voice
1031 def parse_context_voice (self, line):
1032 debug ('voice: ' + line)
1035 def init_context_grids (self, line):
1036 self.parse_function = self.parse_context_grids
1038 def parse_context_grids (self, line):
1039 debug ('grids: ' + line)
1042 def init_context_music (self, line):
1043 self.parse_function = self.parse_context_music
1045 def parse_context_music (self, line):
1046 debug ('music: ' + line)
1047 line = string.lstrip (line)
1048 if line and line[0] in '0123456789':
1049 line = self.parse_compound_location (line)
1050 self.parse_voice (line)
1052 m = re.match ('^(TODOlyrics[ \t]+)', line)
1054 line = line[len (m.group (1)):]
1055 self.parse_lyrics_location (line[7:])
1056 self.parse_lyrics (line)
1060 def parse (self, lines):
1061 # shortcut: set to official mup maximum (duh)
1062 # self.set_staffs (40)
1064 debug ('LINE: ' + `line`)
1065 m = re.match ('^([a-z]+2?)', line)
1069 if word in contexts:
1070 eval ('self.init_context_%s (line)' % word)
1073 warning (_ ("no such context: %s") % word)
1076 debug ('FUNC: ' + `self.parse_function`)
1077 self.parse_function (line)
1079 for c in self.staffs:
1081 if not c.clef and self.clef:
1083 if not c.time and self.time:
1085 if not c.key and self.key:
1093 for s in self.staffs:
1094 str = str + s.dump ()
1095 refs = refs + '\n \\' + s.idstring ()
1109 class Pre_processor:
1110 def __init__ (self, raw_lines):
1113 self.process_function = self.process_line
1114 self.macro_name = ''
1115 self.macro_body = ''
1116 self.process (raw_lines)
1118 def process_line (self, line):
1120 m = re.match ('^([ \t]*([a-zA-Z]+))', line)
1124 debug ('MACRO?: ' + `word`)
1125 if word in pre_processor_commands:
1126 line = line[len (m.group (1)):]
1127 eval ('self.process_macro_%s (line)' % word)
1130 if macros.has_key (word):
1131 s = macros[word] + line[len (m.group (1)):]
1132 if not self.active [-1]:
1136 def process_macro_body (self, line):
1138 # dig this: mup allows ifdefs inside macro bodies
1139 s = self.process_line (line)
1140 m = re.match ('(.*[^\\\\])(@)(.*)', s)
1142 self.macro_body = self.macro_body + '\n' + m.group (1)
1143 macros[self.macro_name] = self.macro_body
1144 debug ('MACROS: ' + `macros`)
1145 # don't do nested multi-line defines
1146 self.process_function = self.process_line
1152 self.macro_body = self.macro_body + '\n' + s
1156 # duh: mup is strictly line-based, except for `define',
1157 # which is `@' terminated and may span several lines
1158 def process_macro_define (self, line):
1160 # don't define new macros in unactive areas
1161 if not self.active[-1]:
1163 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line)
1171 debug ('MACROS: ' + `macros`)
1173 # To support nested multi-line define's
1174 # process_function and macro_name, macro_body
1175 # should become lists (stacks)
1176 # The mup manual is undetermined on this
1177 # and I haven't seen examples doing it.
1179 # don't do nested multi-line define's
1181 self.macro_body = m.group (2)
1183 self.macro_body = ''
1185 self.process_function = self.process_macro_body
1187 def process_macro_ifdef (self, line):
1188 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1191 active = self.active[-1] and macros.has_key (m.group (1))
1192 debug ('ACTIVE: %d' % active)
1193 self.active.append (active)
1195 def process_macro_ifndef (self, line):
1196 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1198 active = self.active[-1] and not macros.has_key (m.group (1))
1199 self.active.append (active)
1201 def process_macro_else (self, line):
1203 self.active[-1] = not self.active[-1]
1205 def process_macro_endif (self, line):
1206 self.active = self.active[:-1]
1208 def process (self, raw_lines):
1210 for line in raw_lines:
1211 ls = string.split (self.process_function (line), '\n')
1214 s = s + string.rstrip (i)
1215 if s and s[-1] == '\\':
1216 s = string.rstrip (s[:-1])
1218 self.lines.append (s)
1223 only_pre_process_p = 0
1226 progress ('DEBUG: ' + s)
1229 if verbose_p or debug_p:
1230 progress ('SKIPPING: ' + s)
1232 (sh, long) = getopt_args (__main__.option_definitions)
1234 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1240 pre_processor_commands = (
1253 elif o== '--debug' or o == '-d':
1255 elif o== '--define' or o == '-D':
1256 if string.find (a, '=') >= 0:
1257 (n, e) = string.split (a, '=')
1262 elif o== '--pre-process' or o == '-E':
1263 only_pre_process_p = 1
1264 elif o== '--help' or o == '-h':
1267 elif o== '--verbose' or o == '-V':
1269 elif o == '--version' or o == '-v':
1272 elif o == '--output' or o == '-o':
1278 # writes to stdout for help2man
1281 # sys.stdout.flush ()
1283 # handy emacs testing
1285 # files = ['template.mup']
1294 elif f and not os.path.isfile (f):
1295 f = strip_extension (f, '.mup') + '.mup'
1297 progress ( _("Processing `%s'..." % f))
1298 raw_lines = h.readlines ()
1299 p = Pre_processor (raw_lines)
1300 if only_pre_process_p:
1302 output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f))
1304 e = Parser (p.lines)
1306 output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
1308 output = os.path.basename (f + '.ly')
1314 out_h = open (output, 'w')
1316 progress (_ ("Writing `%s'...") % output)
1318 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1319 if only_pre_process_p:
1321 ly = string.join (p.lines, '\n')
1323 ly = tag + '\n\n' + e.dump ()