3 # PMX is a Musixtex preprocessor written by Don Simons, see
4 # http://www.gmd.de/Misc/Music/musixtex/software/pmx/
7 # * block openings aren't parsed.
15 program_name
= 'pmx2ly'
16 version
= '@TOPLEVEL_VERSION@'
17 if version
== '@' + 'TOPLEVEL_VERSION' + '@':
18 version
= '(unknown version)' # uGUHGUHGHGUGH
22 return chr ( i
+ ord ('A'))
25 actab
= {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
27 def pitch_to_lily_string (tup
):
30 nm
= chr((n
+ 2) % 7 + ord ('a'))
59 def rat_multiply (a
,b
):
63 return rat_simplify ((x
*p
, y
*q
))
67 return rat_multiply (a
, (q
,p
))
80 return rat_simplify ((x
*q
+ p
*y
, y
*q
))
88 return rat_subtract (a
, b
)[0] > 0
90 def rat_subtract (a
,b
):
91 return rat_add (a
, rat_neg (b
))
93 def rat_to_duration (frac
):
96 while rat_larger (d
, frac
):
97 d
= rat_multiply (d
, (1,2))
100 frac
= rat_subtract (frac
, d
)
102 if frac
== rat_multiply (d
, (1,2)):
104 elif frac
== rat_multiply (d
, (3,4)):
117 def __init__ (self
,nums
):
120 return ' %{ FIXME: meter change %} '
123 def __init__ (self
, ch
):
129 def __init__ (self
,id):
131 self
.start_chord
= None
132 self
.end_chord
= None
133 def calculate (self
):
138 s
.note_suffix
= s
.note_suffix
+ '('
139 e
.note_prefix
= ')' + e
.note_prefix
141 sys
.stderr
.write ("\nOrphaned slur")
148 self
.current_slurs
= []
150 def toggle_slur (self
, id):
152 for s
in self
.current_slurs
:
154 self
.current_slurs
.remove (s
)
155 s
.end_chord
= self
.chords
[-1]
158 s
.start_chord
= self
.chords
[-1]
159 self
.current_slurs
.append (s
)
160 self
.slurs
.append (s
)
162 def last_chord (self
):
163 return self
.chords
[-1]
164 def add_chord (self
, ch
):
165 self
.chords
.append (ch
)
166 self
.entries
.append (ch
)
167 def add_nonchord (self
, nch
):
168 self
.entries
.append (nch
)
171 return 'staff%svoice%s ' % (encodeint (self
.staff
.number
) , encodeint(self
.number
))
175 for e
in self
.entries
:
176 next
= ' ' + e
.dump ()
178 str = str + ln
+ next
182 if len (ln
) +len (next
) > 72:
189 id = self
.idstring ()
191 str = '%s = \\notes { \n %s }\n '% (id, str)
193 def calculate_graces (self
):
196 for c
in self
.chords
:
197 if c
.grace
and not lastgr
:
198 c
.chord_prefix
= c
.chord_prefix
+ '\\grace { '
199 elif not c
.grace
and lastgr
:
200 lastc
.chord_suffix
= lastc
.chord_suffix
+ ' } '
203 def calculate (self
):
204 self
.calculate_graces ()
209 def __init__ (self
, cl
):
212 return '\\clef %s;' % self
.type
226 self
.voices
= (Voice (), Voice())
233 for v
in self
.voices
:
237 def set_clef (self
, letter
):
238 clstr
= clef_table
[letter
]
239 self
.voices
[0].add_nonchord (Clef (clstr
))
241 def current_voice (self
):
242 return self
.voices
[self
.voice_idx
]
243 def next_voice (self
):
244 self
.voice_idx
= (self
.voice_idx
+ 1)%len (self
.voices
)
246 def calculate (self
):
247 for v
in self
.voices
:
250 return 'staff%s' % encodeint (self
.number
)
255 for v
in self
.voices
:
257 refs
= refs
+ '\\' + v
.idstring ()+ ' '
259 str = str + '\n\n%s = \\context Staff = %s \n < \n %s >\n\n\n'% (self
.idstring (), self
.idstring (), refs
)
263 def __init__ (self
, number
, base
, dots
):
266 self
.replaces
= tuplet_table
[number
]
272 length
= rat_multiply (length
, (3,2))
274 length
= rat_multiply (length
, (7,4))
276 length
= rat_multiply (length
, (1,self
.replaces
))
278 (nb
,nd
) =rat_to_duration (length
)
283 def add_chord (self
, ch
):
284 ch
.dots
= self
.note_dots
285 ch
.basic_duration
= self
.note_base
286 self
.chords
.append (ch
)
288 if len (self
.chords
) == 1:
289 ch
.chord_prefix
= '\\times %d/%d { ' % (self
.replaces
, self
.number
)
290 elif len (self
.chords
) == self
.number
:
291 ch
.chord_suffix
= ' }'
297 self
.basic_duration
= 0
300 self
.chord_prefix
= ''
301 self
.chord_suffix
= ''
302 self
.note_prefix
= ''
303 self
.note_suffix
= ''
309 if self
.basic_duration
== 0.5:
312 sd
= '%d' % self
.basic_duration
313 sd
= sd
+ '.' * self
.dots
314 for p
in self
.pitches
:
317 str = str + pitch_to_lily_string (p
) + sd
319 for s
in self
.scripts
:
322 str = self
.note_prefix
+str + self
.note_suffix
324 if len (self
.pitches
) > 1:
326 elif len (self
.pitches
) == 0:
329 str = self
.chord_prefix
+ str + self
.chord_suffix
367 def __init__ (self
, filename
):
369 self
.forced_duration
= None
372 self
.tuplets_expected
= 0
374 self
.last_basic_duration
= 4
376 self
.parse (filename
)
378 def set_staffs (self
, number
):
379 self
.staffs
= map (lambda x
: Staff (), range(0, number
))
384 for s
in self
.staffs
:
387 def current_staff (self
):
388 return self
.staffs
[self
.staff_idx
]
390 def current_voice (self
):
391 return self
.current_staff ().current_voice ()
393 def next_staff (self
):
394 self
.staff_idx
= (self
.staff_idx
+ 1)% len (self
.staffs
)
396 def parse_note (self
, str):
406 ch
= self
.current_voice().last_chord()
410 self
.current_voice().add_chord (ch
)
412 name
= (ord (str[0]) - ord('a') + 5) % 7
416 ch
.grace
= ch
.grace
or grace
426 while str[0] in 'dsfmnul0123456789.,+-':
430 alteration
= alteration
-1
436 alteration
= alteration
+1
439 elif c
in DIGITS
and durdigit
== None and \
440 self
.tuplets_expected
== 0:
441 durdigit
= string
.atoi (c
)
443 oct = string
.atoi (c
) - 3
445 extra_oct
= extra_oct
+ 1
447 extra_oct
= extra_oct
- 1
456 tupnumber
= string
.atoi (str[0])
458 str=re
.sub (r
'^n?f?[+-0-9.]+', '' , str)
463 basic_duration
= basicdur_table
[durdigit
]
464 self
.last_basic_duration
= basic_duration
466 sys
.stderr
.write ("""
467 Huh? expected duration, found %d Left was `%s'""" % (durdigit
, str[:20]))
471 basic_duration
= self
.last_basic_duration
475 if name
<> None and oct == None:
477 if self
.last_name
< name
and name
-self
.last_name
> 3:
479 elif self
.last_name
> name
and self
.last_name
-name
> 3:
482 oct = self
.last_oct
+e
+ extra_oct
486 self
.last_name
= name
489 ch
.pitches
.append ((oct, name
, alteration
))
491 # do before adding to tuplet.
492 ch
.basic_duration
= basic_duration
496 self
.forced_duration
= ch
.basic_duration
/ forced_duration
499 tup
=Tuplet (tupnumber
, basic_duration
, dots
)
500 self
.tuplets_expected
= tupnumber
501 self
.tuplets
.append (tup
)
503 if self
.tuplets_expected
> 0:
504 self
.tuplets
[-1].add_chord (ch
)
505 self
.tuplets_expected
= self
.tuplets_expected
- 1
508 def parse_basso_continuo (self
, str):
509 while str[0] in DIGITS
+'#n-':
513 scr
= '\\\\textsharp'
515 if len(scr
)>1 or scr
not in DIGITS
:
518 self
.current_voice().last_chord ().scripts
.append (scr
)
521 def parse_beams (self
,str):
523 # self.current_voice().add_nonchord (Beam(c))
526 while str[0] in '+-0123456789':
534 def parse_header (self
, ls
):
535 while ls
[0][0] == '%':
541 opening
= re
.sub ('^[ \t]+', '', opening
)
542 opening
= re
.sub ('[ \t]+$', '', opening
)
543 opening
= re
.split ('[\t ]+', opening
)
545 (no_staffs
, no_instruments
, timesig_num
,timesig_den
, ptimesig_num
,
546 ptimesig_den
, pickup_beats
,keysig_number
) = opening
547 (no_staffs
, no_instruments
, timesig_num
, timesig_den
, ptimesig_num
, ptimesig_den
, keysig_number
) = tuple (map (string
.atoi
, [no_staffs
, no_instruments
, timesig_num
, timesig_den
, ptimesig_num
, ptimesig_den
, keysig_number
]))
549 pickup_beats
= string
.atoi (pickup_beats
)
551 pickup_beats
= string
.atof (pickup_beats
)
554 while ls
[0][0] == '%':
561 # opening = map (string.atoi, re.split ('[\t ]+', opening))
562 # (no_pages,no_systems, musicsize, fracindent) = tuple (opening)
565 while len (instruments
) < no_instruments
:
566 instruments
.append (ls
[0])
570 while ls
[0][0] == '%':
576 self
.set_staffs (no_staffs
)
578 for s
in self
.staffs
:
583 while ls
[0][0] == '%':
591 def parse_ornament (self
, left
):
593 e
= self
.current_voice ().last_chord ()
603 orn
= ornament_table
[id]
605 sys
.stderr
.write ("unknown ornament `%s'\n" % id)
607 e
.scripts
.append (orn
)
609 def parse_barcheck (self
, left
):
610 self
.current_voice ().add_nonchord (Barcheck ())
614 def parse_slur (self
, left
):
619 if re
.match ('[A-Z0-9]', left
[0]):
622 while left
[0] in 'uld0123456789+-.':
625 self
.current_voice ().toggle_slur (id)
628 def parse_mumbo_jumbo (self
,left
):
630 while left
and left
[0] <> '\\':
635 def parsex (self
,left
):
637 while left
[0] in DIGITS
:
642 def parse_body (self
, left
):
648 f
= string
.find (left
, '\n')
654 m
= re
.match ('([o0-9]/[o0-9]/[o0-9]/[o0-9])', left
)
657 left
= left
[len (nums
):]
658 nums
= map (string
.atoi
, nums
)
659 self
.current_voice ().add_nonchord (Meter (nums
))
662 m
= re
.match ('([0-9o]+)', left
)
665 self
.current_voice ().add_nonchord (Meter (map (string
.atoi (nums
))))
668 elif left
[0] in 'lh':
669 f
= string
.find (left
, '\n')
675 f
= string
.find (left
, '\n')
678 elif c
in 'Gzabcdefgr':
679 left
= self
.parse_note (left
)
680 elif c
in DIGITS
+ 'n#-':
681 left
= self
.parse_basso_continuo (left
)
685 left
= self
.parse_slur (left
)
687 left
= self
.parse_barcheck (left
)
689 left
= self
.parse_ornament (left
)
691 left
= self
.parsex (left
)
693 left
= self
.parse_beams (left
)
694 elif left
[:2] == "//":
695 self
.current_staff().next_voice ()
701 left
= self
.parse_mumbo_jumbo(left
)
705 sys
.stderr
.write ("""
706 Huh? Unknown directive `%s', before `%s'""" % (c
, left
[:20] ))
713 for s
in self
.staffs
:
714 str = str + s
.dump ()
715 refs
= '\\' + s
.idstring() + refs
717 str = str + "\n\n\\score { <\n %s\n > }" % refs
722 ls
= open (fn
).readlines ()
723 ls
= self
.parse_header (ls
)
724 left
= string
.join (ls
, ' ')
727 self
.parse_body (left
)
728 for c
in self
.staffs
:
737 """Usage: pmx2ly [OPTION]... PMX-FILE
739 Convert PMX to LilyPond.
743 -o, --output=FILE set output filename to FILE
744 -v, --version version information
746 PMX is a Musixtex preprocessor written by Don Simons, see
747 http://www.gmd.de/Misc/Music/musixtex/software/pmx/
749 Report bugs to bug-gnu-music@gnu.org.
751 Written by Han-Wen Nienhuys <hanwen@cs.uu.nl>
755 def print_version ():
756 sys
.stdout
.write ("""pmx2ly (GNU LilyPond) %s
758 This is free software. It is covered by the GNU General Public License,
759 and you are welcome to change it and/or distribute copies of it under
760 certain conditions. Invoke as `midi2ly --warranty' for more information.
762 Copyright (c) 2000 by Han-Wen Nienhuys <hanwen@cs.uu.nl>
765 sys
.stderr
.write ("%s from LilyPond %s\n" % (program_name
, version
))
769 (options
, files
) = getopt
.getopt (sys
.argv
[1:], 'vo:h', ['help','version', 'output='])
774 if o
== '--help' or o
== '-h':
777 if o
== '--version' or o
== '-v':
781 if o
== '--output' or o
== '-o':
793 sys
.stderr
.write ('Processing `%s\'\n' % f
)
796 out_filename
= os
.path
.basename (re
.sub ('(?i).pmx$', '.ly', f
))
798 if out_filename
== f
:
799 out_filename
= os
.path
.basename (f
+ '.ly')
801 sys
.stderr
.write ('Writing `%s\'' % out_filename
)
806 fo
= open (out_filename
, 'w')
807 fo
.write ('%% lily was here -- automatically converted by pmx2ly from %s\n' % f
)