]> git.donarmstrong.com Git - lilypond.git/blob - scripts/mup2ly.py
release: 1.5.29
[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 = '@datadir@'
41 if os.environ.has_key ('LILYPONDPREFIX') \
42    or '@datadir@' == '@' + 'datadir' + '@':
43         datadir = os.environ['LILYPONDPREFIX']
44 else:
45         datadir = '@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 = 'ly2dvi'
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 try:
59         import gettext
60         gettext.bindtextdomain ('lilypond', '@localedir@')
61         gettext.textdomain ('lilypond')
62         _ = gettext.gettext
63 except:
64         def _ (s):
65                 return s
66
67
68 program_name = 'mup2ly'
69 help_summary = _ ("Convert mup to LilyPond source")
70
71 option_definitions = [
72         ('', 'd', 'debug', _ ("debug")),
73         ('NAME[=EXP]', 'D', 'define', _ ("define macro NAME [optional expansion EXP]")),
74         ('', 'h', 'help', _ ("this help")),
75         ('FILE', 'o', 'output', _ ("write output to FILE")),
76         ('', 'E', 'pre-process', _ ("only pre-process")),
77         ('', 'V', 'verbose', _ ("verbose")),
78         ('', 'v', 'version', _ ("print version number")),
79         ('', 'w', 'warranty', _ ("show warranty and copyright")),
80         ]
81
82
83 from lilylib import *
84
85
86
87 output = 0
88
89 #
90 # PMX cut and paste
91 #
92
93 def encodeint (i):
94         return chr (i  + ord ('A'))
95
96         
97 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
98
99 def pitch_to_lily_string (tup):
100         (o,n,a) = tup
101
102         nm = chr((n + 2) % 7 + ord ('a'))
103         nm = nm + actab[a]
104         if o > 0:
105                 nm = nm + "'" * o
106         elif o < 0:
107                 nm = nm + "," * -o
108         return nm
109
110 def gcd (a,b):
111         if b == 0:
112                 return a
113         c = a
114         while c: 
115                 c = a % b
116                 a = b
117                 b = c
118         return a
119
120 def rat_simplify (r):
121         (n,d) = r
122         if d < 0:
123                 d = -d
124                 n = -n
125         if n == 0:
126                 return (0,1)
127         else:
128                 g = gcd (n, d)
129                 return (n/g, d/g)
130         
131 def rat_multiply (a,b):
132         (x,y) = a
133         (p,q) = b
134
135         return rat_simplify ((x*p, y*q))
136
137 def rat_divide (a,b):
138         (p,q) = b
139         return rat_multiply (a, (q,p))
140
141 tuplet_table = {
142         2: 3,
143         3: 2,
144         5: 4
145 }
146
147
148 def rat_add (a,b):
149         (x,y) = a
150         (p,q) = b
151
152         return rat_simplify ((x*q + p*y, y*q))
153
154 def rat_neg (a):
155         (p,q) = a
156         return (-p,q)
157
158
159 def rat_larger (a,b):
160         return rat_subtract (a, b )[0] > 0
161
162 def rat_subtract (a,b ):
163         return rat_add (a, rat_neg (b))
164
165 def rat_to_duration (frac):
166         log = 1
167         d = (1,1)
168         while rat_larger (d, frac):
169                 d = rat_multiply (d, (1,2))
170                 log = log << 1
171
172         frac = rat_subtract (frac, d)
173         dots = 0
174         if frac == rat_multiply (d, (1,2)):
175                 dots = 1
176         elif frac == rat_multiply (d, (3,4)):
177                 dots = 2
178         return (log, dots)      
179
180
181 class Barcheck :
182         def __init__ (self):
183                 pass
184         def dump (self):
185                 return '|\n'
186
187
188 class Meter :
189         def __init__ (self,nums):
190                 self.nums = nums
191         def dump (self):
192                 return ' %{ FIXME: meter change %} '
193                 
194 class Beam:
195         def __init__ (self, ch):
196                 self.char = ch
197         def dump (self):
198                 return self.char
199
200 class Slur:
201         def __init__ (self,id):
202                 self.id = id
203                 self.start_chord = None
204                 self.end_chord = None
205                 
206         def calculate (self):
207                 s =self.start_chord
208                 e= self.end_chord
209
210                 if e and s:
211                         s.note_suffix = s.note_suffix + '('
212                         e.note_prefix = ')' + e.note_prefix
213                 else:
214                         sys.stderr.write ("\nOrphaned slur")
215                         
216 class Voice:
217         def __init__ (self, n):
218                 self.number = n
219                 self.entries = []
220                 self.chords = []
221                 self.staff = None
222                 self.current_slurs = []
223                 self.slurs = []
224                 
225         def toggle_slur (self, id):
226                 
227                 for s in self.current_slurs:
228                         if s.id == id:
229                                 self.current_slurs.remove (s)
230                                 s.end_chord = self.chords[-1]
231                                 return
232                 s = Slur (id)
233                 s.start_chord = self.chords[-1]
234                 self.current_slurs.append (s)
235                 self.slurs.append (s)
236                 
237         def last_chord (self):
238                 if len (self.chords):
239                         return self.chords[-1]
240                 else:
241                         ch = Chord ()
242                         ch.basic_duration = 4
243                         return ch
244                 
245         def add_chord (self, ch):
246                 self.chords.append (ch)
247                 self.entries.append (ch)
248                 
249         def add_nonchord (self, nch):
250                 self.entries.append (nch)
251
252         def idstring (self):
253                 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
254         
255         def dump (self):
256                 str = ''
257                 #if not self.entries:
258                 #        #return '\n'
259                 #        #ugh ugh
260                 #        return '\n%s = {}\n\n' % self.idstring ()
261                 ln = '  '
262                 one_two = ("One", "Two")
263                 if self.staff.voices [1 - self.number].entries:
264                         ln = ln + '\\voice%s\n  ' % one_two[self.number]
265                 for e in self.entries:
266                         next = e.dump ()
267                         if next[-1] == '\n':
268                                 str  = str + ln + next + ' '
269                                 ln = '  '
270                                 continue
271                         
272                         if len (ln) +len (next) > 72:
273                                 str = str+ ln + '\n'
274                                 ln = '  '
275                         ln = ln + next + ' '
276                         
277                         
278                 str = str  + ln
279                 id = self.idstring ()
280                         
281                 str = '''%s = \\context Voice = %s \\notes {
282 %s
283 }
284
285 '''% (id, id, str)
286                 return str
287         
288         def calculate_graces (self):
289                 lastgr = 0
290                 lastc = None
291                 for c in self.chords:
292                         if c.grace and  not lastgr:
293                                 c.chord_prefix = c.chord_prefix + '\\grace { '
294                         elif not c.grace and lastgr:
295                                 lastc.chord_suffix = lastc.chord_suffix + ' } '
296                         lastgr = c.grace
297                         lastc = c
298                         
299         def calculate (self):
300                 self.calculate_graces ()
301                 for s in self.slurs:
302                         s.calculate ()
303
304 class Clef:
305         def __init__ (self, cl):
306                 self.type = cl
307                 
308         def dump (self):
309                 return '\\clef %s' % self.type
310
311 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
312 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
313
314 class Key:
315         def __init__ (self, sharps, flats):
316                 self.flats = flats
317                 self.sharps = sharps
318                 
319         def dump (self):
320                 if self.sharps and self.flats:
321                         k = '\\keysignature %s ' % 'TODO'
322                 elif self.sharps:
323                         k = '\\notes\\key %s \major' % key_sharps[self.sharps]
324                 elif self.flats:
325                         k = '\\notes\\key %s \major' % key_flats[self.flats]
326                 return k
327
328 class Time:
329         def __init__ (self, frac):
330                 self.frac = frac
331                 
332         def dump (self):
333                 return '\\time %d/%d' % (self.frac[0], self.frac[1])
334         
335
336 clef_table = {
337         'b':'bass'  ,
338         'r':'baritone',
339         'n':'tenor',
340         'a':'alto',
341         'm':'mezzosoprano',
342         's':'soprano',
343         't':'treble',
344         'f':'frenchviolin',
345         }
346
347 class Staff:
348         def __init__ (self, n):
349                 # ugh
350                 self.voices = (Voice (0), Voice (1))
351                 
352                 self.clef = None
353                 self.time = None
354                 self.key = None
355                 self.instrument = 0
356                 self.number = n
357                 
358                 i = 0
359                 for v in self.voices:
360                         v.staff = self
361                         v.number = i
362                         i = i+1
363                         
364         #def set_clef (self, letter):
365         #       clstr = clef_table[letter]
366         #       self.voices[0].add_nonchord (Clef (clstr))
367                 
368         def calculate (self):
369                 for v in self.voices:
370                         v.calculate ()
371                         
372         def idstring (self):
373                 return 'staff%s' % encodeint (self.number)
374
375         def dump (self):
376                 str = ''
377
378                 refs = ''
379                 for v in self.voices:
380                         if v.entries:
381                                 # urg
382                                 if v == self.voices[0]:
383                                         if self.clef:
384                                                 refs = refs + self.clef.dump ()
385                                         if self.time:
386                                                 refs = refs + self.time.dump ()
387                                         if self.key:
388                                                 refs = refs + self.key.dump ()
389                                         if refs:
390                                                 refs = '\n  ' + refs
391                                 str = str + v.dump()
392                                 refs = refs + '\n  \\' + v.idstring ()
393                 str = str + '''
394 %s = \context Staff = %s <%s
395 >
396
397 ''' % (self.idstring (), self.idstring (), refs)
398                 return str
399
400 class Tuplet:
401         def __init__ (self, number, base, dots):
402                 self.chords = []
403                 self.number = number
404                 self.replaces = tuplet_table[number]
405                 self.base = base
406                 self.dots = dots
407                 
408                 length = (1,base)
409                 if dots == 1:
410                         length = rat_multiply (length, (3,2))
411                 elif dots == 2:
412                         length = rat_multiply (length, (7,4))
413
414                 length = rat_multiply (length, (1,self.replaces))
415
416                 (nb,nd) =rat_to_duration (length)
417
418                 self.note_base = nb
419                 self.note_dots = nd
420
421         def add_chord (self, ch):
422                 ch.dots = self.note_dots
423                 ch.basic_duration = self.note_base
424                 self.chords.append (ch)
425
426                 if len (self.chords) == 1:
427                         ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
428                 elif len (self.chords) == self.number:
429                         ch.chord_suffix = ' }' 
430                 
431 class Chord:
432         def __init__ (self):
433                 self.pitches = []
434                 self.multimeasure = 0
435                 self.dots = 0
436                 self.basic_duration = 0
437                 self.scripts = []
438                 self.grace = 0
439                 self.chord_prefix = ''
440                 self.chord_suffix = ''
441                 self.note_prefix = ''
442                 self.note_suffix = ''
443
444         # maybe use import copy?
445         def copy (self):
446                 ch = Chord ()
447                 #for i in self.pitches:
448                 #       ch.pitches.append (i)
449                 ch.pitches = self.pitches[:]
450                 ch.multimeasure = self.multimeasure
451                 ch.dots = self.dots
452                 ch.basic_duration = self.basic_duration
453                 #for i in self.scripts:
454                 #       ch.scripts.append (i)
455                 ch.scripts = self.scripts[:]
456                 ch.grace = self.grace
457
458                 ch.chord_prefix = self.chord_prefix
459                 ch.chord_suffix = self.chord_suffix
460                 ch.note_prefix = self.note_prefix
461                 ch.note_suffix = self.note_suffix
462                 return ch
463
464                 
465         def dump (self):
466                 str = ''
467
468                 sd = ''
469                 if self.basic_duration == 0.5:
470                         sd = '\\breve'
471                 else:
472                         sd = '%d' % self.basic_duration
473                 sd = sd + '.' * self.dots 
474                 for p in self.pitches:
475                         if str:
476                                 str = str + ' ' 
477                         str = str + pitch_to_lily_string (p) + sd
478
479                 for s in self.scripts:
480                         str = str + '-' + s
481
482                 str = self.note_prefix +str  + self.note_suffix
483                 
484                 if len (self.pitches) > 1:
485                         str = '<%s>' % str
486                 elif self.multimeasure:
487                         str = 'R' + sd
488                 elif len (self.pitches) == 0:
489                         str = 'r' + sd
490
491                 str = self.chord_prefix + str + self.chord_suffix
492                 
493                 return str
494                 
495 SPACE=' \t\n'
496 DIGITS ='0123456789'
497 basicdur_table = {
498         9: 0.5,
499         0: 0 ,
500         2: 2 ,
501         4: 4 ,
502         8: 8 ,
503         1: 16,
504         3: 32,
505         6: 64
506         }
507
508
509 ornament_table = {
510         't': '\\prall',
511         'm': '\\mordent',
512         'x': '"x"',
513         '+': '+',
514         'u': '"pizz"',
515         'p': '|',
516         '(': '"paren"',
517         ')': '"paren"',
518         'g': '"segno"',
519         '.': '.',
520         'fd': '\\fermata',
521         'f': '\\fermata',
522         '_': '-',
523         'T': '\\trill',
524         '>': '>',
525         '^': '^',
526         }
527
528 # http://www.arkkra.com/doc/uguide/contexts.html
529
530 contexts = (
531         'header', 
532         'footer', 
533         'header2', 
534         'footer2', 
535         'score', 
536         'staff',
537         'voice',
538         'grids', 
539         'music',
540         )
541
542 class Parser:
543         def __init__ (self, lines):
544                 self.parse_function = self.parse_context_music
545                 self.staffs = []
546                 self.current_voices = []
547                 self.forced_duration = None
548                 self.last_name = 0
549                 self.last_oct = 0               
550                 self.tuplets_expected = 0
551                 self.tuplets = []
552                 self.clef = None
553                 self.time = None
554                 self.key = None
555                 
556                 self.parse (lines)
557                 
558         def parse_compound_location (self, line):
559                 colon = string.index (line, ':')
560                 s = line[:colon]
561                 debug (s)
562                 line = line[colon + 1:]
563                 debug (line)
564                 self.current_voices = []
565                 ##self.current_staffs = []
566                 map (self.parse_location, string.split (s, '&'))
567                 return line
568
569         def parse_location (self, line):
570                 m = re.match ('^([-,0-9]+) *([-,0-9]*)', string.lstrip (line))
571                 
572                 def range_list_to_idxs (s):
573                         
574                         # duh
575                         def flatten (l):
576                                 f = []
577                                 for i in l:
578                                         for j in i:
579                                                 f.append (j)
580                                 return f
581                                          
582                         def range_to_list (s):
583                                 if string.find (s, '-') >= 0:
584                                         debug ('s: ' + s)
585                                         l = map (string.lstrip,
586                                                  string.split (s, '-'))
587                                         r = range (string.atoi (l[0]) - 1,
588                                                    string.atoi (l[1]))
589                                 else:
590                                         r = (string.atoi (s) - 1,)
591                                 return r
592                         
593                         ranges = string.split (s, ',')
594                         l = flatten (map (range_to_list, ranges))
595                         l.sort ()
596                         return l
597                 
598                 staff_idxs = range_list_to_idxs (m.group (1))
599                 if m.group (2):
600                         voice_idxs = range_list_to_idxs (m.group (2))
601                 else:
602                         voice_idxs = [0]
603                 for s in staff_idxs:
604                         while s > len (self.staffs) - 1:
605                                 self.staffs.append (Staff (s))
606                         for v in voice_idxs:
607                                 self.current_voices.append (self.staffs[s].voices[v])
608                         
609         def parse_note (self, line):
610                 # FIXME: 1?
611                 oct = 1
612                 name = (ord (line[0]) - ord ('a') + 5) % 7
613                 # FIXME: does key play any role in this?
614                 alteration = 0
615                 debug ('NOTE: ' + `line`)
616                 line = string.lstrip (line[1:])
617                 while line:
618                         if len (line) > 1 and line[:2] == '//':
619                                 line = 0
620                                 break
621                         elif line[0] == '#':
622                                 alteration = alteration + 1
623                         elif line[0] == '&':
624                                 alteration = alteration - 1
625                         elif line[0] == '+':
626                                 oct = oct + 1 
627                         elif line[0] == '-':
628                                 oct = oct - 1
629                         else:
630                                 skipping (line[0])
631                         line = string.lstrip (line[1:])
632                 return (oct, name, alteration)
633                         
634         def parse_chord (self, line):
635                 debug ('CHORD: ' + line)
636                 line = string.lstrip (line)
637                 ch = Chord ()
638                 if not line:
639                         #ch = self.current_voices[0].last_chord ()
640                         ch = self.last_chord.copy ()
641                 else:
642                         m = re.match ('^([0-9]+)([.]*)', line)
643                         if m:
644                                 ch.basic_duration = string.atoi (m.group (1))
645                                 line = line[len (m.group (1)):]
646                                 if m.group (2):
647                                         ch.dots = len (m.group (2))
648                                         line = line[len (m.group (2)):]
649                         else:
650                                 #ch.basic_duration = self.current_voices[0].last_chord ().basic_duration
651                                 ch.basic_duration = self.last_chord.basic_duration
652                                 
653                         line = string.lstrip (line)
654                         if len (line) > 1 and line[:2] == '//':
655                                 line = 0
656                         #ugh
657                         if not line:
658                                 debug ('nline: ' + line)
659                                 #ch = self.current_voices[0].last_chord ()
660                                 n = self.last_chord.copy ()
661                                 n.basic_duration = ch.basic_duration
662                                 n.dots = ch.dots
663                                 ch = n
664                                 debug ('ch.pitsen:' + `ch.pitches`)
665                                 debug ('ch.dur:' + `ch.basic_duration`)
666                         else:
667                                 debug ('eline: ' + line)
668                                 
669                         while line:
670                                 if len (line) > 1 and line[:2] == '//':
671                                         line = 0
672                                         break
673                                 elif line[:1] == 'mr':
674                                         ch.multimeasure = 1
675                                         line = line[2:]
676                                 elif line[:1] == 'ms':
677                                         ch.multimeasure = 1
678                                         line = line[2:]
679                                 elif line[0] in 'rs':
680                                         line = line[1:]
681                                         pass
682                                 elif line[0] in 'abcdefg':
683                                         m = re.match ('([a-g][-#&+]*)', line)
684                                         l = len (m.group (1))
685                                         pitch = self.parse_note (line[:l])
686                                         debug ('PITCH: ' + `pitch`)
687                                         ch.pitches.append (pitch)
688                                         line = line[l:]
689                                         break
690                                 else:
691                                         skipping (line[0])
692                                         line = line[1:]
693                                 line = string.lstrip (line)
694                 debug ('CUR-VOICES: ' + `self.current_voices`)
695                 map (lambda x, ch=ch: x.add_chord (ch), self.current_voices)
696                 self.last_chord = ch
697
698         def parse_lyrics_location (self, line):
699                 line = line.lstrip (line)
700                 addition = 0
701                 m = re.match ('^(between[ \t]+)', line)
702                 if m:
703                         line = line[len (m.group (1)):]
704                         addition = 0.5
705                 else:
706                         m = re.match ('^(above [ \t]+)', line)
707                         if m:
708                                 line = line[len (m.group (1)):]
709                                 addition = -0.5
710                         else:
711                                 addlyrics = 1
712                 
713         def parse_voice (self, line):
714                 line = string.lstrip (line)
715                 # `;' is not a separator, chords end with ';'
716                 chords = string.split (line, ';')[:-1]
717                 # mup resets default duration and pitch each bar
718                 self.last_chord = Chord ()
719                 self.last_chord.basic_duration = 4
720                 map (self.parse_chord, chords)
721
722         def init_context_header (self, line):
723                 self.parse_function = self.parse_context_header
724                                         
725         def parse_context_header (self, line):
726                 debug ('header: ' + line)
727                 skipping (line)
728                 
729         def init_context_footer (self, line):
730                 self.parse_function = self.parse_context_footer
731
732         def parse_context_footer (self, line):
733                 debug ('footer: ' + line)
734                 skipping (line)
735
736         def init_context_header2 (self, line):
737                 self.parse_function = self.parse_context_header2
738
739         def parse_context_header2 (self, line):
740                 debug ('header2: ' + line)
741                 skipping (line)
742
743         def init_context_footer2 (self, line):
744                 self.parse_function = self.parse_context_footer2
745
746         def parse_context_footer2 (self, line):
747                 debug ('footer2: ' + line)
748                 skipping (line)
749
750         def init_context_score (self, line):
751                 self.parse_function = self.parse_context_score
752
753         def parse_context_score (self, line):
754                 debug ('score: ' + line)
755                 line = string.lstrip (line)
756                 # ugh: these (and lots more) should also be parsed in
757                 # context staff.  we should have a class Staff_properties
758                 # and parse/set all those.
759                 m = re.match ('^(time[ \t]*=[ \t]*([0-9]+)[ \t]*/[ \t]*([0-9]+))', line)
760                 if m:
761                         line = line[len (m.group (1)):]
762                         self.time = Time ((string.atoi (m.group (2)),
763                                            string.atoi (m.group (3))))
764
765                 m = re.match ('^(key[ \t]*=[ \t]*([0-9]+)[ \t]*(#|@))', line)
766                 if m:
767                         line = line[len (m.group (1)):]
768                         n = string.atoi (m.group (2))
769                         if m.group (3) == '#':
770                                 self.key = Key (n, 0)
771                         else:
772                                 self.key = Key (0, n)
773                 skipping (line)
774
775         def init_context_staff (self, line):
776                 self.parse_function = self.parse_context_staff
777
778         def parse_context_staff (self, line):
779                 debug ('staff: ' + line)
780                 skipping (line)
781
782         def init_context_voice (self, line):
783                 self.parse_function = self.parse_context_voice
784
785         def parse_context_voice (self, line):
786                 debug ('voice: ' + line)
787                 skipping (line)
788
789         def init_context_grids (self, line):
790                 self.parse_function = self.parse_context_grids
791
792         def parse_context_grids (self, line):
793                 debug ('grids: ' + line)
794                 skipping (line)
795
796         def init_context_music (self, line):
797                 self.parse_function = self.parse_context_music
798
799         def parse_context_music (self, line):
800                 debug ('music: ' + line)
801                 line = string.lstrip (line)
802                 if line and line[0] in '0123456789':
803                         line = self.parse_compound_location (line)
804                         self.parse_voice (line)
805                 else:
806                         m = re.match ('^(TODOlyrics[ \t]+)', line)
807                         if m:
808                                 line = line[len (m.group (1)):]
809                                 self.parse_lyrics_location (line[7:])
810                                 self.parse_lyrics (line)
811                         else:
812                                 skipping (line)
813
814         def parse (self, lines):
815                 # shortcut: set to official mup maximum (duh)
816                 # self.set_staffs (40)
817                 for line in lines:
818                         debug ('LINE: ' + `line`)
819                         m = re.match ('^([a-z]+2?)', line)
820                         
821                         if m:
822                                 word = m.group (1)
823                                 if word in contexts:
824                                         eval ('self.init_context_%s (line)' % word)
825                                         continue
826                                 else:
827                                         warning (_ ("no such context: %s") % word)
828                                         skipping (line)
829                         else:
830                                 debug ('FUNC: ' + `self.parse_function`)
831                                 self.parse_function (line)
832                                 
833                 for c in self.staffs:
834                         # hmm
835                         if not c.clef and self.clef:
836                                 c.clef = self.clef
837                         if not c.time and self.time:
838                                 c.time = self.time
839                         if not c.key and self.key:
840                                 c.key = self.key
841                         c.calculate ()
842
843         def dump (self):
844                 str = ''
845
846                 refs = ''
847                 for s in self.staffs:
848                         str = str +  s.dump ()
849                         refs = refs + '\n    \\' + s.idstring ()
850
851                 str = str + '''
852
853 \score {
854   <%s
855   >
856   \paper {}
857   \midi {}
858 }
859 ''' % refs 
860                 return str
861
862
863 class Pre_processor:
864         def __init__ (self, raw_lines):
865                 self.lines = []
866                 self.active = [1]
867                 self.process_function = self.process_line
868                 self.macro_name = ''
869                 self.macro_body = ''
870                 self.process (raw_lines)
871
872         def process_line (self, line):
873                 global macros
874                 m = re.match ('^([ \t]*([a-zA-Z]+))', line)
875                 s = line
876                 if m:
877                         word = m.group (2)
878                         debug ('MACRO?: ' + `word`)
879                         if word in pre_processor_commands:
880                                 line = line[len (m.group (1)):]
881                                 eval ('self.process_macro_%s (line)' % word)
882                                 s = ''
883                         else:
884                                 if macros.has_key (word):
885                                         s = macros[word] + line[len (m.group (1)):]
886                 if not self.active [-1]:
887                         s = ''
888                 return s
889
890         def process_macro_body (self, line):
891                 global macros
892                 # dig this: mup allows ifdefs inside macro bodies
893                 s = self.process_line (line)
894                 m = re.match ('(.*[^\\\\])(@)(.*)', s)
895                 if m:
896                         self.macro_body = self.macro_body + '\n' + m.group (1)
897                         macros[self.macro_name] = self.macro_body
898                         debug ('MACROS: ' + `macros`)
899                         # don't do nested multi-line defines
900                         self.process_function = self.process_line
901                         if m.group (3):
902                                 s = m.group (3)
903                         else:
904                                 s = ''
905                 else:
906                         self.macro_body = self.macro_body + '\n' + s
907                         s = ''
908                 return s
909
910         # duh: mup is strictly line-based, except for `define',
911         # which is `@' terminated and may span several lines
912         def process_macro_define (self, line):
913                 global macros
914                 # don't define new macros in unactive areas
915                 if not self.active[-1]:
916                         return
917                 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)(([^@]*)|(\\\\@))(@)?', line)
918                 n = m.group (1)
919                 if m.group (5):
920                         if m.group (2):
921                                 e = m.group (2)
922                         else:
923                                 e = ''
924                         macros[n] = e
925                         debug ('MACROS: ' + `macros`)
926                 else:
927                         # To support nested multi-line define's
928                         # process_function and macro_name, macro_body
929                         # should become lists (stacks)
930                         # The mup manual is undetermined on this
931                         # and I haven't seen examples doing it.
932                         #
933                         # don't do nested multi-line define's
934                         if m.group (2):
935                                 self.macro_body = m.group (2)
936                         else:
937                                 self.macro_body = ''
938                         self.macro_name = n
939                         self.process_function = self.process_macro_body
940                 
941         def process_macro_ifdef (self, line):
942                 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
943                 if m:
944                         
945                         active = self.active[-1] and macros.has_key (m.group (1))
946                         debug ('ACTIVE: %d' % active)
947                         self.active.append (active)
948
949         def process_macro_ifndef (self, line):
950                 m = re.match ('^[ \t]*([a-zA-Z][a-zA-Z1-9_]*)', line)
951                 if m:
952                         active = self.active[-1] and not macros.has_key (m.group (1))
953                         self.active.append (active)
954
955         def process_macro_else (self, line):
956                 debug ('ELSE')
957                 self.active[-1] = not self.active[-1]
958                 
959         def process_macro_endif (self, line):
960                 self.active = self.active[:-1]
961                         
962         def process (self, raw_lines):
963                 s = ''
964                 for line in raw_lines:
965                         ls = string.split (self.process_function (line), '\n')
966                         for i in ls:
967                                 if i:
968                                         s = s + string.rstrip (i)
969                                         if s and s[-1] == '\\':
970                                                 s = string.rstrip (s[:-1])
971                                         elif s:
972                                                 self.lines.append (s)
973                                                 s = ''
974
975
976 debug_p = 0
977 only_pre_process_p = 0
978 def debug (s):
979         if debug_p:
980                 progress ('DEBUG: ' + s)
981
982 def skipping (s):
983         if verbose_p or debug_p:
984                 progress ('SKIPPING: ' + s)
985
986 (sh, long) = getopt_args (__main__.option_definitions)
987 try:
988         (options, files) = getopt.getopt (sys.argv[1:], sh, long)
989 except:
990         help ()
991         sys.exit (2)
992
993 macros = {}
994 pre_processor_commands = (
995         'define',
996         'else',
997         'endif',
998         'ifdef',
999         'ifndef',
1000         )
1001
1002 for opt in options:
1003         o = opt[0]
1004         a = opt[1]
1005         if 0:
1006                 pass
1007         elif o== '--debug' or o == '-d':
1008                 debug_p = 1
1009         elif o== '--define' or o == '-D':
1010                 if string.find (a, '=') >= 0:
1011                         (n, e) = string.split (a, '=')
1012                 else:
1013                         n = a
1014                         e = ''
1015                 macros[n] = e
1016         elif o== '--pre-process' or o == '-E':
1017                 only_pre_process_p = 1
1018         elif o== '--help' or o == '-h':
1019                 help ()
1020                 sys.exit (0)
1021         elif o== '--verbose' or o == '-V':
1022                 verbose_p = 1
1023         elif o == '--version' or o == '-v':
1024                 identify ()
1025                 sys.exit (0)
1026         elif o == '--output' or o == '-o':
1027                 output = a
1028         else:
1029                 print o
1030                 raise getopt.error
1031
1032 # writes to stdout for help2man
1033 # don't call 
1034 # identify ()
1035 # sys.stdout.flush ()
1036
1037 # handy emacs testing
1038 # if not files:
1039 #       files = ['template.mup']
1040
1041 if not files:
1042         files = ['-']
1043         
1044 for f in files:
1045
1046         if f == '-':
1047                 h = sys.stdin
1048         elif f and not os.path.isfile (f):
1049                 f = strip_extension (f, '.mup') + '.mup'
1050                 h = open (f)
1051         progress ( _("Processing `%s'..." % f))
1052         raw_lines = h.readlines ()
1053         p = Pre_processor (raw_lines)
1054         if only_pre_process_p:
1055                 if not output:
1056                         output = os.path.basename (re.sub ('(?i).mup$', '.mpp', f))
1057         else:
1058                 e = Parser (p.lines)
1059                 if not output:
1060                         output = os.path.basename (re.sub ('(?i).mup$', '.ly', f))
1061                 if output == f:
1062                         output = os.path.basename (f + '.ly')
1063                         
1064         if f == '-':
1065                 output = '-'
1066                 out_h = sys.stdout
1067         else:
1068                 out_h = open (output, 'w')
1069
1070         progress (_ ("Writing `%s'...") % output)
1071
1072         tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1073         if only_pre_process_p:
1074                 # duh
1075                 ly = string.join (p.lines, '\n')
1076         else:
1077                 ly = tag + '\n\n' + e.dump ()
1078
1079         out_h.write (ly)
1080         out_h.close ()
1081         if debug_p:
1082                 print (ly)
1083