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_suffix
+ ')'
141 sys
.stderr
.write ("\nOrphaned slur")
149 self
.current_slurs
= []
151 def toggle_slur (self
, id):
153 for s
in self
.current_slurs
:
155 self
.current_slurs
.remove (s
)
156 s
.end_chord
= self
.chords
[-1]
159 s
.start_chord
= self
.chords
[-1]
160 self
.current_slurs
.append (s
)
161 self
.slurs
.append (s
)
163 def last_chord (self
):
164 return self
.chords
[-1]
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
)
173 return 'staff%svoice%s ' % (encodeint (self
.staff
.number
) , encodeint(self
.number
))
177 for e
in self
.entries
:
178 next
= ' ' + e
.dump ()
180 str = str + ln
+ next
184 if len (ln
) +len (next
) > 72:
191 id = self
.idstring ()
193 str = '%s = \\notes { \n %s }\n '% (id, str)
195 def calculate_graces (self
):
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
+ ' } '
205 def calculate (self
):
206 self
.calculate_graces ()
211 def __init__ (self
, cl
):
214 return '\\clef %s' % self
.type
217 def __init__ (self
, key
):
220 return '\\key %s' % self
.type
249 self
.voices
= (Voice (), Voice())
257 for v
in self
.voices
:
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
))
266 sys
.stderr
.write ("Clef type `%c' unknown\n" % letter
)
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
)
273 def calculate (self
):
274 for v
in self
.voices
:
277 return 'staff%s' % encodeint (self
.number
)
282 for v
in self
.voices
:
284 refs
= refs
+ '\\' + v
.idstring ()+ ' '
286 str = str + '\n\n%s = \\context Staff = %s \n << \n %s >>\n\n\n'% (self
.idstring (), self
.idstring (), refs
)
290 def __init__ (self
, number
, base
, dots
):
293 self
.replaces
= tuplet_table
[number
]
299 length
= rat_multiply (length
, (3,2))
301 length
= rat_multiply (length
, (7,4))
303 length
= rat_multiply (length
, (1,self
.replaces
))
305 (nb
,nd
) =rat_to_duration (length
)
310 def add_chord (self
, ch
):
311 ch
.dots
= self
.note_dots
312 ch
.basic_duration
= self
.note_base
313 self
.chords
.append (ch
)
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
= ' }'
324 self
.basic_duration
= 0
327 self
.chord_prefix
= ''
328 self
.chord_suffix
= ''
329 self
.note_prefix
= ''
330 self
.note_suffix
= ''
336 if self
.basic_duration
== 0.5:
339 sd
= '%d' % self
.basic_duration
340 sd
= sd
+ '.' * self
.dots
341 for p
in self
.pitches
:
344 str = str + pitch_to_lily_string (p
)
346 if len (self
.pitches
) > 1:
348 elif len (self
.pitches
) == 0:
352 for s
in self
.scripts
:
355 str = self
.note_prefix
+ str + self
.note_suffix
356 str = self
.chord_prefix
+ str + self
.chord_suffix
394 def __init__ (self
, filename
):
396 self
.forced_duration
= None
399 self
.tuplets_expected
= 0
401 self
.last_basic_duration
= 4
403 self
.parse (filename
)
405 def set_staffs (self
, number
):
406 self
.staffs
= map (lambda x
: Staff (), range(0, number
))
411 for s
in self
.staffs
:
414 def current_staff (self
):
415 return self
.staffs
[self
.staff_idx
]
417 def current_voice (self
):
418 return self
.current_staff ().current_voice ()
420 def next_staff (self
):
421 self
.staff_idx
= (self
.staff_idx
+ 1)% len (self
.staffs
)
423 def parse_note (self
, str):
433 ch
= self
.current_voice().last_chord()
437 self
.current_voice().add_chord (ch
)
441 name
= (ord (str[0]) - ord('a') + 5) % 7
445 ch
.grace
= ch
.grace
or grace
455 while str[0] in 'dsfmnul0123456789.,+-':
459 alteration
= alteration
-1
465 alteration
= alteration
+1
468 elif c
in DIGITS
and durdigit
== None and \
469 self
.tuplets_expected
== 0:
470 durdigit
= string
.atoi (c
)
472 oct = string
.atoi (c
) - 3
474 extra_oct
= extra_oct
+ 1
476 extra_oct
= extra_oct
- 1
485 tupnumber
= string
.atoi (str[0])
487 str=re
.sub (r
'^n?f?[+-0-9.]+', '' , str)
492 basic_duration
= basicdur_table
[durdigit
]
493 self
.last_basic_duration
= basic_duration
495 sys
.stderr
.write ("""
496 Huh? expected duration, found %d Left was `%s'""" % (durdigit
, str[:20]))
500 basic_duration
= self
.last_basic_duration
504 if name
<> None and oct == None:
506 if self
.last_name
< name
and name
-self
.last_name
> 3:
508 elif self
.last_name
> name
and self
.last_name
-name
> 3:
511 oct = self
.last_oct
+e
+ extra_oct
515 self
.last_name
= name
518 ch
.pitches
.append ((oct, name
, alteration
))
520 # do before adding to tuplet.
521 ch
.basic_duration
= basic_duration
525 self
.forced_duration
= ch
.basic_duration
/ forced_duration
528 tup
=Tuplet (tupnumber
, basic_duration
, dots
)
529 self
.tuplets_expected
= tupnumber
530 self
.tuplets
.append (tup
)
532 if self
.tuplets_expected
> 0:
533 self
.tuplets
[-1].add_chord (ch
)
534 self
.tuplets_expected
= self
.tuplets_expected
- 1
537 def parse_basso_continuo (self
, str):
538 while str[0] in DIGITS
+'#n-':
542 scr
= '\\\\textsharp'
544 if len(scr
)>1 or scr
not in DIGITS
:
547 self
.current_voice().last_chord ().scripts
.append (scr
)
550 def parse_beams (self
,str):
552 # self.current_voice().add_nonchord (Beam(c))
555 while str[0] in '+-0123456789':
562 def parse_key (self
, str):
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.
568 sys
.stderr
.write("Transposition not implemented yet: ")
569 while str[0] in '+-0123456789':
574 while str[0] in '+-0123456789':
577 keystr
= key_table
[key
]
578 self
.current_voice().add_nonchord (Key(keystr
))
582 def parse_header (self
, ls
):
584 if re
.search('\\.', a
):
585 return string
.atof (a
)
587 return string
.atoi (a
)
592 while len (numbers
) < number_count
:
596 opening
= re
.sub ('[ \t\n]+', ' ', opening
)
597 opening
= re
.sub ('^ ', '', opening
)
598 opening
= re
.sub (' $', '', opening
)
601 opening
= string
.split (opening
, ' ')
603 numbers
= numbers
+ map (atonum
, opening
)
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:])
610 # opening = map (string.atoi, re.split ('[\t ]+', opening))
613 while len (instruments
) < no_instruments
:
614 instruments
.append (ls
[0])
620 self
.set_staffs (no_staffs
)
622 for s
in self
.staffs
:
632 def parse_ornament (self
, left
):
634 e
= self
.current_voice ().last_chord ()
644 orn
= ornament_table
[id]
646 sys
.stderr
.write ("unknown ornament `%s'\n" % id)
648 e
.scripts
.append (orn
)
650 def parse_barcheck (self
, left
):
651 self
.current_voice ().add_nonchord (Barcheck ())
655 def parse_slur (self
, left
):
660 if re
.match ('[A-Z0-9]', left
[0]):
663 while left
[0] in 'uld0123456789+-.':
666 self
.current_voice ().toggle_slur (id)
669 def parse_mumbo_jumbo (self
,left
):
671 while left
and left
[0] <> '\\':
676 def parsex (self
,left
):
678 while left
[0] in DIGITS
:
683 def parse_body (self
, left
):
689 f
= string
.find (left
, '\n')
695 m
= re
.match ('([o0-9]/[o0-9]/[o0-9]/[o0-9])', left
)
698 left
= left
[len (nums
):]
699 nums
= map (string
.atoi
, nums
)
700 self
.current_voice ().add_nonchord (Meter (nums
))
703 m
= re
.match ('([0-9o]+)', left
)
706 self
.current_voice ().add_nonchord (Meter (map (string
.atoi (nums
))))
709 elif left
[0] in 'lh':
710 f
= string
.find (left
, '\n')
716 f
= string
.find (left
, '\n')
719 elif c
in 'Gzabcdefgr':
720 left
= self
.parse_note (left
)
721 elif c
in DIGITS
+ 'n#-':
722 left
= self
.parse_basso_continuo (left
)
726 left
= self
.parse_slur (left
)
728 left
= self
.parse_barcheck (left
)
730 left
= self
.parse_ornament (left
)
732 left
= self
.parsex (left
)
734 self
.current_staff().set_clef(str(left
[1]))
737 left
= self
.parse_key (left
)
739 left
= self
.parse_beams (left
)
740 elif left
[:2] == "//":
741 self
.current_staff().next_voice ()
747 left
= self
.parse_mumbo_jumbo(left
)
751 sys
.stderr
.write ("""
752 Huh? Unknown directive `%s', before `%s'""" % (c
, left
[:20] ))
759 for s
in self
.staffs
:
760 str = str + s
.dump ()
761 refs
= '\\' + s
.idstring() + refs
763 str = str + "\n\n\\score { <<\n %s\n >> }" % refs
768 ls
= open (fn
).readlines ()
770 return re
.sub ('%.*$', '', s
)
773 ls
= filter (lambda x
: x
<> '\n', ls
)
774 ls
= self
.parse_header (ls
)
775 left
= string
.join (ls
, ' ')
778 self
.parse_body (left
)
779 for c
in self
.staffs
:
788 """Usage: pmx2ly [OPTIONS]... PMX-FILE
790 Convert PMX to LilyPond.
793 -h, --help print this help
794 -o, --output=FILE set output filename to FILE
795 -v, --version shown version information
797 PMX is a Musixtex preprocessor written by Don Simons, see
798 http://www.gmd.de/Misc/Music/musixtex/software/pmx/.
800 Report bugs to bug-lilypond@gnu.org.
802 Written by Han-Wen Nienhuys <hanwen@cs.uu.nl>.
807 def print_version ():
808 sys
.stdout
.write ("""pmx2ly (GNU LilyPond) %s
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.
814 Copyright (c) 2000--2003 by Han-Wen Nienhuys <hanwen@cs.uu.nl>
817 sys
.stderr
.write ("%s from LilyPond %s\n" % (program_name
, version
))
821 (options
, files
) = getopt
.getopt (sys
.argv
[1:], 'vo:h', ['help','version', 'output='])
826 if o
== '--help' or o
== '-h':
829 if o
== '--version' or o
== '-v':
833 if o
== '--output' or o
== '-o':
845 sys
.stderr
.write ('Processing `%s\'\n' % f
)
848 out_filename
= os
.path
.basename (re
.sub ('(?i).pmx$', '.ly', f
))
850 if out_filename
== f
:
851 out_filename
= os
.path
.basename (f
+ '.ly')
853 sys
.stderr
.write ('Writing `%s\'' % out_filename
)
858 fo
= open (out_filename
, 'w')
859 fo
.write ('%% lily was here -- automatically converted by pmx2ly from %s\n' % f
)