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