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