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
41 if os.environ.has_key ('LILYPONDPREFIX') \
42 or '@datadir@' == '@' + 'datadir' + '@':
43 datadir = os.environ['LILYPONDPREFIX']
47 sys.path.append (os.path.join (datadir, 'python'))
48 sys.path.append (os.path.join (datadir, 'python/out'))
50 program_name = 'ly2dvi'
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")),
94 return chr (i + ord ('A'))
97 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
99 def pitch_to_lily_string (tup):
102 nm = chr((n + 2) % 7 + ord ('a'))
120 def rat_simplify (r):
131 def rat_multiply (a,b):
135 return rat_simplify ((x*p, y*q))
137 def rat_divide (a,b):
139 return rat_multiply (a, (q,p))
152 return rat_simplify ((x*q + p*y, y*q))
159 def rat_larger (a,b):
160 return rat_subtract (a, b )[0] > 0
162 def rat_subtract (a,b ):
163 return rat_add (a, rat_neg (b))
165 def rat_to_duration (frac):
168 while rat_larger (d, frac):
169 d = rat_multiply (d, (1,2))
172 frac = rat_subtract (frac, d)
174 if frac == rat_multiply (d, (1,2)):
176 elif frac == rat_multiply (d, (3,4)):
189 def __init__ (self,nums):
192 return ' %{ FIXME: meter change %} '
195 def __init__ (self, ch):
201 def __init__ (self,id):
203 self.start_chord = None
204 self.end_chord = None
206 def calculate (self):
211 s.note_suffix = s.note_suffix + '('
212 e.note_prefix = ')' + e.note_prefix
214 sys.stderr.write ("\nOrphaned slur")
217 def __init__ (self, n):
222 self.current_slurs = []
225 def toggle_slur (self, id):
227 for s in self.current_slurs:
229 self.current_slurs.remove (s)
230 s.end_chord = self.chords[-1]
233 s.start_chord = self.chords[-1]
234 self.current_slurs.append (s)
235 self.slurs.append (s)
237 def last_chord (self):
238 if len (self.chords):
239 return self.chords[-1]
242 ch.basic_duration = 4
245 def add_chord (self, ch):
246 self.chords.append (ch)
247 self.entries.append (ch)
249 def add_nonchord (self, nch):
250 self.entries.append (nch)
253 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
257 #if not self.entries:
260 # return '\n%s = {}\n\n' % self.idstring ()
262 one_two = ("One", "Two")
263 if self.staff.voices [1 - self.number].entries:
264 ln = ln + '\\voice%s\n ' % one_two[self.number]
265 for e in self.entries:
268 str = str + ln + next + ' '
272 if len (ln) +len (next) > 72:
279 id = self.idstring ()
281 str = '''%s = \\context Voice = %s \\notes {
288 def calculate_graces (self):
291 for c in self.chords:
292 if c.grace and not lastgr:
293 c.chord_prefix = c.chord_prefix + '\\grace { '
294 elif not c.grace and lastgr:
295 lastc.chord_suffix = lastc.chord_suffix + ' } '
299 def calculate (self):
300 self.calculate_graces ()
305 def __init__ (self, cl):
309 return '\\clef %s' % self.type
311 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
312 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
315 def __init__ (self, sharps, flats):
320 if self.sharps and self.flats:
321 k = '\\keysignature %s ' % 'TODO'
323 k = '\\notes\\key %s \major' % key_sharps[self.sharps]
325 k = '\\notes\\key %s \major' % key_flats[self.flats]
329 def __init__ (self, frac):
333 return '\\time %d/%d' % (self.frac[0], self.frac[1])
348 def __init__ (self, n):
350 self.voices = (Voice (0), Voice (1))
359 for v in self.voices:
364 #def set_clef (self, letter):
365 # clstr = clef_table[letter]
366 # self.voices[0].add_nonchord (Clef (clstr))
368 def calculate (self):
369 for v in self.voices:
373 return 'staff%s' % encodeint (self.number)
379 for v in self.voices:
382 if v == self.voices[0]:
384 refs = refs + self.clef.dump ()
386 refs = refs + self.time.dump ()
388 refs = refs + self.key.dump ()
392 refs = refs + '\n \\' + v.idstring ()
394 %s = \context Staff = %s <%s
397 ''' % (self.idstring (), self.idstring (), refs)
401 def __init__ (self, number, base, dots):
404 self.replaces = tuplet_table[number]
410 length = rat_multiply (length, (3,2))
412 length = rat_multiply (length, (7,4))
414 length = rat_multiply (length, (1,self.replaces))
416 (nb,nd) =rat_to_duration (length)
421 def add_chord (self, ch):
422 ch.dots = self.note_dots
423 ch.basic_duration = self.note_base
424 self.chords.append (ch)
426 if len (self.chords) == 1:
427 ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
428 elif len (self.chords) == self.number:
429 ch.chord_suffix = ' }'
434 self.multimeasure = 0
436 self.basic_duration = 0
439 self.chord_prefix = ''
440 self.chord_suffix = ''
441 self.note_prefix = ''
442 self.note_suffix = ''
444 # maybe use import copy?
447 #for i in self.pitches:
448 # ch.pitches.append (i)
449 ch.pitches = self.pitches[:]
450 ch.multimeasure = self.multimeasure
452 ch.basic_duration = self.basic_duration
453 #for i in self.scripts:
454 # ch.scripts.append (i)
455 ch.scripts = self.scripts[:]
456 ch.grace = self.grace
458 ch.chord_prefix = self.chord_prefix
459 ch.chord_suffix = self.chord_suffix
460 ch.note_prefix = self.note_prefix
461 ch.note_suffix = self.note_suffix
469 if self.basic_duration == 0.5:
472 sd = '%d' % self.basic_duration
473 sd = sd + '.' * self.dots
474 for p in self.pitches:
477 str = str + pitch_to_lily_string (p) + sd
479 for s in self.scripts:
482 str = self.note_prefix +str + self.note_suffix
484 if len (self.pitches) > 1:
486 elif self.multimeasure:
488 elif len (self.pitches) == 0:
491 str = self.chord_prefix + str + self.chord_suffix
528 # http://www.arkkra.com/doc/uguide/contexts.html
543 def __init__ (self, lines):
544 self.parse_function = self.parse_context_music
546 self.current_voices = []
547 self.forced_duration = None
550 self.tuplets_expected = 0
558 def parse_compound_location (self, line):
559 colon = string.index (line, ':')
562 line = line[colon + 1:]
564 self.current_voices = []
565 ##self.current_staffs = []
566 map (self.parse_location, string.split (s, '&'))
569 def parse_location (self, line):
570 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
572 def range_list_to_idxs (s):
582 def range_to_list (s):
583 if string.find (s, '-') >= 0:
585 l = map (string.lstrip,
586 string.split (s, '-'))
587 r = range (string.atoi (l[0]) - 1,
590 r = (string.atoi (s) - 1,)
593 ranges = string.split (s, ',')
594 l = flatten (map (range_to_list, ranges))
598 staff_idxs = range_list_to_idxs (m.group (1))
600 voice_idxs = range_list_to_idxs (m.group (2))
604 while s > len (self.staffs) - 1:
605 self.staffs.append (Staff (s))
607 self.current_voices.append (self.staffs[s].voices[v])
609 def parse_note (self, line):
612 name = (ord (line[0]) - ord ('a') + 5) % 7
613 # FIXME: does key play any role in this?
615 debug ('NOTE: ' + `line`)
616 line = string.lstrip (line[1:])
618 if len (line) > 1 and line[:2] == '//':
622 alteration = alteration + 1
624 alteration = alteration - 1
631 line = string.lstrip (line[1:])
632 return (oct, name, alteration)
634 def parse_chord (self, line):
635 debug ('CHORD: ' + line)
636 line = string.lstrip (line)
639 #ch = self.current_voices[0].last_chord ()
640 ch = self.last_chord.copy ()
642 m = re.match ('^([0-9]+)([.]*)', line)
644 ch.basic_duration = string.atoi (m.group (1))
645 line = line[len (m.group (1)):]
647 ch.dots = len (m.group (2))
648 line = line[len (m.group (2)):]
650 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
651 ch.basic_duration = self.last_chord.basic_duration
653 line = string.lstrip (line)
654 if len (line) > 1 and line[:2] == '//':
658 debug ('nline: ' + line)
659 #ch = self.current_voices[0].last_chord ()
660 n = self.last_chord.copy ()
661 n.basic_duration = ch.basic_duration
664 debug ('ch.pitsen:' + `ch.pitches`)
665 debug ('ch.dur:' + `ch.basic_duration`)
667 debug ('eline: ' + line)
670 if len (line) > 1 and line[:2] == '//':
673 elif line[:1] == 'mr':
676 elif line[:1] == 'ms':
679 elif line[0] in 'rs':
682 elif line[0] in 'abcdefg':
683 m = re.match ('([a-g][-#&+]*)', line)
684 l = len (m.group (1))
685 pitch = self.parse_note (line[:l])
686 debug ('PITCH: ' + `pitch`)
687 ch.pitches.append (pitch)
693 line = string.lstrip (line)
694 debug ('CUR-VOICES: ' + `self.current_voices`)
695 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
698 def parse_lyrics_location (self, line):
699 line = line.lstrip (line)
701 m = re.match ('^(between[ \t]+)', line)
703 line = line[len (m.group (1)):]
706 m = re.match ('^(above [ \t]+)', line)
708 line = line[len (m.group (1)):]
713 def parse_voice (self, line):
714 line = string.lstrip (line)
715 # `;' is not a separator, chords end with ';'
716 chords = string.split (line, ';')[:-1]
717 # mup resets default duration and pitch each bar
718 self.last_chord = Chord ()
719 self.last_chord.basic_duration = 4
720 map (self.parse_chord, chords)
722 def init_context_header (self, line):
723 self.parse_function = self.parse_context_header
725 def parse_context_header (self, line):
726 debug ('header: ' + line)
729 def init_context_footer (self, line):
730 self.parse_function = self.parse_context_footer
732 def parse_context_footer (self, line):
733 debug ('footer: ' + line)
736 def init_context_header2 (self, line):
737 self.parse_function = self.parse_context_header2
739 def parse_context_header2 (self, line):
740 debug ('header2: ' + line)
743 def init_context_footer2 (self, line):
744 self.parse_function = self.parse_context_footer2
746 def parse_context_footer2 (self, line):
747 debug ('footer2: ' + line)
750 def init_context_score (self, line):
751 self.parse_function = self.parse_context_score
753 def parse_context_score (self, line):
754 debug ('score: ' + line)
755 line = string.lstrip (line)
756 # ugh: these (and lots more) should also be parsed in
757 # context staff. we should have a class Staff_properties
758 # and parse/set all those.
759 m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line)
761 line = line[len (m.group (1)):]
762 self.time = Time ((string.atoi (m.group (2)),
763 string.atoi (m.group (3))))
765 m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line)
767 line = line[len (m.group (1)):]
768 n = string.atoi (m.group (2))
769 if m.group (3) == '#':
770 self.key = Key (n, 0)
772 self.key = Key (0, n)
775 def init_context_staff (self, line):
776 self.parse_function = self.parse_context_staff
778 def parse_context_staff (self, line):
779 debug ('staff: ' + line)
782 def init_context_voice (self, line):
783 self.parse_function = self.parse_context_voice
785 def parse_context_voice (self, line):
786 debug ('voice: ' + line)
789 def init_context_grids (self, line):
790 self.parse_function = self.parse_context_grids
792 def parse_context_grids (self, line):
793 debug ('grids: ' + line)
796 def init_context_music (self, line):
797 self.parse_function = self.parse_context_music
799 def parse_context_music (self, line):
800 debug ('music: ' + line)
801 line = string.lstrip (line)
802 if line and line[0] in '0123456789':
803 line = self.parse_compound_location (line)
804 self.parse_voice (line)
806 m = re.match ('^(TODOlyrics[ \t]+)', line)
808 line = line[len (m.group (1)):]
809 self.parse_lyrics_location (line[7:])
810 self.parse_lyrics (line)
814 def parse (self, lines):
815 # shortcut: set to official mup maximum (duh)
816 # self.set_staffs (40)
818 debug ('LINE: ' + `line`)
819 m = re.match ('^([a-z]+2?)', line)
824 eval ('self.init_context_%s (line)' % word)
827 warning (_ ("no such context: %s") % word)
830 debug ('FUNC: ' + `self.parse_function`)
831 self.parse_function (line)
833 for c in self.staffs:
835 if not c.clef and self.clef:
837 if not c.time and self.time:
839 if not c.key and self.key:
847 for s in self.staffs:
848 str = str + s.dump ()
849 refs = refs + '\n \\' + s.idstring ()
864 def __init__ (self, raw_lines):
867 self.process_function = self.process_line
870 self.process (raw_lines)
872 def process_line (self, line):
874 m = re.match ('^([ \t]*([a-zA-Z]+))', line)
878 debug ('MACRO?: ' + `word`)
879 if word in pre_processor_commands:
880 line = line[len (m.group (1)):]
881 eval ('self.process_macro_%s (line)' % word)
884 if macros.has_key (word):
885 s = macros[word] + line[len (m.group (1)):]
886 if not self.active [-1]:
890 def process_macro_body (self, line):
892 # dig this: mup allows ifdefs inside macro bodies
893 s = self.process_line (line)
894 m = re.match ('(.*[^\\\\])(@)(.*)', s)
896 self.macro_body = self.macro_body + '\n' + m.group (1)
897 macros[self.macro_name] = self.macro_body
898 debug ('MACROS: ' + `macros`)
899 # don't do nested multi-line defines
900 self.process_function = self.process_line
906 self.macro_body = self.macro_body + '\n' + s
910 # duh: mup is strictly line-based, except for `define',
911 # which is `@' terminated and may span several lines
912 def process_macro_define (self, line):
914 # don't define new macros in unactive areas
915 if not self.active[-1]:
917 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line)
925 debug ('MACROS: ' + `macros`)
927 # To support nested multi-line define's
928 # process_function and macro_name, macro_body
929 # should become lists (stacks)
930 # The mup manual is undetermined on this
931 # and I haven't seen examples doing it.
933 # don't do nested multi-line define's
935 self.macro_body = m.group (2)
939 self.process_function = self.process_macro_body
941 def process_macro_ifdef (self, line):
942 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
945 active = self.active[-1] and macros.has_key (m.group (1))
946 debug ('ACTIVE: %d' % active)
947 self.active.append (active)
949 def process_macro_ifndef (self, line):
950 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
952 active = self.active[-1] and not macros.has_key (m.group (1))
953 self.active.append (active)
955 def process_macro_else (self, line):
957 self.active[-1] = not self.active[-1]
959 def process_macro_endif (self, line):
960 self.active = self.active[:-1]
962 def process (self, raw_lines):
964 for line in raw_lines:
965 ls = string.split (self.process_function (line), '\n')
968 s = s + string.rstrip (i)
969 if s and s[-1] == '\\':
970 s = string.rstrip (s[:-1])
972 self.lines.append (s)
977 only_pre_process_p = 0
980 progress ('DEBUG: ' + s)
983 if verbose_p or debug_p:
984 progress ('SKIPPING: ' + s)
986 (sh, long) = getopt_args (__main__.option_definitions)
988 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
994 pre_processor_commands = (
1007 elif o== '--debug' or o == '-d':
1009 elif o== '--define' or o == '-D':
1010 if string.find (a, '=') >= 0:
1011 (n, e) = string.split (a, '=')
1016 elif o== '--pre-process' or o == '-E':
1017 only_pre_process_p = 1
1018 elif o== '--help' or o == '-h':
1021 elif o== '--verbose' or o == '-V':
1023 elif o == '--version' or o == '-v':
1026 elif o == '--output' or o == '-o':
1032 # writes to stdout for help2man
1035 # sys.stdout.flush ()
1037 # handy emacs testing
1039 # files = ['template.mup']
1048 elif f and not os.path.isfile (f):
1049 f = strip_extension (f, '.mup') + '.mup'
1051 progress ( _("Processing `%s'..." % f))
1052 raw_lines = h.readlines ()
1053 p = Pre_processor (raw_lines)
1054 if only_pre_process_p:
1056 output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f))
1058 e = Parser (p.lines)
1060 output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
1062 output = os.path.basename (f + '.ly')
1068 out_h = open (output, 'w')
1070 progress (_ ("Writing `%s'...") % output)
1072 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1073 if only_pre_process_p:
1075 ly = string.join (p.lines, '\n')
1077 ly = tag + '\n\n' + e.dump ()