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