3 # (urg! wat een pokkeformaat (pokkenformaat?))
10 program_name = 'pmx2ly'
11 version = '@TOPLEVEL_VERSION@'
12 if version == '@' + 'TOPLEVEL_VERSION' + '@':
13 version = '(unknown version)' # uGUHGUHGHGUGH
17 return chr ( i + ord ('A'))
20 return re.sub ('[ \t]*%.*$\n', '', l)
23 return re.sub ('[ \n\t]+', ' ', l)
26 return re.sub ('^ ', '', re.sub (' $', '', l))
28 actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'}
30 def pitch_to_lily_string (tup):
33 nm = chr((n + 2) % 7 + ord ('a'))
62 def rat_multiply (a,b):
66 return rat_simplify ((x*p, y*q))
70 return rat_multiply (a, (q,p))
83 return rat_simplify ((x*q + p*y, y*q))
91 return rat_subtract (a, b )[0] > 0
93 def rat_subtract (a,b ):
94 return rat_add (a, rat_neg (b))
96 def rat_to_duration (frac):
99 while rat_larger (d, frac):
100 d = rat_multiply (d, (1,2))
103 frac = rat_subtract (frac, d)
105 if frac == rat_multiply (d, (1,2)):
107 elif frac == rat_multiply (d, (3,4)):
119 def __init__ (self, ch):
125 def __init__ (self,id):
127 self.start_chord = None
128 self.end_chord = None
129 def calculate (self):
134 s.note_suffix = s.note_suffix + '('
135 e.note_prefix = ')' + e.note_prefix
137 sys.stderr.write ("\nOrphaned slur")
144 self.current_slurs = []
146 def toggle_slur (self, id):
148 for s in self.current_slurs:
150 self.current_slurs.remove (s)
151 s.end_chord = self.chords[-1]
154 s.start_chord = self.chords[-1]
155 self.current_slurs.append (s)
156 self.slurs.append (s)
158 def last_chord (self):
159 return self.chords[-1]
160 def add_chord (self, ch):
161 self.chords.append (ch)
162 self.entries.append (ch)
163 def add_nonchord (self, nch):
164 self.entries.append (nch)
167 return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number))
171 for e in self.entries:
172 next = ' ' + e.dump ()
174 str = str + ln + next
178 if len (ln) +len (next) > 72:
185 id = self.idstring ()
187 str = '%s = \\notes { \n %s }\n '% (id, str)
189 def calculate_graces (self):
192 for c in self.chords:
193 if c.grace and not lastgr:
194 c.chord_prefix = c.chord_prefix + '\\grace { '
195 elif not c.grace and lastgr:
196 lastc.chord_suffix = lastc.chord_suffix + ' } '
199 def calculate (self):
200 self.calculate_graces ()
205 def __init__ (self, cl):
208 return '\\clef %s;' % self.type
222 self.voices = (Voice (), Voice())
229 for v in self.voices:
233 def set_clef (self, letter):
235 clstr = clef_table[letter]
236 self.voices[0].add_nonchord (Clef (clstr))
238 def current_voice (self):
239 return self.voices[self.voice_idx]
240 def next_voice (self):
241 self.voice_idx = (self.voice_idx + 1)%len (self.voices)
243 def calculate (self):
244 for v in self.voices:
247 return 'staff%s' % encodeint (self.number)
252 for v in self.voices:
254 refs = refs + '\\' + v.idstring ()+ ' '
256 str = str + '\n\n%s = \\context Staff = %s \n < \n %s >\n\n\n'% (self.idstring (), self.idstring (), refs)
260 def __init__ (self, number, base, dots):
263 self.replaces = tuplet_table[number]
269 length = rat_multiply (length, (3,2))
271 length = rat_multiply (length, (7,4))
273 length = rat_multiply (length, (1,self.replaces))
275 (nb,nd) =rat_to_duration (length)
280 def add_chord (self, ch):
281 ch.dots = self.note_dots
282 ch.basic_duration = self.note_base
283 self.chords.append (ch)
285 if len (self.chords) == 1:
286 ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number)
287 elif len (self.chords) == self.number:
288 ch.chord_suffix = ' }'
294 self.basic_duration = 0
297 self.chord_prefix = ''
298 self.chord_suffix = ''
299 self.note_prefix = ''
300 self.note_suffix = ''
306 if self.basic_duration == 0.5:
309 sd = '%d' % self.basic_duration
310 sd = sd + '.' * self.dots
311 for p in self.pitches:
314 str = str + pitch_to_lily_string (p) + sd
316 for s in self.scripts:
319 str = self.note_prefix +str + self.note_suffix
321 if len (self.pitches) > 1:
323 elif len (self.pitches) == 0:
326 str = self.chord_prefix + str + self.chord_suffix
364 def __init__ (self, filename):
366 self.forced_duration = None
369 self.tuplets_expected = 0
371 self.last_basic_duration = 4
373 self.parse (filename)
375 def set_staffs (self, number):
376 self.staffs = map (lambda x: Staff (), range(0, number))
381 for s in self.staffs:
384 def current_staff (self):
385 return self.staffs[self.staff_idx]
387 def current_voice (self):
388 return self.current_staff ().current_voice ()
390 def next_staff (self):
391 self.staff_idx = (self.staff_idx + 1)% len (self.staffs)
393 def parse_note (self, str):
403 ch = self.current_voice().last_chord()
407 self.current_voice().add_chord (ch)
409 name = (ord (str[0]) - ord('a') + 5) % 7
413 ch.grace = ch.grace or grace
423 while str[0] in 'dsfmnul0123456789.,+-':
427 alteration = alteration -1
433 alteration = alteration +1
436 elif c in DIGITS and durdigit == None and \
437 self.tuplets_expected == 0:
438 durdigit = string.atoi (c)
440 oct = string.atoi (c) - 3
442 extra_oct = extra_oct + 1
444 extra_oct = extra_oct - 1
453 tupnumber = string.atoi (str[0])
455 str=re.sub (r'^n?f?[+-0-9.]+', '' , str)
460 basic_duration = basicdur_table[durdigit]
461 self.last_basic_duration = basic_duration
463 sys.stderr.write ("""
464 Huh? expected duration, found %d Left was `%s'""" % (durdigit, str[:20]))
468 basic_duration = self.last_basic_duration
472 if name <> None and oct == None:
474 if self.last_name < name and name -self.last_name > 3:
476 elif self.last_name > name and self.last_name -name > 3:
479 oct = self.last_oct +e + extra_oct
483 self.last_name = name
486 ch.pitches.append ((oct, name, alteration))
488 # do before adding to tuplet.
489 ch.basic_duration = basic_duration
493 self.forced_duration = ch.basic_duration / forced_duration
496 tup =Tuplet (tupnumber, basic_duration, dots)
497 self.tuplets_expected = tupnumber
498 self.tuplets.append (tup)
500 if self.tuplets_expected > 0:
501 self.tuplets[-1].add_chord (ch)
502 self.tuplets_expected = self.tuplets_expected - 1
505 def parse_basso_continuo (self, str):
506 while str[0] in DIGITS +'#n-':
510 scr = '\\\\textsharp'
512 if len(scr)>1 or scr not in DIGITS:
515 self.current_voice().last_chord ().scripts.append (scr)
518 def parse_beams (self,str):
520 # self.current_voice().add_nonchord (Beam(c))
523 while str[0] in '+-0123456789':
530 def clean (self, ls):
531 ls = map (stripcomment, ls)
532 ls = map (stripwhite, ls)
533 ls = map (stripeols, ls)
536 ls = filter (lambda x: x <> '', ls)
539 def parse_header (self, ls):
545 opening = map (string.atoi, re.split ('[\t ]+', opening))
547 (no_staffs, no_instruments, timesig_num,timesig_den, ptimesig_num,
548 ptimesig_den, pickup_beats,keysig_number) = tuple (opening)
554 # opening = map (string.atoi, re.split ('[\t ]+', opening))
555 # (no_pages,no_systems, musicsize, fracindent) = tuple (opening)
558 while len (instruments) < no_instruments:
559 instruments.append (ls[0])
567 self.set_staffs (no_staffs)
568 for s in self.staffs:
580 def parse_ornament (self, left):
582 e = self.current_voice ().last_chord ()
592 orn = ornament_table[id]
594 sys.stderr.write ("unknown ornament `%s'\n" % id)
596 e.scripts.append (orn)
598 def parse_barcheck (self, left):
599 self.current_voice ().add_nonchord (Barcheck ())
603 def parse_slur (self, left):
608 if re.match ('[A-Z0-9]', left[0]):
611 while left[0] in 'uld0123456789+-.':
614 self.current_voice ().toggle_slur (id)
617 def parse_mumbo_jumbo (self,left):
619 while left and left[0] <> '\\':
624 def parsex (self,left):
626 while left[0] in DIGITS:
631 def parse_body (self, left):
632 left = re.sub ('[ \t\n]+', ' ', left)
636 if c in 'Gzabcdefgr':
637 left = self.parse_note (left)
638 elif c in DIGITS + 'n#-':
639 left = self.parse_basso_continuo (left)
643 left = self.parse_slur (left)
645 left = self.parse_barcheck (left)
647 left = self.parse_ornament (left)
649 left = self.parsex (left)
651 left = self.parse_beams (left)
652 elif left[:2] == "//":
653 self.current_staff().next_voice ()
659 left = self.parse_mumbo_jumbo(left)
661 sys.stderr.write ("""
662 Huh? Unknown directive `%s', before `%s'""" % (c, left[:20] ))
665 for c in self.staffs:
672 for s in self.staffs:
673 str = str + s.dump ()
674 refs = '\\' + s.idstring() + refs
676 str = str + "\n\n\\score { <\n %s\n > }" % refs
681 ls = open (fn).readlines ()
683 ls = self.parse_header (ls)
684 left = string.join (ls, ' ')
685 self.parse_body (left)
692 """Usage: pmx2ly [OPTION]... PMX-FILE
694 Convert PMX to LilyPond.
698 -o, --output=FILE set output filename to FILE
699 -v, --version version information
701 PMX is a Musixtex preprocessor written by Don Simons, see
702 http://www.gmd.de/Misc/Music/musixtex/software/pmx/
704 Report bugs to bug-gnu-music@gnu.org.
706 Written by Han-Wen Nienhuys <hanwen@cs.uu.nl>
710 def print_version ():
711 sys.stdout.write ("""pmx2ly (GNU LilyPond) %s
713 This is free software. It is covered by the GNU General Public License,
714 and you are welcome to change it and/or distribute copies of it under
715 certain conditions. Invoke as `midi2ly --warranty' for more information.
717 Copyright (c) 2000 by Han-Wen Nienhuys <hanwen@cs.uu.nl>
720 sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
724 (options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
729 if o== '--help' or o == '-h':
732 if o == '--version' or o == '-v':
736 if o == '--output' or o == '-o':
748 sys.stderr.write ('Processing `%s\'\n' % f)
751 out_filename = os.path.basename (re.sub ('(?i).pmx$', '.ly', f))
753 if out_filename == f:
754 out_filename = os.path.basename (f + '.ly')
756 sys.stderr.write ('Writing `%s\'' % out_filename)
761 fo = open (out_filename, 'w')
762 fo.write ('%% lily was here -- automatically converted by pmx2ly from %s\n' % f)