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