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 [OPTION]... PMX-FILE
790 Convert PMX to LilyPond.
794 -o, --output=FILE set output filename to FILE
795 -v, --version 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>
806 def print_version ():
807 sys.stdout.write ("""pmx2ly (GNU LilyPond) %s
809 This is free software. It is covered by the GNU General Public License,
810 and you are welcome to change it and/or distribute copies of it under
811 certain conditions. Invoke as `midi2ly --warranty' for more information.
813 Copyright (c) 2000--2003 by Han-Wen Nienhuys <hanwen@cs.uu.nl>
816 sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
820 (options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
825 if o== '--help' or o == '-h':
828 if o == '--version' or o == '-v':
832 if o == '--output' or o == '-o':
844 sys.stderr.write ('Processing `%s\'\n' % f)
847 out_filename = os.path.basename (re.sub ('(?i).pmx$', '.ly', f))
849 if out_filename == f:
850 out_filename = os.path.basename (f + '.ly')
852 sys.stderr.write ('Writing `%s\'' % out_filename)
857 fo = open (out_filename, 'w')
858 fo.write ('%% lily was here -- automatically converted by pmx2ly from %s\n' % f)