Document new double repeats and related barline type.
[lilypond.git] / scripts / musicxml2ly.py
blobe2a54008e321eef92b447d57d51f77a525eb710b
1 #!@TARGET_PYTHON@
2 # -*- coding: utf-8 -*-
3 import optparse
4 import sys
5 import re
6 import os
7 import string
8 import codecs
9 import zipfile
10 import StringIO
12 """
13 @relocate-preamble@
14 """
16 import lilylib as ly
17 _ = ly._
19 import musicxml
20 import musicexp
22 from rational import Rational
24 # Store command-line options in a global variable, so we can access them everythwere
25 options = None
27 class Conversion_Settings:
28 def __init__(self):
29 self.ignore_beaming = False
31 conversion_settings = Conversion_Settings ()
32 # Use a global variable to store the setting needed inside a \layout block.
33 # whenever we need to change a setting or add/remove an engraver, we can access
34 # this layout and add the corresponding settings
35 layout_information = musicexp.Layout ()
37 def progress (str):
38 ly.stderr_write (str + '\n')
39 sys.stderr.flush ()
41 def error_message (str):
42 ly.stderr_write (str + '\n')
43 sys.stderr.flush ()
45 needed_additional_definitions = []
46 additional_definitions = {
47 "snappizzicato": """#(define-markup-command (snappizzicato layout props) ()
48 (interpret-markup layout props
49 (markup #:stencil
50 (ly:stencil-translate-axis
51 (ly:stencil-add
52 (make-circle-stencil 0.7 0.1 #f)
53 (ly:make-stencil
54 (list 'draw-line 0.1 0 0.1 0 1)
55 '(-0.1 . 0.1) '(0.1 . 1)))
56 0.7 X))))""",
57 "eyeglasses": """eyeglassesps = #"0.15 setlinewidth
58 -0.9 0 translate
59 1.1 1.1 scale
60 1.2 0.7 moveto
61 0.7 0.7 0.5 0 361 arc
62 stroke
63 2.20 0.70 0.50 0 361 arc
64 stroke
65 1.45 0.85 0.30 0 180 arc
66 stroke
67 0.20 0.70 moveto
68 0.80 2.00 lineto
69 0.92 2.26 1.30 2.40 1.15 1.70 curveto
70 stroke
71 2.70 0.70 moveto
72 3.30 2.00 lineto
73 3.42 2.26 3.80 2.40 3.65 1.70 curveto
74 stroke"
75 eyeglasses = \markup { \with-dimensions #'(0 . 4.4) #'(0 . 2.5) \postscript #eyeglassesps }"""
78 def round_to_two_digits (val):
79 return round (val * 100) / 100
81 def extract_paper_information (tree):
82 paper = musicexp.Paper ()
83 defaults = tree.get_maybe_exist_named_child ('defaults')
84 if not defaults:
85 return None
86 tenths = -1
87 scaling = defaults.get_maybe_exist_named_child ('scaling')
88 if scaling:
89 mm = scaling.get_named_child ('millimeters')
90 mm = string.atof (mm.get_text ())
91 tn = scaling.get_maybe_exist_named_child ('tenths')
92 tn = string.atof (tn.get_text ())
93 tenths = mm / tn
94 paper.global_staff_size = mm * 72.27 / 25.4
95 # We need the scaling (i.e. the size of staff tenths for everything!
96 if tenths < 0:
97 return None
99 def from_tenths (txt):
100 return round_to_two_digits (string.atof (txt) * tenths / 10)
101 def set_paper_variable (varname, parent, element_name):
102 el = parent.get_maybe_exist_named_child (element_name)
103 if el: # Convert to cm from tenths
104 setattr (paper, varname, from_tenths (el.get_text ()))
106 pagelayout = defaults.get_maybe_exist_named_child ('page-layout')
107 if pagelayout:
108 # TODO: How can one have different margins for even and odd pages???
109 set_paper_variable ("page_height", pagelayout, 'page-height')
110 set_paper_variable ("page_width", pagelayout, 'page-width')
112 pmargins = pagelayout.get_named_children ('page-margins')
113 for pm in pmargins:
114 set_paper_variable ("left_margin", pm, 'left-margin')
115 set_paper_variable ("right_margin", pm, 'right-margin')
116 set_paper_variable ("bottom_margin", pm, 'bottom-margin')
117 set_paper_variable ("top_margin", pm, 'top-margin')
119 systemlayout = defaults.get_maybe_exist_named_child ('system-layout')
120 if systemlayout:
121 sl = systemlayout.get_maybe_exist_named_child ('system-margins')
122 if sl:
123 set_paper_variable ("system_left_margin", sl, 'left-margin')
124 set_paper_variable ("system_right_margin", sl, 'right-margin')
125 set_paper_variable ("system_distance", systemlayout, 'system-distance')
126 set_paper_variable ("top_system_distance", systemlayout, 'top-system-distance')
128 stafflayout = defaults.get_named_children ('staff-layout')
129 for sl in stafflayout:
130 nr = getattr (sl, 'number', 1)
131 dist = sl.get_named_child ('staff-distance')
132 #TODO: the staff distance needs to be set in the Staff context!!!
134 # TODO: Finish appearance?, music-font?, word-font?, lyric-font*, lyric-language*
135 appearance = defaults.get_named_child ('appearance')
136 if appearance:
137 lws = appearance.get_named_children ('line-width')
138 for lw in lws:
139 # Possible types are: beam, bracket, dashes,
140 # enclosure, ending, extend, heavy barline, leger,
141 # light barline, octave shift, pedal, slur middle, slur tip,
142 # staff, stem, tie middle, tie tip, tuplet bracket, and wedge
143 tp = lw.type
144 w = from_tenths (lw.get_text ())
145 # TODO: Do something with these values!
146 nss = appearance.get_named_children ('note-size')
147 for ns in nss:
148 # Possible types are: cue, grace and large
149 tp = ns.type
150 sz = from_tenths (ns.get_text ())
151 # TODO: Do something with these values!
152 # <other-appearance> elements have no specified meaning
154 rawmusicfont = defaults.get_named_child ('music-font')
155 if rawmusicfont:
156 # TODO: Convert the font
157 pass
158 rawwordfont = defaults.get_named_child ('word-font')
159 if rawwordfont:
160 # TODO: Convert the font
161 pass
162 rawlyricsfonts = defaults.get_named_children ('lyric-font')
163 for lyricsfont in rawlyricsfonts:
164 # TODO: Convert the font
165 pass
167 return paper
171 # score information is contained in the <work>, <identification> or <movement-title> tags
172 # extract those into a hash, indexed by proper lilypond header attributes
173 def extract_score_information (tree):
174 header = musicexp.Header ()
175 def set_if_exists (field, value):
176 if value:
177 header.set_field (field, musicxml.escape_ly_output_string (value))
179 work = tree.get_maybe_exist_named_child ('work')
180 if work:
181 set_if_exists ('title', work.get_work_title ())
182 set_if_exists ('worknumber', work.get_work_number ())
183 set_if_exists ('opus', work.get_opus ())
184 else:
185 movement_title = tree.get_maybe_exist_named_child ('movement-title')
186 if movement_title:
187 set_if_exists ('title', movement_title.get_text ())
189 identifications = tree.get_named_children ('identification')
190 for ids in identifications:
191 set_if_exists ('copyright', ids.get_rights ())
192 set_if_exists ('composer', ids.get_composer ())
193 set_if_exists ('arranger', ids.get_arranger ())
194 set_if_exists ('editor', ids.get_editor ())
195 set_if_exists ('poet', ids.get_poet ())
197 set_if_exists ('tagline', ids.get_encoding_software ())
198 set_if_exists ('encodingsoftware', ids.get_encoding_software ())
199 set_if_exists ('encodingdate', ids.get_encoding_date ())
200 set_if_exists ('encoder', ids.get_encoding_person ())
201 set_if_exists ('encodingdescription', ids.get_encoding_description ())
203 set_if_exists ('texidoc', ids.get_file_description ());
205 # Finally, apply the required compatibility modes
206 # Some applications created wrong MusicXML files, so we need to
207 # apply some compatibility mode, e.g. ignoring some features/tags
208 # in those files
209 software = ids.get_encoding_software_list ()
211 # Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin
212 # is missing all beam ends => ignore all beaming information
213 if "Dolet 3.4 for Sibelius" in software:
214 conversion_settings.ignore_beaming = True
215 progress (_ ("Encountered file created by Dolet 3.4 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
216 if "Noteworthy Composer" in software:
217 conversion_settings.ignore_beaming = True
218 progress (_ ("Encountered file created by Noteworthy Composer's nwc2xml, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
219 # TODO: Check for other unsupported features
221 return header
223 class PartGroupInfo:
224 def __init__ (self):
225 self.start = {}
226 self.end = {}
227 def is_empty (self):
228 return len (self.start) + len (self.end) == 0
229 def add_start (self, g):
230 self.start[getattr (g, 'number', "1")] = g
231 def add_end (self, g):
232 self.end[getattr (g, 'number', "1")] = g
233 def print_ly (self, printer):
234 error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
235 def ly_expression (self):
236 error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
237 return ''
239 def staff_attributes_to_string_tunings (mxl_attr):
240 details = mxl_attr.get_maybe_exist_named_child ('staff-details')
241 if not details:
242 return []
243 lines = 6
244 staff_lines = details.get_maybe_exist_named_child ('staff-lines')
245 if staff_lines:
246 lines = string.atoi (staff_lines.get_text ())
248 tunings = [0]*lines
249 staff_tunings = details.get_named_children ('staff-tuning')
250 for i in staff_tunings:
251 p = musicexp.Pitch()
252 line = 0
253 try:
254 line = string.atoi (i.line) - 1
255 except ValueError:
256 pass
257 tunings[line] = p
259 step = i.get_named_child (u'tuning-step')
260 step = step.get_text ().strip ()
261 p.step = musicxml_step_to_lily (step)
263 octave = i.get_named_child (u'tuning-octave')
264 octave = octave.get_text ().strip ()
265 p.octave = int (octave) - 4
267 alter = i.get_named_child (u'tuning-alter')
268 if alter:
269 p.alteration = int (alter.get_text ().strip ())
270 # lilypond seems to use the opposite ordering than MusicXML...
271 tunings.reverse ()
273 return tunings
276 def staff_attributes_to_lily_staff (mxl_attr):
277 if not mxl_attr:
278 return musicexp.Staff ()
280 (staff_id, attributes) = mxl_attr.items ()[0]
282 # distinguish by clef:
283 # percussion (percussion and rhythmic), tab, and everything else
284 clef_sign = None
285 clef = attributes.get_maybe_exist_named_child ('clef')
286 if clef:
287 sign = clef.get_maybe_exist_named_child ('sign')
288 if sign:
289 clef_sign = {"percussion": "percussion", "TAB": "tab"}.get (sign.get_text (), None)
291 lines = 5
292 details = attributes.get_named_children ('staff-details')
293 for d in details:
294 staff_lines = d.get_maybe_exist_named_child ('staff-lines')
295 if staff_lines:
296 lines = string.atoi (staff_lines.get_text ())
298 staff = None
299 if clef_sign == "percussion" and lines == 1:
300 staff = musicexp.RhythmicStaff ()
301 elif clef_sign == "percussion":
302 staff = musicexp.DrumStaff ()
303 # staff.drum_style_table = ???
304 elif clef_sign == "tab":
305 staff = musicexp.TabStaff ()
306 staff.string_tunings = staff_attributes_to_string_tunings (attributes)
307 # staff.tablature_format = ???
308 else:
309 # TODO: Handle case with lines <> 5!
310 staff = musicexp.Staff ()
312 return staff
315 def extract_score_structure (part_list, staffinfo):
316 score = musicexp.Score ()
317 structure = musicexp.StaffGroup (None)
318 score.set_contents (structure)
320 if not part_list:
321 return structure
323 def read_score_part (el):
324 if not isinstance (el, musicxml.Score_part):
325 return
326 # Depending on the attributes of the first measure, we create different
327 # types of staves (Staff, RhythmicStaff, DrumStaff, TabStaff, etc.)
328 staff = staff_attributes_to_lily_staff (staffinfo.get (el.id, None))
329 if not staff:
330 return None
331 staff.id = el.id
332 partname = el.get_maybe_exist_named_child ('part-name')
333 # Finale gives unnamed parts the name "MusicXML Part" automatically!
334 if partname and partname.get_text() != "MusicXML Part":
335 staff.instrument_name = partname.get_text ()
336 if el.get_maybe_exist_named_child ('part-abbreviation'):
337 staff.short_instrument_name = el.get_maybe_exist_named_child ('part-abbreviation').get_text ()
338 # TODO: Read in the MIDI device / instrument
339 return staff
341 def read_score_group (el):
342 if not isinstance (el, musicxml.Part_group):
343 return
344 group = musicexp.StaffGroup ()
345 if hasattr (el, 'number'):
346 id = el.number
347 group.id = id
348 #currentgroups_dict[id] = group
349 #currentgroups.append (id)
350 if el.get_maybe_exist_named_child ('group-name'):
351 group.instrument_name = el.get_maybe_exist_named_child ('group-name').get_text ()
352 if el.get_maybe_exist_named_child ('group-abbreviation'):
353 group.short_instrument_name = el.get_maybe_exist_named_child ('group-abbreviation').get_text ()
354 if el.get_maybe_exist_named_child ('group-symbol'):
355 group.symbol = el.get_maybe_exist_named_child ('group-symbol').get_text ()
356 if el.get_maybe_exist_named_child ('group-barline'):
357 group.spanbar = el.get_maybe_exist_named_child ('group-barline').get_text ()
358 return group
361 parts_groups = part_list.get_all_children ()
363 # the start/end group tags are not necessarily ordered correctly and groups
364 # might even overlap, so we can't go through the children sequentially!
366 # 1) Replace all Score_part objects by their corresponding Staff objects,
367 # also collect all group start/stop points into one PartGroupInfo object
368 staves = []
369 group_info = PartGroupInfo ()
370 for el in parts_groups:
371 if isinstance (el, musicxml.Score_part):
372 if not group_info.is_empty ():
373 staves.append (group_info)
374 group_info = PartGroupInfo ()
375 staff = read_score_part (el)
376 if staff:
377 staves.append (staff)
378 elif isinstance (el, musicxml.Part_group):
379 if el.type == "start":
380 group_info.add_start (el)
381 elif el.type == "stop":
382 group_info.add_end (el)
383 if not group_info.is_empty ():
384 staves.append (group_info)
386 # 2) Now, detect the groups:
387 group_starts = []
388 pos = 0
389 while pos < len (staves):
390 el = staves[pos]
391 if isinstance (el, PartGroupInfo):
392 prev_start = 0
393 if len (group_starts) > 0:
394 prev_start = group_starts[-1]
395 elif len (el.end) > 0: # no group to end here
396 el.end = {}
397 if len (el.end) > 0: # closes an existing group
398 ends = el.end.keys ()
399 prev_started = staves[prev_start].start.keys ()
400 grpid = None
401 intersection = filter(lambda x:x in ends, prev_started)
402 if len (intersection) > 0:
403 grpid = intersection[0]
404 else:
405 # Close the last started group
406 grpid = staves[prev_start].start.keys () [0]
407 # Find the corresponding closing tag and remove it!
408 j = pos + 1
409 foundclosing = False
410 while j < len (staves) and not foundclosing:
411 if isinstance (staves[j], PartGroupInfo) and staves[j].end.has_key (grpid):
412 foundclosing = True
413 del staves[j].end[grpid]
414 if staves[j].is_empty ():
415 del staves[j]
416 j += 1
417 grpobj = staves[prev_start].start[grpid]
418 group = read_score_group (grpobj)
419 # remove the id from both the start and end
420 if el.end.has_key (grpid):
421 del el.end[grpid]
422 del staves[prev_start].start[grpid]
423 if el.is_empty ():
424 del staves[pos]
425 # replace the staves with the whole group
426 for j in staves[(prev_start + 1):pos]:
427 group.append_staff (j)
428 del staves[(prev_start + 1):pos]
429 staves.insert (prev_start + 1, group)
430 # reset pos so that we continue at the correct position
431 pos = prev_start
432 # remove an empty start group
433 if staves[prev_start].is_empty ():
434 del staves[prev_start]
435 group_starts.remove (prev_start)
436 pos -= 1
437 elif len (el.start) > 0: # starts new part groups
438 group_starts.append (pos)
439 pos += 1
441 if len (staves) == 1:
442 return staves[0]
443 for i in staves:
444 structure.append_staff (i)
445 return score
448 def musicxml_duration_to_lily (mxl_note):
449 d = musicexp.Duration ()
450 # if the note has no Type child, then that method returns None. In that case,
451 # use the <duration> tag instead. If that doesn't exist, either -> Error
452 d.duration_log = mxl_note.get_duration_log ()
453 if d.duration_log == None:
454 if mxl_note._duration > 0:
455 return rational_to_lily_duration (mxl_note._duration)
456 else:
457 mxl_note.message (_ ("Encountered note at %s without type and duration (=%s)") % (mxl_note.start, mxl_note._duration) )
458 return None
459 else:
460 d.dots = len (mxl_note.get_typed_children (musicxml.Dot))
461 # Grace notes by specification have duration 0, so no time modification
462 # factor is possible. It even messes up the output with *0/1
463 if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
464 d.factor = mxl_note._duration / d.get_length ()
465 return d
467 def rational_to_lily_duration (rational_len):
468 d = musicexp.Duration ()
470 rational_len.normalize_self ()
471 d_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (rational_len.denominator (), -1)
473 # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration
474 if (d_log >= 0 and rational_len.numerator() in (1,3,5,7) ):
475 # account for the dots!
476 d.dots = (rational_len.numerator()-1)/2
477 d.duration_log = d_log - d.dots
478 elif (d_log >= 0):
479 d.duration_log = d_log
480 d.factor = Rational (rational_len.numerator ())
481 else:
482 error_message (_ ("Encountered rational duration with denominator %s, "
483 "unable to convert to lilypond duration") %
484 rational_len.denominator ())
485 # TODO: Test the above error message
486 return None
488 return d
490 def musicxml_partial_to_lily (partial_len):
491 if partial_len > 0:
492 p = musicexp.Partial ()
493 p.partial = rational_to_lily_duration (partial_len)
494 return p
495 else:
496 return Null
498 # Detect repeats and alternative endings in the chord event list (music_list)
499 # and convert them to the corresponding musicexp objects, containing nested
500 # music
501 def group_repeats (music_list):
502 repeat_replaced = True
503 music_start = 0
504 i = 0
505 # Walk through the list of expressions, looking for repeat structure
506 # (repeat start/end, corresponding endings). If we find one, try to find the
507 # last event of the repeat, replace the whole structure and start over again.
508 # For nested repeats, as soon as we encounter another starting repeat bar,
509 # treat that one first, and start over for the outer repeat.
510 while repeat_replaced and i < 100:
511 i += 1
512 repeat_start = -1 # position of repeat start / end
513 repeat_end = -1 # position of repeat start / end
514 repeat_times = 0
515 ending_start = -1 # position of current ending start
516 endings = [] # list of already finished endings
517 pos = 0
518 last = len (music_list) - 1
519 repeat_replaced = False
520 final_marker = 0
521 while pos < len (music_list) and not repeat_replaced:
522 e = music_list[pos]
523 repeat_finished = False
524 if isinstance (e, RepeatMarker):
525 if not repeat_times and e.times:
526 repeat_times = e.times
527 if e.direction == -1:
528 if repeat_end >= 0:
529 repeat_finished = True
530 else:
531 repeat_start = pos
532 repeat_end = -1
533 ending_start = -1
534 endings = []
535 elif e.direction == 1:
536 if repeat_start < 0:
537 repeat_start = 0
538 if repeat_end < 0:
539 repeat_end = pos
540 final_marker = pos
541 elif isinstance (e, EndingMarker):
542 if e.direction == -1:
543 if repeat_start < 0:
544 repeat_start = 0
545 if repeat_end < 0:
546 repeat_end = pos
547 ending_start = pos
548 elif e.direction == 1:
549 if ending_start < 0:
550 ending_start = 0
551 endings.append ([ending_start, pos])
552 ending_start = -1
553 final_marker = pos
554 elif not isinstance (e, musicexp.BarLine):
555 # As soon as we encounter an element when repeat start and end
556 # is set and we are not inside an alternative ending,
557 # this whole repeat structure is finished => replace it
558 if repeat_start >= 0 and repeat_end > 0 and ending_start < 0:
559 repeat_finished = True
561 # Finish off all repeats without explicit ending bar (e.g. when
562 # we convert only one page of a multi-page score with repeats)
563 if pos == last and repeat_start >= 0:
564 repeat_finished = True
565 final_marker = pos
566 if repeat_end < 0:
567 repeat_end = pos
568 if ending_start >= 0:
569 endings.append ([ending_start, pos])
570 ending_start = -1
572 if repeat_finished:
573 # We found the whole structure replace it!
574 r = musicexp.RepeatedMusic ()
575 if repeat_times <= 0:
576 repeat_times = 2
577 r.repeat_count = repeat_times
578 # don't erase the first element for "implicit" repeats (i.e. no
579 # starting repeat bars at the very beginning)
580 start = repeat_start+1
581 if repeat_start == music_start:
582 start = music_start
583 r.set_music (music_list[start:repeat_end])
584 for (start, end) in endings:
585 s = musicexp.SequentialMusic ()
586 s.elements = music_list[start+1:end]
587 r.add_ending (s)
588 del music_list[repeat_start:final_marker+1]
589 music_list.insert (repeat_start, r)
590 repeat_replaced = True
591 pos += 1
592 # TODO: Implement repeats until the end without explicit ending bar
593 return music_list
597 def group_tuplets (music_list, events):
600 """Collect Musics from
601 MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
605 indices = []
606 brackets = {}
608 j = 0
609 for (ev_chord, tuplet_elt, fraction) in events:
610 while (j < len (music_list)):
611 if music_list[j] == ev_chord:
612 break
613 j += 1
614 nr = tuplet_elt.number
615 if tuplet_elt.type == 'start':
616 tuplet_info = [j, None, fraction]
617 indices.append (tuplet_info)
618 brackets[nr] = tuplet_info
619 elif tuplet_elt.type == 'stop':
620 bracket_info = brackets.get (nr, None)
621 if bracket_info:
622 bracket_info[1] = j
623 del brackets[nr]
625 new_list = []
626 last = 0
627 for (i1, i2, frac) in indices:
628 if i1 > i2:
629 continue
631 new_list.extend (music_list[last:i1])
632 seq = musicexp.SequentialMusic ()
633 last = i2 + 1
634 seq.elements = music_list[i1:last]
636 tsm = musicexp.TimeScaledMusic ()
637 tsm.element = seq
639 tsm.numerator = frac[0]
640 tsm.denominator = frac[1]
642 new_list.append (tsm)
644 new_list.extend (music_list[last:])
645 return new_list
648 def musicxml_clef_to_lily (attributes):
649 change = musicexp.ClefChange ()
650 (change.type, change.position, change.octave) = attributes.get_clef_information ()
651 return change
653 def musicxml_time_to_lily (attributes):
654 (beats, type) = attributes.get_time_signature ()
656 change = musicexp.TimeSignatureChange()
657 change.fraction = (beats, type)
659 return change
661 def musicxml_key_to_lily (attributes):
662 start_pitch = musicexp.Pitch ()
663 (fifths, mode) = attributes.get_key_signature ()
664 try:
665 (n,a) = {
666 'major' : (0,0),
667 'minor' : (5,0),
668 'ionian' : (0,0),
669 'dorian' : (1,0),
670 'phrygian' : (2,0),
671 'lydian' : (3,0),
672 'mixolydian': (4,0),
673 'aeolian' : (5,0),
674 'locrian' : (6,0),
675 }[mode]
676 start_pitch.step = n
677 start_pitch.alteration = a
678 except KeyError:
679 error_message (_ ("unknown mode %s, expecting 'major' or 'minor'") % mode)
681 fifth = musicexp.Pitch()
682 fifth.step = 4
683 if fifths < 0:
684 fifths *= -1
685 fifth.step *= -1
686 fifth.normalize ()
688 for x in range (fifths):
689 start_pitch = start_pitch.transposed (fifth)
691 start_pitch.octave = 0
693 change = musicexp.KeySignatureChange()
694 change.mode = mode
695 change.tonic = start_pitch
696 return change
698 def musicxml_transpose_to_lily (attributes):
699 transpose = attributes.get_transposition ()
700 if not transpose:
701 return None
703 shift = musicexp.Pitch ()
704 octave_change = transpose.get_maybe_exist_named_child ('octave-change')
705 if octave_change:
706 shift.octave = string.atoi (octave_change.get_text ())
707 chromatic_shift = string.atoi (transpose.get_named_child ('chromatic').get_text ())
708 chromatic_shift_normalized = chromatic_shift % 12;
709 (shift.step, shift.alteration) = [
710 (0,0), (0,1), (1,0), (2,-1), (2,0),
711 (3,0), (3,1), (4,0), (5,-1), (5,0),
712 (6,-1), (6,0)][chromatic_shift_normalized];
714 shift.octave += (chromatic_shift - chromatic_shift_normalized) / 12
716 diatonic = transpose.get_maybe_exist_named_child ('diatonic')
717 if diatonic:
718 diatonic_step = string.atoi (diatonic.get_text ()) % 7
719 if diatonic_step != shift.step:
720 # We got the alter incorrect!
721 old_semitones = shift.semitones ()
722 shift.step = diatonic_step
723 new_semitones = shift.semitones ()
724 shift.alteration += old_semitones - new_semitones
726 transposition = musicexp.Transposition ()
727 transposition.pitch = musicexp.Pitch ().transposed (shift)
728 return transposition
731 def musicxml_attributes_to_lily (attrs):
732 elts = []
733 attr_dispatch = {
734 'clef': musicxml_clef_to_lily,
735 'time': musicxml_time_to_lily,
736 'key': musicxml_key_to_lily,
737 'transpose': musicxml_transpose_to_lily,
739 for (k, func) in attr_dispatch.items ():
740 children = attrs.get_named_children (k)
741 if children:
742 elts.append (func (attrs))
744 return elts
746 class Marker (musicexp.Music):
747 def __init__ (self):
748 self.direction = 0
749 self.event = None
750 def print_ly (self, printer):
751 ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self)
752 pass
753 def ly_expression (self):
754 return ""
755 class RepeatMarker (Marker):
756 def __init__ (self):
757 Marker.__init__ (self)
758 self.times = 0
759 class EndingMarker (Marker):
760 pass
762 # Convert the <barline> element to musicxml.BarLine (for non-standard barlines)
763 # and to RepeatMarker and EndingMarker objects for repeat and
764 # alternatives start/stops
765 def musicxml_barline_to_lily (barline):
766 # retval contains all possible markers in the order:
767 # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending
768 retval = {}
769 bartype_element = barline.get_maybe_exist_named_child ("bar-style")
770 repeat_element = barline.get_maybe_exist_named_child ("repeat")
771 ending_element = barline.get_maybe_exist_named_child ("ending")
773 bartype = None
774 if bartype_element:
775 bartype = bartype_element.get_text ()
777 if repeat_element and hasattr (repeat_element, 'direction'):
778 repeat = RepeatMarker ()
779 repeat.direction = {"forward": -1, "backward": 1}.get (repeat_element.direction, 0)
781 if ( (repeat_element.direction == "forward" and bartype == "heavy-light") or
782 (repeat_element.direction == "backward" and bartype == "light-heavy") ):
783 bartype = None
784 if hasattr (repeat_element, 'times'):
785 try:
786 repeat.times = int (repeat_element.times)
787 except ValueError:
788 repeat.times = 2
789 repeat.event = barline
790 if repeat.direction == -1:
791 retval[3] = repeat
792 else:
793 retval[1] = repeat
795 if ending_element and hasattr (ending_element, 'type'):
796 ending = EndingMarker ()
797 ending.direction = {"start": -1, "stop": 1, "discontinue": 1}.get (ending_element.type, 0)
798 ending.event = barline
799 if ending.direction == -1:
800 retval[4] = ending
801 else:
802 retval[0] = ending
804 if bartype:
805 b = musicexp.BarLine ()
806 b.type = bartype
807 retval[2] = b
809 return retval.values ()
811 spanner_event_dict = {
812 'beam' : musicexp.BeamEvent,
813 'dashes' : musicexp.TextSpannerEvent,
814 'bracket' : musicexp.BracketSpannerEvent,
815 'glissando' : musicexp.GlissandoEvent,
816 'octave-shift' : musicexp.OctaveShiftEvent,
817 'pedal' : musicexp.PedalEvent,
818 'slide' : musicexp.GlissandoEvent,
819 'slur' : musicexp.SlurEvent,
820 'wavy-line' : musicexp.TrillSpanEvent,
821 'wedge' : musicexp.HairpinEvent
823 spanner_type_dict = {
824 'start': -1,
825 'begin': -1,
826 'crescendo': -1,
827 'decreschendo': -1,
828 'diminuendo': -1,
829 'continue': 0,
830 'change': 0,
831 'up': -1,
832 'down': -1,
833 'stop': 1,
834 'end' : 1
837 def musicxml_spanner_to_lily_event (mxl_event):
838 ev = None
840 name = mxl_event.get_name()
841 func = spanner_event_dict.get (name)
842 if func:
843 ev = func()
844 else:
845 error_message (_ ('unknown span event %s') % mxl_event)
848 type = mxl_event.get_type ()
849 span_direction = spanner_type_dict.get (type)
850 # really check for None, because some types will be translated to 0, which
851 # would otherwise also lead to the unknown span warning
852 if span_direction != None:
853 ev.span_direction = span_direction
854 else:
855 error_message (_ ('unknown span type %s for %s') % (type, name))
857 ev.set_span_type (type)
858 ev.line_type = getattr (mxl_event, 'line-type', 'solid')
860 # assign the size, which is used for octave-shift, etc.
861 ev.size = mxl_event.get_size ()
863 return ev
865 def musicxml_direction_to_indicator (direction):
866 return { "above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1 }.get (direction, 0)
868 def musicxml_fermata_to_lily_event (mxl_event):
869 ev = musicexp.ArticulationEvent ()
870 txt = mxl_event.get_text ()
871 # The contents of the element defined the shape, possible are normal, angled and square
872 ev.type = { "angled": "shortfermata", "square": "longfermata" }.get (txt, "fermata")
873 if hasattr (mxl_event, 'type'):
874 dir = musicxml_direction_to_indicator (mxl_event.type)
875 if dir and options.convert_directions:
876 ev.force_direction = dir
877 return ev
879 def musicxml_arpeggiate_to_lily_event (mxl_event):
880 ev = musicexp.ArpeggioEvent ()
881 ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
882 return ev
884 def musicxml_nonarpeggiate_to_lily_event (mxl_event):
885 ev = musicexp.ArpeggioEvent ()
886 ev.non_arpeggiate = True
887 ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
888 return ev
890 def musicxml_tremolo_to_lily_event (mxl_event):
891 ev = musicexp.TremoloEvent ()
892 txt = mxl_event.get_text ()
893 if txt:
894 ev.bars = txt
895 else:
896 ev.bars = "3"
897 return ev
899 def musicxml_falloff_to_lily_event (mxl_event):
900 ev = musicexp.BendEvent ()
901 ev.alter = -4
902 return ev
904 def musicxml_doit_to_lily_event (mxl_event):
905 ev = musicexp.BendEvent ()
906 ev.alter = 4
907 return ev
909 def musicxml_bend_to_lily_event (mxl_event):
910 ev = musicexp.BendEvent ()
911 ev.alter = mxl_event.bend_alter ()
912 return ev
914 def musicxml_caesura_to_lily_event (mxl_event):
915 ev = musicexp.MarkupEvent ()
916 # FIXME: default to straight or curved caesura?
917 ev.contents = "\\musicglyph #\"scripts.caesura.straight\""
918 ev.force_direction = 1
919 return ev
921 def musicxml_fingering_event (mxl_event):
922 ev = musicexp.ShortArticulationEvent ()
923 ev.type = mxl_event.get_text ()
924 return ev
926 def musicxml_snappizzicato_event (mxl_event):
927 needed_additional_definitions.append ("snappizzicato")
928 ev = musicexp.MarkupEvent ()
929 ev.contents = "\\snappizzicato"
930 return ev
932 def musicxml_string_event (mxl_event):
933 ev = musicexp.NoDirectionArticulationEvent ()
934 ev.type = mxl_event.get_text ()
935 return ev
937 def musicxml_accidental_mark (mxl_event):
938 ev = musicexp.MarkupEvent ()
939 contents = { "sharp": "\\sharp",
940 "natural": "\\natural",
941 "flat": "\\flat",
942 "double-sharp": "\\doublesharp",
943 "sharp-sharp": "\\sharp\\sharp",
944 "flat-flat": "\\flat\\flat",
945 "flat-flat": "\\doubleflat",
946 "natural-sharp": "\\natural\\sharp",
947 "natural-flat": "\\natural\\flat",
948 "quarter-flat": "\\semiflat",
949 "quarter-sharp": "\\semisharp",
950 "three-quarters-flat": "\\sesquiflat",
951 "three-quarters-sharp": "\\sesquisharp",
952 }.get (mxl_event.get_text ())
953 if contents:
954 ev.contents = contents
955 return ev
956 else:
957 return None
959 # translate articulations, ornaments and other notations into ArticulationEvents
960 # possible values:
961 # -) string (ArticulationEvent with that name)
962 # -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
963 # -) (class, name) (like string, only that a different class than ArticulationEvent is used)
964 # TODO: Some translations are missing!
965 articulations_dict = {
966 "accent": (musicexp.ShortArticulationEvent, ">"), # or "accent"
967 "accidental-mark": musicxml_accidental_mark,
968 "bend": musicxml_bend_to_lily_event,
969 "breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"),
970 "caesura": musicxml_caesura_to_lily_event,
971 #"delayed-turn": "?",
972 "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato"
973 "doit": musicxml_doit_to_lily_event,
974 #"double-tongue": "?",
975 "down-bow": "downbow",
976 "falloff": musicxml_falloff_to_lily_event,
977 "fingering": musicxml_fingering_event,
978 #"fingernails": "?",
979 #"fret": "?",
980 #"hammer-on": "?",
981 "harmonic": "flageolet",
982 #"heel": "?",
983 "inverted-mordent": "prall",
984 "inverted-turn": "reverseturn",
985 "mordent": "mordent",
986 "open-string": "open",
987 #"plop": "?",
988 #"pluck": "?",
989 #"pull-off": "?",
990 #"schleifer": "?",
991 #"scoop": "?",
992 #"shake": "?",
993 "snap-pizzicato": musicxml_snappizzicato_event,
994 #"spiccato": "?",
995 "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo"
996 "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
997 "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
998 #"stress": "?",
999 "string": musicxml_string_event,
1000 "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato"
1001 #"tap": "?",
1002 "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto"
1003 "thumb-position": "thumb",
1004 #"toe": "?",
1005 "turn": "turn",
1006 "tremolo": musicxml_tremolo_to_lily_event,
1007 "trill-mark": "trill",
1008 #"triple-tongue": "?",
1009 #"unstress": "?"
1010 "up-bow": "upbow",
1011 #"wavy-line": "?",
1013 articulation_spanners = [ "wavy-line" ]
1015 def musicxml_articulation_to_lily_event (mxl_event):
1016 # wavy-line elements are treated as trill spanners, not as articulation ornaments
1017 if mxl_event.get_name () in articulation_spanners:
1018 return musicxml_spanner_to_lily_event (mxl_event)
1020 tmp_tp = articulations_dict.get (mxl_event.get_name ())
1021 if not tmp_tp:
1022 return
1024 if isinstance (tmp_tp, str):
1025 ev = musicexp.ArticulationEvent ()
1026 ev.type = tmp_tp
1027 elif isinstance (tmp_tp, tuple):
1028 ev = tmp_tp[0] ()
1029 ev.type = tmp_tp[1]
1030 else:
1031 ev = tmp_tp (mxl_event)
1033 # Some articulations use the type attribute, other the placement...
1034 dir = None
1035 if hasattr (mxl_event, 'type') and options.convert_directions:
1036 dir = musicxml_direction_to_indicator (mxl_event.type)
1037 if hasattr (mxl_event, 'placement') and options.convert_directions:
1038 dir = musicxml_direction_to_indicator (mxl_event.placement)
1039 if dir:
1040 ev.force_direction = dir
1041 return ev
1045 def musicxml_dynamics_to_lily_event (dynentry):
1046 dynamics_available = (
1047 "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf",
1048 "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" )
1049 dynamicsname = dynentry.get_name ()
1050 if dynamicsname == "other-dynamics":
1051 dynamicsname = dynentry.get_text ()
1052 if not dynamicsname or dynamicsname=="#text":
1053 return
1055 if not dynamicsname in dynamics_available:
1056 # Get rid of - in tag names (illegal in ly tags!)
1057 dynamicstext = dynamicsname
1058 dynamicsname = string.replace (dynamicsname, "-", "")
1059 additional_definitions[dynamicsname] = dynamicsname + \
1060 " = #(make-dynamic-script \"" + dynamicstext + "\")"
1061 needed_additional_definitions.append (dynamicsname)
1062 event = musicexp.DynamicsEvent ()
1063 event.type = dynamicsname
1064 return event
1066 # Convert single-color two-byte strings to numbers 0.0 - 1.0
1067 def hexcolorval_to_nr (hex_val):
1068 try:
1069 v = int (hex_val, 16)
1070 if v == 255:
1071 v = 256
1072 return v / 256.
1073 except ValueError:
1074 return 0.
1076 def hex_to_color (hex_val):
1077 res = re.match (r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', hex_val, re.IGNORECASE)
1078 if res:
1079 return map (lambda x: hexcolorval_to_nr (x), res.group (2,3,4))
1080 else:
1081 return None
1083 def musicxml_words_to_lily_event (words):
1084 event = musicexp.TextEvent ()
1085 text = words.get_text ()
1086 text = re.sub ('^ *\n? *', '', text)
1087 text = re.sub (' *\n? *$', '', text)
1088 event.text = text
1090 if hasattr (words, 'default-y') and options.convert_directions:
1091 offset = getattr (words, 'default-y')
1092 try:
1093 off = string.atoi (offset)
1094 if off > 0:
1095 event.force_direction = 1
1096 else:
1097 event.force_direction = -1
1098 except ValueError:
1099 event.force_direction = 0
1101 if hasattr (words, 'font-weight'):
1102 font_weight = { "normal": '', "bold": '\\bold' }.get (getattr (words, 'font-weight'), '')
1103 if font_weight:
1104 event.markup += font_weight
1106 if hasattr (words, 'font-size'):
1107 size = getattr (words, 'font-size')
1108 font_size = {
1109 "xx-small": '\\teeny',
1110 "x-small": '\\tiny',
1111 "small": '\\small',
1112 "medium": '',
1113 "large": '\\large',
1114 "x-large": '\\huge',
1115 "xx-large": '\\larger\\huge'
1116 }.get (size, '')
1117 if font_size:
1118 event.markup += font_size
1120 if hasattr (words, 'color'):
1121 color = getattr (words, 'color')
1122 rgb = hex_to_color (color)
1123 if rgb:
1124 event.markup += "\\with-color #(rgb-color %s %s %s)" % (rgb[0], rgb[1], rgb[2])
1126 if hasattr (words, 'font-style'):
1127 font_style = { "italic": '\\italic' }.get (getattr (words, 'font-style'), '')
1128 if font_style:
1129 event.markup += font_style
1131 # TODO: How should I best convert the font-family attribute?
1133 # TODO: How can I represent the underline, overline and line-through
1134 # attributes in Lilypond? Values of these attributes indicate
1135 # the number of lines
1137 return event
1140 # convert accordion-registration to lilypond.
1141 # Since lilypond does not have any built-in commands, we need to create
1142 # the markup commands manually and define our own variables.
1143 # Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
1144 def musicxml_accordion_to_markup (mxl_event):
1145 commandname = "accReg"
1146 command = ""
1148 high = mxl_event.get_maybe_exist_named_child ('accordion-high')
1149 if high:
1150 commandname += "H"
1151 command += """\\combine
1152 \\raise #2.5 \\musicglyph #\"accordion.accDot\"
1154 middle = mxl_event.get_maybe_exist_named_child ('accordion-middle')
1155 if middle:
1156 # By default, use one dot (when no or invalid content is given). The
1157 # MusicXML spec is quiet about this case...
1158 txt = 1
1159 try:
1160 txt = string.atoi (middle.get_text ())
1161 except ValueError:
1162 pass
1163 if txt == 3:
1164 commandname += "MMM"
1165 command += """\\combine
1166 \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1167 \\combine
1168 \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\"
1169 \\combine
1170 \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\"
1172 elif txt == 2:
1173 commandname += "MM"
1174 command += """\\combine
1175 \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.accDot\"
1176 \\combine
1177 \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\"
1179 elif not txt <= 0:
1180 commandname += "M"
1181 command += """\\combine
1182 \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1184 low = mxl_event.get_maybe_exist_named_child ('accordion-low')
1185 if low:
1186 commandname += "L"
1187 command += """\\combine
1188 \\raise #0.5 \musicglyph #\"accordion.accDot\"
1191 command += "\musicglyph #\"accordion.accDiscant\""
1192 command = "\\markup { \\normalsize %s }" % command
1193 # Define the newly built command \accReg[H][MMM][L]
1194 additional_definitions[commandname] = "%s = %s" % (commandname, command)
1195 needed_additional_definitions.append (commandname)
1196 return "\\%s" % commandname
1198 def musicxml_accordion_to_ly (mxl_event):
1199 txt = musicxml_accordion_to_markup (mxl_event)
1200 if txt:
1201 ev = musicexp.MarkEvent (txt)
1202 return ev
1203 return
1206 def musicxml_rehearsal_to_ly_mark (mxl_event):
1207 text = mxl_event.get_text ()
1208 if not text:
1209 return
1210 # default is boxed rehearsal marks!
1211 encl = "box"
1212 if hasattr (mxl_event, 'enclosure'):
1213 encl = {"none": None, "square": "box", "circle": "circle" }.get (mxl_event.enclosure, None)
1214 if encl:
1215 text = "\\%s { %s }" % (encl, text)
1216 ev = musicexp.MarkEvent ("\\markup { %s }" % text)
1217 return ev
1219 def musicxml_harp_pedals_to_ly (mxl_event):
1220 count = 0
1221 result = "\\harp-pedal #\""
1222 for t in mxl_event.get_named_children ('pedal-tuning'):
1223 alter = t.get_named_child ('pedal-alter')
1224 if alter:
1225 val = int (alter.get_text ().strip ())
1226 result += {1: "v", 0: "-", -1: "^"}.get (val, "")
1227 count += 1
1228 if count == 3:
1229 result += "|"
1230 ev = musicexp.MarkupEvent ()
1231 ev.contents = result + "\""
1232 return ev
1234 def musicxml_eyeglasses_to_ly (mxl_event):
1235 needed_additional_definitions.append ("eyeglasses")
1236 return musicexp.MarkEvent ("\\eyeglasses")
1238 def next_non_hash_index (lst, pos):
1239 pos += 1
1240 while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
1241 pos += 1
1242 return pos
1244 def musicxml_metronome_to_ly (mxl_event):
1245 children = mxl_event.get_all_children ()
1246 if not children:
1247 return
1249 index = -1
1250 index = next_non_hash_index (children, index)
1251 if isinstance (children[index], musicxml.BeatUnit):
1252 # first form of metronome-mark, using unit and beats/min or other unit
1253 ev = musicexp.TempoMark ()
1254 if hasattr (mxl_event, 'parentheses'):
1255 ev.set_parentheses (mxl_event.parentheses == "yes")
1257 d = musicexp.Duration ()
1258 d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1259 index = next_non_hash_index (children, index)
1260 if isinstance (children[index], musicxml.BeatUnitDot):
1261 d.dots = 1
1262 index = next_non_hash_index (children, index)
1263 ev.set_base_duration (d)
1264 if isinstance (children[index], musicxml.BeatUnit):
1265 # Form "note = newnote"
1266 newd = musicexp.Duration ()
1267 newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1268 index = next_non_hash_index (children, index)
1269 if isinstance (children[index], musicxml.BeatUnitDot):
1270 newd.dots = 1
1271 index = next_non_hash_index (children, index)
1272 ev.set_new_duration (newd)
1273 elif isinstance (children[index], musicxml.PerMinute):
1274 # Form "note = bpm"
1275 try:
1276 beats = int (children[index].get_text ())
1277 ev.set_beats_per_minute (beats)
1278 except ValueError:
1279 pass
1280 else:
1281 error_message (_ ("Unknown metronome mark, ignoring"))
1282 return
1283 return ev
1284 else:
1285 #TODO: Implement the other (more complex) way for tempo marks!
1286 error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
1287 return
1289 # translate directions into Events, possible values:
1290 # -) string (MarkEvent with that command)
1291 # -) function (function(mxl_event) needs to return a full Event-derived object
1292 # -) (class, name) (like string, only that a different class than MarkEvent is used)
1293 directions_dict = {
1294 'accordion-registration' : musicxml_accordion_to_ly,
1295 'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
1296 # 'damp' : ???
1297 # 'damp-all' : ???
1298 'eyeglasses': musicxml_eyeglasses_to_ly,
1299 'harp-pedals' : musicxml_harp_pedals_to_ly,
1300 # 'image' : ???
1301 'metronome' : musicxml_metronome_to_ly,
1302 'rehearsal' : musicxml_rehearsal_to_ly_mark,
1303 # 'scordatura' : ???
1304 'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
1305 'words' : musicxml_words_to_lily_event,
1307 directions_spanners = [ 'octave-shift', 'pedal', 'wedge', 'dashes', 'bracket' ]
1309 def musicxml_direction_to_lily (n):
1310 # TODO: Handle the <staff> element!
1311 res = []
1312 # placement applies to all children!
1313 dir = None
1314 if hasattr (n, 'placement') and options.convert_directions:
1315 dir = musicxml_direction_to_indicator (n.placement)
1316 dirtype_children = []
1317 # TODO: The direction-type is used for grouping (e.g. dynamics with text),
1318 # so we can't simply flatten them out!
1319 for dt in n.get_typed_children (musicxml.DirType):
1320 dirtype_children += dt.get_all_children ()
1322 for entry in dirtype_children:
1323 # backets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
1324 if entry.get_name() in directions_spanners:
1325 event = musicxml_spanner_to_lily_event (entry)
1326 if event:
1327 res.append (event)
1328 continue
1330 # now treat all the "simple" ones, that can be translated using the dict
1331 ev = None
1332 tmp_tp = directions_dict.get (entry.get_name (), None)
1333 if isinstance (tmp_tp, str): # string means MarkEvent
1334 ev = musicexp.MarkEvent (tmp_tp)
1335 elif isinstance (tmp_tp, tuple): # tuple means (EventClass, "text")
1336 ev = tmp_tp[0] (tmp_tp[1])
1337 elif tmp_tp:
1338 ev = tmp_tp (entry)
1339 if ev:
1340 # TODO: set the correct direction! Unfortunately, \mark in ly does
1341 # not seem to support directions!
1342 res.append (ev)
1343 continue
1345 if entry.get_name () == "dynamics":
1346 for dynentry in entry.get_all_children ():
1347 ev = musicxml_dynamics_to_lily_event (dynentry)
1348 if ev:
1349 res.append (ev)
1351 return res
1353 def musicxml_frame_to_lily_event (frame):
1354 ev = musicexp.FretEvent ()
1355 ev.strings = frame.get_strings ()
1356 ev.frets = frame.get_frets ()
1357 #offset = frame.get_first_fret () - 1
1358 barre = []
1359 for fn in frame.get_named_children ('frame-note'):
1360 fret = fn.get_fret ()
1361 if fret <= 0:
1362 fret = "o"
1363 el = [ fn.get_string (), fret ]
1364 fingering = fn.get_fingering ()
1365 if fingering >= 0:
1366 el.append (fingering)
1367 ev.elements.append (el)
1368 b = fn.get_barre ()
1369 if b == 'start':
1370 barre[0] = el[0] # start string
1371 barre[2] = el[1] # fret
1372 elif b == 'stop':
1373 barre[1] = el[0] # end string
1374 if barre:
1375 ev.barre = barre
1376 return ev
1378 def musicxml_harmony_to_lily (n):
1379 res = []
1380 for f in n.get_named_children ('frame'):
1381 ev = musicxml_frame_to_lily_event (f)
1382 if ev:
1383 res.append (ev)
1384 return res
1387 def musicxml_chordpitch_to_lily (mxl_cpitch):
1388 r = musicexp.ChordPitch ()
1389 r.alteration = mxl_cpitch.get_alteration ()
1390 r.step = musicxml_step_to_lily (mxl_cpitch.get_step ())
1391 return r
1393 chordkind_dict = {
1394 'major': '5',
1395 'minor': 'm5',
1396 'augmented': 'aug5',
1397 'diminished': 'dim5',
1398 # Sevenths:
1399 'dominant': '7',
1400 'major-seventh': 'maj7',
1401 'minor-seventh': 'm7',
1402 'diminished-seventh': 'dim7',
1403 'augmented-seventh': 'aug7',
1404 'half-diminished': 'dim5m7',
1405 'major-minor': 'maj7m5',
1406 # Sixths:
1407 'major-sixth': '6',
1408 'minor-sixth': 'm6',
1409 # Ninths:
1410 'dominant-ninth': '9',
1411 'major-ninth': 'maj9',
1412 'minor-ninth': 'm9',
1413 # 11ths (usually as the basis for alteration):
1414 'dominant-11th': '11',
1415 'major-11th': 'maj11',
1416 'minor-11th': 'm11',
1417 # 13ths (usually as the basis for alteration):
1418 'dominant-13th': '13.11',
1419 'major-13th': 'maj13.11',
1420 'minor-13th': 'm13',
1421 # Suspended:
1422 'suspended-second': 'sus2',
1423 'suspended-fourth': 'sus4',
1424 # Functional sixths:
1425 # TODO
1426 #'Neapolitan': '???',
1427 #'Italian': '???',
1428 #'French': '???',
1429 #'German': '???',
1430 # Other:
1431 #'pedal': '???',(pedal-point bass)
1432 'power': '5^3',
1433 #'Tristan': '???',
1434 'other': '1',
1435 'none': None,
1438 def musicxml_chordkind_to_lily (kind):
1439 res = chordkind_dict.get (kind, None)
1440 # Check for None, since a major chord is converted to ''
1441 if res == None:
1442 error_message (_ ("Unable to convert chord type %s to lilypond.") % kind)
1443 return res
1445 def musicxml_harmony_to_lily_chordname (n):
1446 res = []
1447 root = n.get_maybe_exist_named_child ('root')
1448 if root:
1449 ev = musicexp.ChordNameEvent ()
1450 ev.root = musicxml_chordpitch_to_lily (root)
1451 kind = n.get_maybe_exist_named_child ('kind')
1452 if kind:
1453 ev.kind = musicxml_chordkind_to_lily (kind.get_text ())
1454 if not ev.kind:
1455 return res
1456 bass = n.get_maybe_exist_named_child ('bass')
1457 if bass:
1458 ev.bass = musicxml_chordpitch_to_lily (bass)
1459 inversion = n.get_maybe_exist_named_child ('inversion')
1460 if inversion:
1461 # TODO: Lilypond does not support inversions, does it?
1463 # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
1464 # 4. LilyPond supports the first inversion in the form of added
1465 # bass notes. So the first inversion of C major would be c:/g.
1466 # To get the second inversion of C major, you would need to do
1467 # e:6-3-^5 or e:m6-^5. However, both of these techniques
1468 # require you to know the chord and calculate either the fifth
1469 # pitch (for the first inversion) or the third pitch (for the
1470 # second inversion) so they may not be helpful for musicxml2ly.
1471 inversion_count = string.atoi (inversion.get_text ())
1472 if inversion_count == 1:
1473 # TODO: Calculate the bass note for the inversion...
1474 pass
1475 pass
1476 for deg in n.get_named_children ('degree'):
1477 d = musicexp.ChordModification ()
1478 d.type = deg.get_type ()
1479 d.step = deg.get_value ()
1480 d.alteration = deg.get_alter ()
1481 ev.add_modification (d)
1482 #TODO: convert the user-symbols attribute:
1483 #major: a triangle, like Unicode 25B3
1484 #minor: -, like Unicode 002D
1485 #augmented: +, like Unicode 002B
1486 #diminished: (degree), like Unicode 00B0
1487 #half-diminished: (o with slash), like Unicode 00F8
1488 if ev and ev.root:
1489 res.append (ev)
1491 return res
1493 def musicxml_figured_bass_note_to_lily (n):
1494 res = musicexp.FiguredBassNote ()
1495 suffix_dict = { 'sharp' : "+",
1496 'flat' : "-",
1497 'natural' : "!",
1498 'double-sharp' : "++",
1499 'flat-flat' : "--",
1500 'sharp-sharp' : "++",
1501 'slash' : "/" }
1502 prefix = n.get_maybe_exist_named_child ('prefix')
1503 if prefix:
1504 res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
1505 fnumber = n.get_maybe_exist_named_child ('figure-number')
1506 if fnumber:
1507 res.set_number (fnumber.get_text ())
1508 suffix = n.get_maybe_exist_named_child ('suffix')
1509 if suffix:
1510 res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
1511 if n.get_maybe_exist_named_child ('extend'):
1512 # TODO: Implement extender lines (unfortunately, in lilypond you have
1513 # to use \set useBassFigureExtenders = ##t, which turns them on
1514 # globally, while MusicXML has a property for each note...
1515 # I'm not sure there is a proper way to implement this cleanly
1516 #n.extend
1517 pass
1518 return res
1522 def musicxml_figured_bass_to_lily (n):
1523 if not isinstance (n, musicxml.FiguredBass):
1524 return
1525 res = musicexp.FiguredBassEvent ()
1526 for i in n.get_named_children ('figure'):
1527 note = musicxml_figured_bass_note_to_lily (i)
1528 if note:
1529 res.append (note)
1530 dur = n.get_maybe_exist_named_child ('duration')
1531 if dur:
1532 # apply the duration to res
1533 length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
1534 res.set_real_duration (length)
1535 duration = rational_to_lily_duration (length)
1536 if duration:
1537 res.set_duration (duration)
1538 if hasattr (n, 'parentheses') and n.parentheses == "yes":
1539 res.set_parentheses (True)
1540 return res
1542 instrument_drumtype_dict = {
1543 'Acoustic Snare Drum': 'acousticsnare',
1544 'Side Stick': 'sidestick',
1545 'Open Triangle': 'opentriangle',
1546 'Mute Triangle': 'mutetriangle',
1547 'Tambourine': 'tambourine',
1548 'Bass Drum': 'bassdrum',
1551 def musicxml_note_to_lily_main_event (n):
1552 pitch = None
1553 duration = None
1554 event = None
1556 mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
1557 if mxl_pitch:
1558 pitch = musicxml_pitch_to_lily (mxl_pitch)
1559 event = musicexp.NoteEvent ()
1560 event.pitch = pitch
1562 acc = n.get_maybe_exist_named_child ('accidental')
1563 if acc:
1564 # let's not force accs everywhere.
1565 event.cautionary = acc.editorial
1567 elif n.get_maybe_exist_typed_child (musicxml.Unpitched):
1568 # Unpitched elements have display-step and can also have
1569 # display-octave.
1570 unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
1571 event = musicexp.NoteEvent ()
1572 event.pitch = musicxml_unpitched_to_lily (unpitched)
1574 elif n.get_maybe_exist_typed_child (musicxml.Rest):
1575 # rests can have display-octave and display-step, which are
1576 # treated like an ordinary note pitch
1577 rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1578 event = musicexp.RestEvent ()
1579 pitch = musicxml_restdisplay_to_lily (rest)
1580 event.pitch = pitch
1582 elif n.instrument_name:
1583 event = musicexp.NoteEvent ()
1584 drum_type = instrument_drumtype_dict.get (n.instrument_name)
1585 if drum_type:
1586 event.drum_type = drum_type
1587 else:
1588 n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
1589 event.drum_type = 'acousticsnare'
1591 else:
1592 n.message (_ ("cannot find suitable event"))
1594 if event:
1595 event.duration = musicxml_duration_to_lily (n)
1597 return event
1600 ## TODO
1601 class NegativeSkip:
1602 def __init__ (self, here, dest):
1603 self.here = here
1604 self.dest = dest
1606 class LilyPondVoiceBuilder:
1607 def __init__ (self):
1608 self.elements = []
1609 self.pending_dynamics = []
1610 self.end_moment = Rational (0)
1611 self.begin_moment = Rational (0)
1612 self.pending_multibar = Rational (0)
1613 self.ignore_skips = False
1614 self.has_relevant_elements = False
1615 self.measure_length = (4, 4)
1617 def _insert_multibar (self):
1618 layout_information.set_context_item ('Score', 'skipBars = ##t')
1619 r = musicexp.MultiMeasureRest ()
1620 lenfrac = Rational (self.measure_length[0], self.measure_length[1])
1621 r.duration = rational_to_lily_duration (lenfrac)
1622 r.duration.factor *= self.pending_multibar / lenfrac
1623 self.elements.append (r)
1624 self.begin_moment = self.end_moment
1625 self.end_moment = self.begin_moment + self.pending_multibar
1626 self.pending_multibar = Rational (0)
1628 def set_measure_length (self, mlen):
1629 if (mlen != self.measure_length) and self.pending_multibar:
1630 self._insert_multibar ()
1631 self.measure_length = mlen
1633 def add_multibar_rest (self, duration):
1634 self.pending_multibar += duration
1636 def set_duration (self, duration):
1637 self.end_moment = self.begin_moment + duration
1638 def current_duration (self):
1639 return self.end_moment - self.begin_moment
1641 def add_music (self, music, duration):
1642 assert isinstance (music, musicexp.Music)
1643 if self.pending_multibar > Rational (0):
1644 self._insert_multibar ()
1646 self.has_relevant_elements = True
1647 self.elements.append (music)
1648 self.begin_moment = self.end_moment
1649 self.set_duration (duration)
1651 # Insert all pending dynamics right after the note/rest:
1652 if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
1653 for d in self.pending_dynamics:
1654 music.append (d)
1655 self.pending_dynamics = []
1657 # Insert some music command that does not affect the position in the measure
1658 def add_command (self, command):
1659 assert isinstance (command, musicexp.Music)
1660 if self.pending_multibar > Rational (0):
1661 self._insert_multibar ()
1662 self.has_relevant_elements = True
1663 self.elements.append (command)
1664 def add_barline (self, barline):
1665 # TODO: Implement merging of default barline and custom bar line
1666 self.add_music (barline, Rational (0))
1667 def add_partial (self, command):
1668 self.ignore_skips = True
1669 self.add_command (command)
1671 def add_dynamics (self, dynamic):
1672 # store the dynamic item(s) until we encounter the next note/rest:
1673 self.pending_dynamics.append (dynamic)
1675 def add_bar_check (self, number):
1676 # re/store has_relevant_elements, so that a barline alone does not
1677 # trigger output for figured bass, chord names
1678 has_relevant = self.has_relevant_elements
1679 b = musicexp.BarLine ()
1680 b.bar_number = number
1681 self.add_barline (b)
1682 self.has_relevant_elements = has_relevant
1684 def jumpto (self, moment):
1685 current_end = self.end_moment + self.pending_multibar
1686 diff = moment - current_end
1688 if diff < Rational (0):
1689 error_message (_ ('Negative skip %s (from position %s to %s)') %
1690 (diff, current_end, moment))
1691 diff = Rational (0)
1693 if diff > Rational (0) and not (self.ignore_skips and moment == 0):
1694 skip = musicexp.SkipEvent()
1695 duration_factor = 1
1696 duration_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (diff.denominator (), -1)
1697 duration_dots = 0
1698 # TODO: Use the time signature for skips, too. Problem: The skip
1699 # might not start at a measure boundary!
1700 if duration_log > 0: # denominator is a power of 2...
1701 if diff.numerator () == 3:
1702 duration_log -= 1
1703 duration_dots = 1
1704 else:
1705 duration_factor = Rational (diff.numerator ())
1706 else:
1707 # for skips of a whole or more, simply use s1*factor
1708 duration_log = 0
1709 duration_factor = diff
1710 skip.duration.duration_log = duration_log
1711 skip.duration.factor = duration_factor
1712 skip.duration.dots = duration_dots
1714 evc = musicexp.ChordEvent ()
1715 evc.elements.append (skip)
1716 self.add_music (evc, diff)
1718 if diff > Rational (0) and moment == 0:
1719 self.ignore_skips = False
1721 def last_event_chord (self, starting_at):
1723 value = None
1725 # if the position matches, find the last ChordEvent, do not cross a bar line!
1726 at = len( self.elements ) - 1
1727 while (at >= 0 and
1728 not isinstance (self.elements[at], musicexp.ChordEvent) and
1729 not isinstance (self.elements[at], musicexp.BarLine)):
1730 at -= 1
1732 if (self.elements
1733 and at >= 0
1734 and isinstance (self.elements[at], musicexp.ChordEvent)
1735 and self.begin_moment == starting_at):
1736 value = self.elements[at]
1737 else:
1738 self.jumpto (starting_at)
1739 value = None
1740 return value
1742 def correct_negative_skip (self, goto):
1743 self.end_moment = goto
1744 self.begin_moment = goto
1745 evc = musicexp.ChordEvent ()
1746 self.elements.append (evc)
1749 class VoiceData:
1750 def __init__ (self):
1751 self.voicename = None
1752 self.voicedata = None
1753 self.ly_voice = None
1754 self.figured_bass = None
1755 self.chordnames = None
1756 self.lyrics_dict = {}
1757 self.lyrics_order = []
1759 def musicxml_step_to_lily (step):
1760 if step:
1761 return (ord (step) - ord ('A') + 7 - 2) % 7
1762 else:
1763 return None
1765 def measure_length_from_attributes (attr, current_measure_length):
1766 mxl = attr.get_named_attribute ('time')
1767 if mxl:
1768 return attr.get_time_signature ()
1769 else:
1770 return current_measure_length
1772 def musicxml_voice_to_lily_voice (voice):
1773 tuplet_events = []
1774 modes_found = {}
1775 lyrics = {}
1776 return_value = VoiceData ()
1777 return_value.voicedata = voice
1779 # First pitch needed for relative mode (if selected in command-line options)
1780 first_pitch = None
1782 # Needed for melismata detection (ignore lyrics on those notes!):
1783 inside_slur = False
1784 is_tied = False
1785 is_chord = False
1786 is_beamed = False
1787 ignore_lyrics = False
1789 current_staff = None
1791 pending_figured_bass = []
1792 pending_chordnames = []
1794 # Make sure that the keys in the dict don't get reordered, since
1795 # we need the correct ordering of the lyrics stanzas! By default,
1796 # a dict will reorder its keys
1797 return_value.lyrics_order = voice.get_lyrics_numbers ()
1798 for k in return_value.lyrics_order:
1799 lyrics[k] = []
1801 voice_builder = LilyPondVoiceBuilder ()
1802 figured_bass_builder = LilyPondVoiceBuilder ()
1803 chordnames_builder = LilyPondVoiceBuilder ()
1804 current_measure_length = (4, 4)
1805 voice_builder.set_measure_length (current_measure_length)
1807 for n in voice._elements:
1808 if n.get_name () == 'forward':
1809 continue
1810 staff = n.get_maybe_exist_named_child ('staff')
1811 if staff:
1812 staff = staff.get_text ()
1813 if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'):
1814 voice_builder.add_command (musicexp.StaffChange (staff))
1815 current_staff = staff
1817 if isinstance (n, musicxml.Partial) and n.partial > 0:
1818 a = musicxml_partial_to_lily (n.partial)
1819 if a:
1820 voice_builder.add_partial (a)
1821 continue
1823 is_chord = n.get_maybe_exist_named_child ('chord')
1824 is_after_grace = (isinstance (n, musicxml.Note) and n.is_after_grace ());
1825 if not is_chord and not is_after_grace:
1826 try:
1827 voice_builder.jumpto (n._when)
1828 except NegativeSkip, neg:
1829 voice_builder.correct_negative_skip (n._when)
1830 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
1832 if isinstance (n, musicxml.Barline):
1833 barlines = musicxml_barline_to_lily (n)
1834 for a in barlines:
1835 if isinstance (a, musicexp.BarLine):
1836 voice_builder.add_barline (a)
1837 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
1838 voice_builder.add_command (a)
1839 continue
1841 # Continue any multimeasure-rests before trying to add bar checks!
1842 # Don't handle new MM rests yet, because for them we want bar checks!
1843 rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1844 if (rest and rest.is_whole_measure ()
1845 and voice_builder.pending_multibar > Rational (0)):
1846 voice_builder.add_multibar_rest (n._duration)
1847 continue
1850 # print a bar check at the beginning of each measure!
1851 if n.is_first () and n._measure_position == Rational (0) and n != voice._elements[0]:
1852 try:
1853 num = int (n.get_parent ().number)
1854 except ValueError:
1855 num = 0
1856 if num > 0:
1857 voice_builder.add_bar_check (num)
1858 figured_bass_builder.add_bar_check (num)
1859 chordnames_builder.add_bar_check (num)
1861 # Start any new multimeasure rests
1862 if (rest and rest.is_whole_measure ()):
1863 voice_builder.add_multibar_rest (n._duration)
1864 continue
1867 if isinstance (n, musicxml.Direction):
1868 for a in musicxml_direction_to_lily (n):
1869 if a.wait_for_note ():
1870 voice_builder.add_dynamics (a)
1871 else:
1872 voice_builder.add_command (a)
1873 continue
1875 if isinstance (n, musicxml.Harmony):
1876 for a in musicxml_harmony_to_lily (n):
1877 if a.wait_for_note ():
1878 voice_builder.add_dynamics (a)
1879 else:
1880 voice_builder.add_command (a)
1881 for a in musicxml_harmony_to_lily_chordname (n):
1882 pending_chordnames.append (a)
1883 continue
1885 if isinstance (n, musicxml.FiguredBass):
1886 a = musicxml_figured_bass_to_lily (n)
1887 if a:
1888 pending_figured_bass.append (a)
1889 continue
1891 if isinstance (n, musicxml.Attributes):
1892 for a in musicxml_attributes_to_lily (n):
1893 voice_builder.add_command (a)
1894 measure_length = measure_length_from_attributes (n, current_measure_length)
1895 if current_measure_length != measure_length:
1896 current_measure_length = measure_length
1897 voice_builder.set_measure_length (current_measure_length)
1898 continue
1900 if not n.__class__.__name__ == 'Note':
1901 n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
1902 continue
1904 main_event = musicxml_note_to_lily_main_event (n)
1905 if main_event and not first_pitch:
1906 first_pitch = main_event.pitch
1907 # ignore lyrics for notes inside a slur, tie, chord or beam
1908 ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
1910 if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
1911 modes_found['drummode'] = True
1913 ev_chord = voice_builder.last_event_chord (n._when)
1914 if not ev_chord:
1915 ev_chord = musicexp.ChordEvent()
1916 voice_builder.add_music (ev_chord, n._duration)
1918 # For grace notes:
1919 grace = n.get_maybe_exist_typed_child (musicxml.Grace)
1920 if n.is_grace ():
1921 is_after_grace = ev_chord.has_elements () or n.is_after_grace ();
1922 is_chord = n.get_maybe_exist_typed_child (musicxml.Chord)
1924 grace_chord = None
1926 # after-graces and other graces use different lists; Depending on
1927 # whether we have a chord or not, obtain either a new ChordEvent or
1928 # the previous one to create a chord
1929 if is_after_grace:
1930 if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
1931 grace_chord = ev_chord.after_grace_elements.get_last_event_chord ()
1932 if not grace_chord:
1933 grace_chord = musicexp.ChordEvent ()
1934 ev_chord.append_after_grace (grace_chord)
1935 elif n.is_grace ():
1936 if ev_chord.grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
1937 grace_chord = ev_chord.grace_elements.get_last_event_chord ()
1938 if not grace_chord:
1939 grace_chord = musicexp.ChordEvent ()
1940 ev_chord.append_grace (grace_chord)
1942 if hasattr (grace, 'slash') and not is_after_grace:
1943 # TODO: use grace_type = "appoggiatura" for slurred grace notes
1944 if grace.slash == "yes":
1945 ev_chord.grace_type = "acciaccatura"
1946 # now that we have inserted the chord into the grace music, insert
1947 # everything into that chord instead of the ev_chord
1948 ev_chord = grace_chord
1949 ev_chord.append (main_event)
1950 ignore_lyrics = True
1951 else:
1952 ev_chord.append (main_event)
1953 # When a note/chord has grace notes (duration==0), the duration of the
1954 # event chord is not yet known, but the event chord was already added
1955 # with duration 0. The following correct this when we hit the real note!
1956 if voice_builder.current_duration () == 0 and n._duration > 0:
1957 voice_builder.set_duration (n._duration)
1959 # if we have a figured bass, set its voice builder to the correct position
1960 # and insert the pending figures
1961 if pending_figured_bass:
1962 try:
1963 figured_bass_builder.jumpto (n._when)
1964 except NegativeSkip, neg:
1965 pass
1966 for fb in pending_figured_bass:
1967 # if a duration is given, use that, otherwise the one of the note
1968 dur = fb.real_duration
1969 if not dur:
1970 dur = ev_chord.get_length ()
1971 if not fb.duration:
1972 fb.duration = ev_chord.get_duration ()
1973 figured_bass_builder.add_music (fb, dur)
1974 pending_figured_bass = []
1976 if pending_chordnames:
1977 try:
1978 chordnames_builder.jumpto (n._when)
1979 except NegativeSkip, neg:
1980 pass
1981 for cn in pending_chordnames:
1982 # Assign the duration of the EventChord
1983 cn.duration = ev_chord.get_duration ()
1984 chordnames_builder.add_music (cn, ev_chord.get_length ())
1985 pending_chordnames = []
1988 notations_children = n.get_typed_children (musicxml.Notations)
1989 tuplet_event = None
1990 span_events = []
1992 # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
1993 # +tied | +slur | +tuplet | glissando | slide |
1994 # ornaments | technical | articulations | dynamics |
1995 # +fermata | arpeggiate | non-arpeggiate |
1996 # accidental-mark | other-notation
1997 for notations in notations_children:
1998 for tuplet_event in notations.get_tuplets():
1999 mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
2000 frac = (1,1)
2001 if mod:
2002 frac = mod.get_fraction ()
2004 tuplet_events.append ((ev_chord, tuplet_event, frac))
2006 # First, close all open slurs, only then start any new slur
2007 # TODO: Record the number of the open slur to dtermine the correct
2008 # closing slur!
2009 endslurs = [s for s in notations.get_named_children ('slur')
2010 if s.get_type () in ('stop')]
2011 if endslurs and not inside_slur:
2012 endslurs[0].message (_ ('Encountered closing slur, but no slur is open'))
2013 elif endslurs:
2014 if len (endslurs) > 1:
2015 endslurs[0].message (_ ('Cannot have two simultaneous (closing) slurs'))
2016 # record the slur status for the next note in the loop
2017 if not grace:
2018 inside_slur = False
2019 lily_ev = musicxml_spanner_to_lily_event (endslurs[0])
2020 ev_chord.append (lily_ev)
2022 startslurs = [s for s in notations.get_named_children ('slur')
2023 if s.get_type () in ('start')]
2024 if startslurs and inside_slur:
2025 startslurs[0].message (_ ('Cannot have a slur inside another slur'))
2026 elif startslurs:
2027 if len (startslurs) > 1:
2028 startslurs[0].message (_ ('Cannot have two simultaneous slurs'))
2029 # record the slur status for the next note in the loop
2030 if not grace:
2031 inside_slur = True
2032 lily_ev = musicxml_spanner_to_lily_event (startslurs[0])
2033 ev_chord.append (lily_ev)
2036 if not grace:
2037 mxl_tie = notations.get_tie ()
2038 if mxl_tie and mxl_tie.type == 'start':
2039 ev_chord.append (musicexp.TieEvent ())
2040 is_tied = True
2041 else:
2042 is_tied = False
2044 fermatas = notations.get_named_children ('fermata')
2045 for a in fermatas:
2046 ev = musicxml_fermata_to_lily_event (a)
2047 if ev:
2048 ev_chord.append (ev)
2050 arpeggiate = notations.get_named_children ('arpeggiate')
2051 for a in arpeggiate:
2052 ev = musicxml_arpeggiate_to_lily_event (a)
2053 if ev:
2054 ev_chord.append (ev)
2056 arpeggiate = notations.get_named_children ('non-arpeggiate')
2057 for a in arpeggiate:
2058 ev = musicxml_nonarpeggiate_to_lily_event (a)
2059 if ev:
2060 ev_chord.append (ev)
2062 glissandos = notations.get_named_children ('glissando')
2063 glissandos += notations.get_named_children ('slide')
2064 for a in glissandos:
2065 ev = musicxml_spanner_to_lily_event (a)
2066 if ev:
2067 ev_chord.append (ev)
2069 # accidental-marks are direct children of <notation>!
2070 for a in notations.get_named_children ('accidental-mark'):
2071 ev = musicxml_articulation_to_lily_event (a)
2072 if ev:
2073 ev_chord.append (ev)
2075 # Articulations can contain the following child elements:
2076 # accent | strong-accent | staccato | tenuto |
2077 # detached-legato | staccatissimo | spiccato |
2078 # scoop | plop | doit | falloff | breath-mark |
2079 # caesura | stress | unstress
2080 # Technical can contain the following child elements:
2081 # up-bow | down-bow | harmonic | open-string |
2082 # thumb-position | fingering | pluck | double-tongue |
2083 # triple-tongue | stopped | snap-pizzicato | fret |
2084 # string | hammer-on | pull-off | bend | tap | heel |
2085 # toe | fingernails | other-technical
2086 # Ornaments can contain the following child elements:
2087 # trill-mark | turn | delayed-turn | inverted-turn |
2088 # shake | wavy-line | mordent | inverted-mordent |
2089 # schleifer | tremolo | other-ornament, accidental-mark
2090 ornaments = notations.get_named_children ('ornaments')
2091 ornaments += notations.get_named_children ('articulations')
2092 ornaments += notations.get_named_children ('technical')
2094 for a in ornaments:
2095 for ch in a.get_all_children ():
2096 ev = musicxml_articulation_to_lily_event (ch)
2097 if ev:
2098 ev_chord.append (ev)
2100 dynamics = notations.get_named_children ('dynamics')
2101 for a in dynamics:
2102 for ch in a.get_all_children ():
2103 ev = musicxml_dynamics_to_lily_event (ch)
2104 if ev:
2105 ev_chord.append (ev)
2108 mxl_beams = [b for b in n.get_named_children ('beam')
2109 if (b.get_type () in ('begin', 'end')
2110 and b.is_primary ())]
2111 if mxl_beams and not conversion_settings.ignore_beaming:
2112 beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
2113 if beam_ev:
2114 ev_chord.append (beam_ev)
2115 if beam_ev.span_direction == -1: # beam and thus melisma starts here
2116 is_beamed = True
2117 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
2118 is_beamed = False
2120 # Extract the lyrics
2121 if not rest and not ignore_lyrics:
2122 note_lyrics_processed = []
2123 note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
2124 for l in note_lyrics_elements:
2125 if l.get_number () < 0:
2126 for k in lyrics.keys ():
2127 lyrics[k].append (l.lyric_to_text ())
2128 note_lyrics_processed.append (k)
2129 else:
2130 lyrics[l.number].append(l.lyric_to_text ())
2131 note_lyrics_processed.append (l.number)
2132 for lnr in lyrics.keys ():
2133 if not lnr in note_lyrics_processed:
2134 lyrics[lnr].append ("\skip4")
2136 ## force trailing mm rests to be written out.
2137 voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
2139 ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
2140 ly_voice = group_repeats (ly_voice)
2142 seq_music = musicexp.SequentialMusic ()
2144 if 'drummode' in modes_found.keys ():
2145 ## \key <pitch> barfs in drummode.
2146 ly_voice = [e for e in ly_voice
2147 if not isinstance(e, musicexp.KeySignatureChange)]
2149 seq_music.elements = ly_voice
2150 for k in lyrics.keys ():
2151 return_value.lyrics_dict[k] = musicexp.Lyrics ()
2152 return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2155 if len (modes_found) > 1:
2156 error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
2158 if options.relative:
2159 v = musicexp.RelativeMusic ()
2160 v.element = seq_music
2161 v.basepitch = first_pitch
2162 seq_music = v
2164 return_value.ly_voice = seq_music
2165 for mode in modes_found.keys ():
2166 v = musicexp.ModeChangingMusicWrapper()
2167 v.element = seq_music
2168 v.mode = mode
2169 return_value.ly_voice = v
2171 # create \figuremode { figured bass elements }
2172 if figured_bass_builder.has_relevant_elements:
2173 fbass_music = musicexp.SequentialMusic ()
2174 fbass_music.elements = figured_bass_builder.elements
2175 v = musicexp.ModeChangingMusicWrapper()
2176 v.mode = 'figuremode'
2177 v.element = fbass_music
2178 return_value.figured_bass = v
2180 # create \chordmode { chords }
2181 if chordnames_builder.has_relevant_elements:
2182 cname_music = musicexp.SequentialMusic ()
2183 cname_music.elements = chordnames_builder.elements
2184 v = musicexp.ModeChangingMusicWrapper()
2185 v.mode = 'chordmode'
2186 v.element = cname_music
2187 return_value.chordnames = v
2189 return return_value
2191 def musicxml_id_to_lily (id):
2192 digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2193 'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2195 for digit in digits:
2196 d = digits.index (digit)
2197 id = re.sub ('%d' % d, digit, id)
2199 id = re.sub ('[^a-zA-Z]', 'X', id)
2200 return id
2202 def musicxml_pitch_to_lily (mxl_pitch):
2203 p = musicexp.Pitch ()
2204 p.alteration = mxl_pitch.get_alteration ()
2205 p.step = musicxml_step_to_lily (mxl_pitch.get_step ())
2206 p.octave = mxl_pitch.get_octave () - 4
2207 return p
2209 def musicxml_unpitched_to_lily (mxl_unpitched):
2210 p = None
2211 step = mxl_unpitched.get_step ()
2212 if step:
2213 p = musicexp.Pitch ()
2214 p.step = musicxml_step_to_lily (step)
2215 octave = mxl_unpitched.get_octave ()
2216 if octave and p:
2217 p.octave = octave - 4
2218 return p
2220 def musicxml_restdisplay_to_lily (mxl_rest):
2221 p = None
2222 step = mxl_rest.get_step ()
2223 if step:
2224 p = musicexp.Pitch ()
2225 p.step = musicxml_step_to_lily (step)
2226 octave = mxl_rest.get_octave ()
2227 if octave and p:
2228 p.octave = octave - 4
2229 return p
2231 def voices_in_part (part):
2232 """Return a Name -> Voice dictionary for PART"""
2233 part.interpret ()
2234 part.extract_voices ()
2235 voices = part.get_voices ()
2236 part_info = part.get_staff_attributes ()
2238 return (voices, part_info)
2240 def voices_in_part_in_parts (parts):
2241 """return a Part -> Name -> Voice dictionary"""
2242 return dict([(p.id, voices_in_part (p)) for p in parts])
2245 def get_all_voices (parts):
2246 all_voices = voices_in_part_in_parts (parts)
2248 all_ly_voices = {}
2249 all_ly_staffinfo = {}
2250 for p, (name_voice, staff_info) in all_voices.items ():
2252 part_ly_voices = {}
2253 for n, v in name_voice.items ():
2254 progress (_ ("Converting to LilyPond expressions..."))
2255 # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2256 part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
2258 all_ly_voices[p] = part_ly_voices
2259 all_ly_staffinfo[p] = staff_info
2261 return (all_ly_voices, all_ly_staffinfo)
2264 def option_parser ():
2265 p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"),
2266 description =
2267 _ ("""Convert MusicXML from FILE.xml to LilyPond input.
2268 If the given filename is -, musicxml2ly reads from the command line.
2269 """), add_help_option=False)
2271 p.add_option("-h", "--help",
2272 action="help",
2273 help=_ ("show this help and exit"))
2275 p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
2277 _ ("""Copyright (c) 2005--2008 by
2278 Han-Wen Nienhuys <hanwen@xs4all.nl>,
2279 Jan Nieuwenhuizen <janneke@gnu.org> and
2280 Reinhold Kainhofer <reinhold@kainhofer.com>
2284 This program is free software. It is covered by the GNU General Public
2285 License and you are welcome to change it and/or distribute copies of it
2286 under certain conditions. Invoke as `%s --warranty' for more
2287 information.""") % 'lilypond')
2289 p.add_option("--version",
2290 action="version",
2291 help=_ ("show version number and exit"))
2293 p.add_option ('-v', '--verbose',
2294 action = "store_true",
2295 dest = 'verbose',
2296 help = _ ("be verbose"))
2298 p.add_option ('', '--lxml',
2299 action = "store_true",
2300 default = False,
2301 dest = "use_lxml",
2302 help = _ ("use lxml.etree; uses less memory and cpu time"))
2304 p.add_option ('-z', '--compressed',
2305 action = "store_true",
2306 dest = 'compressed',
2307 default = False,
2308 help = _ ("input file is a zip-compressed MusicXML file"))
2310 p.add_option ('-r', '--relative',
2311 action = "store_true",
2312 default = True,
2313 dest = "relative",
2314 help = _ ("convert pitches in relative mode (default)"))
2316 p.add_option ('-a', '--absolute',
2317 action = "store_false",
2318 dest = "relative",
2319 help = _ ("convert pitches in absolute mode"))
2321 p.add_option ('-l', '--language',
2322 metavar = _ ("LANG"),
2323 action = "store",
2324 help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
2326 p.add_option ('--nd', '--no-articulation-directions',
2327 action = "store_false",
2328 default = True,
2329 dest = "convert_directions",
2330 help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2332 p.add_option ('--no-beaming',
2333 action = "store_false",
2334 default = True,
2335 dest = "convert_beaming",
2336 help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
2338 p.add_option ('-o', '--output',
2339 metavar = _ ("FILE"),
2340 action = "store",
2341 default = None,
2342 type = 'string',
2343 dest = 'output_name',
2344 help = _ ("set output filename to FILE, stdout if -"))
2345 p.add_option_group ('',
2346 description = (_ ("Report bugs via")
2347 + ''' http://post.gmane.org/post.php'''
2348 '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
2349 return p
2351 def music_xml_voice_name_to_lily_name (part_id, name):
2352 str = "Part%sVoice%s" % (part_id, name)
2353 return musicxml_id_to_lily (str)
2355 def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
2356 str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
2357 return musicxml_id_to_lily (str)
2359 def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
2360 str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
2361 return musicxml_id_to_lily (str)
2363 def music_xml_chordnames_name_to_lily_name (part_id, voicename):
2364 str = "Part%sVoice%sChords" % (part_id, voicename)
2365 return musicxml_id_to_lily (str)
2367 def print_voice_definitions (printer, part_list, voices):
2368 for part in part_list:
2369 part_id = part.id
2370 nv_dict = voices.get (part_id, {})
2371 for (name, voice) in nv_dict.items ():
2372 k = music_xml_voice_name_to_lily_name (part_id, name)
2373 printer.dump ('%s = ' % k)
2374 voice.ly_voice.print_ly (printer)
2375 printer.newline()
2376 if voice.chordnames:
2377 cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
2378 printer.dump ('%s = ' % cnname )
2379 voice.chordnames.print_ly (printer)
2380 printer.newline()
2381 for l in voice.lyrics_order:
2382 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
2383 printer.dump ('%s = ' % lname )
2384 voice.lyrics_dict[l].print_ly (printer)
2385 printer.newline()
2386 if voice.figured_bass:
2387 fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
2388 printer.dump ('%s = ' % fbname )
2389 voice.figured_bass.print_ly (printer)
2390 printer.newline()
2393 def uniq_list (l):
2394 return dict ([(elt,1) for elt in l]).keys ()
2396 # format the information about the staff in the form
2397 # [staffid,
2399 # [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
2400 # [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
2401 # ...
2404 # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
2405 def format_staff_info (part_id, staff_id, raw_voices):
2406 voices = []
2407 for (v, lyricsids, figured_bass, chordnames) in raw_voices:
2408 voice_name = music_xml_voice_name_to_lily_name (part_id, v)
2409 voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
2410 for l in lyricsids]
2411 figured_bass_name = ''
2412 if figured_bass:
2413 figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
2414 chordnames_name = ''
2415 if chordnames:
2416 chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
2417 voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
2418 return [staff_id, voices]
2420 def update_score_setup (score_structure, part_list, voices):
2422 for part_definition in part_list:
2423 part_id = part_definition.id
2424 nv_dict = voices.get (part_id)
2425 if not nv_dict:
2426 error_message (_ ('unknown part in part-list: %s') % part_id)
2427 continue
2429 staves = reduce (lambda x,y: x+ y,
2430 [voice.voicedata._staves.keys ()
2431 for voice in nv_dict.values ()],
2433 staves_info = []
2434 if len (staves) > 1:
2435 staves_info = []
2436 staves = uniq_list (staves)
2437 staves.sort ()
2438 for s in staves:
2439 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames)
2440 for (voice_name, voice) in nv_dict.items ()
2441 if voice.voicedata._start_staff == s]
2442 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
2443 else:
2444 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames)
2445 for (voice_name, voice) in nv_dict.items ()]
2446 staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
2447 score_structure.set_part_information (part_id, staves_info)
2449 # Set global values in the \layout block, like auto-beaming etc.
2450 def update_layout_information ():
2451 if not conversion_settings.ignore_beaming and layout_information:
2452 layout_information.set_context_item ('Score', 'autoBeaming = ##f')
2454 def print_ly_preamble (printer, filename):
2455 printer.dump_version ()
2456 printer.print_verbatim ('%% automatically converted from %s\n' % filename)
2458 def print_ly_additional_definitions (printer, filename):
2459 if needed_additional_definitions:
2460 printer.newline ()
2461 printer.print_verbatim ('%% additional definitions required by the score:')
2462 printer.newline ()
2463 for a in set(needed_additional_definitions):
2464 printer.print_verbatim (additional_definitions.get (a, ''))
2465 printer.newline ()
2466 printer.newline ()
2468 # Read in the tree from the given I/O object (either file or string) and
2469 # demarshall it using the classes from the musicxml.py file
2470 def read_xml (io_object, use_lxml):
2471 if use_lxml:
2472 import lxml.etree
2473 tree = lxml.etree.parse (io_object)
2474 mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
2475 return mxl_tree
2476 else:
2477 from xml.dom import minidom, Node
2478 doc = minidom.parse(io_object)
2479 node = doc.documentElement
2480 return musicxml.minidom_demarshal_node (node)
2481 return None
2484 def read_musicxml (filename, compressed, use_lxml):
2485 raw_string = None
2486 if compressed:
2487 if filename == "-":
2488 progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
2489 z = zipfile.ZipFile (sys.stdin)
2490 else:
2491 progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
2492 z = zipfile.ZipFile (filename, "r")
2493 container_xml = z.read ("META-INF/container.xml")
2494 if not container_xml:
2495 return None
2496 container = read_xml (StringIO.StringIO (container_xml), use_lxml)
2497 if not container:
2498 return None
2499 rootfiles = container.get_maybe_exist_named_child ('rootfiles')
2500 if not rootfiles:
2501 return None
2502 rootfile_list = rootfiles.get_named_children ('rootfile')
2503 mxml_file = None
2504 if len (rootfile_list) > 0:
2505 mxml_file = getattr (rootfile_list[0], 'full-path', None)
2506 if mxml_file:
2507 raw_string = z.read (mxml_file)
2509 if raw_string:
2510 io_object = StringIO.StringIO (raw_string)
2511 elif filename == "-":
2512 io_object = sys.stdin
2513 else:
2514 io_object = filename
2516 return read_xml (io_object, use_lxml)
2519 def convert (filename, options):
2520 if filename == "-":
2521 progress (_ ("Reading MusicXML from Standard input ...") )
2522 else:
2523 progress (_ ("Reading MusicXML from %s ...") % filename)
2525 tree = read_musicxml (filename, options.compressed, options.use_lxml)
2526 score_information = extract_score_information (tree)
2527 paper_information = extract_paper_information (tree)
2529 parts = tree.get_typed_children (musicxml.Part)
2530 (voices, staff_info) = get_all_voices (parts)
2532 score = None
2533 mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
2534 if mxl_pl:
2535 score = extract_score_structure (mxl_pl, staff_info)
2536 part_list = mxl_pl.get_named_children ("score-part")
2538 # score information is contained in the <work>, <identification> or <movement-title> tags
2539 update_score_setup (score, part_list, voices)
2540 # After the conversion, update the list of settings for the \layout block
2541 update_layout_information ()
2543 if not options.output_name:
2544 options.output_name = os.path.basename (filename)
2545 options.output_name = os.path.splitext (options.output_name)[0]
2546 elif re.match (".*\.ly", options.output_name):
2547 options.output_name = os.path.splitext (options.output_name)[0]
2550 #defs_ly_name = options.output_name + '-defs.ly'
2551 if (options.output_name == "-"):
2552 output_ly_name = 'Standard output'
2553 else:
2554 output_ly_name = options.output_name + '.ly'
2556 progress (_ ("Output to `%s'") % output_ly_name)
2557 printer = musicexp.Output_printer()
2558 #progress (_ ("Output to `%s'") % defs_ly_name)
2559 if (options.output_name == "-"):
2560 printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
2561 else:
2562 printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
2563 print_ly_preamble (printer, filename)
2564 print_ly_additional_definitions (printer, filename)
2565 if score_information:
2566 score_information.print_ly (printer)
2567 if paper_information:
2568 paper_information.print_ly (printer)
2569 if layout_information:
2570 layout_information.print_ly (printer)
2571 print_voice_definitions (printer, part_list, voices)
2573 printer.newline ()
2574 printer.dump ("% The score definition")
2575 printer.newline ()
2576 score.print_ly (printer)
2577 printer.newline ()
2579 return voices
2581 def get_existing_filename_with_extension (filename, ext):
2582 if os.path.exists (filename):
2583 return filename
2584 newfilename = filename + "." + ext
2585 if os.path.exists (newfilename):
2586 return newfilename;
2587 newfilename = filename + ext
2588 if os.path.exists (newfilename):
2589 return newfilename;
2590 return ''
2592 def main ():
2593 opt_parser = option_parser()
2595 global options
2596 (options, args) = opt_parser.parse_args ()
2597 if not args:
2598 opt_parser.print_usage()
2599 sys.exit (2)
2601 if options.language:
2602 musicexp.set_pitch_language (options.language)
2603 needed_additional_definitions.append (options.language)
2604 additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
2605 conversion_settings.ignore_beaming = not options.convert_beaming
2607 # Allow the user to leave out the .xml or xml on the filename
2608 basefilename = args[0].decode('utf-8')
2609 if basefilename == "-": # Read from stdin
2610 basefilename = "-"
2611 else:
2612 filename = get_existing_filename_with_extension (basefilename, "xml")
2613 if not filename:
2614 filename = get_existing_filename_with_extension (basefilename, "mxl")
2615 options.compressed = True
2616 if filename and filename.endswith ("mxl"):
2617 options.compressed = True
2619 if filename and (filename == "-" or os.path.exists (filename)):
2620 voices = convert (filename, options)
2621 else:
2622 progress (_ ("Unable to find input file %s") % basefilename)
2624 if __name__ == '__main__':
2625 main()