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