]> git.donarmstrong.com Git - lilypond.git/blob - scripts/mup2ly.py
88868a8e03f5f13f5c4468add89df1f0bf24fd45
[lilypond.git] / scripts / mup2ly.py
1 #!@PYTHON@
2 # mup2ly.py -- mup input converter
3
4 # source file of the GNU LilyPond music typesetter
5 #
6 # (c) 2001
7
8 '''
9 TODO:
10    LOTS: we get all notes out now, rest after 1.4
11
12    * lyrics (partly done)
13    * bars
14    * slurs,ties
15    * staff settings
16    * tuplets
17    * grace
18    * ornaments
19    * midi settings
20    * titling
21    * chords entry mode
22    * repeats, percent repeats
23    
24 '''
25
26 import os
27 import fnmatch
28 import stat
29 import string
30 import re
31 import getopt
32 import sys
33 import __main__
34 import operator
35 import tempfile
36
37
38 # if set, LILYPONDPREFIX must take prevalence
39 # if datadir is not set, we're doing a build and LILYPONDPREFIX 
40 datadir = '@local_lilypond_datadir@'
41 if os.environ.has_key ('LILYPONDPREFIX') \
42    or '@local_lilypond_datadir@' == '@' + 'local_lilypond_datadir' + '@':
43         datadir = os.environ['LILYPONDPREFIX']
44 else:
45         datadir = '@local_lilypond_datadir@'
46
47 sys.path.append (os.path.join (datadir, 'python'))
48 sys.path.append (os.path.join (datadir, 'python/out'))
49
50 program_name = 'mup2ly'
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
55 keep_temp_dir_p = 0
56 verbose_p = 0
57
58 localedir = '@localedir@'
59 try:
60         import gettext
61         gettext.bindtextdomain ('lilypond', localedir)
62         gettext.textdomain ('lilypond')
63         _ = gettext.gettext
64 except:
65         def _ (s):
66                 return s
67
68
69 program_name = 'mup2ly'
70 help_summary = _ ("Convert mup to LilyPond source")
71
72 option_definitions = [
73         ('', 'd', 'debug', _ ("debug")),
74         ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")),
75         ('', 'h', 'help', _ ("this help")),
76         ('FILE', 'o', 'output', _ ("write output to FILE")),
77         ('', 'E', 'pre-process', _ ("only pre-process")),
78         ('', 'V', 'verbose', _ ("verbose")),
79         ('', 'v', 'version', _ ("print version number")),
80         ('', 'w', 'warranty', _ ("show warranty and copyright")),
81         ]
82
83
84 ################################################################
85 # lilylib.py -- options and stuff
86
87 # source file of the GNU LilyPond music typesetter
88
89 # Handle bug in Python 1.6-2.1
90 #
91 # there are recursion limits for some patterns in Python 1.6 til 2.1. 
92 # fix this by importing pre instead. Fix by Mats.
93
94 # todo: should check Python version first.
95 try:
96         import pre
97         re = pre
98         del pre
99 except ImportError:
100         import re
101
102 # Attempt to fix problems with limited stack size set by Python!
103 # Sets unlimited stack size. Note that the resource module only
104 # is available on UNIX.
105 try:
106        import resource
107        resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
108 except:
109        pass
110
111 try:
112         import gettext
113         gettext.bindtextdomain ('lilypond', localedir)
114         gettext.textdomain ('lilypond')
115         _ = gettext.gettext
116 except:
117         def _ (s):
118                 return s
119
120 program_version = '@TOPLEVEL_VERSION@'
121 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
122         program_version = '1.5.54'
123
124 def identify ():
125         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
126
127 def warranty ():
128         identify ()
129         sys.stdout.write ('\n')
130         sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2002'))
131         sys.stdout.write ('\n')
132         sys.stdout.write ('  Han-Wen Nienhuys')
133         sys.stdout.write ('  Jan Nieuwenhuizen')
134         sys.stdout.write ('\n')
135         sys.stdout.write (_ (r'''
136 Distributed under terms of the GNU General Public License. It comes with
137 NO WARRANTY.'''))
138         sys.stdout.write ('\n')
139
140 def progress (s):
141         errorport.write (s + '\n')
142
143 def warning (s):
144         progress (_ ("warning: ") + s)
145
146 def user_error (s, e=1):
147         errorport.write (program_name + ":" + _ ("error: ") + s + '\n')
148         sys.exit (e)
149         
150 def error (s):
151         '''Report the error S.  Exit by raising an exception. Please
152         do not abuse by trying to catch this error. If you do not want
153         a stack trace, write to the output directly.
154
155         RETURN VALUE
156
157         None
158         
159         '''
160         
161         progress (_ ("error: ") + s)
162         raise _ ("Exiting ... ")
163
164 def getopt_args (opts):
165         '''Construct arguments (LONG, SHORT) for getopt from  list of options.'''
166         short = ''
167         long = []
168         for o in opts:
169                 if o[1]:
170                         short = short + o[1]
171                         if o[0]:
172                                 short = short + ':'
173                 if o[2]:
174                         l = o[2]
175                         if o[0]:
176                                 l = l + '='
177                         long.append (l)
178         return (short, long)
179
180 def option_help_str (o):
181         '''Transform one option description (4-tuple ) into neatly formatted string'''
182         sh = '  '       
183         if o[1]:
184                 sh = '-%s' % o[1]
185
186         sep = ' '
187         if o[1] and o[2]:
188                 sep = ','
189                 
190         long = ''
191         if o[2]:
192                 long= '--%s' % o[2]
193
194         arg = ''
195         if o[0]:
196                 if o[2]:
197                         arg = '='
198                 arg = arg + o[0]
199         return '  ' + sh + sep + long + arg
200
201
202 def options_help_str (opts):
203         '''Convert a list of options into a neatly formatted string'''
204         w = 0
205         strs =[]
206         helps = []
207
208         for o in opts:
209                 s = option_help_str (o)
210                 strs.append ((s, o[3]))
211                 if len (s) > w:
212                         w = len (s)
213
214         str = ''
215         for s in strs:
216                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
217         return str
218
219 def help ():
220         ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name),
221                 ('\n\n'),
222                 (help_summary),
223                 ('\n\n'),
224                 (_ ("Options:")),
225                 ('\n'),
226                 (options_help_str (option_definitions)),
227                 ('\n\n'),
228                 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
229                 ('\n')]
230         map (sys.stdout.write, ls)
231         
232 def setup_temp ():
233         """
234         Create a temporary directory, and return its name. 
235         """
236         global temp_dir
237         if not keep_temp_dir_p:
238                 temp_dir = tempfile.mktemp (program_name)
239         try:
240                 os.mkdir (temp_dir, 0777)
241         except OSError:
242                 pass
243
244         return temp_dir
245
246
247 def system (cmd, ignore_error = 0, quiet =0):
248         """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
249
250         RETURN VALUE
251
252         Exit status of CMD
253         """
254         
255         if verbose_p:
256                 progress (_ ("Invoking `%s\'") % cmd)
257
258         st = os.system (cmd)
259         if st:
260                 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
261                 msg = name + ': ' + _ ("command exited with value %d") % st
262                 if ignore_error:
263                         if not quiet:
264                                 warning (msg + ' ' + _ ("(ignored)") + ' ')
265                 else:
266                         error (msg)
267
268         return st
269
270
271 def cleanup_temp ():
272         if not keep_temp_dir_p:
273                 if verbose_p:
274                         progress (_ ("Cleaning %s...") % temp_dir)
275                 shutil.rmtree (temp_dir)
276
277
278 def strip_extension (f, ext):
279         (p, e) = os.path.splitext (f)
280         if e == ext:
281                 e = ''
282         return p + e
283
284
285 def cp_to_dir (pattern, dir):
286         "Copy files matching re PATTERN from cwd to DIR"
287         # Duh.  Python style portable: cp *.EXT OUTDIR
288         # system ('cp *.%s %s' % (ext, outdir), 1)
289         files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.'))
290         map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files)
291
292
293 # Python < 1.5.2 compatibility
294 #
295 # On most platforms, this is equivalent to
296 #`normpath(join(os.getcwd()), PATH)'.  *Added in Python version 1.5.2*
297 if os.path.__dict__.has_key ('abspath'):
298         abspath = os.path.abspath
299 else:
300         def abspath (path):
301                 return os.path.normpath (os.path.join (os.getcwd (), path))
302
303 if os.__dict__.has_key ('makedirs'):
304         makedirs = os.makedirs
305 else:
306         def makedirs (dir, mode=0777):
307                 system ('mkdir -p %s' % dir)
308
309
310 def mkdir_p (dir, mode=0777):
311         if not os.path.isdir (dir):
312                 makedirs (dir, mode)
313
314
315 # if set, LILYPONDPREFIX must take prevalence
316 # if datadir is not set, we're doing a build and LILYPONDPREFIX 
317 datadir = '@local_lilypond_datadir@'
318
319 if os.environ.has_key ('LILYPONDPREFIX') :
320         datadir = os.environ['LILYPONDPREFIX']
321 else:
322         datadir = '@local_lilypond_datadir@'
323
324
325 while datadir[-1] == os.sep:
326         datadir= datadir[:-1]
327
328 sys.path.insert (0, os.path.join (datadir, 'python'))
329
330 ################################################################
331 # END Library
332
333
334 output = 0
335
336 #
337 # PMX cut and paste
338 #
339
340 def encodeint (i):
341         return chr (i  + ord ('A'))
342
343         
344 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
345
346 def pitch_to_lily_string (tup):
347         (o,n,a) = tup
348
349         nm = chr((n + 2) % 7 + ord ('a'))
350         nm = nm + actab[a]
351         if o > 0:
352                 nm = nm + "'" * o
353         elif o < 0:
354                 nm = nm + "," * -o
355         return nm
356
357 def gcd (a,b):
358         if b == 0:
359                 return a
360         c = a
361         while c: 
362                 c = a % b
363                 a = b
364                 b = c
365         return a
366
367 def rat_simplify (r):
368         (n,d) = r
369         if d < 0:
370                 d = -d
371                 n = -n
372         if n == 0:
373                 return (0,1)
374         else:
375                 g = gcd (n, d)
376                 return (n/g, d/g)
377         
378 def rat_multiply (a,b):
379         (x,y) = a
380         (p,q) = b
381
382         return rat_simplify ((x*p, y*q))
383
384 def rat_divide (a,b):
385         (p,q) = b
386         return rat_multiply (a, (q,p))
387
388 tuplet_table = {
389         2: 3,
390         3: 2,
391         5: 4
392 }
393
394
395 def rat_add (a,b):
396         (x,y) = a
397         (p,q) = b
398
399         return rat_simplify ((x*q + p*y, y*q))
400
401 def rat_neg (a):
402         (p,q) = a
403         return (-p,q)
404
405
406 def rat_larger (a,b):
407         return rat_subtract (a, b )[0] > 0
408
409 def rat_subtract (a,b ):
410         return rat_add (a, rat_neg (b))
411
412 def rat_to_duration (frac):
413         log = 1
414         d = (1,1)
415         while rat_larger (d, frac):
416                 d = rat_multiply (d, (1,2))
417                 log = log << 1
418
419         frac = rat_subtract (frac, d)
420         dots = 0
421         if frac == rat_multiply (d, (1,2)):
422                 dots = 1
423         elif frac == rat_multiply (d, (3,4)):
424                 dots = 2
425         return (log, dots)      
426
427
428 class Barcheck :
429         def __init__ (self):
430                 pass
431         def dump (self):
432                 return '|\n'
433
434
435 class Meter :
436         def __init__ (self,nums):
437                 self.nums = nums
438         def dump (self):
439                 return ' %{ FIXME: meter change %} '
440                 
441 class Beam:
442         def __init__ (self, ch):
443                 self.char = ch
444         def dump (self):
445                 return self.char
446
447 class Slur:
448         def __init__ (self,id):
449                 self.id = id
450                 self.start_chord = None
451                 self.end_chord = None
452                 
453         def calculate (self):
454                 s =self.start_chord
455                 e= self.end_chord
456
457                 if e and s:
458                         s.note_suffix = s.note_suffix + '('
459                         e.note_prefix = ')' + e.note_prefix
460                 else:
461                         sys.stderr.write ("\nOrphaned slur")
462                         
463 class Voice:
464         def __init__ (self, n):
465                 self.number = n
466                 self.entries = []
467                 self.chords = []
468                 self.staff = None
469                 self.current_slurs = []
470                 self.slurs = []
471                 
472         def toggle_slur (self, id):
473                 
474                 for s in self.current_slurs:
475                         if s.id == id:
476                                 self.current_slurs.remove (s)
477                                 s.end_chord = self.chords[-1]
478                                 return
479                 s = Slur (id)
480                 s.start_chord = self.chords[-1]
481                 self.current_slurs.append (s)
482                 self.slurs.append (s)
483                 
484         def last_chord (self):
485                 if len (self.chords):
486                         return self.chords[-1]
487                 else:
488                         ch = Chord ()
489                         ch.basic_duration = 4
490                         return ch
491                 
492         def add_chord (self, ch):
493                 self.chords.append (ch)
494                 self.entries.append (ch)
495                 
496         def add_nonchord (self, nch):
497                 self.entries.append (nch)
498
499         def idstring (self):
500                 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
501         
502         def dump (self):
503                 str = ''
504                 #if not self.entries:
505                 #        #return '\n'
506                 #        #ugh ugh
507                 #        return '\n%s = {}\n\n' % self.idstring ()
508                 ln = '  '
509                 one_two = ("One", "Two")
510                 if self.staff.voices [1 - self.number].entries:
511                         ln = ln + '\\voice%s\n  ' % one_two[self.number]
512                 for e in self.entries:
513                         next = e.dump ()
514                         if next[-1] == '\n':
515                                 str  = str + ln + next + ' '
516                                 ln = '  '
517                                 continue
518                         
519                         if len (ln) +len (next) > 72:
520                                 str = str+ ln + '\n'
521                                 ln = '  '
522                         ln = ln + next + ' '
523                         
524                         
525                 str = str  + ln
526                 id = self.idstring ()
527                         
528                 str = '''%s = \\context Voice = %s \\notes {
529 %s
530 }
531
532 '''% (id, id, str)
533                 return str
534         
535         def calculate_graces (self):
536                 lastgr = 0
537                 lastc = None
538                 for c in self.chords:
539                         if c.grace and  not lastgr:
540                                 c.chord_prefix = c.chord_prefix + '\\grace { '
541                         elif not c.grace and lastgr:
542                                 lastc.chord_suffix = lastc.chord_suffix + ' } '
543                         lastgr = c.grace
544                         lastc = c
545                         
546         def calculate (self):
547                 self.calculate_graces ()
548                 for s in self.slurs:
549                         s.calculate ()
550
551 class Clef:
552         def __init__ (self, cl):
553                 self.type = cl
554                 
555         def dump (self):
556                 return '\\clef %s' % self.type
557
558 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
559 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
560
561 class Key:
562         def __init__ (self, sharps, flats):
563                 self.flats = flats
564                 self.sharps = sharps
565                 
566         def dump (self):
567                 if self.sharps and self.flats:
568                         k = '\\keysignature %s ' % 'TODO'
569                 elif self.sharps:
570                         k = '\\notes\\key %s \major' % key_sharps[self.sharps]
571                 elif self.flats:
572                         k = '\\notes\\key %s \major' % key_flats[self.flats]
573                 return k
574
575 class Time:
576         def __init__ (self, frac):
577                 self.frac = frac
578                 
579         def dump (self):
580                 return '\\time %d/%d' % (self.frac[0], self.frac[1])
581         
582
583 clef_table = {
584         'b':'bass'  ,
585         'r':'baritone',
586         'n':'tenor',
587         'a':'alto',
588         'm':'mezzosoprano',
589         's':'soprano',
590         't':'treble',
591         'f':'frenchviolin',
592         }
593
594 class Staff:
595         def __init__ (self, n):
596                 # ugh
597                 self.voices = (Voice (0), Voice (1))
598                 
599                 self.clef = None
600                 self.time = None
601                 self.key = None
602                 self.instrument = 0
603                 self.number = n
604                 
605                 i = 0
606                 for v in self.voices:
607                         v.staff = self
608                         v.number = i
609                         i = i+1
610                         
611         #def set_clef (self, letter):
612         #       clstr = clef_table[letter]
613         #       self.voices[0].add_nonchord (Clef (clstr))
614                 
615         def calculate (self):
616                 for v in self.voices:
617                         v.calculate ()
618                         
619         def idstring (self):
620                 return 'staff%s' % encodeint (self.number)
621
622         def dump (self):
623                 str = ''
624
625                 refs = ''
626                 for v in self.voices:
627                         if v.entries:
628                                 # urg
629                                 if v == self.voices[0]:
630                                         if self.clef:
631                                                 refs = refs + self.clef.dump ()
632                                         if self.time:
633                                                 refs = refs + self.time.dump ()
634                                         if self.key:
635                                                 refs = refs + self.key.dump ()
636                                         if refs:
637                                                 refs = '\n  ' + refs
638                                 str = str + v.dump()
639                                 refs = refs + '\n  \\' + v.idstring ()
640                 str = str + '''
641 %s = \context Staff = %s <%s
642 >
643
644 ''' % (self.idstring (), self.idstring (), refs)
645                 return str
646
647 class Tuplet:
648         def __init__ (self, number, base, dots):
649                 self.chords = []
650                 self.number = number
651                 self.replaces = tuplet_table[number]
652                 self.base = base
653                 self.dots = dots
654                 
655                 length = (1,base)
656                 if dots == 1:
657                         length = rat_multiply (length, (3,2))
658                 elif dots == 2:
659                         length = rat_multiply (length, (7,4))
660
661                 length = rat_multiply (length, (1,self.replaces))
662
663                 (nb,nd) =rat_to_duration (length)
664
665                 self.note_base = nb
666                 self.note_dots = nd
667
668         def add_chord (self, ch):
669                 ch.dots = self.note_dots
670                 ch.basic_duration = self.note_base
671                 self.chords.append (ch)
672
673                 if len (self.chords) == 1:
674                         ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
675                 elif len (self.chords) == self.number:
676                         ch.chord_suffix = ' }' 
677                 
678 class Chord:
679         def __init__ (self):
680                 self.pitches = []
681                 self.multimeasure = 0
682                 self.dots = 0
683                 self.basic_duration = 0
684                 self.scripts = []
685                 self.grace = 0
686                 self.chord_prefix = ''
687                 self.chord_suffix = ''
688                 self.note_prefix = ''
689                 self.note_suffix = ''
690
691         # maybe use import copy?
692         def copy (self):
693                 ch = Chord ()
694                 #for i in self.pitches:
695                 #       ch.pitches.append (i)
696                 ch.pitches = self.pitches[:]
697                 ch.multimeasure = self.multimeasure
698                 ch.dots = self.dots
699                 ch.basic_duration = self.basic_duration
700                 #for i in self.scripts:
701                 #       ch.scripts.append (i)
702                 ch.scripts = self.scripts[:]
703                 ch.grace = self.grace
704
705                 ch.chord_prefix = self.chord_prefix
706                 ch.chord_suffix = self.chord_suffix
707                 ch.note_prefix = self.note_prefix
708                 ch.note_suffix = self.note_suffix
709                 return ch
710
711                 
712         def dump (self):
713                 str = ''
714
715                 sd = ''
716                 if self.basic_duration == 0.5:
717                         sd = '\\breve'
718                 else:
719                         sd = '%d' % self.basic_duration
720                 sd = sd + '.' * self.dots 
721                 for p in self.pitches:
722                         if str:
723                                 str = str + ' ' 
724                         str = str + pitch_to_lily_string (p) + sd
725
726                 for s in self.scripts:
727                         str = str + '-' + s
728
729                 str = self.note_prefix +str  + self.note_suffix
730                 
731                 if len (self.pitches) > 1:
732                         str = '<%s>' % str
733                 elif self.multimeasure:
734                         str = 'R' + sd
735                 elif len (self.pitches) == 0:
736                         str = 'r' + sd
737
738                 str = self.chord_prefix + str + self.chord_suffix
739                 
740                 return str
741                 
742 SPACE=' \t\n'
743 DIGITS ='0123456789'
744 basicdur_table = {
745         9: 0.5,
746         0: 0 ,
747         2: 2 ,
748         4: 4 ,
749         8: 8 ,
750         1: 16,
751         3: 32,
752         6: 64
753         }
754
755
756 ornament_table = {
757         't': '\\prall',
758         'm': '\\mordent',
759         'x': '"x"',
760         '+': '+',
761         'u': '"pizz"',
762         'p': '|',
763         '(': '"paren"',
764         ')': '"paren"',
765         'g': '"segno"',
766         '.': '.',
767         'fd': '\\fermata',
768         'f': '\\fermata',
769         '_': '-',
770         'T': '\\trill',
771         '>': '>',
772         '^': '^',
773         }
774
775 # http://www.arkkra.com/doc/uguide/contexts.html
776
777 contexts = (
778         'header', 
779         'footer', 
780         'header2', 
781         'footer2', 
782         'score', 
783         'staff',
784         'voice',
785         'grids', 
786         'music',
787         )
788
789 class Parser:
790         def __init__ (self, lines):
791                 self.parse_function = self.parse_context_music
792                 self.staffs = []
793                 self.current_voices = []
794                 self.forced_duration = None
795                 self.last_name = 0
796                 self.last_oct = 0               
797                 self.tuplets_expected = 0
798                 self.tuplets = []
799                 self.clef = None
800                 self.time = None
801                 self.key = None
802                 
803                 self.parse (lines)
804                 
805         def parse_compound_location (self, line):
806                 colon = string.index (line, ':')
807                 s = line[:colon]
808                 debug (s)
809                 line = line[colon + 1:]
810                 debug (line)
811                 self.current_voices = []
812                 ##self.current_staffs = []
813                 map (self.parse_location, string.split (s, '&'))
814                 return line
815
816         def parse_location (self, line):
817                 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
818                 
819                 def range_list_to_idxs (s):
820                         
821                         # duh
822                         def flatten (l):
823                                 f = []
824                                 for i in l:
825                                         for j in i:
826                                                 f.append (j)
827                                 return f
828                                          
829                         def range_to_list (s):
830                                 if string.find (s, '-') >= 0:
831                                         debug ('s: ' + s)
832                                         l = map (string.lstrip,
833                                                  string.split (s, '-'))
834                                         r = range (string.atoi (l[0]) - 1,
835                                                    string.atoi (l[1]))
836                                 else:
837                                         r = (string.atoi (s) - 1,)
838                                 return r
839                         
840                         ranges = string.split (s, ',')
841                         l = flatten (map (range_to_list, ranges))
842                         l.sort ()
843                         return l
844                 
845                 staff_idxs = range_list_to_idxs (m.group (1))
846                 if m.group (2):
847                         voice_idxs = range_list_to_idxs (m.group (2))
848                 else:
849                         voice_idxs = [0]
850                 for s in staff_idxs:
851                         while s > len (self.staffs) - 1:
852                                 self.staffs.append (Staff (s))
853                         for v in voice_idxs:
854                                 self.current_voices.append (self.staffs[s].voices[v])
855                         
856         def parse_note (self, line):
857                 # FIXME: 1?
858                 oct = 1
859                 name = (ord (line[0]) - ord ('a') + 5) % 7
860                 # FIXME: does key play any role in this?
861                 alteration = 0
862                 debug ('NOTE: ' + `line`)
863                 line = string.lstrip (line[1:])
864                 while line:
865                         if len (line) > 1 and line[:2] == '//':
866                                 line = 0
867                                 break
868                         elif line[0] == '#':
869                                 alteration = alteration + 1
870                         elif line[0] == '&':
871                                 alteration = alteration - 1
872                         elif line[0] == '+':
873                                 oct = oct + 1 
874                         elif line[0] == '-':
875                                 oct = oct - 1
876                         else:
877                                 skipping (line[0])
878                         line = string.lstrip (line[1:])
879                 return (oct, name, alteration)
880                         
881         def parse_chord (self, line):
882                 debug ('CHORD: ' + line)
883                 line = string.lstrip (line)
884                 ch = Chord ()
885                 if not line:
886                         #ch = self.current_voices[0].last_chord ()
887                         ch = self.last_chord.copy ()
888                 else:
889                         m = re.match ('^([0-9]+)([.]*)', line)
890                         if m:
891                                 ch.basic_duration = string.atoi (m.group (1))
892                                 line = line[len (m.group (1)):]
893                                 if m.group (2):
894                                         ch.dots = len (m.group (2))
895                                         line = line[len (m.group (2)):]
896                         else:
897                                 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
898                                 ch.basic_duration = self.last_chord.basic_duration
899                                 
900                         line = string.lstrip (line)
901                         if len (line) > 1 and line[:2] == '//':
902                                 line = 0
903                         #ugh
904                         if not line:
905                                 debug ('nline: ' + line)
906                                 #ch = self.current_voices[0].last_chord ()
907                                 n = self.last_chord.copy ()
908                                 n.basic_duration = ch.basic_duration
909                                 n.dots = ch.dots
910                                 ch = n
911                                 debug ('ch.pitsen:' + `ch.pitches`)
912                                 debug ('ch.dur:' + `ch.basic_duration`)
913                         else:
914                                 debug ('eline: ' + line)
915                                 
916                         while line:
917                                 if len (line) > 1 and line[:2] == '//':
918                                         line = 0
919                                         break
920                                 elif line[:1] == 'mr':
921                                         ch.multimeasure = 1
922                                         line = line[2:]
923                                 elif line[:1] == 'ms':
924                                         ch.multimeasure = 1
925                                         line = line[2:]
926                                 elif line[0] in 'rs':
927                                         line = line[1:]
928                                         pass
929                                 elif line[0] in 'abcdefg':
930                                         m = re.match ('([a-g][-#&+]*)', line)
931                                         l = len (m.group (1))
932                                         pitch = self.parse_note (line[:l])
933                                         debug ('PITCH: ' + `pitch`)
934                                         ch.pitches.append (pitch)
935                                         line = line[l:]
936                                         break
937                                 else:
938                                         skipping (line[0])
939                                         line = line[1:]
940                                 line = string.lstrip (line)
941                 debug ('CUR-VOICES: ' + `self.current_voices`)
942                 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
943                 self.last_chord = ch
944
945         def parse_lyrics_location (self, line):
946                 line = line.lstrip (line)
947                 addition = 0
948                 m = re.match ('^(between[ \t]+)', line)
949                 if m:
950                         line = line[len (m.group (1)):]
951                         addition = 0.5
952                 else:
953                         m = re.match ('^(above [ \t]+)', line)
954                         if m:
955                                 line = line[len (m.group (1)):]
956                                 addition = -0.5
957                         else:
958                                 addlyrics = 1
959                 
960         def parse_voice (self, line):
961                 line = string.lstrip (line)
962                 # `;' is not a separator, chords end with ';'
963                 chords = string.split (line, ';')[:-1]
964                 # mup resets default duration and pitch each bar
965                 self.last_chord = Chord ()
966                 self.last_chord.basic_duration = 4
967                 map (self.parse_chord, chords)
968
969         def init_context_header (self, line):
970                 self.parse_function = self.parse_context_header
971                                         
972         def parse_context_header (self, line):
973                 debug ('header: ' + line)
974                 skipping (line)
975                 
976         def init_context_footer (self, line):
977                 self.parse_function = self.parse_context_footer
978
979         def parse_context_footer (self, line):
980                 debug ('footer: ' + line)
981                 skipping (line)
982
983         def init_context_header2 (self, line):
984                 self.parse_function = self.parse_context_header2
985
986         def parse_context_header2 (self, line):
987                 debug ('header2: ' + line)
988                 skipping (line)
989
990         def init_context_footer2 (self, line):
991                 self.parse_function = self.parse_context_footer2
992
993         def parse_context_footer2 (self, line):
994                 debug ('footer2: ' + line)
995                 skipping (line)
996
997         def init_context_score (self, line):
998                 self.parse_function = self.parse_context_score
999
1000         def parse_context_score (self, line):
1001                 debug ('score: ' + line)
1002                 line = string.lstrip (line)
1003                 # ugh: these (and lots more) should also be parsed in
1004                 # context staff.  we should have a class Staff_properties
1005                 # and parse/set all those.
1006                 m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line)
1007                 if m:
1008                         line = line[len (m.group (1)):]
1009                         self.time = Time ((string.atoi (m.group (2)),
1010                                            string.atoi (m.group (3))))
1011
1012                 m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line)
1013                 if m:
1014                         line = line[len (m.group (1)):]
1015                         n = string.atoi (m.group (2))
1016                         if m.group (3) == '#':
1017                                 self.key = Key (n, 0)
1018                         else:
1019                                 self.key = Key (0, n)
1020                 skipping (line)
1021
1022         def init_context_staff (self, line):
1023                 self.parse_function = self.parse_context_staff
1024
1025         def parse_context_staff (self, line):
1026                 debug ('staff: ' + line)
1027                 skipping (line)
1028
1029         def init_context_voice (self, line):
1030                 self.parse_function = self.parse_context_voice
1031
1032         def parse_context_voice (self, line):
1033                 debug ('voice: ' + line)
1034                 skipping (line)
1035
1036         def init_context_grids (self, line):
1037                 self.parse_function = self.parse_context_grids
1038
1039         def parse_context_grids (self, line):
1040                 debug ('grids: ' + line)
1041                 skipping (line)
1042
1043         def init_context_music (self, line):
1044                 self.parse_function = self.parse_context_music
1045
1046         def parse_context_music (self, line):
1047                 debug ('music: ' + line)
1048                 line = string.lstrip (line)
1049                 if line and line[0] in '0123456789':
1050                         line = self.parse_compound_location (line)
1051                         self.parse_voice (line)
1052                 else:
1053                         m = re.match ('^(TODOlyrics[ \t]+)', line)
1054                         if m:
1055                                 line = line[len (m.group (1)):]
1056                                 self.parse_lyrics_location (line[7:])
1057                                 self.parse_lyrics (line)
1058                         else:
1059                                 skipping (line)
1060
1061         def parse (self, lines):
1062                 # shortcut: set to official mup maximum (duh)
1063                 # self.set_staffs (40)
1064                 for line in lines:
1065                         debug ('LINE: ' + `line`)
1066                         m = re.match ('^([a-z]+2?)', line)
1067                         
1068                         if m:
1069                                 word = m.group (1)
1070                                 if word in contexts:
1071                                         eval ('self.init_context_%s (line)' % word)
1072                                         continue
1073                                 else:
1074                                         warning (_ ("no such context: %s") % word)
1075                                         skipping (line)
1076                         else:
1077                                 debug ('FUNC: ' + `self.parse_function`)
1078                                 self.parse_function (line)
1079                                 
1080                 for c in self.staffs:
1081                         # hmm
1082                         if not c.clef and self.clef:
1083                                 c.clef = self.clef
1084                         if not c.time and self.time:
1085                                 c.time = self.time
1086                         if not c.key and self.key:
1087                                 c.key = self.key
1088                         c.calculate ()
1089
1090         def dump (self):
1091                 str = ''
1092
1093                 refs = ''
1094                 for s in self.staffs:
1095                         str = str +  s.dump ()
1096                         refs = refs + '\n    \\' + s.idstring ()
1097
1098                 str = str + '''
1099
1100 \score {
1101   <%s
1102   >
1103   \paper {}
1104   \midi {}
1105 }
1106 ''' % refs 
1107                 return str
1108
1109
1110 class Pre_processor:
1111         def __init__ (self, raw_lines):
1112                 self.lines = []
1113                 self.active = [1]
1114                 self.process_function = self.process_line
1115                 self.macro_name = ''
1116                 self.macro_body = ''
1117                 self.process (raw_lines)
1118
1119         def process_line (self, line):
1120                 global macros
1121                 m = re.match ('^([ \t]*([a-zA-Z]+))', line)
1122                 s = line
1123                 if m:
1124                         word = m.group (2)
1125                         debug ('MACRO?: ' + `word`)
1126                         if word in pre_processor_commands:
1127                                 line = line[len (m.group (1)):]
1128                                 eval ('self.process_macro_%s (line)' % word)
1129                                 s = ''
1130                         else:
1131                                 if macros.has_key (word):
1132                                         s = macros[word] + line[len (m.group (1)):]
1133                 if not self.active [-1]:
1134                         s = ''
1135                 return s
1136
1137         def process_macro_body (self, line):
1138                 global macros
1139                 # dig this: mup allows ifdefs inside macro bodies
1140                 s = self.process_line (line)
1141                 m = re.match ('(.*[^\\\\])(@)(.*)', s)
1142                 if m:
1143                         self.macro_body = self.macro_body + '\n' + m.group (1)
1144                         macros[self.macro_name] = self.macro_body
1145                         debug ('MACROS: ' + `macros`)
1146                         # don't do nested multi-line defines
1147                         self.process_function = self.process_line
1148                         if m.group (3):
1149                                 s = m.group (3)
1150                         else:
1151                                 s = ''
1152                 else:
1153                         self.macro_body = self.macro_body + '\n' + s
1154                         s = ''
1155                 return s
1156
1157         # duh: mup is strictly line-based, except for `define',
1158         # which is `@' terminated and may span several lines
1159         def process_macro_define (self, line):
1160                 global macros
1161                 # don't define new macros in unactive areas
1162                 if not self.active[-1]:
1163                         return
1164                 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line)
1165                 n = m.group (1)
1166                 if m.group (5):
1167                         if m.group (2):
1168                                 e = m.group (2)
1169                         else:
1170                                 e = ''
1171                         macros[n] = e
1172                         debug ('MACROS: ' + `macros`)
1173                 else:
1174                         # To support nested multi-line define's
1175                         # process_function and macro_name, macro_body
1176                         # should become lists (stacks)
1177                         # The mup manual is undetermined on this
1178                         # and I haven't seen examples doing it.
1179                         #
1180                         # don't do nested multi-line define's
1181                         if m.group (2):
1182                                 self.macro_body = m.group (2)
1183                         else:
1184                                 self.macro_body = ''
1185                         self.macro_name = n
1186                         self.process_function = self.process_macro_body
1187                 
1188         def process_macro_ifdef (self, line):
1189                 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1190                 if m:
1191                         
1192                         active = self.active[-1] and macros.has_key (m.group (1))
1193                         debug ('ACTIVE: %d' % active)
1194                         self.active.append (active)
1195
1196         def process_macro_ifndef (self, line):
1197                 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
1198                 if m:
1199                         active = self.active[-1] and not macros.has_key (m.group (1))
1200                         self.active.append (active)
1201
1202         def process_macro_else (self, line):
1203                 debug ('ELSE')
1204                 self.active[-1] = not self.active[-1]
1205                 
1206         def process_macro_endif (self, line):
1207                 self.active = self.active[:-1]
1208                         
1209         def process (self, raw_lines):
1210                 s = ''
1211                 for line in raw_lines:
1212                         ls = string.split (self.process_function (line), '\n')
1213                         for i in ls:
1214                                 if i:
1215                                         s = s + string.rstrip (i)
1216                                         if s and s[-1] == '\\':
1217                                                 s = string.rstrip (s[:-1])
1218                                         elif s:
1219                                                 self.lines.append (s)
1220                                                 s = ''
1221
1222
1223 debug_p = 0
1224 only_pre_process_p = 0
1225 def debug (s):
1226         if debug_p:
1227                 progress ('DEBUG: ' + s)
1228
1229 def skipping (s):
1230         if verbose_p or debug_p:
1231                 progress ('SKIPPING: ' + s)
1232
1233 (sh, long) = getopt_args (__main__.option_definitions)
1234 try:
1235         (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1236 except:
1237         help ()
1238         sys.exit (2)
1239
1240 macros = {}
1241 pre_processor_commands = (
1242         'define',
1243         'else',
1244         'endif',
1245         'ifdef',
1246         'ifndef',
1247         )
1248
1249 for opt in options:
1250         o = opt[0]
1251         a = opt[1]
1252         if 0:
1253                 pass
1254         elif o== '--debug' or o == '-d':
1255                 debug_p = 1
1256         elif o== '--define' or o == '-D':
1257                 if string.find (a, '=') >= 0:
1258                         (n, e) = string.split (a, '=')
1259                 else:
1260                         n = a
1261                         e = ''
1262                 macros[n] = e
1263         elif o== '--pre-process' or o == '-E':
1264                 only_pre_process_p = 1
1265         elif o== '--help' or o == '-h':
1266                 help ()
1267                 sys.exit (0)
1268         elif o== '--verbose' or o == '-V':
1269                 verbose_p = 1
1270         elif o == '--version' or o == '-v':
1271                 identify ()
1272                 sys.exit (0)
1273         elif o == '--output' or o == '-o':
1274                 output = a
1275         else:
1276                 print o
1277                 raise getopt.error
1278
1279 # writes to stdout for help2man
1280 # don't call 
1281 # identify ()
1282 # sys.stdout.flush ()
1283
1284 # handy emacs testing
1285 # if not files:
1286 #       files = ['template.mup']
1287
1288 if not files:
1289         files = ['-']
1290         
1291 for f in files:
1292
1293         if f == '-':
1294                 h = sys.stdin
1295         elif f and not os.path.isfile (f):
1296                 f = strip_extension (f, '.mup') + '.mup'
1297                 h = open (f)
1298         progress ( _("Processing `%s'..." % f))
1299         raw_lines = h.readlines ()
1300         p = Pre_processor (raw_lines)
1301         if only_pre_process_p:
1302                 if not output:
1303                         output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f))
1304         else:
1305                 e = Parser (p.lines)
1306                 if not output:
1307                         output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
1308                 if output == f:
1309                         output = os.path.basename (f + '.ly')
1310                         
1311         if f == '-':
1312                 output = '-'
1313                 out_h = sys.stdout
1314         else:
1315                 out_h = open (output, 'w')
1316
1317         progress (_ ("Writing `%s'...") % output)
1318
1319         tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1320         if only_pre_process_p:
1321                 # duh
1322                 ly = string.join (p.lines, '\n')
1323         else:
1324                 ly = tag + '\n\n' + e.dump ()
1325
1326         out_h.write (ly)
1327         out_h.close ()
1328         if debug_p:
1329                 print (ly)
1330