]> git.donarmstrong.com Git - lilypond.git/blob - scripts/pmx2ly.py
2003 -> 2004
[lilypond.git] / scripts / pmx2ly.py
1 #!@PYTHON@
2
3 # PMX is a Musixtex preprocessor written by Don Simons, see
4 # http://www.gmd.de/Misc/Music/musixtex/software/pmx/
5
6 # TODO:
7 #  * block openings aren't parsed.
8
9 import os
10 import string
11 import sys
12 import re
13 import getopt
14
15 program_name = 'pmx2ly'
16 version = '@TOPLEVEL_VERSION@'
17 if version == '@' + 'TOPLEVEL_VERSION' + '@':
18         version = '(unknown version)'      # uGUHGUHGHGUGH
19
20
21 def encodeint (i):
22         return chr ( i  + ord ('A'))
23
24         
25 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
26
27 def pitch_to_lily_string (tup):
28         (o,n,a) = tup
29
30         nm = chr((n + 2) % 7 + ord ('a'))
31         nm = nm + actab[a]
32         if o > 0:
33                 nm = nm + "'" * o
34         elif o < 0:
35                 nm = nm + "," * -o
36         return nm
37
38 def gcd (a,b):
39         if b == 0:
40                 return a
41         c = a
42         while c: 
43                 c = a % b
44                 a = b
45                 b = c
46         return a
47
48 def rat_simplify (r):
49         (n,d) = r
50         if d < 0:
51                 d = -d
52                 n = -n
53         if n == 0:
54                 return (0,1)
55         else:
56                 g = gcd (n, d)
57                 return (n/g, d/g)
58         
59 def rat_multiply (a,b):
60         (x,y) = a
61         (p,q) = b
62
63         return rat_simplify ((x*p, y*q))
64
65 def rat_divide (a,b):
66         (p,q) = b
67         return rat_multiply (a, (q,p))
68
69 tuplet_table = {
70         2: 3,
71         3: 2,
72         5: 4
73 }
74
75
76 def rat_add (a,b):
77         (x,y) = a
78         (p,q) = b
79
80         return rat_simplify ((x*q + p*y, y*q))
81
82 def rat_neg (a):
83         (p,q) = a
84         return (-p,q)
85
86
87 def rat_larger (a,b):
88         return rat_subtract (a, b )[0] > 0
89
90 def rat_subtract (a,b ):
91         return rat_add (a, rat_neg (b))
92
93 def rat_to_duration (frac):
94         log = 1
95         d = (1,1)
96         while rat_larger (d, frac):
97                 d = rat_multiply (d, (1,2))
98                 log = log << 1
99
100         frac = rat_subtract (frac, d)
101         dots = 0
102         if frac == rat_multiply (d, (1,2)):
103                 dots = 1
104         elif frac == rat_multiply (d, (3,4)):
105                 dots = 2
106         return (log, dots)      
107
108
109 class Barcheck :
110         def __init__ (self):
111                 pass
112         def dump (self):
113                 return '|\n'
114
115
116 class Meter :
117         def __init__ (self,nums):
118                 self.nums = nums
119         def dump (self):
120                 return ' %{ FIXME: meter change %} '
121                 
122 class Beam:
123         def __init__ (self, ch):
124                 self.char = ch
125         def dump (self):
126                 return self.char
127
128 class Slur:
129         def __init__ (self,id):
130                 self.id = id
131                 self.start_chord = None
132                 self.end_chord = None
133         def calculate (self):
134                 s =self.start_chord
135                 e= self.end_chord
136
137                 if e and s:
138                         s.note_suffix = s.note_suffix + '('
139                         e.note_prefix = e.note_suffix + ')' 
140                 else:
141                         sys.stderr.write ("\nOrphaned slur")
142
143
144 class Voice:
145         def __init__ (self):
146                 self.entries = []
147                 self.chords = []
148                 self.staff = None
149                 self.current_slurs = []
150                 self.slurs = []
151         def toggle_slur (self, id):
152                 
153                 for s in self.current_slurs:
154                         if s.id == id:
155                                 self.current_slurs.remove (s)
156                                 s.end_chord = self.chords[-1]
157                                 return
158                 s = Slur (id)
159                 s.start_chord = self.chords[-1]
160                 self.current_slurs.append (s)
161                 self.slurs.append (s)
162                 
163         def last_chord (self):
164                 return self.chords[-1]
165         
166         def add_chord (self, ch):
167                 self.chords.append (ch)
168                 self.entries.append (ch)
169         def add_nonchord (self, nch):
170                 self.entries.append (nch)
171
172         def idstring (self):
173                 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
174         def dump (self):
175                 str = ''
176                 ln = ''
177                 for e in self.entries:
178                         next = ' ' + e.dump ()
179                         if next[-1] == '\n':
180                                 str  = str + ln + next
181                                 ln = ''
182                                 continue
183                         
184                         if len (ln) +len (next) > 72:
185                                 str = str+ ln + '\n'
186                                 ln = ''
187                         ln = ln + next
188                         
189                         
190                 str = str  + ln
191                 id = self.idstring ()
192                         
193                 str = '%s =  \\notes { \n %s }\n '% (id, str)
194                 return str
195         def calculate_graces (self):
196                 lastgr = 0
197                 lastc = None
198                 for c in self.chords:
199                         if c.grace and  not lastgr:
200                                 c.chord_prefix = c.chord_prefix + '\\grace { '
201                         elif not c.grace and lastgr:
202                                 lastc.chord_suffix = lastc.chord_suffix + ' } '
203                         lastgr = c.grace
204                         lastc = c
205         def calculate (self):
206                 self.calculate_graces ()
207                 for s in self.slurs:
208                         s.calculate ()
209
210 class Clef:
211         def __init__ (self, cl):
212                 self.type = cl
213         def dump(self):
214                 return '\\clef %s' % self.type
215
216 class Key:
217         def __init__ (self, key):
218                 self.type = key
219         def dump(self):
220                 return '\\key %s' % self.type
221
222 clef_table = {
223         'b':'bass'  ,
224         'r':'baritone',
225         'n':'tenor',
226         'a':'alto',
227         'm':'mezzosoprano',
228         's':'soprano',
229         't':'treble',
230         'f':'frenchviolin',
231         }
232 key_table = {
233         '+0':'c \major',
234         '+1':'g \major',
235         '+2':'d \major',
236         '+3':'a \major',
237         '+4':'e \major',
238         '+5':'b \major',
239         '+6':'fis \major',
240         '-1':'f \major',
241         '-2':'bes \major',
242         '-3':'ees \major',
243         '-4':'aes \major',
244         '-5':'des \major',
245         '-6':'ges \major'
246         }
247 class Staff:
248         def __init__ (self): 
249                 self.voices = (Voice (), Voice())
250                 self.clef = None
251                 self.instrument = 0
252                 self.voice_idx = 0
253                 self.number = None
254                 self.key = 0
255                 
256                 i = 0
257                 for v  in self.voices:
258                         v.staff = self
259                         v.number = i
260                         i = i+1
261         def set_clef (self, letter):
262                 if clef_table.has_key (letter):
263                         clstr = clef_table[letter]
264                         self.voices[0].add_nonchord (Clef (clstr))
265                 else:
266                         sys.stderr.write ("Clef type `%c' unknown\n" % letter)
267         
268         def current_voice (self):
269                 return self.voices[self.voice_idx]
270         def next_voice (self):
271                 self.voice_idx = (self.voice_idx + 1)%len (self.voices)
272
273         def calculate (self):
274                 for v in self.voices:
275                         v.calculate ()
276         def idstring (self):
277                 return 'staff%s' % encodeint (self.number)
278         def dump (self):
279                 str = ''
280
281                 refs = ''
282                 for v in self.voices:
283                         str = str + v.dump()
284                         refs = refs + '\\' + v.idstring ()+  ' '
285                 
286                 str = str + '\n\n%s = \\context Staff = %s \n  << \n %s >>\n\n\n'% (self.idstring (), self.idstring (), refs)
287                 return str
288
289 class Tuplet:
290         def __init__ (self, number, base, dots):
291                 self.chords = []
292                 self.number = number
293                 self.replaces = tuplet_table[number]
294                 self.base = base
295                 self.dots = dots
296                 
297                 length = (1,base)
298                 if dots == 1:
299                         length = rat_multiply (length, (3,2))
300                 elif dots == 2:
301                         length = rat_multiply (length, (7,4))
302
303                 length = rat_multiply (length, (1,self.replaces))
304
305                 (nb,nd) =rat_to_duration (length)
306
307                 self.note_base = nb
308                 self.note_dots = nd
309
310         def add_chord (self, ch):
311                 ch.dots = self.note_dots
312                 ch.basic_duration = self.note_base
313                 self.chords.append (ch)
314
315                 if len (self.chords) == 1:
316                         ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
317                 elif len (self.chords) == self.number:
318                         ch.chord_suffix = ' }' 
319                 
320 class Chord:
321         def __init__ (self):
322                 self.pitches = []
323                 self.dots = 0
324                 self.basic_duration = 0
325                 self.scripts = []
326                 self.grace = 0
327                 self.chord_prefix = ''
328                 self.chord_suffix = ''
329                 self.note_prefix = ''
330                 self.note_suffix = ''
331                 
332         def dump (self):
333                 str = ''
334
335                 sd = ''
336                 if self.basic_duration == 0.5:
337                         sd = '\\breve'
338                 else:
339                         sd = '%d' % self.basic_duration
340                 sd = sd + '.' * self.dots 
341                 for p in self.pitches:
342                         if str:
343                                 str = str + ' ' 
344                         str = str + pitch_to_lily_string (p) 
345
346                 if len (self.pitches) > 1:
347                         str = '<%s>' % str
348                 elif len (self.pitches) == 0:
349                         str = 'r'
350
351                 str = str + sd
352                 for s in self.scripts:
353                         str = str + '-' + s
354
355                 str = self.note_prefix + str + self.note_suffix
356                 str = self.chord_prefix + str + self.chord_suffix
357                 
358                 return str
359                 
360 SPACE=' \t\n'
361 DIGITS ='0123456789'
362 basicdur_table = {
363         9: 0.5,
364         0: 0 ,
365         2: 2 ,
366         4: 4 ,
367         8: 8 ,
368         1: 16,
369         3: 32,
370         6: 64
371         }
372
373
374 ornament_table = {
375         't': '\\prall',
376         'm': '\\mordent',
377         'x': '"x"',
378         '+': '+',
379         'u': '"pizz"',
380         'p': '|',
381         '(': '"paren"',
382         ')': '"paren"',
383         'g': '"segno"',
384         '.': '.',
385         'fd': '\\fermata',
386         'f': '\\fermata',
387         '_': '-',
388         'T': '\\trill',
389         '>': '>',
390         '^': '^',
391         }
392
393 class Parser:
394         def __init__ (self, filename):
395                 self.staffs = []
396                 self.forced_duration = None
397                 self.last_name = 0
398                 self.last_oct = 0               
399                 self.tuplets_expected = 0
400                 self.tuplets = []
401                 self.last_basic_duration = 4
402
403                 self.parse (filename)
404                 
405         def set_staffs (self, number):
406                 self.staffs = map (lambda x: Staff (), range(0, number))
407                 
408                 self.staff_idx = 0
409
410                 i =0
411                 for s in self.staffs:
412                         s.number = i
413                         i = i+1
414         def current_staff (self):
415                 return self.staffs[self.staff_idx]
416
417         def current_voice (self):
418                 return self.current_staff ().current_voice ()
419         
420         def next_staff (self):
421                 self.staff_idx = (self.staff_idx + 1)% len (self.staffs)
422                 
423         def parse_note (self, str):
424                 name = None
425                 ch = None
426
427                 grace = 0
428                 if str[0] == 'G':
429                         grace = 1
430                         str = str[1:]
431                         
432                 if str[0] == 'z':
433                         ch = self.current_voice().last_chord()
434                         str = str[1:]
435                 else:
436                         ch = Chord ()
437                         self.current_voice().add_chord (ch)
438
439                 # what about 's'?
440                 if str[0] <> 'r':
441                         name = (ord (str[0]) - ord('a') + 5) % 7
442
443                 str = str[1:]
444
445                 ch.grace = ch.grace or grace 
446                 
447                 forced_duration  = 0
448                 alteration = 0
449                 dots = 0
450                 oct = None
451                 durdigit = None
452                 multibar = 0
453                 tupnumber = 0
454                 extra_oct = 0
455                 while str[0] in 'dsfmnul0123456789.,+-':
456                         c = str[0]
457                         str = str[1:]
458                         if c == 'f':
459                                 alteration = alteration -1
460                         elif c == 'n':
461                                 alteration = 0
462                         elif c == 'm':
463                                 multibar = 1
464                         elif c == 's':
465                                 alteration = alteration +1
466                         elif c == 'd':
467                                 dots = dots + 1
468                         elif c in DIGITS and durdigit == None and \
469                              self.tuplets_expected == 0:
470                                 durdigit = string.atoi (c)
471                         elif c in DIGITS:
472                                 oct = string.atoi (c) - 3
473                         elif c == '+':
474                                 extra_oct = extra_oct + 1
475                         elif c == '-':
476                                 extra_oct = extra_oct - 1
477                         elif c == '.':
478                                 dots = dots+ 1
479                                 forced_duration = 2
480                         elif c == ',':
481                                 forced_duration = 2
482
483                 if str[0] == 'x':
484                         str = str[1:]
485                         tupnumber = string.atoi (str[0])
486                         str = str[1:]
487                         str=re.sub (r'^n?f?[+-0-9.]+', '' , str)
488
489                 
490                 if durdigit:
491                         try:
492                                 basic_duration =  basicdur_table[durdigit]
493                                 self.last_basic_duration = basic_duration
494                         except KeyError:
495                                 sys.stderr.write ("""
496 Huh? expected duration, found %d Left was `%s'""" % (durdigit, str[:20]))
497
498                                 basic_duration = 4
499                 else:
500                         basic_duration = self.last_basic_duration
501
502
503                 
504                 if name <> None and oct == None:
505                         e = 0
506                         if self.last_name < name and name -self.last_name > 3:
507                                 e = -1
508                         elif self.last_name > name and self.last_name -name > 3:
509                                 e = 1
510
511                         oct = self.last_oct  +e + extra_oct
512
513                 if name <> None:
514                         self.last_oct = oct
515                         self.last_name = name
516                                 
517                 if name <> None:
518                         ch.pitches.append ((oct, name,  alteration))
519
520                 # do before adding to tuplet.
521                 ch.basic_duration = basic_duration
522                 ch.dots = dots
523
524                 if forced_duration:
525                         self.forced_duration = ch.basic_duration / forced_duration
526
527                 if tupnumber:
528                         tup =Tuplet (tupnumber, basic_duration, dots)
529                         self.tuplets_expected = tupnumber
530                         self.tuplets.append (tup)
531
532                 if self.tuplets_expected > 0:
533                         self.tuplets[-1].add_chord (ch)
534                         self.tuplets_expected = self.tuplets_expected - 1
535                         
536                 return str
537         def parse_basso_continuo (self, str):
538                 while str[0] in DIGITS +'#n-':
539                         scr = str[0]
540
541                         if scr == '#':
542                                 scr = '\\\\textsharp'
543                         
544                         if len(scr)>1 or scr not in DIGITS:
545                                 scr = '"%s"' % scr
546                                 
547                         self.current_voice().last_chord ().scripts.append (scr)
548                         str=str[1:]
549                 return str
550         def parse_beams (self,str):
551                 c = str[0]
552         #       self.current_voice().add_nonchord (Beam(c))
553                 if str[0] == '[':
554                         str = str[1:]
555                         while str[0] in '+-0123456789':
556                                 str=str[1:]
557                 else:
558                         str = str[1:]
559                                         
560                 return str
561
562         def parse_key (self, str):
563                 key = ""
564                 #The key is changed by a string of the form K[+-]<num>[+-]<num>
565                 #where the first number is the transposition and the second number is the
566                 #new key signature.  For now, we won't bother with the transposition.
567                 if str[2] != '0':
568                         sys.stderr.write("Transposition not implemented yet: ")
569                         while str[0] in '+-0123456789':
570                                 str = str[1:]
571                 else:
572                         str=str[3:]
573                         key = ''
574                         while str[0] in '+-0123456789':
575                                 key=key + str[0]
576                                 str=str[1:]
577                         keystr = key_table[key]
578                         self.current_voice().add_nonchord (Key(keystr))
579                 return(str)
580
581
582         def parse_header  (self, ls):
583                 def atonum(a):
584                         if re.search('\\.', a):
585                                 return string.atof (a)
586                         else:
587                                 return string.atoi (a)
588
589                 number_count = 12
590                 numbers = []
591
592                 while len (numbers) < number_count:
593                         opening = ls[0]
594                         ls = ls[1:]
595
596                         opening = re.sub ('[ \t\n]+', ' ', opening)
597                         opening = re.sub ('^ ', '', opening)
598                         opening = re.sub (' $', '', opening)                                            
599                         if opening == '':
600                                 continue
601                         opening = string.split (opening, ' ')
602
603                         numbers = numbers + map (atonum, opening)
604
605                 (no_staffs, no_instruments, timesig_num, timesig_den, ptimesig_num,
606                  esig_den, pickup_beats,keysig_number) = tuple (numbers[0:8])
607                 (no_pages,no_systems, musicsize, fracindent) = tuple (numbers[8:])
608
609                 # ignore this.
610                 # opening = map (string.atoi, re.split ('[\t ]+', opening))
611                 
612                 instruments = []
613                 while len (instruments) < no_instruments:
614                         instruments.append (ls[0])
615                         ls = ls[1:]
616
617                 l = ls[0]
618                 ls = ls[1:]
619
620                 self.set_staffs (no_staffs)
621
622                 for s in self.staffs:
623                         s.set_clef(l[0])
624                         l = l[1:]
625
626                 # dump path 
627                 ls = ls[1:] 
628
629                 # dump more ?
630                 return ls
631
632         def parse_ornament (self, left):
633                 left = left[1:]
634                 e = self.current_voice ().last_chord ()
635
636                 id = left[0]
637                 left = left[1:]
638                 if left[0] == 'd':
639                         id = id +'d'
640                         left = left [1:]
641
642                 orn = '"orn"'
643                 try:
644                         orn = ornament_table[id]
645                 except KeyError:
646                         sys.stderr.write ("unknown ornament `%s'\n" % id)
647                         
648                 e.scripts.append (orn)
649                 return left
650         def parse_barcheck (self, left):
651                 self.current_voice ().add_nonchord (Barcheck ())
652                 
653                 return left [1:]
654
655         def parse_slur (self, left):
656                 left = left[1:]
657
658                 id = None
659
660                 if re.match ('[A-Z0-9]', left[0]):
661                         id = left[0]
662                         left= left[1:]
663                 while left[0] in 'uld0123456789+-.':
664                         left= left[1:]
665                         
666                 self.current_voice ().toggle_slur (id)
667                 return left
668
669         def parse_mumbo_jumbo (self,left):
670                 left = left[1:]
671                 while left and  left[0] <> '\\':
672                         left = left[1:]
673
674                 left  = left[1:]
675                 return left
676         def parsex (self,left):
677                 left = left[1:]
678                 while left[0] in DIGITS:
679                         left = left[1:]
680
681                 return left
682         
683         def parse_body (self, left):
684                 preamble = 1
685                 
686                 while left:
687                         c = left[0]
688                         if c == '%':
689                                 f = string.find (left, '\n')
690                                 if f < 0:
691                                         left = ''
692                                 left = left[f+1:]
693                         elif c == 'm':
694                                 left = left[1:]
695                                 m = re.match ('([o0-9]/[o0-9]/[o0-9]/[o0-9])', left)
696                                 if m:
697                                         nums = m.group (1)
698                                         left = left[len (nums):]
699                                         nums = map (string.atoi , nums)
700                                         self.current_voice ().add_nonchord (Meter (nums))
701                                         continue
702
703                                 m= re.match ('([0-9o]+)', left)
704                                 if m:
705                                         nums = m.group (1)
706                                         self.current_voice ().add_nonchord (Meter (map (string.atoi (nums))))
707                                         continue
708                                 
709                         elif left[0] in 'lh':
710                                 f = string.find (left, '\n')
711                                 if f <0 :
712                                         left = ''
713                                 else:
714                                         left = left[f+1:]
715                                         
716                                 f = string.find (left, '\n')
717                                 title = left[:f]
718                                 left=left[f+1:]
719                         elif c in 'Gzabcdefgr':
720                                 left = self.parse_note (left)
721                         elif c in DIGITS + 'n#-':
722                                 left = self.parse_basso_continuo (left)
723                         elif c in SPACE:
724                                 left = left[1:]
725                         elif c == 's':
726                                 left = self.parse_slur (left)
727                         elif c == '|':
728                                 left = self.parse_barcheck (left)
729                         elif c == 'o':
730                                 left = self.parse_ornament (left)
731                         elif c == 'x':
732                                 left = self.parsex (left)
733                         elif c == 'C':
734                                 self.current_staff().set_clef(str(left[1]))
735                                 left = left[2:]
736                         elif c == 'K':
737                                 left = self.parse_key (left)
738                         elif c in "[]":
739                                 left = self.parse_beams (left)
740                         elif left[:2] == "//":
741                                 self.current_staff().next_voice ()
742                                 left = left[2:]
743                         elif c == '/':
744                                 self.next_staff ()
745                                 left = left[1:]
746                         elif c == '\\':
747                                 left = self.parse_mumbo_jumbo(left)
748                         elif c == '\r':
749                                 left = left[1:]
750                         else:
751                                 sys.stderr.write ("""
752 Huh? Unknown directive `%s', before `%s'""" % (c, left[:20] ))
753                                 left = left[1:]
754
755         def dump (self):
756                 str = ''
757
758                 refs = ''
759                 for s in self.staffs:
760                         str = str +  s.dump ()
761                         refs = '\\' + s.idstring() + refs
762
763                 str = str + "\n\n\\score { <<\n %s\n >> }" % refs 
764                 return str
765                         
766
767         def parse (self,fn):
768                 ls = open (fn).readlines ()
769                 def subst(s):
770                         return re.sub ('%.*$', '', s)
771                 
772                 ls = map (subst, ls)
773                 ls = filter (lambda x: x <> '\n', ls)
774                 ls = self.parse_header (ls)
775                 left = string.join (ls, ' ')
776
777 #               print left
778                 self.parse_body (left)
779                 for c in self.staffs:
780                         c.calculate ()
781
782                 
783
784
785
786 def help ():
787         sys.stdout.write (
788 """Usage: pmx2ly [OPTIONS]... PMX-FILE
789
790 Convert PMX to LilyPond.
791
792 Options:
793   -h, --help          print this help
794   -o, --output=FILE   set output filename to FILE
795   -v, --version       shown version information
796
797 PMX is a Musixtex preprocessor written by Don Simons, see
798 http://www.gmd.de/Misc/Music/musixtex/software/pmx/.
799
800 Report bugs to bug-lilypond@gnu.org.
801
802 Written by Han-Wen Nienhuys <hanwen@cs.uu.nl>.
803
804 """)
805
806
807 def print_version ():
808         sys.stdout.write ("""pmx2ly (GNU LilyPond) %s
809
810 This is free software.  It is covered by the GNU General Public License,
811 and you are welcome to change it and/or distribute copies of it under
812 certain conditions.  Invoke as `midi2ly --warranty' for more information.
813
814 Copyright (c) 2000--2004 by Han-Wen Nienhuys <hanwen@cs.uu.nl>
815 """ % version)
816 def identify():
817         sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
818
819
820
821 (options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
822 out_filename = None
823 for opt in options:
824         o = opt[0]
825         a = opt[1]
826         if o== '--help' or o == '-h':
827                 help ()
828                 sys.exit (0)
829         if o == '--version' or o == '-v':
830                 print_version ()
831                 sys.exit(0)
832                 
833         if o == '--output' or o == '-o':
834                 out_filename = a
835         else:
836                 print o
837                 raise getopt.error
838
839 identify()
840
841 for f in files:
842         if f == '-':
843                 f = ''
844
845         sys.stderr.write ('Processing `%s\'\n' % f)
846         e = Parser(f)
847         if not out_filename:
848                 out_filename = os.path.basename (re.sub ('(?i).pmx$', '.ly', f))
849                 
850         if out_filename == f:
851                 out_filename = os.path.basename (f + '.ly')
852                 
853         sys.stderr.write ('Writing `%s\'' % out_filename)
854         ly = e.dump()
855
856         
857         
858         fo = open (out_filename, 'w')
859         fo.write ('%% lily was here -- automatically converted by pmx2ly from %s\n' % f)
860         fo.write(ly)
861         fo.close ()
862         
863