X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmup2ly.py;h=e1ba8b8fda828d88eae24873d9c23d484e35279e;hb=06b1da466f99f6c4a01ee6a3e611d25bd5359d4c;hp=7011a7b1e459df15f33d7dad964a940885e67894;hpb=8c83d4ae4eafdefb5e2088ef8f210db09737d00d;p=lilypond.git diff --git a/scripts/mup2ly.py b/scripts/mup2ly.py index 7011a7b1e4..e1ba8b8fda 100644 --- a/scripts/mup2ly.py +++ b/scripts/mup2ly.py @@ -7,6 +7,20 @@ ''' TODO: + 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 + ''' import os @@ -21,33 +35,91 @@ import operator import tempfile -sys.path.append ('@datadir@/python') -import gettext -gettext.bindtextdomain ('lilypond', '@localedir@') -gettext.textdomain('lilypond') -_ = gettext.gettext +# if set, LILYPONDPREFIX must take prevalence +# if datadir is not set, we're doing a build and LILYPONDPREFIX +datadir = '@local_lilypond_datadir@' +if os.environ.has_key ('LILYPONDPREFIX') \ + or '@local_lilypond_datadir@' == '@' + 'local_lilypond_datadir' + '@': + datadir = os.environ['LILYPONDPREFIX'] +else: + datadir = '@local_lilypond_datadir@' + +sys.path.append (os.path.join (datadir, 'python')) +sys.path.append (os.path.join (datadir, 'python/out')) + +program_name = sys.argv[0] +program_version = '@TOPLEVEL_VERSION@' +original_dir = os.getcwd () +temp_dir = os.path.join (original_dir, '%s.dir' % program_name) +errorport = sys.stderr +keep_temp_dir_p = 0 +verbose_p = 0 + +localedir = '@localedir@' +try: + import gettext + gettext.bindtextdomain ('lilypond', localedir) + gettext.textdomain ('lilypond') + _ = gettext.gettext +except: + def _ (s): + return s program_name = 'mup2ly' -help_summary = _("Convert mup to ly") -output = 0 +help_summary = _ ("Convert mup to LilyPond source.") + +option_definitions = [ + ('', 'd', 'debug', _ ("debug")), + ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")), + ('', 'h', 'help', _ ("print this help")), + ('FILE', 'o', 'output', _ ("write output to FILE")), + ('', 'E', 'pre-process', _ ("only pre-process")), + ('', 'V', 'verbose', _ ("be verbose")), + ('', 'v', 'version', _ ("print version number")), + ('', 'w', 'warranty', _ ("show warranty and copyright")), + ] + -# lily_py.py -- options and stuff +################################################################ +# lilylib.py -- options and stuff # # source file of the GNU LilyPond music typesetter -# BEGIN Library for these? -# cut-n-paste from ly2dvi +# Handle bug in Python 1.6-2.1 +# +# there are recursion limits for some patterns in Python 1.6 til 2.1. +# fix this by importing pre instead. Fix by Mats. -program_version = '@TOPLEVEL_VERSION@' -if program_version == '@' + 'TOPLEVEL_VERSION' + '@': - program_version = '1.3.142' +# todo: should check Python version first. +try: + import pre + re = pre + del pre +except ImportError: + import re + +# Attempt to fix problems with limited stack size set by Python! +# Sets unlimited stack size. Note that the resource module only +# is available on UNIX. +try: + import resource + resource.setrlimit (resource.RLIMIT_STACK, (-1, -1)) +except: + pass +try: + import gettext + gettext.bindtextdomain ('lilypond', localedir) + gettext.textdomain ('lilypond') + _ = gettext.gettext +except: + def _ (s): + return s -original_dir = os.getcwd () -temp_dir = '%s.dir' % program_name -keep_temp_dir_p = 0 -verbose_p = 0 +program_version = '@TOPLEVEL_VERSION@' +if program_version == '@' + 'TOPLEVEL_VERSION' + '@': + program_version = '1.5.54' def identify (): sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version)) @@ -55,27 +127,37 @@ def identify (): def warranty (): identify () sys.stdout.write ('\n') - sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001')) + sys.stdout.write (_ ('Copyright (c) %s by') % '2001--2004') 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\n') + sys.stdout.write (_ ("Distributed under terms of the GNU General Public License.")) + sys.stdout.write (_ ("It comes with NO WARRANTY.")) sys.stdout.write ('\n') def progress (s): - sys.stderr.write (s + '\n') + errorport.write (s + '\n') def warning (s): - sys.stderr.write (_ ("warning: ") + s) - sys.stderr.write ('\n') + progress (_ ("warning: ") + s) + +def user_error (s, e=1): + errorport.write (program_name + ":" + _ ("error: ") + s + '\n') + sys.exit (e) - def error (s): - sys.stderr.write (_ ("error: ") + s) - sys.stderr.write ('\n') + '''Report the error S. Exit by raising an exception. Please + do not abuse by trying to catch this error. If you do not want + a stack trace, write to the output directly. + + RETURN VALUE + + None + + ''' + + progress (_ ("error: ") + s) raise _ ("Exiting ... ") def getopt_args (opts): @@ -100,9 +182,9 @@ def option_help_str (o): if o[1]: sh = '-%s' % o[1] - sep = ' ' + sep = ' ' if o[1] and o[2]: - sep = ',' + sep = ', ' long = '' if o[2]: @@ -134,20 +216,22 @@ def options_help_str (opts): 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) - - + ls = [(_ ("Usage: %s [OPTIONS]... FILE") % program_name), + ('\n\n'), + (help_summary), + ('\n\n'), + (_ ("Options:")), + ('\n'), + (options_help_str (option_definitions)), + ('\n\n'), + (_ ("Report bugs to %s.") % 'bug-lilypond@gnu.org'), + ('\n')] + map (sys.stdout.write, ls) + def setup_temp (): + """ + Create a temporary directory, and return its name. + """ global temp_dir if not keep_temp_dir_p: temp_dir = tempfile.mktemp (program_name) @@ -155,16 +239,28 @@ def setup_temp (): os.mkdir (temp_dir, 0777) except OSError: pass - + + return temp_dir + + +def system (cmd, ignore_error = 0, quiet =0): + """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero. + + RETURN VALUE + + Exit status of CMD + """ -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) + name = re.match ('[ \t]*([^ \t]*)', cmd).group (1) + msg = name + ': ' + _ ("command exited with value %d") % st if ignore_error: - sys.stderr.write (msg + ' ' + _ ("(ignored)") + ' ') + if not quiet: + warning (msg + ' ' + _ ("(ignored)") + ' ') else: error (msg) @@ -174,32 +270,74 @@ def system (cmd, ignore_error = 0): def cleanup_temp (): if not keep_temp_dir_p: if verbose_p: - progress (_ ('Cleaning up `%s\'') % temp_dir) - system ('rm -rf %s' % temp_dir) + progress (_ ("Cleaning %s...") % temp_dir) + shutil.rmtree (temp_dir) -def set_setting (dict, key, val): - try: - val = string.atof (val) - except ValueError: - #warning (_ ("invalid value: %s") % `val`) - pass +def strip_extension (f, ext): + (p, e) = os.path.splitext (f) + if e == ext: + e = '' + return p + e + + +def cp_to_dir (pattern, dir): + "Copy files matching re PATTERN from cwd to DIR" + # Duh. Python style portable: cp *.EXT OUTDIR + # system ('cp *.%s %s' % (ext, outdir), 1) + files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.')) + map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files) + + +# Python < 1.5.2 compatibility +# +# On most platforms, this is equivalent to +#`normpath(join(os.getcwd()), PATH)'. *Added in Python version 1.5.2* +if os.path.__dict__.has_key ('abspath'): + abspath = os.path.abspath +else: + def abspath (path): + return os.path.normpath (os.path.join (os.getcwd (), path)) + +if os.__dict__.has_key ('makedirs'): + makedirs = os.makedirs +else: + def makedirs (dir, mode=0777): + system ('mkdir -p %s' % dir) + + +def mkdir_p (dir, mode=0777): + if not os.path.isdir (dir): + makedirs (dir, mode) - try: - dict[key].append (val) - except KeyError: - warning (_ ("no such setting: %s") % `key`) - dict[key] = [val] +# if set, LILYPONDPREFIX must take prevalence +# if datadir is not set, we're doing a build and LILYPONDPREFIX +datadir = '@local_lilypond_datadir@' + +if os.environ.has_key ('LILYPONDPREFIX') : + datadir = os.environ['LILYPONDPREFIX'] +else: + datadir = '@local_lilypond_datadir@' + + +while datadir[-1] == os.sep: + datadir= datadir[:-1] + +sys.path.insert (0, os.path.join (datadir, 'python')) + +################################################################ # END Library +output = 0 + # # PMX cut and paste # def encodeint (i): - return chr ( i + ord ('A')) + return chr (i + ord ('A')) actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'} @@ -207,7 +345,7 @@ actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'} def pitch_to_lily_string (tup): (o,n,a) = tup - nm = chr((n + 2) % 7 + ord ('a')) + nm = chr((n + 2) % 7 + ord ('a')) nm = nm + actab[a] if o > 0: nm = nm + "'" * o @@ -310,23 +448,26 @@ class Slur: self.id = id self.start_chord = None self.end_chord = None + def calculate (self): s =self.start_chord e= self.end_chord if e and s: - s.note_suffix = s.note_suffix + '(' - e.note_prefix = ')' + e.note_prefix + s.note_suffix = s.note_suffix + '-(' + e.note_prefix = e.note_suffix + "-)" else: sys.stderr.write ("\nOrphaned slur") class Voice: - def __init__ (self): + def __init__ (self, n): + self.number = n self.entries = [] self.chords = [] self.staff = None self.current_slurs = [] self.slurs = [] + def toggle_slur (self, id): for s in self.current_slurs: @@ -340,36 +481,56 @@ class Voice: self.slurs.append (s) def last_chord (self): - return self.chords[-1] + if len (self.chords): + return self.chords[-1] + else: + ch = Chord () + ch.basic_duration = 4 + return ch + def add_chord (self, ch): self.chords.append (ch) self.entries.append (ch) + def add_nonchord (self, nch): self.entries.append (nch) def idstring (self): return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number)) + def dump (self): str = '' - ln = '' + #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: + ln = ln + '\\voice%s\n ' % one_two[self.number] for e in self.entries: - next = ' ' + e.dump () + next = e.dump () if next[-1] == '\n': - str = str + ln + next - ln = '' + str = str + ln + next + ' ' + ln = ' ' continue if len (ln) +len (next) > 72: str = str+ ln + '\n' - ln = '' - ln = ln + next + ln = ' ' + ln = ln + next + ' ' str = str + ln id = self.idstring () - str = '%s = \\notes { \n %s }\n '% (id, str) + str = '''%s = \\context Voice = %s \\notes { +%s +} + +'''% (id, id, str) return str + def calculate_graces (self): lastgr = 0 lastc = None @@ -380,6 +541,7 @@ class Voice: lastc.chord_suffix = lastc.chord_suffix + ' } ' lastgr = c.grace lastc = c + def calculate (self): self.calculate_graces () for s in self.slurs: @@ -388,8 +550,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' , @@ -400,43 +588,59 @@ clef_table = { 's':'soprano', 't':'treble', 'f':'frenchviolin', - } + } + class Staff: - def __init__ (self): - self.voices = (Voice (), Voice()) + def __init__ (self, n): + # ugh + self.voices = (Voice (0), Voice (1)) + self.clef = None + self.time = None + self.key = None self.instrument = 0 - self.voice_idx = 0 - self.number = None + self.number = n i = 0 - for v in self.voices: + for v in self.voices: v.staff = self 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 + '\\' + v.idstring ()+ ' ' - - str = str + '\n\n%s = \\context Staff = %s \n < \n %s >\n\n\n'% (self.idstring (), self.idstring (), refs) + 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 +>> + +''' % (self.idstring (), self.idstring (), refs) return str class Tuplet: @@ -482,6 +686,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 = '' @@ -495,19 +720,21 @@ class Chord: for p in self.pitches: if str: str = str + ' ' - str = str + pitch_to_lily_string (p) + sd + str = str + pitch_to_lily_string (p) - for s in self.scripts: - str = str + '-' + s str = self.note_prefix +str + self.note_suffix if len (self.pitches) > 1: str = '<%s>' % str elif self.multimeasure: - str = 'R' + sd + str = 'R' elif len (self.pitches) == 0: - str = 'r' + sd + str = 'r' + + str = str + sd + for s in self.scripts: + str = str + '-' + s str = self.chord_prefix + str + self.chord_suffix @@ -548,7 +775,7 @@ ornament_table = { # http://www.arkkra.com/doc/uguide/contexts.html -contexts = [ +contexts = ( 'header', 'footer', 'header2', @@ -558,170 +785,307 @@ 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 = [] self.forced_duration = None self.last_name = 0 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 (), range (0, number)) + self.clef = None + self.time = None + self.key = None - self.staff_idx = 0 - - i =0 - for s in self.staffs: - s.number = i - i = i+1 - - 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, ':') + s = line[:colon] + debug (s) + line = line[colon + 1:] + debug (line) + self.current_voices = [] + ##self.current_staffs = [] + map (self.parse_location, string.split (s, '&')) + return line + + def parse_location (self, line): + m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line)) + + def range_list_to_idxs (s): + + # duh + def flatten (l): + f = [] + for i in l: + for j in i: + f.append (j) + return f + + def range_to_list (s): + if string.find (s, '-') >= 0: + debug ('s: ' + s) + l = map (string.lstrip, + string.split (s, '-')) + r = range (string.atoi (l[0]) - 1, + string.atoi (l[1])) + else: + r = (string.atoi (s) - 1,) + return r + + ranges = string.split (s, ',') + l = flatten (map (range_to_list, ranges)) + l.sort () + return l + + staff_idxs = range_list_to_idxs (m.group (1)) + if m.group (2): + voice_idxs = range_list_to_idxs (m.group (2)) + else: + voice_idxs = [0] + for s in staff_idxs: + while s > len (self.staffs) - 1: + self.staffs.append (Staff (s)) + for v in voice_idxs: + self.current_voices.append (self.staffs[s].voices[v]) + def parse_note (self, line): - name = line[0] + # FIXME: 1? + oct = 1 + name = (ord (line[0]) - ord ('a') + 5) % 7 + # FIXME: does key play any role in this? alteration = 0 - line = line[1:] + debug ('NOTE: ' + `line`) + line = string.lstrip (line[1:]) while line: - if line[0] == '#': + if len (line) > 1 and line[:2] == '//': + line = 0 + break + elif line[0] == '#': alteration = alteration + 1 elif line[0] == '&': alteration = alteration - 1 - line = line[1:] - # shortcut - line = 0 + elif line[0] == '+': + oct = oct + 1 + elif line[0] == '-': + oct = oct - 1 + else: + skipping (line[0]) + line = string.lstrip (line[1:]) return (oct, name, alteration) - def parse_chord (self, line): - line = string.strip (line) + debug ('CHORD: ' + line) + line = string.lstrip (line) ch = Chord () if not line: - ch = self.current_voice ().last_chord () + #ch = self.current_voices[0].last_chord () + ch = self.last_chord.copy () else: - m = re.match ('([0-9]+)([.]*)', line) + m = re.match ('^([0-9]+)([.]*)', line) if m: ch.basic_duration = string.atoi (m.group (1)) - line = line[len (m.group (1))-1:] + line = line[len (m.group (1)):] if m.group (2): - ch.basic_duration = len (m.group (2)) - line = line[len (m.group (1))-1:] - line = string.strip (line) - m = re.match ('([0-9]+)([.]*)', line) + 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.last_chord.basic_duration + + line = string.lstrip (line) + if len (line) > 1 and line[:2] == '//': + line = 0 + #ugh + if not line: + 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: - c = line[0] - if line[:1] == 'mr': + if len (line) > 1 and line[:2] == '//': + line = 0 + break + elif line[:1] == 'mr': + ch.multimeasure = 1 + line = line[2:] + elif line[:1] == 'ms': ch.multimeasure = 1 - line = 0 - elif c in 'abcdefgrs': - pitch = parse_note (line) - ch.add_pitches (pitch) - line = 0 + line = line[2:] + elif line[0] in 'rs': + line = line[1:] + pass + elif line[0] in 'abcdefg': + 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 = line[l:] + break else: - progress ( _("skipping: %s") % line) - line = 0 - self.current_voice ().add_chord (ch) - + 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): self.parse_function = self.parse_context_header def parse_context_header (self, line): - sys.stderr.write ('header: ' + 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): - sys.stderr.write ('footer: ' + 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): - sys.stderr.write ('header2: ' + 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): - sys.stderr.write ('footer2: ' + 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): - sys.stderr.write ('score: ' + 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): - sys.stderr.write ('staff: ' + 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): - sys.stderr.write ('voice: ' + 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): - sys.stderr.write ('grids: ' + line) + debug ('grids: ' + line) + skipping (line) def init_context_music (self, line): self.parse_function = self.parse_context_music def parse_context_music (self, line): - sys.stderr.write ('music: ' + line) - m = re.match ('^([0-9]+):([0-9]*) ', line) - if m: - self.staff_idx = string.atoi (m.group (1)) - line = line[len (m.group (1)):] - if m.group (2): - self.current_staff ().voice_idx = string.atoi (m.group (2)) - 1 - line = line[len (m.group (2))-1:] - else: - self.current_staff ().voice_idx = 0 - self.parse_voice (line) + debug ('music: ' + line) + line = string.lstrip (line) + if line and line[0] in '0123456789': + line = self.parse_compound_location (line) + self.parse_voice (line) else: - progress ( _("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: - m = re.match ('^([a-z2]+)', line) + debug ('LINE: ' + `line`) + m = re.match ('^([a-z]+2?)', line) if m: word = m.group (1) if word in contexts: eval ('self.init_context_%s (line)' % word) continue + else: + warning (_ ("no such context: %s") % word) + skipping (line) else: + debug ('FUNC: ' + `self.parse_function`) 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): @@ -730,21 +1094,142 @@ class Parser: refs = '' for s in self.staffs: str = str + s.dump () - refs = '\\' + s.idstring() + refs + refs = refs + '\n \\' + s.idstring () + + str = str + ''' - str = str + "\n\n\\score { <\n %s\n > }" % refs +\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 = [ - ('', '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 verbose_p or debug_p: + progress ('SKIPPING: ' + s) (sh, long) = getopt_args (__main__.option_definitions) try: @@ -753,17 +1238,40 @@ except: help () sys.exit (2) +macros = {} +pre_processor_commands = ( + 'define', + 'else', + 'endif', + 'ifdef', + 'ifndef', + ) for opt in options: o = opt[0] a = opt[1] - if o== '--help' or o == '-h': + if 0: + 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) - if o == '--version' or o == '-v': + elif o== '--verbose' or o == '-V': + verbose_p = 1 + elif o == '--version' or o == '-v': identify () sys.exit (0) - if o == '--output' or o == '-o': + elif o == '--output' or o == '-o': output = a else: print o @@ -775,28 +1283,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: + h = None + if 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 == '-': - 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 + 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)