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
37 # let's not yet clutter lily's po with this mup converter junk
41 #sys.path.append ('@datadir@/python')
43 #gettext.bindtextdomain ('lilypond', '@localedir@')
44 #gettext.textdomain('lilypond')
50 program_name
= 'mup2ly'
51 help_summary
= _("Convert mup to ly")
54 # lily_py.py -- options and stuff
56 # source file of the GNU LilyPond music typesetter
58 # BEGIN Library for these?
59 # cut-n-paste from ly2dvi
61 program_version
= '@TOPLEVEL_VERSION@'
62 if program_version
== '@' + 'TOPLEVEL_VERSION' + '@':
63 program_version
= '1.3.142'
66 original_dir
= os
.getcwd ()
67 temp_dir
= '%s.dir' % program_name
72 sys
.stdout
.write ('%s (GNU LilyPond) %s\n' % (program_name
, program_version
))
76 sys
.stdout
.write ('\n')
77 sys
.stdout
.write (_ ('Copyright (c) %s by' % ' 2001'))
78 sys
.stdout
.write ('\n')
79 sys
.stdout
.write (' Han-Wen Nienhuys')
80 sys
.stdout
.write (' Jan Nieuwenhuizen')
81 sys
.stdout
.write ('\n')
82 sys
.stdout
.write (_ (r
'''
83 Distributed under terms of the GNU General Public License. It comes with
84 absolutely NO WARRANTY.'''))
85 sys
.stdout
.write ('\n')
93 sys
.stderr
.write (_ ("warning: ") + s
)
94 sys
.stderr
.write ('\n')
98 sys
.stderr
.write (_ ("error: ") + s
)
99 sys
.stderr
.write ('\n')
100 raise _ ("Exiting ... ")
102 def getopt_args (opts
):
103 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
118 def option_help_str (o
):
119 '''Transform one option description (4-tuple ) into neatly formatted string'''
137 return ' ' + sh
+ sep
+ long + arg
140 def options_help_str (opts
):
141 '''Convert a list of options into a neatly formatted string'''
147 s
= option_help_str (o
)
148 strs
.append ((s
, o
[3]))
154 str = str + '%s%s%s\n' % (s
[0], ' ' * (w
- len(s
[0]) + 3), s
[1])
158 sys
.stdout
.write (_ ("Usage: %s [OPTION]... FILE") % program_name
)
159 sys
.stdout
.write ('\n\n')
160 sys
.stdout
.write (help_summary
)
161 sys
.stdout
.write ('\n\n')
162 sys
.stdout
.write (_ ("Options:"))
163 sys
.stdout
.write ('\n')
164 sys
.stdout
.write (options_help_str (option_definitions
))
165 sys
.stdout
.write ('\n')
166 warning (_ ("%s is far from completed. Not all constructs are recognised.") % program_name
)
167 sys
.stdout
.write ('\n')
168 sys
.stdout
.write (_ ("Report bugs to %s") % 'bug-gnu-music@gnu.org')
169 sys
.stdout
.write ('\n')
175 if not keep_temp_dir_p
:
176 temp_dir
= tempfile
.mktemp (program_name
)
178 os
.mkdir (temp_dir
, 0777)
183 def system (cmd
, ignore_error
= 0):
185 progress (_ ("Invoking `%s\'") % cmd
)
188 msg
= ( _ ("error: ") + _ ("command exited with value %d") % st
)
190 sys
.stderr
.write (msg
+ ' ' + _ ("(ignored)") + ' ')
198 if not keep_temp_dir_p
:
200 progress (_ ('Cleaning up `%s\'') % temp_dir
)
201 system ('rm -rf %s' % temp_dir
)
204 def set_setting (dict, key
, val
):
206 val
= string
.atof (val
)
208 #warning (_ ("invalid value: %s") % `val`)
212 dict[key
].append (val
)
214 warning (_ ("no such setting: %s") % `key`
)
217 def strip_extension (f
, ext
):
218 (p
, e
) = os
.path
.splitext (f
)
231 return chr (i
+ ord ('A'))
234 actab
= {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
236 def pitch_to_lily_string (tup
):
239 nm
= chr((n
+ 2) % 7 + ord ('a'))
257 def rat_simplify (r
):
268 def rat_multiply (a
,b
):
272 return rat_simplify ((x
*p
, y
*q
))
274 def rat_divide (a
,b
):
276 return rat_multiply (a
, (q
,p
))
289 return rat_simplify ((x
*q
+ p
*y
, y
*q
))
296 def rat_larger (a
,b
):
297 return rat_subtract (a
, b
)[0] > 0
299 def rat_subtract (a
,b
):
300 return rat_add (a
, rat_neg (b
))
302 def rat_to_duration (frac
):
305 while rat_larger (d
, frac
):
306 d
= rat_multiply (d
, (1,2))
309 frac
= rat_subtract (frac
, d
)
311 if frac
== rat_multiply (d
, (1,2)):
313 elif frac
== rat_multiply (d
, (3,4)):
326 def __init__ (self
,nums
):
329 return ' %{ FIXME: meter change %} '
332 def __init__ (self
, ch
):
338 def __init__ (self
,id):
340 self
.start_chord
= None
341 self
.end_chord
= None
343 def calculate (self
):
348 s
.note_suffix
= s
.note_suffix
+ '('
349 e
.note_prefix
= ')' + e
.note_prefix
351 sys
.stderr
.write ("\nOrphaned slur")
354 def __init__ (self
, n
):
359 self
.current_slurs
= []
362 def toggle_slur (self
, id):
364 for s
in self
.current_slurs
:
366 self
.current_slurs
.remove (s
)
367 s
.end_chord
= self
.chords
[-1]
370 s
.start_chord
= self
.chords
[-1]
371 self
.current_slurs
.append (s
)
372 self
.slurs
.append (s
)
374 def last_chord (self
):
375 if len (self
.chords
):
376 return self
.chords
[-1]
379 ch
.basic_duration
= 4
382 def add_chord (self
, ch
):
383 self
.chords
.append (ch
)
384 self
.entries
.append (ch
)
386 def add_nonchord (self
, nch
):
387 self
.entries
.append (nch
)
390 return 'staff%svoice%s ' % (encodeint (self
.staff
.number
) , encodeint(self
.number
))
394 #if not self.entries:
397 # return '\n%s = {}\n\n' % self.idstring ()
399 one_two
= ("One", "Two")
400 if self
.staff
.voices
[1 - self
.number
].entries
:
401 ln
= ln
+ '\\voice%s\n ' % one_two
[self
.number
]
402 for e
in self
.entries
:
405 str = str + ln
+ next
+ ' '
409 if len (ln
) +len (next
) > 72:
416 id = self
.idstring ()
418 str = '''%s = \\context Voice = %s \\notes {
425 def calculate_graces (self
):
428 for c
in self
.chords
:
429 if c
.grace
and not lastgr
:
430 c
.chord_prefix
= c
.chord_prefix
+ '\\grace { '
431 elif not c
.grace
and lastgr
:
432 lastc
.chord_suffix
= lastc
.chord_suffix
+ ' } '
436 def calculate (self
):
437 self
.calculate_graces ()
442 def __init__ (self
, cl
):
446 return '\\clef %s' % self
.type
448 key_sharps
= ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
449 key_flats
= ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
452 def __init__ (self
, sharps
, flats
):
457 if self
.sharps
and self
.flats
:
458 k
= '\\keysignature %s ' % 'TODO'
460 k
= '\\notes\\key %s \major' % key_sharps
[self
.sharps
]
462 k
= '\\notes\\key %s \major' % key_flats
[self
.flats
]
466 def __init__ (self
, frac
):
470 return '\\time %d/%d' % (self
.frac
[0], self
.frac
[1])
485 def __init__ (self
, n
):
487 self
.voices
= (Voice (0), Voice (1))
496 for v
in self
.voices
:
501 #def set_clef (self, letter):
502 # clstr = clef_table[letter]
503 # self.voices[0].add_nonchord (Clef (clstr))
505 def calculate (self
):
506 for v
in self
.voices
:
510 return 'staff%s' % encodeint (self
.number
)
516 for v
in self
.voices
:
519 if v
== self
.voices
[0]:
521 refs
= refs
+ self
.clef
.dump ()
523 refs
= refs
+ self
.time
.dump ()
525 refs
= refs
+ self
.key
.dump ()
529 refs
= refs
+ '\n \\' + v
.idstring ()
531 %s = \context Staff = %s <%s
534 ''' % (self
.idstring (), self
.idstring (), refs
)
538 def __init__ (self
, number
, base
, dots
):
541 self
.replaces
= tuplet_table
[number
]
547 length
= rat_multiply (length
, (3,2))
549 length
= rat_multiply (length
, (7,4))
551 length
= rat_multiply (length
, (1,self
.replaces
))
553 (nb
,nd
) =rat_to_duration (length
)
558 def add_chord (self
, ch
):
559 ch
.dots
= self
.note_dots
560 ch
.basic_duration
= self
.note_base
561 self
.chords
.append (ch
)
563 if len (self
.chords
) == 1:
564 ch
.chord_prefix
= '\\times %d/%d { ' % (self
.replaces
, self
.number
)
565 elif len (self
.chords
) == self
.number
:
566 ch
.chord_suffix
= ' }'
571 self
.multimeasure
= 0
573 self
.basic_duration
= 0
576 self
.chord_prefix
= ''
577 self
.chord_suffix
= ''
578 self
.note_prefix
= ''
579 self
.note_suffix
= ''
581 # maybe use import copy?
584 #for i in self.pitches:
585 # ch.pitches.append (i)
586 ch
.pitches
= self
.pitches
[:]
587 ch
.multimeasure
= self
.multimeasure
589 ch
.basic_duration
= self
.basic_duration
590 #for i in self.scripts:
591 # ch.scripts.append (i)
592 ch
.scripts
= self
.scripts
[:]
593 ch
.grace
= self
.grace
595 ch
.chord_prefix
= self
.chord_prefix
596 ch
.chord_suffix
= self
.chord_suffix
597 ch
.note_prefix
= self
.note_prefix
598 ch
.note_suffix
= self
.note_suffix
606 if self
.basic_duration
== 0.5:
609 sd
= '%d' % self
.basic_duration
610 sd
= sd
+ '.' * self
.dots
611 for p
in self
.pitches
:
614 str = str + pitch_to_lily_string (p
) + sd
616 for s
in self
.scripts
:
619 str = self
.note_prefix
+str + self
.note_suffix
621 if len (self
.pitches
) > 1:
623 elif self
.multimeasure
:
625 elif len (self
.pitches
) == 0:
628 str = self
.chord_prefix
+ str + self
.chord_suffix
665 # http://www.arkkra.com/doc/uguide/contexts.html
680 def __init__ (self
, lines
):
681 self
.parse_function
= self
.parse_context_music
683 self
.current_voices
= []
684 self
.forced_duration
= None
687 self
.tuplets_expected
= 0
695 def parse_compound_location (self
, line
):
696 colon
= string
.index (line
, ':')
699 line
= line
[colon
+ 1:]
701 self
.current_voices
= []
702 ##self.current_staffs = []
703 map (self
.parse_location
, string
.split (s
, '&'))
706 def parse_location (self
, line
):
707 m
= re
.match ('^([-,0-9]+) *([-,0-9]*)', string
.lstrip (line
))
709 def range_list_to_idxs (s
):
719 def range_to_list (s
):
720 if string
.find (s
, '-') >= 0:
722 l
= map (string
.lstrip
,
723 string
.split (s
, '-'))
724 r
= range (string
.atoi (l
[0]) - 1,
727 r
= (string
.atoi (s
) - 1,)
730 ranges
= string
.split (s
, ',')
731 l
= flatten (map (range_to_list
, ranges
))
735 staff_idxs
= range_list_to_idxs (m
.group (1))
737 voice_idxs
= range_list_to_idxs (m
.group (2))
741 while s
> len (self
.staffs
) - 1:
742 self
.staffs
.append (Staff (s
))
744 self
.current_voices
.append (self
.staffs
[s
].voices
[v
])
746 def parse_note (self
, line
):
749 name
= (ord (line
[0]) - ord ('a') + 5) % 7
750 # FIXME: does key play any role in this?
752 debug ('NOTE: ' + `line`
)
753 line
= string
.lstrip (line
[1:])
755 if len (line
) > 1 and line
[:2] == '//':
759 alteration
= alteration
+ 1
761 alteration
= alteration
- 1
768 line
= string
.lstrip (line
[1:])
769 return (oct, name
, alteration
)
771 def parse_chord (self
, line
):
772 debug ('CHORD: ' + line
)
773 line
= string
.lstrip (line
)
776 #ch = self.current_voices[0].last_chord ()
777 ch
= self
.last_chord
.copy ()
779 m
= re
.match ('^([0-9]+)([.]*)', line
)
781 ch
.basic_duration
= string
.atoi (m
.group (1))
782 line
= line
[len (m
.group (1)):]
784 ch
.dots
= len (m
.group (2))
785 line
= line
[len (m
.group (2)):]
787 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
788 ch
.basic_duration
= self
.last_chord
.basic_duration
790 line
= string
.lstrip (line
)
791 if len (line
) > 1 and line
[:2] == '//':
795 debug ('nline: ' + line
)
796 #ch = self.current_voices[0].last_chord ()
797 n
= self
.last_chord
.copy ()
798 n
.basic_duration
= ch
.basic_duration
801 debug ('ch.pitsen:' + `ch
.pitches`
)
802 debug ('ch.dur:' + `ch
.basic_duration`
)
804 debug ('eline: ' + line
)
807 if len (line
) > 1 and line
[:2] == '//':
810 elif line
[:1] == 'mr':
813 elif line
[:1] == 'ms':
816 elif line
[0] in 'rs':
819 elif line
[0] in 'abcdefg':
820 m
= re
.match ('([a-g][-#&+]*)', line
)
821 l
= len (m
.group (1))
822 pitch
= self
.parse_note (line
[:l
])
823 debug ('PITCH: ' + `pitch`
)
824 ch
.pitches
.append (pitch
)
830 line
= string
.lstrip (line
)
831 debug ('CUR-VOICES: ' + `self
.current_voices`
)
832 map (lambda x
, ch
=ch
: x
.add_chord (ch
), self
.current_voices
)
835 def parse_lyrics_location (self
, line
):
836 line
= line
.lstrip (line
)
838 m
= re
.match ('^(between[ \t]+)', line
)
840 line
= line
[len (m
.group (1)):]
843 m
= re
.match ('^(above [ \t]+)', line
)
845 line
= line
[len (m
.group (1)):]
850 def parse_voice (self
, line
):
851 line
= string
.lstrip (line
)
852 # `;' is not a separator, chords end with ';'
853 chords
= string
.split (line
, ';')[:-1]
854 # mup resets default duration and pitch each bar
855 self
.last_chord
= Chord ()
856 self
.last_chord
.basic_duration
= 4
857 map (self
.parse_chord
, chords
)
859 def init_context_header (self
, line
):
860 self
.parse_function
= self
.parse_context_header
862 def parse_context_header (self
, line
):
863 debug ('header: ' + line
)
866 def init_context_footer (self
, line
):
867 self
.parse_function
= self
.parse_context_footer
869 def parse_context_footer (self
, line
):
870 debug ('footer: ' + line
)
873 def init_context_header2 (self
, line
):
874 self
.parse_function
= self
.parse_context_header2
876 def parse_context_header2 (self
, line
):
877 debug ('header2: ' + line
)
880 def init_context_footer2 (self
, line
):
881 self
.parse_function
= self
.parse_context_footer2
883 def parse_context_footer2 (self
, line
):
884 debug ('footer2: ' + line
)
887 def init_context_score (self
, line
):
888 self
.parse_function
= self
.parse_context_score
890 def parse_context_score (self
, line
):
891 debug ('score: ' + line
)
892 line
= string
.lstrip (line
)
893 # ugh: these (and lots more) should also be parsed in
894 # context staff. we should have a class Staff_properties
895 # and parse/set all those.
896 m
= re
.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line
)
898 line
= line
[len (m
.group (1)):]
899 self
.time
= Time ((string
.atoi (m
.group (2)),
900 string
.atoi (m
.group (3))))
902 m
= re
.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line
)
904 line
= line
[len (m
.group (1)):]
905 n
= string
.atoi (m
.group (2))
906 if m
.group (3) == '#':
907 self
.key
= Key (n
, 0)
909 self
.key
= Key (0, n
)
912 def init_context_staff (self
, line
):
913 self
.parse_function
= self
.parse_context_staff
915 def parse_context_staff (self
, line
):
916 debug ('staff: ' + line
)
919 def init_context_voice (self
, line
):
920 self
.parse_function
= self
.parse_context_voice
922 def parse_context_voice (self
, line
):
923 debug ('voice: ' + line
)
926 def init_context_grids (self
, line
):
927 self
.parse_function
= self
.parse_context_grids
929 def parse_context_grids (self
, line
):
930 debug ('grids: ' + line
)
933 def init_context_music (self
, line
):
934 self
.parse_function
= self
.parse_context_music
936 def parse_context_music (self
, line
):
937 debug ('music: ' + line
)
938 line
= string
.lstrip (line
)
939 if line
and line
[0] in '0123456789':
940 line
= self
.parse_compound_location (line
)
941 self
.parse_voice (line
)
943 m
= re
.match ('^(TODOlyrics[ \t]+)', line
)
945 line
= line
[len (m
.group (1)):]
946 self
.parse_lyrics_location (line
[7:])
947 self
.parse_lyrics (line
)
951 def parse (self
, lines
):
952 # shortcut: set to official mup maximum (duh)
953 # self.set_staffs (40)
955 debug ('LINE: ' + `line`
)
956 m
= re
.match ('^([a-z]+2?)', line
)
961 eval ('self.init_context_%s (line)' % word
)
964 warning (_ ("no such context: %s") % word
)
967 debug ('FUNC: ' + `self
.parse_function`
)
968 self
.parse_function (line
)
970 for c
in self
.staffs
:
972 if not c
.clef
and self
.clef
:
974 if not c
.time
and self
.time
:
976 if not c
.key
and self
.key
:
984 for s
in self
.staffs
:
985 str = str + s
.dump ()
986 refs
= refs
+ '\n \\' + s
.idstring ()
1000 class Pre_processor
:
1001 def __init__ (self
, raw_lines
):
1004 self
.process_function
= self
.process_line
1005 self
.macro_name
= ''
1006 self
.macro_body
= ''
1007 self
.process (raw_lines
)
1009 def process_line (self
, line
):
1011 m
= re
.match ('^([ \t]*([a-zA-Z]+))', line
)
1015 debug ('MACRO?: ' + `word`
)
1016 if word
in pre_processor_commands
:
1017 line
= line
[len (m
.group (1)):]
1018 eval ('self.process_macro_%s (line)' % word
)
1021 if macros
.has_key (word
):
1022 s
= macros
[word
] + line
[len (m
.group (1)):]
1023 if not self
.active
[-1]:
1027 def process_macro_body (self
, line
):
1029 # dig this: mup allows ifdefs inside macro bodies
1030 s
= self
.process_line (line
)
1031 m
= re
.match ('(.*[^\\\\])(@)(.*)', s
)
1033 self
.macro_body
= self
.macro_body
+ '\n' + m
.group (1)
1034 macros
[self
.macro_name
] = self
.macro_body
1035 debug ('MACROS: ' + `macros`
)
1036 # don't do nested multi-line defines
1037 self
.process_function
= self
.process_line
1043 self
.macro_body
= self
.macro_body
+ '\n' + s
1047 # duh: mup is strictly line-based, except for `define',
1048 # which is `@' terminated and may span several lines
1049 def process_macro_define (self
, line
):
1051 # don't define new macros in unactive areas
1052 if not self
.active
[-1]:
1054 m
= re
.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line
)
1062 debug ('MACROS: ' + `macros`
)
1064 # To support nested multi-line define's
1065 # process_function and macro_name, macro_body
1066 # should become lists (stacks)
1067 # The mup manual is undetermined on this
1068 # and I haven't seen examples doing it.
1070 # don't do nested multi-line define's
1072 self
.macro_body
= m
.group (2)
1074 self
.macro_body
= ''
1076 self
.process_function
= self
.process_macro_body
1078 def process_macro_ifdef (self
, line
):
1079 m
= re
.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line
)
1082 active
= self
.active
[-1] and macros
.has_key (m
.group (1))
1083 debug ('ACTIVE: %d' % active
)
1084 self
.active
.append (active
)
1086 def process_macro_ifndef (self
, line
):
1087 m
= re
.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line
)
1089 active
= self
.active
[-1] and not macros
.has_key (m
.group (1))
1090 self
.active
.append (active
)
1092 def process_macro_else (self
, line
):
1094 self
.active
[-1] = not self
.active
[-1]
1096 def process_macro_endif (self
, line
):
1097 self
.active
= self
.active
[:-1]
1099 def process (self
, raw_lines
):
1101 for line
in raw_lines
:
1102 ls
= string
.split (self
.process_function (line
), '\n')
1105 s
= s
+ string
.rstrip (i
)
1106 if s
and s
[-1] == '\\':
1107 s
= string
.rstrip (s
[:-1])
1109 self
.lines
.append (s
)
1114 option_definitions
= [
1115 ('', 'd', 'debug', _ ("debug")),
1116 ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")),
1117 ('', 'h', 'help', _ ("this help")),
1118 ('FILE', 'o', 'output', _ ("write output to FILE")),
1119 ('', 'E', 'pre-process', _ ("only pre-process")),
1120 ('', 'V', 'verbose', _ ("verbose")),
1121 ('', 'v', 'version', _ ("print version number")),
1122 ('', 'w', 'warranty', _ ("show warranty and copyright")),
1126 only_pre_process_p
= 0
1129 progress ('DEBUG: ' + s
)
1132 if verbose_p
or debug_p
:
1133 progress ('SKIPPING: ' + s
)
1135 (sh
, long) = getopt_args (__main__
.option_definitions
)
1137 (options
, files
) = getopt
.getopt (sys
.argv
[1:], sh
, long)
1143 pre_processor_commands
= (
1156 elif o
== '--debug' or o
== '-d':
1158 elif o
== '--define' or o
== '-D':
1159 if string
.find (a
, '=') >= 0:
1160 (n
, e
) = string
.split (a
, '=')
1165 elif o
== '--pre-process' or o
== '-E':
1166 only_pre_process_p
= 1
1167 elif o
== '--help' or o
== '-h':
1170 elif o
== '--verbose' or o
== '-V':
1172 elif o
== '--version' or o
== '-v':
1175 elif o
== '--output' or o
== '-o':
1181 # writes to stdout for help2man
1184 # sys.stdout.flush ()
1186 # handy emacs testing
1188 # files = ['template.mup']
1197 elif f
and not os
.path
.isfile (f
):
1198 f
= strip_extension (f
, '.mup') + '.mup'
1200 progress ( _("Processing `%s'..." % f
))
1201 raw_lines
= h
.readlines ()
1202 p
= Pre_processor (raw_lines
)
1203 if only_pre_process_p
:
1205 output
= os
.path
.basename (re
.sub ('(?i).mup$', '.mpp', f
))
1207 e
= Parser (p
.lines
)
1209 output
= os
.path
.basename (re
.sub ('(?i).mup$', '.ly', f
))
1211 output
= os
.path
.basename (f
+ '.ly')
1217 out_h
= open (output
, 'w')
1219 progress (_ ("Writing `%s'...") % output
)
1221 tag
= '%% Lily was here -- automatically converted by %s from %s' % ( program_name
, f
)
1222 if only_pre_process_p
:
1224 ly
= string
.join (p
.lines
, '\n')
1226 ly
= tag
+ '\n\n' + e
.dump ()