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