2 # mup2ly.py -- mup input converter
4 # source file of the GNU LilyPond music typesetter
27 sys.path.append ('@datadir@/python')
29 gettext.bindtextdomain ('lilypond', '@localedir@')
30 gettext.textdomain('lilypond')
34 program_name = 'mup2ly'
35 help_summary = _("Convert mup to ly")
38 # lily_py.py -- options and stuff
40 # source file of the GNU LilyPond music typesetter
42 # BEGIN Library for these?
43 # cut-n-paste from ly2dvi
45 program_version = '@TOPLEVEL_VERSION@'
46 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
47 program_version = '1.3.142'
50 original_dir = os.getcwd ()
51 temp_dir = '%s.dir' % program_name
56 sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
60 sys.stdout.write ('\n')
61 sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001'))
62 sys.stdout.write ('\n')
63 sys.stdout.write (' Han-Wen Nienhuys')
64 sys.stdout.write (' Jan Nieuwenhuizen')
65 sys.stdout.write ('\n')
66 sys.stdout.write (_ (r'''
67 Distributed under terms of the GNU General Public License. It comes with
69 sys.stdout.write ('\n')
77 sys.stderr.write (_ ("warning: ") + s)
78 sys.stderr.write ('\n')
82 sys.stderr.write (_ ("error: ") + s)
83 sys.stderr.write ('\n')
84 raise _ ("Exiting ... ")
86 def getopt_args (opts):
87 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
102 def option_help_str (o):
103 '''Transform one option description (4-tuple ) into neatly formatted string'''
121 return ' ' + sh + sep + long + arg
124 def options_help_str (opts):
125 '''Convert a list of options into a neatly formatted string'''
131 s = option_help_str (o)
132 strs.append ((s, o[3]))
138 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
142 sys.stdout.write (_ ("Usage: %s [OPTION]... FILE") % program_name)
143 sys.stdout.write ('\n\n')
144 sys.stdout.write (help_summary)
145 sys.stdout.write ('\n\n')
146 sys.stdout.write (_ ("Options:"))
147 sys.stdout.write ('\n')
148 sys.stdout.write (options_help_str (option_definitions))
149 sys.stdout.write ('\n\n')
150 sys.stdout.write (_ ("Report bugs to %s") % 'bug-gnu-music@gnu.org')
151 sys.stdout.write ('\n')
157 if not keep_temp_dir_p:
158 temp_dir = tempfile.mktemp (program_name)
160 os.mkdir (temp_dir, 0777)
165 def system (cmd, ignore_error = 0):
167 progress (_ ("Invoking `%s\'") % cmd)
170 msg = ( _ ("error: ") + _ ("command exited with value %d") % st)
172 sys.stderr.write (msg + ' ' + _ ("(ignored)") + ' ')
180 if not keep_temp_dir_p:
182 progress (_ ('Cleaning up `%s\'') % temp_dir)
183 system ('rm -rf %s' % temp_dir)
186 def set_setting (dict, key, val):
188 val = string.atof (val)
190 #warning (_ ("invalid value: %s") % `val`)
194 dict[key].append (val)
196 warning (_ ("no such setting: %s") % `key`)
207 return chr (i + ord ('A'))
210 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
212 def pitch_to_lily_string (tup):
215 nm = chr((n + 2) % 7 + ord ('a'))
233 def rat_simplify (r):
244 def rat_multiply (a,b):
248 return rat_simplify ((x*p, y*q))
250 def rat_divide (a,b):
252 return rat_multiply (a, (q,p))
265 return rat_simplify ((x*q + p*y, y*q))
272 def rat_larger (a,b):
273 return rat_subtract (a, b )[0] > 0
275 def rat_subtract (a,b ):
276 return rat_add (a, rat_neg (b))
278 def rat_to_duration (frac):
281 while rat_larger (d, frac):
282 d = rat_multiply (d, (1,2))
285 frac = rat_subtract (frac, d)
287 if frac == rat_multiply (d, (1,2)):
289 elif frac == rat_multiply (d, (3,4)):
302 def __init__ (self,nums):
305 return ' %{ FIXME: meter change %} '
308 def __init__ (self, ch):
314 def __init__ (self,id):
316 self.start_chord = None
317 self.end_chord = None
318 def calculate (self):
323 s.note_suffix = s.note_suffix + '('
324 e.note_prefix = ')' + e.note_prefix
326 sys.stderr.write ("\nOrphaned slur")
329 def __init__ (self, n):
334 self.current_slurs = []
337 def toggle_slur (self, id):
339 for s in self.current_slurs:
341 self.current_slurs.remove (s)
342 s.end_chord = self.chords[-1]
345 s.start_chord = self.chords[-1]
346 self.current_slurs.append (s)
347 self.slurs.append (s)
349 def last_chord (self):
350 if len (self.chords):
351 return self.chords[-1]
354 ch.basic_duration = 4
357 def add_chord (self, ch):
358 self.chords.append (ch)
359 self.entries.append (ch)
361 def add_nonchord (self, nch):
362 self.entries.append (nch)
365 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
372 return '\n%s = {}\n\n' % self.idstring ()
374 one_two = ("One", "Two")
375 if self.staff.voices [1 - self.number].entries:
376 ln = ln + '\\voice%s\n ' % one_two[self.number]
377 for e in self.entries:
380 str = str + ln + next + ' '
384 if len (ln) +len (next) > 72:
391 id = self.idstring ()
393 str = '''%s = \\notes {
400 def calculate_graces (self):
403 for c in self.chords:
404 if c.grace and not lastgr:
405 c.chord_prefix = c.chord_prefix + '\\grace { '
406 elif not c.grace and lastgr:
407 lastc.chord_suffix = lastc.chord_suffix + ' } '
411 def calculate (self):
412 self.calculate_graces ()
417 def __init__ (self, cl):
420 return '\\clef %s;' % self.type
434 def __init__ (self, n):
436 self.voices = (Voice (0), Voice (1))
444 for v in self.voices:
449 def set_clef (self, letter):
450 clstr = clef_table[letter]
451 self.voices[0].add_nonchord (Clef (clstr))
453 #def current_voice (self):
454 # return self.voices[self.voice_idx]
456 #def next_voice (self):
457 # self.voice_idx = (self.voice_idx + 1)%len (self.voices)
459 def calculate (self):
460 for v in self.voices:
464 return 'staff%s' % encodeint (self.number)
470 for v in self.voices:
472 refs = refs + '\n \\' + v.idstring ()
475 %s = \context Staff = %s <%s
478 ''' % (self.idstring (), self.idstring (), refs)
482 def __init__ (self, number, base, dots):
485 self.replaces = tuplet_table[number]
491 length = rat_multiply (length, (3,2))
493 length = rat_multiply (length, (7,4))
495 length = rat_multiply (length, (1,self.replaces))
497 (nb,nd) =rat_to_duration (length)
502 def add_chord (self, ch):
503 ch.dots = self.note_dots
504 ch.basic_duration = self.note_base
505 self.chords.append (ch)
507 if len (self.chords) == 1:
508 ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
509 elif len (self.chords) == self.number:
510 ch.chord_suffix = ' }'
515 self.multimeasure = 0
517 self.basic_duration = 0
520 self.chord_prefix = ''
521 self.chord_suffix = ''
522 self.note_prefix = ''
523 self.note_suffix = ''
529 if self.basic_duration == 0.5:
532 sd = '%d' % self.basic_duration
533 sd = sd + '.' * self.dots
534 for p in self.pitches:
537 str = str + pitch_to_lily_string (p) + sd
539 for s in self.scripts:
542 str = self.note_prefix +str + self.note_suffix
544 if len (self.pitches) > 1:
546 elif self.multimeasure:
548 elif len (self.pitches) == 0:
551 str = self.chord_prefix + str + self.chord_suffix
588 # http://www.arkkra.com/doc/uguide/contexts.html
603 def __init__ (self, filename):
604 self.parse_function = self.parse_context_music
606 self.current_voices = []
607 self.forced_duration = None
610 self.tuplets_expected = 0
612 self.last_basic_duration = 4
614 self.parse (filename)
616 #def set_staffs (self, number):
617 # self.staffs = map (lambda x: Staff (x), range (0, number))
619 #def current_staff (self):
620 # return self.staffs[self.staff_idx]
622 #def current_voice (self):
623 # return self.current_staff ().current_voice ()
625 #def next_staff (self):
626 # self.staff_idx = (self.staff_idx + 1)% len (self.staffs)
628 def parse_compound_location (self, line):
629 colon = string.index (line, ':')
632 line = line[colon + 1:]
634 self.current_voices = []
635 ##self.current_staffs = []
636 map (self.parse_location, string.split (s, '&'))
639 def parse_location (self, line):
640 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
642 def range_list_to_idxs (s):
652 def range_to_list (s):
653 if string.find (s, '-') >= 0:
655 l = map (string.lstrip,
656 string.split (s, '-'))
657 r = range (string.atoi (l[0]) - 1,
660 r = (string.atoi (s) - 1,)
663 ranges = string.split (s, ',')
664 l = flatten (map (range_to_list, ranges))
668 staff_idxs = range_list_to_idxs (m.group (1))
670 voice_idxs = range_list_to_idxs (m.group (2))
674 while s > len (self.staffs) - 1:
675 self.staffs.append (Staff (s))
677 self.current_voices.append (self.staffs[s].voices[v])
679 def parse_note (self, line):
682 name = (ord (line[0]) - ord ('a') + 5) % 7
683 # FIXME: does key play any role in this?
685 line = string.lstrip (line[1:])
687 if len (line) > 1 and line[:2] == '//':
691 alteration = alteration + 1
693 alteration = alteration - 1
699 skipping (_ ("%s") % line[0])
700 line = string.lstrip (line[1:])
701 return (oct, name, alteration)
703 def parse_chord (self, line):
704 line = string.lstrip (line)
707 ch = self.current_voices[0].last_chord ()
709 m = re.match ('^([0-9]+)([.]*)', line)
711 ch.basic_duration = string.atoi (m.group (1))
712 line = line[len (m.group (1)):]
714 ch.basic_duration = len (m.group (2))
715 line = line[len (m.group (1)):]
717 ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
719 line = string.lstrip (line)
720 if len (line) > 1 and line[:2] == '//':
724 duration = ch.basic_duration
725 ch = self.current_voices[0].last_chord ()
726 ch.basic_duration = duration
729 if len (line) > 1 and line[:2] == '//':
732 elif line[:1] == 'mr':
735 elif line[:1] == 'ms':
738 elif line[0] in 'rs':
740 elif line[0] in 'abcdefg':
741 pitch = self.parse_note (line)
742 debug ('PITCH: ' + `pitch`)
743 ch.pitches.append (pitch)
747 skipping (_ ("%s") % line[0])
748 line = string.lstrip (line[1:])
749 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
751 def parse_voice (self, line):
752 chords = string.split (line, ';')
753 map (self.parse_chord, chords)
755 def init_context_header (self, line):
756 self.parse_function = self.parse_context_header
758 def parse_context_header (self, line):
759 debug ('header: ' + line)
761 def init_context_footer (self, line):
762 self.parse_function = self.parse_context_footer
764 def parse_context_footer (self, line):
765 debug ('footer: ' + line)
767 def init_context_header2 (self, line):
768 self.parse_function = self.parse_context_header2
770 def parse_context_header2 (self, line):
771 debug ('header2: ' + line)
773 def init_context_footer2 (self, line):
774 self.parse_function = self.parse_context_footer2
776 def parse_context_footer2 (self, line):
777 debug ('footer2: ' + line)
779 def init_context_score (self, line):
780 self.parse_function = self.parse_context_score
782 def parse_context_score (self, line):
783 debug ('score: ' + line)
785 def init_context_staff (self, line):
786 self.parse_function = self.parse_context_staff
788 def parse_context_staff (self, line):
789 debug ('staff: ' + line)
791 def init_context_voice (self, line):
792 self.parse_function = self.parse_context_voice
794 def parse_context_voice (self, line):
795 debug ('voice: ' + line)
797 def init_context_grids (self, line):
798 self.parse_function = self.parse_context_line
800 def parse_context_grids (self, line):
801 debug ('grids: ' + line)
803 def init_context_music (self, line):
804 self.parse_function = self.parse_context_music
806 def parse_context_music (self, line):
807 debug ('music: ' + line)
808 line = string.lstrip (line)
809 if line and line[0] in '0123456789':
810 line = string.lstrip (self.parse_compound_location (line))
811 self.parse_voice (line)
813 skipping (_ ("%s") % line)
815 def parse (self, file):
816 # shortcut: set to official mup maximum (duh)
817 # self.set_staffs (40)
818 lines = open (file).readlines ()
820 debug ('LINE: ' + line)
821 m = re.match ('^([a-z]+2?)', line)
826 eval ('self.init_context_%s (line)' % word)
829 warning (_ ("no such context: %s") % word)
832 debug ('FUNC: ' + `self.parse_function`)
833 self.parse_function (line)
835 for c in self.staffs:
842 for s in self.staffs:
843 str = str + s.dump ()
844 refs = refs + '\n \\' + s.idstring ()
856 option_definitions = [
857 ('', 'd', 'debug', _ ("debug")),
858 ('', 'h', 'help', _ ("this help")),
859 ('FILE', 'o', 'output', _ ("write output to FILE")),
860 ('', 'V', 'verbose', _ ("verbose")),
861 ('', 'v', 'version', _ ("print version number")),
862 ('', 'w', 'warranty', _ ("show warranty and copyright")),
868 progress ('DEBUG: ' + s)
871 progress ('SKIPPING: ' + s)
873 (sh, long) = getopt_args (__main__.option_definitions)
875 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
886 elif o== '--debug' or o == '-d':
888 elif o== '--help' or o == '-h':
891 elif o== '--verbose' or o == '-V':
893 elif o == '--version' or o == '-v':
896 elif o == '--output' or o == '-o':
902 # writes to stdout for help2man
905 # sys.stdout.flush ()
907 # handy emacs testing
909 files = ['template.mup']
915 progress ( _("Processing %s..." % f))
918 output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
921 output = os.path.basename (f + '.ly')
923 progress (_ ("Writing %s...") % output)
925 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
926 ly = tag + '\n' + e.dump ()
928 o = open (output, 'w')