X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmup2ly.py;h=81279de3f8e694871949a55cf3f102913ae924f3;hb=1bc064f3bcf74b35760da152cdde04020394d554;hp=2808dc1b653f571ee803678ec8402998790d591d;hpb=a2991295b2c1fb36bec19de207161855ca3c1756;p=lilypond.git diff --git a/scripts/mup2ly.py b/scripts/mup2ly.py index 2808dc1b65..81279de3f8 100644 --- a/scripts/mup2ly.py +++ b/scripts/mup2ly.py @@ -7,8 +7,19 @@ ''' TODO: - - WIP:lots of stuff + LOTS: we get all notes out now, rest after 1.4 + + * lyrics (partly done) + * bars + * slurs,ties + * staff settings + * tuplets + * grace + * ornaments + * midi settings + * titling + * chords entry mode + * repeats, percent repeats ''' @@ -24,180 +35,56 @@ import operator import tempfile -sys.path.append ('@datadir@/python') -import gettext -gettext.bindtextdomain ('lilypond', '@localedir@') -gettext.textdomain('lilypond') -_ = gettext.gettext - - -program_name = 'mup2ly' -help_summary = _("Convert mup to ly") -output = 0 - -# lily_py.py -- options and stuff -# -# source file of the GNU LilyPond music typesetter +# if set, LILYPONDPREFIX must take prevalence +# if datadir is not set, we're doing a build and LILYPONDPREFIX +datadir = '@datadir@' +if os.environ.has_key ('LILYPONDPREFIX') \ + or '@datadir@' == '@' + 'datadir' + '@': + datadir = os.environ['LILYPONDPREFIX'] +else: + datadir = '@datadir@' -# BEGIN Library for these? -# cut-n-paste from ly2dvi +sys.path.append (os.path.join (datadir, 'python')) +sys.path.append (os.path.join (datadir, 'python/out')) +program_name = 'ly2dvi' program_version = '@TOPLEVEL_VERSION@' -if program_version == '@' + 'TOPLEVEL_VERSION' + '@': - program_version = '1.3.142' - - original_dir = os.getcwd () -temp_dir = '%s.dir' % program_name +temp_dir = os.path.join (original_dir, '%s.dir' % program_name) +errorport = sys.stderr keep_temp_dir_p = 0 verbose_p = 0 -def identify (): - sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version)) - -def warranty (): - identify () - sys.stdout.write ('\n') - sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001')) - sys.stdout.write ('\n') - sys.stdout.write (' Han-Wen Nienhuys') - sys.stdout.write (' Jan Nieuwenhuizen') - sys.stdout.write ('\n') - sys.stdout.write (_ (r''' -Distributed under terms of the GNU General Public License. It comes with -NO WARRANTY.''')) - sys.stdout.write ('\n') - -def progress (s): - if s[-1] != '\n': - s = s + '\n' - sys.stderr.write (s) - -def warning (s): - sys.stderr.write (_ ("warning: ") + s) - sys.stderr.write ('\n') - - -def error (s): - sys.stderr.write (_ ("error: ") + s) - sys.stderr.write ('\n') - raise _ ("Exiting ... ") - -def getopt_args (opts): - '''Construct arguments (LONG, SHORT) for getopt from list of options.''' - short = '' - long = [] - for o in opts: - if o[1]: - short = short + o[1] - if o[0]: - short = short + ':' - if o[2]: - l = o[2] - if o[0]: - l = l + '=' - long.append (l) - return (short, long) - -def option_help_str (o): - '''Transform one option description (4-tuple ) into neatly formatted string''' - sh = ' ' - if o[1]: - sh = '-%s' % o[1] - - sep = ' ' - if o[1] and o[2]: - sep = ',' - - long = '' - if o[2]: - long= '--%s' % o[2] - - arg = '' - if o[0]: - if o[2]: - arg = '=' - arg = arg + o[0] - return ' ' + sh + sep + long + arg - - -def options_help_str (opts): - '''Convert a list of options into a neatly formatted string''' - w = 0 - strs =[] - helps = [] - - for o in opts: - s = option_help_str (o) - strs.append ((s, o[3])) - if len (s) > w: - w = len (s) - - str = '' - for s in strs: - str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1]) - return str - -def help (): - sys.stdout.write (_ ("Usage: %s [OPTION]... FILE") % program_name) - sys.stdout.write ('\n\n') - sys.stdout.write (help_summary) - sys.stdout.write ('\n\n') - sys.stdout.write (_ ("Options:")) - sys.stdout.write ('\n') - sys.stdout.write (options_help_str (option_definitions)) - sys.stdout.write ('\n\n') - sys.stdout.write (_ ("Report bugs to %s") % 'bug-gnu-music@gnu.org') - sys.stdout.write ('\n') - sys.exit (0) - - -def setup_temp (): - global temp_dir - if not keep_temp_dir_p: - temp_dir = tempfile.mktemp (program_name) - try: - os.mkdir (temp_dir, 0777) - except OSError: - pass - - -def system (cmd, ignore_error = 0): - if verbose_p: - progress (_ ("Invoking `%s\'") % cmd) - st = os.system (cmd) - if st: - msg = ( _ ("error: ") + _ ("command exited with value %d") % st) - if ignore_error: - sys.stderr.write (msg + ' ' + _ ("(ignored)") + ' ') - else: - error (msg) +try: + import gettext + gettext.bindtextdomain ('lilypond', '@localedir@') + gettext.textdomain ('lilypond') + _ = gettext.gettext +except: + def _ (s): + return s - return st +program_name = 'mup2ly' +help_summary = _ ("Convert mup to LilyPond source") -def cleanup_temp (): - if not keep_temp_dir_p: - if verbose_p: - progress (_ ('Cleaning up `%s\'') % temp_dir) - system ('rm -rf %s' % temp_dir) +option_definitions = [ + ('', 'd', 'debug', _ ("debug")), + ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")), + ('', 'h', 'help', _ ("this help")), + ('FILE', 'o', 'output', _ ("write output to FILE")), + ('', 'E', 'pre-process', _ ("only pre-process")), + ('', 'V', 'verbose', _ ("verbose")), + ('', 'v', 'version', _ ("print version number")), + ('', 'w', 'warranty', _ ("show warranty and copyright")), + ] -def set_setting (dict, key, val): - try: - val = string.atof (val) - except ValueError: - #warning (_ ("invalid value: %s") % `val`) - pass +from lilylib import * - try: - dict[key].append (val) - except KeyError: - warning (_ ("no such setting: %s") % `key`) - dict[key] = [val] -# END Library +output = 0 # # PMX cut and paste @@ -315,6 +202,7 @@ class Slur: self.id = id self.start_chord = None self.end_chord = None + def calculate (self): s =self.start_chord e= self.end_chord @@ -366,10 +254,10 @@ class Voice: def dump (self): str = '' - if not self.entries: - #return '\n' - #ugh ugh - return '\n%s = {}\n\n' % self.idstring () + #if not self.entries: + # #return '\n' + # #ugh ugh + # return '\n%s = {}\n\n' % self.idstring () ln = ' ' one_two = ("One", "Two") if self.staff.voices [1 - self.number].entries: @@ -390,11 +278,11 @@ class Voice: str = str + ln id = self.idstring () - str = '''%s = \\notes { + str = '''%s = \\context Voice = %s \\notes { %s } -'''% (id, str) +'''% (id, id, str) return str def calculate_graces (self): @@ -416,8 +304,34 @@ class Voice: class Clef: def __init__ (self, cl): self.type = cl - def dump(self): - return '\\clef %s;' % self.type + + def dump (self): + return '\\clef %s' % self.type + +key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis') +key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges') + +class Key: + def __init__ (self, sharps, flats): + self.flats = flats + self.sharps = sharps + + def dump (self): + if self.sharps and self.flats: + k = '\\keysignature %s ' % 'TODO' + elif self.sharps: + k = '\\notes\\key %s \major' % key_sharps[self.sharps] + elif self.flats: + k = '\\notes\\key %s \major' % key_flats[self.flats] + return k + +class Time: + def __init__ (self, frac): + self.frac = frac + + def dump (self): + return '\\time %d/%d' % (self.frac[0], self.frac[1]) + clef_table = { 'b':'bass' , @@ -435,8 +349,9 @@ class Staff: # ugh self.voices = (Voice (0), Voice (1)) - # self.voice_idx = 0 self.clef = None + self.time = None + self.key = None self.instrument = 0 self.number = n @@ -446,31 +361,35 @@ class Staff: v.number = i i = i+1 - def set_clef (self, letter): - clstr = clef_table[letter] - self.voices[0].add_nonchord (Clef (clstr)) + #def set_clef (self, letter): + # clstr = clef_table[letter] + # self.voices[0].add_nonchord (Clef (clstr)) - #def current_voice (self): - # return self.voices[self.voice_idx] - # - #def next_voice (self): - # self.voice_idx = (self.voice_idx + 1)%len (self.voices) - def calculate (self): for v in self.voices: v.calculate () def idstring (self): return 'staff%s' % encodeint (self.number) - + def dump (self): str = '' refs = '' for v in self.voices: - str = str + v.dump() - refs = refs + '\n \\' + v.idstring () - + if v.entries: + # urg + if v == self.voices[0]: + if self.clef: + refs = refs + self.clef.dump () + if self.time: + refs = refs + self.time.dump () + if self.key: + refs = refs + self.key.dump () + if refs: + refs = '\n ' + refs + str = str + v.dump() + refs = refs + '\n \\' + v.idstring () str = str + ''' %s = \context Staff = %s <%s > @@ -521,6 +440,27 @@ class Chord: self.chord_suffix = '' self.note_prefix = '' self.note_suffix = '' + + # maybe use import copy? + def copy (self): + ch = Chord () + #for i in self.pitches: + # ch.pitches.append (i) + ch.pitches = self.pitches[:] + ch.multimeasure = self.multimeasure + ch.dots = self.dots + ch.basic_duration = self.basic_duration + #for i in self.scripts: + # ch.scripts.append (i) + ch.scripts = self.scripts[:] + ch.grace = self.grace + + ch.chord_prefix = self.chord_prefix + ch.chord_suffix = self.chord_suffix + ch.note_prefix = self.note_prefix + ch.note_suffix = self.note_suffix + return ch + def dump (self): str = '' @@ -587,7 +527,7 @@ ornament_table = { # http://www.arkkra.com/doc/uguide/contexts.html -contexts = [ +contexts = ( 'header', 'footer', 'header2', @@ -597,10 +537,10 @@ contexts = [ 'voice', 'grids', 'music', - ] + ) class Parser: - def __init__ (self, filename): + def __init__ (self, lines): self.parse_function = self.parse_context_music self.staffs = [] self.current_voices = [] @@ -609,24 +549,14 @@ class Parser: self.last_oct = 0 self.tuplets_expected = 0 self.tuplets = [] - self.last_basic_duration = 4 - - self.parse (filename) - - #def set_staffs (self, number): - # self.staffs = map (lambda x: Staff (x), range (0, number)) + self.clef = None + self.time = None + self.key = None - #def current_staff (self): - # return self.staffs[self.staff_idx] - - #def current_voice (self): - # return self.current_staff ().current_voice () - - #def next_staff (self): - # self.staff_idx = (self.staff_idx + 1)% len (self.staffs) + self.parse (lines) def parse_compound_location (self, line): - colon = string.index (line, ':') + colon = string.index (line, ':') s = line[:colon] debug (s) line = line[colon + 1:] @@ -648,7 +578,7 @@ class Parser: for j in i: f.append (j) return f - + def range_to_list (s): if string.find (s, '-') >= 0: debug ('s: ' + s) @@ -682,6 +612,7 @@ class Parser: name = (ord (line[0]) - ord ('a') + 5) % 7 # FIXME: does key play any role in this? alteration = 0 + debug ('NOTE: ' + `line`) line = string.lstrip (line[1:]) while line: if len (line) > 1 and line[:2] == '//': @@ -696,60 +627,96 @@ class Parser: elif line[0] == '-': oct = oct - 1 else: - skipping (_ ("%s") % line[0]) + skipping (line[0]) line = string.lstrip (line[1:]) return (oct, name, alteration) def parse_chord (self, line): + debug ('CHORD: ' + line) line = string.lstrip (line) ch = Chord () if not line: - ch = self.current_voices[0].last_chord () + #ch = self.current_voices[0].last_chord () + ch = self.last_chord.copy () else: m = re.match ('^([0-9]+)([.]*)', line) if m: ch.basic_duration = string.atoi (m.group (1)) line = line[len (m.group (1)):] if m.group (2): - ch.basic_duration = len (m.group (2)) - line = line[len (m.group (1)):] + ch.dots = len (m.group (2)) + line = line[len (m.group (2)):] else: - ch.basic_duration = self.current_voices[0].last_chord ().basic_duration + #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration + ch.basic_duration = self.last_chord.basic_duration line = string.lstrip (line) if len (line) > 1 and line[:2] == '//': line = 0 #ugh if not line: - duration = ch.basic_duration - ch = self.current_voices[0].last_chord () - ch.basic_duration = duration - + debug ('nline: ' + line) + #ch = self.current_voices[0].last_chord () + n = self.last_chord.copy () + n.basic_duration = ch.basic_duration + n.dots = ch.dots + ch = n + debug ('ch.pitsen:' + `ch.pitches`) + debug ('ch.dur:' + `ch.basic_duration`) + else: + debug ('eline: ' + line) + while line: if len (line) > 1 and line[:2] == '//': line = 0 break elif line[:1] == 'mr': ch.multimeasure = 1 - line = line[1:] + line = line[2:] elif line[:1] == 'ms': ch.multimeasure = 1 - line = line[1:] + line = line[2:] elif line[0] in 'rs': + line = line[1:] pass elif line[0] in 'abcdefg': - pitch = self.parse_note (line) + m = re.match ('([a-g][-#&+]*)', line) + l = len (m.group (1)) + pitch = self.parse_note (line[:l]) debug ('PITCH: ' + `pitch`) ch.pitches.append (pitch) - line = 0 + line = line[l:] break else: - skipping (_ ("%s") % line[0]) - line = string.lstrip (line[1:]) + skipping (line[0]) + line = line[1:] + line = string.lstrip (line) + debug ('CUR-VOICES: ' + `self.current_voices`) map (lambda x, ch=ch: x.add_chord (ch), self.current_voices) - + self.last_chord = ch + + def parse_lyrics_location (self, line): + line = line.lstrip (line) + addition = 0 + m = re.match ('^(between[ \t]+)', line) + if m: + line = line[len (m.group (1)):] + addition = 0.5 + else: + m = re.match ('^(above [ \t]+)', line) + if m: + line = line[len (m.group (1)):] + addition = -0.5 + else: + addlyrics = 1 + def parse_voice (self, line): - chords = string.split (line, ';') + line = string.lstrip (line) + # `;' is not a separator, chords end with ';' + chords = string.split (line, ';')[:-1] + # mup resets default duration and pitch each bar + self.last_chord = Chord () + self.last_chord.basic_duration = 4 map (self.parse_chord, chords) def init_context_header (self, line): @@ -757,48 +724,74 @@ class Parser: def parse_context_header (self, line): debug ('header: ' + line) - + skipping (line) + def init_context_footer (self, line): self.parse_function = self.parse_context_footer def parse_context_footer (self, line): debug ('footer: ' + line) + skipping (line) def init_context_header2 (self, line): self.parse_function = self.parse_context_header2 def parse_context_header2 (self, line): debug ('header2: ' + line) + skipping (line) def init_context_footer2 (self, line): self.parse_function = self.parse_context_footer2 def parse_context_footer2 (self, line): debug ('footer2: ' + line) + skipping (line) def init_context_score (self, line): self.parse_function = self.parse_context_score def parse_context_score (self, line): debug ('score: ' + line) + line = string.lstrip (line) + # ugh: these (and lots more) should also be parsed in + # context staff. we should have a class Staff_properties + # and parse/set all those. + m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line) + if m: + line = line[len (m.group (1)):] + self.time = Time ((string.atoi (m.group (2)), + string.atoi (m.group (3)))) + + m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line) + if m: + line = line[len (m.group (1)):] + n = string.atoi (m.group (2)) + if m.group (3) == '#': + self.key = Key (n, 0) + else: + self.key = Key (0, n) + skipping (line) def init_context_staff (self, line): self.parse_function = self.parse_context_staff def parse_context_staff (self, line): debug ('staff: ' + line) + skipping (line) def init_context_voice (self, line): self.parse_function = self.parse_context_voice def parse_context_voice (self, line): debug ('voice: ' + line) + skipping (line) def init_context_grids (self, line): - self.parse_function = self.parse_context_line + self.parse_function = self.parse_context_grids def parse_context_grids (self, line): debug ('grids: ' + line) + skipping (line) def init_context_music (self, line): self.parse_function = self.parse_context_music @@ -807,17 +800,22 @@ class Parser: debug ('music: ' + line) line = string.lstrip (line) if line and line[0] in '0123456789': - line = string.lstrip (self.parse_compound_location (line)) + line = self.parse_compound_location (line) self.parse_voice (line) else: - skipping (_ ("%s") % line) - - def parse (self, file): + m = re.match ('^(TODOlyrics[ \t]+)', line) + if m: + line = line[len (m.group (1)):] + self.parse_lyrics_location (line[7:]) + self.parse_lyrics (line) + else: + skipping (line) + + def parse (self, lines): # shortcut: set to official mup maximum (duh) # self.set_staffs (40) - lines = open (file).readlines () for line in lines: - debug ('LINE: ' + line) + debug ('LINE: ' + `line`) m = re.match ('^([a-z]+2?)', line) if m: @@ -833,6 +831,13 @@ class Parser: self.parse_function (line) for c in self.staffs: + # hmm + if not c.clef and self.clef: + c.clef = self.clef + if not c.time and self.time: + c.time = self.time + if not c.key and self.key: + c.key = self.key c.calculate () def dump (self): @@ -847,27 +852,135 @@ class Parser: \score { <%s - > + > + \paper {} + \midi {} } ''' % refs return str + +class Pre_processor: + def __init__ (self, raw_lines): + self.lines = [] + self.active = [1] + self.process_function = self.process_line + self.macro_name = '' + self.macro_body = '' + self.process (raw_lines) + + def process_line (self, line): + global macros + m = re.match ('^([ \t]*([a-zA-Z]+))', line) + s = line + if m: + word = m.group (2) + debug ('MACRO?: ' + `word`) + if word in pre_processor_commands: + line = line[len (m.group (1)):] + eval ('self.process_macro_%s (line)' % word) + s = '' + else: + if macros.has_key (word): + s = macros[word] + line[len (m.group (1)):] + if not self.active [-1]: + s = '' + return s + + def process_macro_body (self, line): + global macros + # dig this: mup allows ifdefs inside macro bodies + s = self.process_line (line) + m = re.match ('(.*[^\\\\])(@)(.*)', s) + if m: + self.macro_body = self.macro_body + '\n' + m.group (1) + macros[self.macro_name] = self.macro_body + debug ('MACROS: ' + `macros`) + # don't do nested multi-line defines + self.process_function = self.process_line + if m.group (3): + s = m.group (3) + else: + s = '' + else: + self.macro_body = self.macro_body + '\n' + s + s = '' + return s + + # duh: mup is strictly line-based, except for `define', + # which is `@' terminated and may span several lines + def process_macro_define (self, line): + global macros + # don't define new macros in unactive areas + if not self.active[-1]: + return + m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line) + n = m.group (1) + if m.group (5): + if m.group (2): + e = m.group (2) + else: + e = '' + macros[n] = e + debug ('MACROS: ' + `macros`) + else: + # To support nested multi-line define's + # process_function and macro_name, macro_body + # should become lists (stacks) + # The mup manual is undetermined on this + # and I haven't seen examples doing it. + # + # don't do nested multi-line define's + if m.group (2): + self.macro_body = m.group (2) + else: + self.macro_body = '' + self.macro_name = n + self.process_function = self.process_macro_body -option_definitions = [ - ('', 'd', 'debug', _ ("debug")), - ('', 'h', 'help', _ ("this help")), - ('FILE', 'o', 'output', _ ("write output to FILE")), - ('', 'V', 'verbose', _ ("verbose")), - ('', 'v', 'version', _ ("print version number")), - ('', 'w', 'warranty', _ ("show warranty and copyright")), - ] + def process_macro_ifdef (self, line): + m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line) + if m: + + active = self.active[-1] and macros.has_key (m.group (1)) + debug ('ACTIVE: %d' % active) + self.active.append (active) + + def process_macro_ifndef (self, line): + m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line) + if m: + active = self.active[-1] and not macros.has_key (m.group (1)) + self.active.append (active) + + def process_macro_else (self, line): + debug ('ELSE') + self.active[-1] = not self.active[-1] + + def process_macro_endif (self, line): + self.active = self.active[:-1] + + def process (self, raw_lines): + s = '' + for line in raw_lines: + ls = string.split (self.process_function (line), '\n') + for i in ls: + if i: + s = s + string.rstrip (i) + if s and s[-1] == '\\': + s = string.rstrip (s[:-1]) + elif s: + self.lines.append (s) + s = '' + debug_p = 0 +only_pre_process_p = 0 def debug (s): if debug_p: progress ('DEBUG: ' + s) + def skipping (s): - if debug_p: + if verbose_p or debug_p: progress ('SKIPPING: ' + s) (sh, long) = getopt_args (__main__.option_definitions) @@ -877,6 +990,14 @@ except: help () sys.exit (2) +macros = {} +pre_processor_commands = ( + 'define', + 'else', + 'endif', + 'ifdef', + 'ifndef', + ) for opt in options: o = opt[0] @@ -885,6 +1006,15 @@ for opt in options: pass elif o== '--debug' or o == '-d': debug_p = 1 + elif o== '--define' or o == '-D': + if string.find (a, '=') >= 0: + (n, e) = string.split (a, '=') + else: + n = a + e = '' + macros[n] = e + elif o== '--pre-process' or o == '-E': + only_pre_process_p = 1 elif o== '--help' or o == '-h': help () sys.exit (0) @@ -905,28 +1035,49 @@ for opt in options: # sys.stdout.flush () # handy emacs testing -if not files: - files = ['template.mup'] +# if not files: +# files = ['template.mup'] +if not files: + files = ['-'] + for f in files: + if f == '-': - f = '' + h = sys.stdin + elif f and not os.path.isfile (f): + f = strip_extension (f, '.mup') + '.mup' + h = open (f) + progress ( _("Processing `%s'..." % f)) + raw_lines = h.readlines () + p = Pre_processor (raw_lines) + if only_pre_process_p: + if not output: + output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f)) + else: + e = Parser (p.lines) + if not output: + output = os.path.basename (re.sub ('(?i).mup$', '.ly', f)) + if output == f: + output = os.path.basename (f + '.ly') + + if f == '-': + output = '-' + out_h = sys.stdout + else: + out_h = open (output, 'w') - progress ( _("Processing %s..." % f)) - e = Parser (f) - if not output: - output = os.path.basename (re.sub ('(?i).mup$', '.ly', f)) - - if output == f: - output = os.path.basename (f + '.ly') - - progress (_ ("Writing %s...") % output) + progress (_ ("Writing `%s'...") % output) tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f) - ly = tag + '\n' + e.dump () + if only_pre_process_p: + # duh + ly = string.join (p.lines, '\n') + else: + ly = tag + '\n\n' + e.dump () - o = open (output, 'w') - o.write (ly) - o.close () - print ly + out_h.write (ly) + out_h.close () + if debug_p: + print (ly)