Fix InstrumentSwitch grob definition.
[lilypond.git] / scripts / musicxml2ly.py
blob6ee2a755e26b8c0c7dc53c3e53b441ccf8cbc137
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 set_if_exists ('texidoc', ids.get_file_description ());
205 # Finally, apply the required compatibility modes
206 # Some applications created wrong MusicXML files, so we need to
207 # apply some compatibility mode, e.g. ignoring some features/tags
208 # in those files
209 software = ids.get_encoding_software_list ()
211 # Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin
212 # is missing all beam ends => ignore all beaming information
213 if "Dolet 3.4 for Sibelius" in software:
214 conversion_settings.ignore_beaming = True
215 progress (_ ("Encountered file created by Dolet 3.4 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
216 # TODO: Check for other unsupported features
218 return header
220 class PartGroupInfo:
221 def __init__ (self):
222 self.start = {}
223 self.end = {}
224 def is_empty (self):
225 return len (self.start) + len (self.end) == 0
226 def add_start (self, g):
227 self.start[getattr (g, 'number', "1")] = g
228 def add_end (self, g):
229 self.end[getattr (g, 'number', "1")] = g
230 def print_ly (self, printer):
231 error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
232 def ly_expression (self):
233 error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
234 return ''
236 def staff_attributes_to_string_tunings (mxl_attr):
237 details = mxl_attr.get_maybe_exist_named_child ('staff-details')
238 if not details:
239 return []
240 lines = 6
241 staff_lines = details.get_maybe_exist_named_child ('staff-lines')
242 if staff_lines:
243 lines = string.atoi (staff_lines.get_text ())
245 tunings = [0]*lines
246 staff_tunings = details.get_named_children ('staff-tuning')
247 for i in staff_tunings:
248 p = musicexp.Pitch()
249 line = 0
250 try:
251 line = string.atoi (i.line) - 1
252 except ValueError:
253 pass
254 tunings[line] = p
256 step = i.get_named_child (u'tuning-step')
257 step = step.get_text ().strip ()
258 p.step = musicxml_step_to_lily (step)
260 octave = i.get_named_child (u'tuning-octave')
261 octave = octave.get_text ().strip ()
262 p.octave = int (octave) - 4
264 alter = i.get_named_child (u'tuning-alter')
265 if alter:
266 p.alteration = int (alter.get_text ().strip ())
267 # lilypond seems to use the opposite ordering than MusicXML...
268 tunings.reverse ()
270 return tunings
273 def staff_attributes_to_lily_staff (mxl_attr):
274 if not mxl_attr:
275 return musicexp.Staff ()
277 (staff_id, attributes) = mxl_attr.items ()[0]
279 # distinguish by clef:
280 # percussion (percussion and rhythmic), tab, and everything else
281 clef_sign = None
282 clef = attributes.get_maybe_exist_named_child ('clef')
283 if clef:
284 sign = clef.get_maybe_exist_named_child ('sign')
285 if sign:
286 clef_sign = {"percussion": "percussion", "TAB": "tab"}.get (sign.get_text (), None)
288 lines = 5
289 details = attributes.get_named_children ('staff-details')
290 for d in details:
291 staff_lines = d.get_maybe_exist_named_child ('staff-lines')
292 if staff_lines:
293 lines = string.atoi (staff_lines.get_text ())
295 staff = None
296 if clef_sign == "percussion" and lines == 1:
297 staff = musicexp.RhythmicStaff ()
298 elif clef_sign == "percussion":
299 staff = musicexp.DrumStaff ()
300 # staff.drum_style_table = ???
301 elif clef_sign == "tab":
302 staff = musicexp.TabStaff ()
303 staff.string_tunings = staff_attributes_to_string_tunings (attributes)
304 # staff.tablature_format = ???
305 else:
306 # TODO: Handle case with lines <> 5!
307 staff = musicexp.Staff ()
309 return staff
312 def extract_score_structure (part_list, staffinfo):
313 score = musicexp.Score ()
314 structure = musicexp.StaffGroup (None)
315 score.set_contents (structure)
317 if not part_list:
318 return structure
320 def read_score_part (el):
321 if not isinstance (el, musicxml.Score_part):
322 return
323 # Depending on the attributes of the first measure, we create different
324 # types of staves (Staff, RhythmicStaff, DrumStaff, TabStaff, etc.)
325 staff = staff_attributes_to_lily_staff (staffinfo.get (el.id, None))
326 if not staff:
327 return None
328 staff.id = el.id
329 partname = el.get_maybe_exist_named_child ('part-name')
330 # Finale gives unnamed parts the name "MusicXML Part" automatically!
331 if partname and partname.get_text() != "MusicXML Part":
332 staff.instrument_name = partname.get_text ()
333 if el.get_maybe_exist_named_child ('part-abbreviation'):
334 staff.short_instrument_name = el.get_maybe_exist_named_child ('part-abbreviation').get_text ()
335 # TODO: Read in the MIDI device / instrument
336 return staff
338 def read_score_group (el):
339 if not isinstance (el, musicxml.Part_group):
340 return
341 group = musicexp.StaffGroup ()
342 if hasattr (el, 'number'):
343 id = el.number
344 group.id = id
345 #currentgroups_dict[id] = group
346 #currentgroups.append (id)
347 if el.get_maybe_exist_named_child ('group-name'):
348 group.instrument_name = el.get_maybe_exist_named_child ('group-name').get_text ()
349 if el.get_maybe_exist_named_child ('group-abbreviation'):
350 group.short_instrument_name = el.get_maybe_exist_named_child ('group-abbreviation').get_text ()
351 if el.get_maybe_exist_named_child ('group-symbol'):
352 group.symbol = el.get_maybe_exist_named_child ('group-symbol').get_text ()
353 if el.get_maybe_exist_named_child ('group-barline'):
354 group.spanbar = el.get_maybe_exist_named_child ('group-barline').get_text ()
355 return group
358 parts_groups = part_list.get_all_children ()
360 # the start/end group tags are not necessarily ordered correctly and groups
361 # might even overlap, so we can't go through the children sequentially!
363 # 1) Replace all Score_part objects by their corresponding Staff objects,
364 # also collect all group start/stop points into one PartGroupInfo object
365 staves = []
366 group_info = PartGroupInfo ()
367 for el in parts_groups:
368 if isinstance (el, musicxml.Score_part):
369 if not group_info.is_empty ():
370 staves.append (group_info)
371 group_info = PartGroupInfo ()
372 staff = read_score_part (el)
373 if staff:
374 staves.append (staff)
375 elif isinstance (el, musicxml.Part_group):
376 if el.type == "start":
377 group_info.add_start (el)
378 elif el.type == "stop":
379 group_info.add_end (el)
380 if not group_info.is_empty ():
381 staves.append (group_info)
383 # 2) Now, detect the groups:
384 group_starts = []
385 pos = 0
386 while pos < len (staves):
387 el = staves[pos]
388 if isinstance (el, PartGroupInfo):
389 prev_start = 0
390 if len (group_starts) > 0:
391 prev_start = group_starts[-1]
392 elif len (el.end) > 0: # no group to end here
393 el.end = {}
394 if len (el.end) > 0: # closes an existing group
395 ends = el.end.keys ()
396 prev_started = staves[prev_start].start.keys ()
397 grpid = None
398 intersection = filter(lambda x:x in ends, prev_started)
399 if len (intersection) > 0:
400 grpid = intersection[0]
401 else:
402 # Close the last started group
403 grpid = staves[prev_start].start.keys () [0]
404 # Find the corresponding closing tag and remove it!
405 j = pos + 1
406 foundclosing = False
407 while j < len (staves) and not foundclosing:
408 if isinstance (staves[j], PartGroupInfo) and staves[j].end.has_key (grpid):
409 foundclosing = True
410 del staves[j].end[grpid]
411 if staves[j].is_empty ():
412 del staves[j]
413 j += 1
414 grpobj = staves[prev_start].start[grpid]
415 group = read_score_group (grpobj)
416 # remove the id from both the start and end
417 if el.end.has_key (grpid):
418 del el.end[grpid]
419 del staves[prev_start].start[grpid]
420 if el.is_empty ():
421 del staves[pos]
422 # replace the staves with the whole group
423 for j in staves[(prev_start + 1):pos]:
424 if j.is_group:
425 j.stafftype = "InnerStaffGroup"
426 group.append_staff (j)
427 del staves[(prev_start + 1):pos]
428 staves.insert (prev_start + 1, group)
429 # reset pos so that we continue at the correct position
430 pos = prev_start
431 # remove an empty start group
432 if staves[prev_start].is_empty ():
433 del staves[prev_start]
434 group_starts.remove (prev_start)
435 pos -= 1
436 elif len (el.start) > 0: # starts new part groups
437 group_starts.append (pos)
438 pos += 1
440 if len (staves) == 1:
441 return staves[0]
442 for i in staves:
443 structure.append_staff (i)
444 return score
447 def musicxml_duration_to_lily (mxl_note):
448 d = musicexp.Duration ()
449 # if the note has no Type child, then that method spits out a warning and
450 # returns 0, i.e. a whole note
451 d.duration_log = mxl_note.get_duration_log ()
453 d.dots = len (mxl_note.get_typed_children (musicxml.Dot))
454 # Grace notes by specification have duration 0, so no time modification
455 # factor is possible. It even messes up the output with *0/1
456 if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
457 d.factor = mxl_note._duration / d.get_length ()
459 return d
461 def rational_to_lily_duration (rational_len):
462 d = musicexp.Duration ()
464 rational_len.normalize_self ()
465 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)
467 # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration
468 if (d_log >= 0 and rational_len.numerator() in (1,3,5,7) ):
469 # account for the dots!
470 d.dots = (rational_len.numerator()-1)/2
471 d.duration_log = d_log - d.dots
472 elif (d_log >= 0):
473 d.duration_log = d_log
474 d.factor = Rational (rational_len.numerator ())
475 else:
476 error_message (_ ("Encountered rational duration with denominator %s, "
477 "unable to convert to lilypond duration") %
478 rational_len.denominator ())
479 # TODO: Test the above error message
480 return None
482 return d
484 def musicxml_partial_to_lily (partial_len):
485 if partial_len > 0:
486 p = musicexp.Partial ()
487 p.partial = rational_to_lily_duration (partial_len)
488 return p
489 else:
490 return Null
492 # Detect repeats and alternative endings in the chord event list (music_list)
493 # and convert them to the corresponding musicexp objects, containing nested
494 # music
495 def group_repeats (music_list):
496 repeat_replaced = True
497 music_start = 0
498 i = 0
499 # Walk through the list of expressions, looking for repeat structure
500 # (repeat start/end, corresponding endings). If we find one, try to find the
501 # last event of the repeat, replace the whole structure and start over again.
502 # For nested repeats, as soon as we encounter another starting repeat bar,
503 # treat that one first, and start over for the outer repeat.
504 while repeat_replaced and i < 100:
505 i += 1
506 repeat_start = -1 # position of repeat start / end
507 repeat_end = -1 # position of repeat start / end
508 repeat_times = 0
509 ending_start = -1 # position of current ending start
510 endings = [] # list of already finished endings
511 pos = 0
512 last = len (music_list) - 1
513 repeat_replaced = False
514 final_marker = 0
515 while pos < len (music_list) and not repeat_replaced:
516 e = music_list[pos]
517 repeat_finished = False
518 if isinstance (e, RepeatMarker):
519 if not repeat_times and e.times:
520 repeat_times = e.times
521 if e.direction == -1:
522 if repeat_end >= 0:
523 repeat_finished = True
524 else:
525 repeat_start = pos
526 repeat_end = -1
527 ending_start = -1
528 endings = []
529 elif e.direction == 1:
530 if repeat_start < 0:
531 repeat_start = 0
532 if repeat_end < 0:
533 repeat_end = pos
534 final_marker = pos
535 elif isinstance (e, EndingMarker):
536 if e.direction == -1:
537 if repeat_start < 0:
538 repeat_start = 0
539 if repeat_end < 0:
540 repeat_end = pos
541 ending_start = pos
542 elif e.direction == 1:
543 if ending_start < 0:
544 ending_start = 0
545 endings.append ([ending_start, pos])
546 ending_start = -1
547 final_marker = pos
548 elif not isinstance (e, musicexp.BarLine):
549 # As soon as we encounter an element when repeat start and end
550 # is set and we are not inside an alternative ending,
551 # this whole repeat structure is finished => replace it
552 if repeat_start >= 0 and repeat_end > 0 and ending_start < 0:
553 repeat_finished = True
555 # Finish off all repeats without explicit ending bar (e.g. when
556 # we convert only one page of a multi-page score with repeats)
557 if pos == last and repeat_start >= 0:
558 repeat_finished = True
559 final_marker = pos
560 if repeat_end < 0:
561 repeat_end = pos
562 if ending_start >= 0:
563 endings.append ([ending_start, pos])
564 ending_start = -1
566 if repeat_finished:
567 # We found the whole structure replace it!
568 r = musicexp.RepeatedMusic ()
569 if repeat_times <= 0:
570 repeat_times = 2
571 r.repeat_count = repeat_times
572 # don't erase the first element for "implicit" repeats (i.e. no
573 # starting repeat bars at the very beginning)
574 start = repeat_start+1
575 if repeat_start == music_start:
576 start = music_start
577 r.set_music (music_list[start:repeat_end])
578 for (start, end) in endings:
579 s = musicexp.SequentialMusic ()
580 s.elements = music_list[start+1:end]
581 r.add_ending (s)
582 del music_list[repeat_start:final_marker+1]
583 music_list.insert (repeat_start, r)
584 repeat_replaced = True
585 pos += 1
586 # TODO: Implement repeats until the end without explicit ending bar
587 return music_list
591 def group_tuplets (music_list, events):
594 """Collect Musics from
595 MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
599 indices = []
601 j = 0
602 for (ev_chord, tuplet_elt, fraction) in events:
603 while (j < len (music_list)):
604 if music_list[j] == ev_chord:
605 break
606 j += 1
607 if tuplet_elt.type == 'start':
608 indices.append ((j, None, fraction))
609 elif tuplet_elt.type == 'stop':
610 indices[-1] = (indices[-1][0], j, indices[-1][2])
612 new_list = []
613 last = 0
614 for (i1, i2, frac) in indices:
615 if i1 >= i2:
616 continue
618 new_list.extend (music_list[last:i1])
619 seq = musicexp.SequentialMusic ()
620 last = i2 + 1
621 seq.elements = music_list[i1:last]
623 tsm = musicexp.TimeScaledMusic ()
624 tsm.element = seq
626 tsm.numerator = frac[0]
627 tsm.denominator = frac[1]
629 new_list.append (tsm)
631 new_list.extend (music_list[last:])
632 return new_list
635 def musicxml_clef_to_lily (attributes):
636 change = musicexp.ClefChange ()
637 (change.type, change.position, change.octave) = attributes.get_clef_information ()
638 return change
640 def musicxml_time_to_lily (attributes):
641 (beats, type) = attributes.get_time_signature ()
643 change = musicexp.TimeSignatureChange()
644 change.fraction = (beats, type)
646 return change
648 def musicxml_key_to_lily (attributes):
649 start_pitch = musicexp.Pitch ()
650 (fifths, mode) = attributes.get_key_signature ()
651 try:
652 (n,a) = {
653 'major' : (0,0),
654 'minor' : (5,0),
655 }[mode]
656 start_pitch.step = n
657 start_pitch.alteration = a
658 except KeyError:
659 error_message (_ ("unknown mode %s, expecting 'major' or 'minor'") % mode)
661 fifth = musicexp.Pitch()
662 fifth.step = 4
663 if fifths < 0:
664 fifths *= -1
665 fifth.step *= -1
666 fifth.normalize ()
668 for x in range (fifths):
669 start_pitch = start_pitch.transposed (fifth)
671 start_pitch.octave = 0
673 change = musicexp.KeySignatureChange()
674 change.mode = mode
675 change.tonic = start_pitch
676 return change
678 def musicxml_attributes_to_lily (attrs):
679 elts = []
680 attr_dispatch = {
681 'clef': musicxml_clef_to_lily,
682 'time': musicxml_time_to_lily,
683 'key': musicxml_key_to_lily
685 for (k, func) in attr_dispatch.items ():
686 children = attrs.get_named_children (k)
687 if children:
688 elts.append (func (attrs))
690 return elts
692 class Marker (musicexp.Music):
693 def __init__ (self):
694 self.direction = 0
695 self.event = None
696 def print_ly (self, printer):
697 ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self)
698 pass
699 def ly_expression (self):
700 return ""
701 class RepeatMarker (Marker):
702 def __init__ (self):
703 Marker.__init__ (self)
704 self.times = 0
705 class EndingMarker (Marker):
706 pass
708 # Convert the <barline> element to musicxml.BarLine (for non-standard barlines)
709 # and to RepeatMarker and EndingMarker objects for repeat and
710 # alternatives start/stops
711 def musicxml_barline_to_lily (barline):
712 # retval contains all possible markers in the order:
713 # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending
714 retval = {}
715 bartype_element = barline.get_maybe_exist_named_child ("bar-style")
716 repeat_element = barline.get_maybe_exist_named_child ("repeat")
717 ending_element = barline.get_maybe_exist_named_child ("ending")
719 bartype = None
720 if bartype_element:
721 bartype = bartype_element.get_text ()
723 if repeat_element and hasattr (repeat_element, 'direction'):
724 repeat = RepeatMarker ()
725 repeat.direction = {"forward": -1, "backward": 1}.get (repeat_element.direction, 0)
727 if ( (repeat_element.direction == "forward" and bartype == "heavy-light") or
728 (repeat_element.direction == "backward" and bartype == "light-heavy") ):
729 bartype = None
730 if hasattr (repeat_element, 'times'):
731 try:
732 repeat.times = int (repeat_element.times)
733 except ValueError:
734 repeat.times = 2
735 repeat.event = barline
736 if repeat.direction == -1:
737 retval[3] = repeat
738 else:
739 retval[1] = repeat
741 if ending_element and hasattr (ending_element, 'type'):
742 ending = EndingMarker ()
743 ending.direction = {"start": -1, "stop": 1, "discontinue": 1}.get (ending_element.type, 0)
744 ending.event = barline
745 if ending.direction == -1:
746 retval[4] = ending
747 else:
748 retval[0] = ending
750 if bartype:
751 b = musicexp.BarLine ()
752 b.type = bartype
753 retval[2] = b
755 return retval.values ()
757 spanner_event_dict = {
758 'beam' : musicexp.BeamEvent,
759 'dashes' : musicexp.TextSpannerEvent,
760 'bracket' : musicexp.BracketSpannerEvent,
761 'glissando' : musicexp.GlissandoEvent,
762 'octave-shift' : musicexp.OctaveShiftEvent,
763 'pedal' : musicexp.PedalEvent,
764 'slide' : musicexp.GlissandoEvent,
765 'slur' : musicexp.SlurEvent,
766 'wavy-line' : musicexp.TrillSpanEvent,
767 'wedge' : musicexp.HairpinEvent
769 spanner_type_dict = {
770 'start': -1,
771 'begin': -1,
772 'crescendo': -1,
773 'decreschendo': -1,
774 'diminuendo': -1,
775 'continue': 0,
776 'change': 0,
777 'up': -1,
778 'down': -1,
779 'stop': 1,
780 'end' : 1
783 def musicxml_spanner_to_lily_event (mxl_event):
784 ev = None
786 name = mxl_event.get_name()
787 func = spanner_event_dict.get (name)
788 if func:
789 ev = func()
790 else:
791 error_message (_ ('unknown span event %s') % mxl_event)
794 type = mxl_event.get_type ()
795 span_direction = spanner_type_dict.get (type)
796 # really check for None, because some types will be translated to 0, which
797 # would otherwise also lead to the unknown span warning
798 if span_direction != None:
799 ev.span_direction = span_direction
800 else:
801 error_message (_ ('unknown span type %s for %s') % (type, name))
803 ev.set_span_type (type)
804 ev.line_type = getattr (mxl_event, 'line-type', 'solid')
806 # assign the size, which is used for octave-shift, etc.
807 ev.size = mxl_event.get_size ()
809 return ev
811 def musicxml_direction_to_indicator (direction):
812 return { "above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1 }.get (direction, 0)
814 def musicxml_fermata_to_lily_event (mxl_event):
815 ev = musicexp.ArticulationEvent ()
816 txt = mxl_event.get_text ()
817 # The contents of the element defined the shape, possible are normal, angled and square
818 ev.type = { "angled": "shortfermata", "square": "longfermata" }.get (txt, "fermata")
819 if hasattr (mxl_event, 'type'):
820 dir = musicxml_direction_to_indicator (mxl_event.type)
821 if dir and options.convert_directions:
822 ev.force_direction = dir
823 return ev
825 def musicxml_arpeggiate_to_lily_event (mxl_event):
826 ev = musicexp.ArpeggioEvent ()
827 ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
828 return ev
830 def musicxml_nonarpeggiate_to_lily_event (mxl_event):
831 ev = musicexp.ArpeggioEvent ()
832 ev.non_arpeggiate = True
833 ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
834 return ev
836 def musicxml_tremolo_to_lily_event (mxl_event):
837 ev = musicexp.TremoloEvent ()
838 txt = mxl_event.get_text ()
839 if txt:
840 ev.bars = txt
841 else:
842 ev.bars = "3"
843 return ev
845 def musicxml_falloff_to_lily_event (mxl_event):
846 ev = musicexp.BendEvent ()
847 ev.alter = -4
848 return ev
850 def musicxml_doit_to_lily_event (mxl_event):
851 ev = musicexp.BendEvent ()
852 ev.alter = 4
853 return ev
855 def musicxml_bend_to_lily_event (mxl_event):
856 ev = musicexp.BendEvent ()
857 ev.alter = mxl_event.bend_alter ()
858 return ev
860 def musicxml_caesura_to_lily_event (mxl_event):
861 ev = musicexp.MarkupEvent ()
862 # FIXME: default to straight or curved caesura?
863 ev.contents = "\\musicglyph #\"scripts.caesura.straight\""
864 ev.force_direction = 1
865 return ev
867 def musicxml_fingering_event (mxl_event):
868 ev = musicexp.ShortArticulationEvent ()
869 ev.type = mxl_event.get_text ()
870 return ev
872 def musicxml_snappizzicato_event (mxl_event):
873 needed_additional_definitions.append ("snappizzicato")
874 ev = musicexp.MarkupEvent ()
875 ev.contents = "\\snappizzicato"
876 return ev
878 def musicxml_string_event (mxl_event):
879 ev = musicexp.NoDirectionArticulationEvent ()
880 ev.type = mxl_event.get_text ()
881 return ev
883 def musicxml_accidental_mark (mxl_event):
884 ev = musicexp.MarkupEvent ()
885 contents = { "sharp": "\\sharp",
886 "natural": "\\natural",
887 "flat": "\\flat",
888 "double-sharp": "\\doublesharp",
889 "sharp-sharp": "\\sharp\\sharp",
890 "flat-flat": "\\flat\\flat",
891 "flat-flat": "\\doubleflat",
892 "natural-sharp": "\\natural\\sharp",
893 "natural-flat": "\\natural\\flat",
894 "quarter-flat": "\\semiflat",
895 "quarter-sharp": "\\semisharp",
896 "three-quarters-flat": "\\sesquiflat",
897 "three-quarters-sharp": "\\sesquisharp",
898 }.get (mxl_event.get_text ())
899 if contents:
900 ev.contents = contents
901 return ev
902 else:
903 return None
905 # translate articulations, ornaments and other notations into ArticulationEvents
906 # possible values:
907 # -) string (ArticulationEvent with that name)
908 # -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
909 # -) (class, name) (like string, only that a different class than ArticulationEvent is used)
910 # TODO: Some translations are missing!
911 articulations_dict = {
912 "accent": (musicexp.ShortArticulationEvent, ">"), # or "accent"
913 "accidental-mark": musicxml_accidental_mark,
914 "bend": musicxml_bend_to_lily_event,
915 "breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"),
916 "caesura": musicxml_caesura_to_lily_event,
917 #"delayed-turn": "?",
918 "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato"
919 "doit": musicxml_doit_to_lily_event,
920 #"double-tongue": "?",
921 "down-bow": "downbow",
922 "falloff": musicxml_falloff_to_lily_event,
923 "fingering": musicxml_fingering_event,
924 #"fingernails": "?",
925 #"fret": "?",
926 #"hammer-on": "?",
927 "harmonic": "flageolet",
928 #"heel": "?",
929 "inverted-mordent": "prall",
930 "inverted-turn": "reverseturn",
931 "mordent": "mordent",
932 "open-string": "open",
933 #"plop": "?",
934 #"pluck": "?",
935 #"pull-off": "?",
936 #"schleifer": "?",
937 #"scoop": "?",
938 #"shake": "?",
939 "snap-pizzicato": musicxml_snappizzicato_event,
940 #"spiccato": "?",
941 "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo"
942 "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
943 "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
944 #"stress": "?",
945 "string": musicxml_string_event,
946 "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato"
947 #"tap": "?",
948 "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto"
949 "thumb-position": "thumb",
950 #"toe": "?",
951 "turn": "turn",
952 "tremolo": musicxml_tremolo_to_lily_event,
953 "trill-mark": "trill",
954 #"triple-tongue": "?",
955 #"unstress": "?"
956 "up-bow": "upbow",
957 #"wavy-line": "?",
959 articulation_spanners = [ "wavy-line" ]
961 def musicxml_articulation_to_lily_event (mxl_event):
962 # wavy-line elements are treated as trill spanners, not as articulation ornaments
963 if mxl_event.get_name () in articulation_spanners:
964 return musicxml_spanner_to_lily_event (mxl_event)
966 tmp_tp = articulations_dict.get (mxl_event.get_name ())
967 if not tmp_tp:
968 return
970 if isinstance (tmp_tp, str):
971 ev = musicexp.ArticulationEvent ()
972 ev.type = tmp_tp
973 elif isinstance (tmp_tp, tuple):
974 ev = tmp_tp[0] ()
975 ev.type = tmp_tp[1]
976 else:
977 ev = tmp_tp (mxl_event)
979 # Some articulations use the type attribute, other the placement...
980 dir = None
981 if hasattr (mxl_event, 'type') and options.convert_directions:
982 dir = musicxml_direction_to_indicator (mxl_event.type)
983 if hasattr (mxl_event, 'placement') and options.convert_directions:
984 dir = musicxml_direction_to_indicator (mxl_event.placement)
985 if dir:
986 ev.force_direction = dir
987 return ev
991 def musicxml_dynamics_to_lily_event (dynentry):
992 dynamics_available = (
993 "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf",
994 "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" )
995 dynamicsname = dynentry.get_name ()
996 if dynamicsname == "other-dynamics":
997 dynamicsname = dynentry.get_text ()
998 if not dynamicsname or dynamicsname=="#text":
999 return
1001 if not dynamicsname in dynamics_available:
1002 # Get rid of - in tag names (illegal in ly tags!)
1003 dynamicstext = dynamicsname
1004 dynamicsname = string.replace (dynamicsname, "-", "")
1005 additional_definitions[dynamicsname] = dynamicsname + \
1006 " = #(make-dynamic-script \"" + dynamicstext + "\")"
1007 needed_additional_definitions.append (dynamicsname)
1008 event = musicexp.DynamicsEvent ()
1009 event.type = dynamicsname
1010 return event
1012 # Convert single-color two-byte strings to numbers 0.0 - 1.0
1013 def hexcolorval_to_nr (hex_val):
1014 try:
1015 v = int (hex_val, 16)
1016 if v == 255:
1017 v = 256
1018 return v / 256.
1019 except ValueError:
1020 return 0.
1022 def hex_to_color (hex_val):
1023 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)
1024 if res:
1025 return map (lambda x: hexcolorval_to_nr (x), res.group (2,3,4))
1026 else:
1027 return None
1029 def musicxml_words_to_lily_event (words):
1030 event = musicexp.TextEvent ()
1031 text = words.get_text ()
1032 text = re.sub ('^ *\n? *', '', text)
1033 text = re.sub (' *\n? *$', '', text)
1034 event.text = text
1036 if hasattr (words, 'default-y') and options.convert_directions:
1037 offset = getattr (words, 'default-y')
1038 try:
1039 off = string.atoi (offset)
1040 if off > 0:
1041 event.force_direction = 1
1042 else:
1043 event.force_direction = -1
1044 except ValueError:
1045 event.force_direction = 0
1047 if hasattr (words, 'font-weight'):
1048 font_weight = { "normal": '', "bold": '\\bold' }.get (getattr (words, 'font-weight'), '')
1049 if font_weight:
1050 event.markup += font_weight
1052 if hasattr (words, 'font-size'):
1053 size = getattr (words, 'font-size')
1054 font_size = {
1055 "xx-small": '\\teeny',
1056 "x-small": '\\tiny',
1057 "small": '\\small',
1058 "medium": '',
1059 "large": '\\large',
1060 "x-large": '\\huge',
1061 "xx-large": '\\larger\\huge'
1062 }.get (size, '')
1063 if font_size:
1064 event.markup += font_size
1066 if hasattr (words, 'color'):
1067 color = getattr (words, 'color')
1068 rgb = hex_to_color (color)
1069 if rgb:
1070 event.markup += "\\with-color #(rgb-color %s %s %s)" % (rgb[0], rgb[1], rgb[2])
1072 if hasattr (words, 'font-style'):
1073 font_style = { "italic": '\\italic' }.get (getattr (words, 'font-style'), '')
1074 if font_style:
1075 event.markup += font_style
1077 # TODO: How should I best convert the font-family attribute?
1079 # TODO: How can I represent the underline, overline and line-through
1080 # attributes in Lilypond? Values of these attributes indicate
1081 # the number of lines
1083 return event
1086 # convert accordion-registration to lilypond.
1087 # Since lilypond does not have any built-in commands, we need to create
1088 # the markup commands manually and define our own variables.
1089 # Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
1090 def musicxml_accordion_to_markup (mxl_event):
1091 commandname = "accReg"
1092 command = ""
1094 high = mxl_event.get_maybe_exist_named_child ('accordion-high')
1095 if high:
1096 commandname += "H"
1097 command += """\\combine
1098 \\raise #2.5 \\musicglyph #\"accordion.accDot\"
1100 middle = mxl_event.get_maybe_exist_named_child ('accordion-middle')
1101 if middle:
1102 # By default, use one dot (when no or invalid content is given). The
1103 # MusicXML spec is quiet about this case...
1104 txt = 1
1105 try:
1106 txt = string.atoi (middle.get_text ())
1107 except ValueError:
1108 pass
1109 if txt == 3:
1110 commandname += "MMM"
1111 command += """\\combine
1112 \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1113 \\combine
1114 \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\"
1115 \\combine
1116 \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\"
1118 elif txt == 2:
1119 commandname += "MM"
1120 command += """\\combine
1121 \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.accDot\"
1122 \\combine
1123 \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\"
1125 elif not txt <= 0:
1126 commandname += "M"
1127 command += """\\combine
1128 \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1130 low = mxl_event.get_maybe_exist_named_child ('accordion-low')
1131 if low:
1132 commandname += "L"
1133 command += """\\combine
1134 \\raise #0.5 \musicglyph #\"accordion.accDot\"
1137 command += "\musicglyph #\"accordion.accDiscant\""
1138 command = "\\markup { \\normalsize %s }" % command
1139 # Define the newly built command \accReg[H][MMM][L]
1140 additional_definitions[commandname] = "%s = %s" % (commandname, command)
1141 needed_additional_definitions.append (commandname)
1142 return "\\%s" % commandname
1144 def musicxml_accordion_to_ly (mxl_event):
1145 txt = musicxml_accordion_to_markup (mxl_event)
1146 if txt:
1147 ev = musicexp.MarkEvent (txt)
1148 return ev
1149 return
1152 def musicxml_rehearsal_to_ly_mark (mxl_event):
1153 text = mxl_event.get_text ()
1154 if not text:
1155 return
1156 # default is boxed rehearsal marks!
1157 encl = "box"
1158 if hasattr (mxl_event, 'enclosure'):
1159 encl = {"none": None, "square": "box", "circle": "circle" }.get (mxl_event.enclosure, None)
1160 if encl:
1161 text = "\\%s { %s }" % (encl, text)
1162 ev = musicexp.MarkEvent ("\\markup { %s }" % text)
1163 return ev
1165 def musicxml_harp_pedals_to_ly (mxl_event):
1166 count = 0
1167 result = "\\harp-pedal #\""
1168 for t in mxl_event.get_named_children ('pedal-tuning'):
1169 alter = t.get_named_child ('pedal-alter')
1170 if alter:
1171 val = int (alter.get_text ().strip ())
1172 result += {1: "v", 0: "-", -1: "^"}.get (val, "")
1173 count += 1
1174 if count == 3:
1175 result += "|"
1176 ev = musicexp.MarkupEvent ()
1177 ev.contents = result + "\""
1178 return ev
1180 def musicxml_eyeglasses_to_ly (mxl_event):
1181 needed_additional_definitions.append ("eyeglasses")
1182 return musicexp.MarkEvent ("\\eyeglasses")
1184 def next_non_hash_index (lst, pos):
1185 pos += 1
1186 while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
1187 pos += 1
1188 return pos
1190 def musicxml_metronome_to_ly (mxl_event):
1191 children = mxl_event.get_all_children ()
1192 if not children:
1193 return
1195 index = -1
1196 index = next_non_hash_index (children, index)
1197 if isinstance (children[index], musicxml.BeatUnit):
1198 # first form of metronome-mark, using unit and beats/min or other unit
1199 ev = musicexp.TempoMark ()
1200 if hasattr (mxl_event, 'parentheses'):
1201 ev.set_parentheses (mxl_event.parentheses == "yes")
1203 d = musicexp.Duration ()
1204 d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1205 index = next_non_hash_index (children, index)
1206 if isinstance (children[index], musicxml.BeatUnitDot):
1207 d.dots = 1
1208 index = next_non_hash_index (children, index)
1209 ev.set_base_duration (d)
1210 if isinstance (children[index], musicxml.BeatUnit):
1211 # Form "note = newnote"
1212 newd = musicexp.Duration ()
1213 newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1214 index = next_non_hash_index (children, index)
1215 if isinstance (children[index], musicxml.BeatUnitDot):
1216 newd.dots = 1
1217 index = next_non_hash_index (children, index)
1218 ev.set_new_duration (newd)
1219 elif isinstance (children[index], musicxml.PerMinute):
1220 # Form "note = bpm"
1221 try:
1222 beats = int (children[index].get_text ())
1223 ev.set_beats_per_minute (beats)
1224 except ValueError:
1225 pass
1226 else:
1227 error_message (_ ("Unknown metronome mark, ignoring"))
1228 return
1229 return ev
1230 else:
1231 #TODO: Implement the other (more complex) way for tempo marks!
1232 error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
1233 return
1235 # translate directions into Events, possible values:
1236 # -) string (MarkEvent with that command)
1237 # -) function (function(mxl_event) needs to return a full Event-derived object
1238 # -) (class, name) (like string, only that a different class than MarkEvent is used)
1239 directions_dict = {
1240 'accordion-registration' : musicxml_accordion_to_ly,
1241 'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
1242 # 'damp' : ???
1243 # 'damp-all' : ???
1244 'eyeglasses': musicxml_eyeglasses_to_ly,
1245 'harp-pedals' : musicxml_harp_pedals_to_ly,
1246 # 'image' : ???
1247 'metronome' : musicxml_metronome_to_ly,
1248 'rehearsal' : musicxml_rehearsal_to_ly_mark,
1249 # 'scordatura' : ???
1250 'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
1251 'words' : musicxml_words_to_lily_event,
1253 directions_spanners = [ 'octave-shift', 'pedal', 'wedge', 'dashes', 'bracket' ]
1255 def musicxml_direction_to_lily (n):
1256 # TODO: Handle the <staff> element!
1257 res = []
1258 # placement applies to all children!
1259 dir = None
1260 if hasattr (n, 'placement') and options.convert_directions:
1261 dir = musicxml_direction_to_indicator (n.placement)
1262 dirtype_children = []
1263 # TODO: The direction-type is used for grouping (e.g. dynamics with text),
1264 # so we can't simply flatten them out!
1265 for dt in n.get_typed_children (musicxml.DirType):
1266 dirtype_children += dt.get_all_children ()
1268 for entry in dirtype_children:
1269 # backets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
1270 if entry.get_name() in directions_spanners:
1271 event = musicxml_spanner_to_lily_event (entry)
1272 if event:
1273 res.append (event)
1274 continue
1276 # now treat all the "simple" ones, that can be translated using the dict
1277 ev = None
1278 tmp_tp = directions_dict.get (entry.get_name (), None)
1279 if isinstance (tmp_tp, str): # string means MarkEvent
1280 ev = musicexp.MarkEvent (tmp_tp)
1281 elif isinstance (tmp_tp, tuple): # tuple means (EventClass, "text")
1282 ev = tmp_tp[0] (tmp_tp[1])
1283 elif tmp_tp:
1284 ev = tmp_tp (entry)
1285 if ev:
1286 # TODO: set the correct direction! Unfortunately, \mark in ly does
1287 # not seem to support directions!
1288 res.append (ev)
1289 continue
1291 if entry.get_name () == "dynamics":
1292 for dynentry in entry.get_all_children ():
1293 ev = musicxml_dynamics_to_lily_event (dynentry)
1294 if ev:
1295 res.append (ev)
1297 return res
1299 def musicxml_frame_to_lily_event (frame):
1300 ev = musicexp.FretEvent ()
1301 ev.strings = frame.get_strings ()
1302 ev.frets = frame.get_frets ()
1303 #offset = frame.get_first_fret () - 1
1304 barre = []
1305 for fn in frame.get_named_children ('frame-note'):
1306 fret = fn.get_fret ()
1307 if fret <= 0:
1308 fret = "o"
1309 el = [ fn.get_string (), fret ]
1310 fingering = fn.get_fingering ()
1311 if fingering >= 0:
1312 el.append (fingering)
1313 ev.elements.append (el)
1314 b = fn.get_barre ()
1315 if b == 'start':
1316 barre[0] = el[0] # start string
1317 barre[2] = el[1] # fret
1318 elif b == 'stop':
1319 barre[1] = el[0] # end string
1320 if barre:
1321 ev.barre = barre
1322 return ev
1324 def musicxml_harmony_to_lily (n):
1325 res = []
1326 for f in n.get_named_children ('frame'):
1327 ev = musicxml_frame_to_lily_event (f)
1328 if ev:
1329 res.append (ev)
1330 return res
1333 def musicxml_chordpitch_to_lily (mxl_cpitch):
1334 r = musicexp.ChordPitch ()
1335 r.alteration = mxl_cpitch.get_alteration ()
1336 r.step = musicxml_step_to_lily (mxl_cpitch.get_step ())
1337 return r
1339 chordkind_dict = {
1340 'major': '5',
1341 'minor': 'm5',
1342 'augmented': 'aug5',
1343 'diminished': 'dim5',
1344 # Sevenths:
1345 'dominant': '7',
1346 'major-seventh': 'maj7',
1347 'minor-seventh': 'm7',
1348 'diminished-seventh': 'dim7',
1349 'augmented-seventh': 'aug7',
1350 'half-diminished': 'dim5m7',
1351 'major-minor': 'maj7m5',
1352 # Sixths:
1353 'major-sixth': '6',
1354 'minor-sixth': 'm6',
1355 # Ninths:
1356 'dominant-ninth': '9',
1357 'major-ninth': 'maj9',
1358 'minor-ninth': 'm9',
1359 # 11ths (usually as the basis for alteration):
1360 'dominant-11th': '11',
1361 'major-11th': 'maj11',
1362 'minor-11th': 'm11',
1363 # 13ths (usually as the basis for alteration):
1364 'dominant-13th': '13.11',
1365 'major-13th': 'maj13.11',
1366 'minor-13th': 'm13',
1367 # Suspended:
1368 'suspended-second': 'sus2',
1369 'suspended-fourth': 'sus4',
1370 # Functional sixths:
1371 # TODO
1372 #'Neapolitan': '???',
1373 #'Italian': '???',
1374 #'French': '???',
1375 #'German': '???',
1376 # Other:
1377 #'pedal': '???',(pedal-point bass)
1378 'power': '5^3',
1379 #'Tristan': '???',
1380 'other': '1',
1381 'none': None,
1384 def musicxml_chordkind_to_lily (kind):
1385 res = chordkind_dict.get (kind, None)
1386 # Check for None, since a major chord is converted to ''
1387 if res == None:
1388 error_message (_ ("Unable to convert chord type %s to lilypond.") % kind)
1389 return res
1391 def musicxml_harmony_to_lily_chordname (n):
1392 res = []
1393 root = n.get_maybe_exist_named_child ('root')
1394 if root:
1395 ev = musicexp.ChordNameEvent ()
1396 ev.root = musicxml_chordpitch_to_lily (root)
1397 kind = n.get_maybe_exist_named_child ('kind')
1398 if kind:
1399 ev.kind = musicxml_chordkind_to_lily (kind.get_text ())
1400 if not ev.kind:
1401 return res
1402 bass = n.get_maybe_exist_named_child ('bass')
1403 if bass:
1404 ev.bass = musicxml_chordpitch_to_lily (bass)
1405 inversion = n.get_maybe_exist_named_child ('inversion')
1406 if inversion:
1407 # TODO: Lilypond does not support inversions, does it?
1409 # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
1410 # 4. LilyPond supports the first inversion in the form of added
1411 # bass notes. So the first inversion of C major would be c:/g.
1412 # To get the second inversion of C major, you would need to do
1413 # e:6-3-^5 or e:m6-^5. However, both of these techniques
1414 # require you to know the chord and calculate either the fifth
1415 # pitch (for the first inversion) or the third pitch (for the
1416 # second inversion) so they may not be helpful for musicxml2ly.
1417 inversion_count = string.atoi (inversion.get_text ())
1418 if inversion_count == 1:
1419 # TODO: Calculate the bass note for the inversion...
1420 pass
1421 pass
1422 for deg in n.get_named_children ('degree'):
1423 d = musicexp.ChordModification ()
1424 d.type = deg.get_type ()
1425 d.step = deg.get_value ()
1426 d.alteration = deg.get_alter ()
1427 ev.add_modification (d)
1428 #TODO: convert the user-symbols attribute:
1429 #major: a triangle, like Unicode 25B3
1430 #minor: -, like Unicode 002D
1431 #augmented: +, like Unicode 002B
1432 #diminished: (degree), like Unicode 00B0
1433 #half-diminished: (o with slash), like Unicode 00F8
1434 if ev and ev.root:
1435 res.append (ev)
1437 return res
1439 def musicxml_figured_bass_note_to_lily (n):
1440 res = musicexp.FiguredBassNote ()
1441 suffix_dict = { 'sharp' : "+",
1442 'flat' : "-",
1443 'natural' : "!",
1444 'double-sharp' : "++",
1445 'flat-flat' : "--",
1446 'sharp-sharp' : "++",
1447 'slash' : "/" }
1448 prefix = n.get_maybe_exist_named_child ('prefix')
1449 if prefix:
1450 res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
1451 fnumber = n.get_maybe_exist_named_child ('figure-number')
1452 if fnumber:
1453 res.set_number (fnumber.get_text ())
1454 suffix = n.get_maybe_exist_named_child ('suffix')
1455 if suffix:
1456 res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
1457 if n.get_maybe_exist_named_child ('extend'):
1458 # TODO: Implement extender lines (unfortunately, in lilypond you have
1459 # to use \set useBassFigureExtenders = ##t, which turns them on
1460 # globally, while MusicXML has a property for each note...
1461 # I'm not sure there is a proper way to implement this cleanly
1462 #n.extend
1463 pass
1464 return res
1468 def musicxml_figured_bass_to_lily (n):
1469 if not isinstance (n, musicxml.FiguredBass):
1470 return
1471 res = musicexp.FiguredBassEvent ()
1472 for i in n.get_named_children ('figure'):
1473 note = musicxml_figured_bass_note_to_lily (i)
1474 if note:
1475 res.append (note)
1476 dur = n.get_maybe_exist_named_child ('duration')
1477 if dur:
1478 # apply the duration to res
1479 length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
1480 res.set_real_duration (length)
1481 duration = rational_to_lily_duration (length)
1482 if duration:
1483 res.set_duration (duration)
1484 if hasattr (n, 'parentheses') and n.parentheses == "yes":
1485 res.set_parentheses (True)
1486 return res
1488 instrument_drumtype_dict = {
1489 'Acoustic Snare Drum': 'acousticsnare',
1490 'Side Stick': 'sidestick',
1491 'Open Triangle': 'opentriangle',
1492 'Mute Triangle': 'mutetriangle',
1493 'Tambourine': 'tambourine',
1494 'Bass Drum': 'bassdrum',
1497 def musicxml_note_to_lily_main_event (n):
1498 pitch = None
1499 duration = None
1500 event = None
1502 mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
1503 if mxl_pitch:
1504 pitch = musicxml_pitch_to_lily (mxl_pitch)
1505 event = musicexp.NoteEvent ()
1506 event.pitch = pitch
1508 acc = n.get_maybe_exist_named_child ('accidental')
1509 if acc:
1510 # let's not force accs everywhere.
1511 event.cautionary = acc.editorial
1513 elif n.get_maybe_exist_typed_child (musicxml.Unpitched):
1514 # Unpitched elements have display-step and can also have
1515 # display-octave.
1516 unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
1517 event = musicexp.NoteEvent ()
1518 event.pitch = musicxml_unpitched_to_lily (unpitched)
1520 elif n.get_maybe_exist_typed_child (musicxml.Rest):
1521 # rests can have display-octave and display-step, which are
1522 # treated like an ordinary note pitch
1523 rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1524 event = musicexp.RestEvent ()
1525 pitch = musicxml_restdisplay_to_lily (rest)
1526 event.pitch = pitch
1528 elif n.instrument_name:
1529 event = musicexp.NoteEvent ()
1530 drum_type = instrument_drumtype_dict.get (n.instrument_name)
1531 if drum_type:
1532 event.drum_type = drum_type
1533 else:
1534 n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
1535 event.drum_type = 'acousticsnare'
1537 else:
1538 n.message (_ ("cannot find suitable event"))
1540 if event:
1541 event.duration = musicxml_duration_to_lily (n)
1543 return event
1546 ## TODO
1547 class NegativeSkip:
1548 def __init__ (self, here, dest):
1549 self.here = here
1550 self.dest = dest
1552 class LilyPondVoiceBuilder:
1553 def __init__ (self):
1554 self.elements = []
1555 self.pending_dynamics = []
1556 self.end_moment = Rational (0)
1557 self.begin_moment = Rational (0)
1558 self.pending_multibar = Rational (0)
1559 self.ignore_skips = False
1560 self.has_relevant_elements = False
1561 self.measure_length = (4, 4)
1563 def _insert_multibar (self):
1564 layout_information.set_context_item ('Score', 'skipBars = ##t')
1565 r = musicexp.MultiMeasureRest ()
1566 lenfrac = Rational (self.measure_length[0], self.measure_length[1])
1567 r.duration = rational_to_lily_duration (lenfrac)
1568 r.duration.factor *= self.pending_multibar / lenfrac
1569 self.elements.append (r)
1570 self.begin_moment = self.end_moment
1571 self.end_moment = self.begin_moment + self.pending_multibar
1572 self.pending_multibar = Rational (0)
1574 def set_measure_length (self, mlen):
1575 if (mlen != self.measure_length) and self.pending_multibar:
1576 self._insert_multibar ()
1577 self.measure_length = mlen
1579 def add_multibar_rest (self, duration):
1580 self.pending_multibar += duration
1582 def set_duration (self, duration):
1583 self.end_moment = self.begin_moment + duration
1584 def current_duration (self):
1585 return self.end_moment - self.begin_moment
1587 def add_music (self, music, duration):
1588 assert isinstance (music, musicexp.Music)
1589 if self.pending_multibar > Rational (0):
1590 self._insert_multibar ()
1592 self.has_relevant_elements = True
1593 self.elements.append (music)
1594 self.begin_moment = self.end_moment
1595 self.set_duration (duration)
1597 # Insert all pending dynamics right after the note/rest:
1598 if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
1599 for d in self.pending_dynamics:
1600 music.append (d)
1601 self.pending_dynamics = []
1603 # Insert some music command that does not affect the position in the measure
1604 def add_command (self, command):
1605 assert isinstance (command, musicexp.Music)
1606 if self.pending_multibar > Rational (0):
1607 self._insert_multibar ()
1608 self.has_relevant_elements = True
1609 self.elements.append (command)
1610 def add_barline (self, barline):
1611 # TODO: Implement merging of default barline and custom bar line
1612 self.add_music (barline, Rational (0))
1613 def add_partial (self, command):
1614 self.ignore_skips = True
1615 self.add_command (command)
1617 def add_dynamics (self, dynamic):
1618 # store the dynamic item(s) until we encounter the next note/rest:
1619 self.pending_dynamics.append (dynamic)
1621 def add_bar_check (self, number):
1622 # re/store has_relevant_elements, so that a barline alone does not
1623 # trigger output for figured bass, chord names
1624 has_relevant = self.has_relevant_elements
1625 b = musicexp.BarLine ()
1626 b.bar_number = number
1627 self.add_barline (b)
1628 self.has_relevant_elements = has_relevant
1630 def jumpto (self, moment):
1631 current_end = self.end_moment + self.pending_multibar
1632 diff = moment - current_end
1634 if diff < Rational (0):
1635 error_message (_ ('Negative skip %s (from position %s to %s)') %
1636 (diff, current_end, moment))
1637 diff = Rational (0)
1639 if diff > Rational (0) and not (self.ignore_skips and moment == 0):
1640 skip = musicexp.SkipEvent()
1641 duration_factor = 1
1642 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)
1643 duration_dots = 0
1644 # TODO: Use the time signature for skips, too. Problem: The skip
1645 # might not start at a measure boundary!
1646 if duration_log > 0: # denominator is a power of 2...
1647 if diff.numerator () == 3:
1648 duration_log -= 1
1649 duration_dots = 1
1650 else:
1651 duration_factor = Rational (diff.numerator ())
1652 else:
1653 # for skips of a whole or more, simply use s1*factor
1654 duration_log = 0
1655 duration_factor = diff
1656 skip.duration.duration_log = duration_log
1657 skip.duration.factor = duration_factor
1658 skip.duration.dots = duration_dots
1660 evc = musicexp.ChordEvent ()
1661 evc.elements.append (skip)
1662 self.add_music (evc, diff)
1664 if diff > Rational (0) and moment == 0:
1665 self.ignore_skips = False
1667 def last_event_chord (self, starting_at):
1669 value = None
1671 # if the position matches, find the last ChordEvent, do not cross a bar line!
1672 at = len( self.elements ) - 1
1673 while (at >= 0 and
1674 not isinstance (self.elements[at], musicexp.ChordEvent) and
1675 not isinstance (self.elements[at], musicexp.BarLine)):
1676 at -= 1
1678 if (self.elements
1679 and at >= 0
1680 and isinstance (self.elements[at], musicexp.ChordEvent)
1681 and self.begin_moment == starting_at):
1682 value = self.elements[at]
1683 else:
1684 self.jumpto (starting_at)
1685 value = None
1686 return value
1688 def correct_negative_skip (self, goto):
1689 self.end_moment = goto
1690 self.begin_moment = goto
1691 evc = musicexp.ChordEvent ()
1692 self.elements.append (evc)
1695 class VoiceData:
1696 def __init__ (self):
1697 self.voicename = None
1698 self.voicedata = None
1699 self.ly_voice = None
1700 self.figured_bass = None
1701 self.chordnames = None
1702 self.lyrics_dict = {}
1703 self.lyrics_order = []
1705 def musicxml_step_to_lily (step):
1706 if step:
1707 return (ord (step) - ord ('A') + 7 - 2) % 7
1708 else:
1709 return None
1711 def measure_length_from_attributes (attr, current_measure_length):
1712 mxl = attr.get_named_attribute ('time')
1713 if mxl:
1714 return attr.get_time_signature ()
1715 else:
1716 return current_measure_length
1718 def musicxml_voice_to_lily_voice (voice):
1719 tuplet_events = []
1720 modes_found = {}
1721 lyrics = {}
1722 return_value = VoiceData ()
1723 return_value.voicedata = voice
1725 # First pitch needed for relative mode (if selected in command-line options)
1726 first_pitch = None
1728 # Needed for melismata detection (ignore lyrics on those notes!):
1729 inside_slur = False
1730 is_tied = False
1731 is_chord = False
1732 is_beamed = False
1733 ignore_lyrics = False
1735 current_staff = None
1737 pending_figured_bass = []
1738 pending_chordnames = []
1740 # Make sure that the keys in the dict don't get reordered, since
1741 # we need the correct ordering of the lyrics stanzas! By default,
1742 # a dict will reorder its keys
1743 return_value.lyrics_order = voice.get_lyrics_numbers ()
1744 for k in return_value.lyrics_order:
1745 lyrics[k] = []
1747 voice_builder = LilyPondVoiceBuilder ()
1748 figured_bass_builder = LilyPondVoiceBuilder ()
1749 chordnames_builder = LilyPondVoiceBuilder ()
1750 current_measure_length = (4, 4)
1751 voice_builder.set_measure_length (current_measure_length)
1753 for n in voice._elements:
1754 if n.get_name () == 'forward':
1755 continue
1756 staff = n.get_maybe_exist_named_child ('staff')
1757 if staff:
1758 staff = staff.get_text ()
1759 if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'):
1760 voice_builder.add_command (musicexp.StaffChange (staff))
1761 current_staff = staff
1763 if isinstance (n, musicxml.Partial) and n.partial > 0:
1764 a = musicxml_partial_to_lily (n.partial)
1765 if a:
1766 voice_builder.add_partial (a)
1767 continue
1769 is_chord = n.get_maybe_exist_named_child ('chord')
1770 is_after_grace = (isinstance (n, musicxml.Note) and n.is_after_grace ());
1771 if not is_chord and not is_after_grace:
1772 try:
1773 voice_builder.jumpto (n._when)
1774 except NegativeSkip, neg:
1775 voice_builder.correct_negative_skip (n._when)
1776 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
1778 if isinstance (n, musicxml.Barline):
1779 barlines = musicxml_barline_to_lily (n)
1780 for a in barlines:
1781 if isinstance (a, musicexp.BarLine):
1782 voice_builder.add_barline (a)
1783 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
1784 voice_builder.add_command (a)
1785 continue
1787 # Continue any multimeasure-rests before trying to add bar checks!
1788 # Don't handle new MM rests yet, because for them we want bar checks!
1789 rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1790 if (rest and rest.is_whole_measure ()
1791 and voice_builder.pending_multibar > Rational (0)):
1792 voice_builder.add_multibar_rest (n._duration)
1793 continue
1796 # print a bar check at the beginning of each measure!
1797 if n.is_first () and n._measure_position == Rational (0) and n != voice._elements[0]:
1798 try:
1799 num = int (n.get_parent ().number)
1800 except ValueError:
1801 num = 0
1802 if num > 0:
1803 voice_builder.add_bar_check (num)
1804 figured_bass_builder.add_bar_check (num)
1805 chordnames_builder.add_bar_check (num)
1807 # Start any new multimeasure rests
1808 if (rest and rest.is_whole_measure ()):
1809 voice_builder.add_multibar_rest (n._duration)
1810 continue
1813 if isinstance (n, musicxml.Direction):
1814 for a in musicxml_direction_to_lily (n):
1815 if a.wait_for_note ():
1816 voice_builder.add_dynamics (a)
1817 else:
1818 voice_builder.add_command (a)
1819 continue
1821 if isinstance (n, musicxml.Harmony):
1822 for a in musicxml_harmony_to_lily (n):
1823 if a.wait_for_note ():
1824 voice_builder.add_dynamics (a)
1825 else:
1826 voice_builder.add_command (a)
1827 for a in musicxml_harmony_to_lily_chordname (n):
1828 pending_chordnames.append (a)
1829 continue
1831 if isinstance (n, musicxml.FiguredBass):
1832 a = musicxml_figured_bass_to_lily (n)
1833 if a:
1834 pending_figured_bass.append (a)
1835 continue
1837 if isinstance (n, musicxml.Attributes):
1838 for a in musicxml_attributes_to_lily (n):
1839 voice_builder.add_command (a)
1840 measure_length = measure_length_from_attributes (n, current_measure_length)
1841 if current_measure_length != measure_length:
1842 current_measure_length = measure_length
1843 voice_builder.set_measure_length (current_measure_length)
1844 continue
1846 if not n.__class__.__name__ == 'Note':
1847 n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
1848 continue
1850 main_event = musicxml_note_to_lily_main_event (n)
1851 if main_event and not first_pitch:
1852 first_pitch = main_event.pitch
1853 # ignore lyrics for notes inside a slur, tie, chord or beam
1854 ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
1856 if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
1857 modes_found['drummode'] = True
1859 ev_chord = voice_builder.last_event_chord (n._when)
1860 if not ev_chord:
1861 ev_chord = musicexp.ChordEvent()
1862 voice_builder.add_music (ev_chord, n._duration)
1864 # For grace notes:
1865 grace = n.get_maybe_exist_typed_child (musicxml.Grace)
1866 if n.is_grace ():
1867 is_after_grace = ev_chord.has_elements () or n.is_after_grace ();
1868 is_chord = n.get_maybe_exist_typed_child (musicxml.Chord)
1870 grace_chord = None
1872 # after-graces and other graces use different lists; Depending on
1873 # whether we have a chord or not, obtain either a new ChordEvent or
1874 # the previous one to create a chord
1875 if is_after_grace:
1876 if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
1877 grace_chord = ev_chord.after_grace_elements.get_last_event_chord ()
1878 if not grace_chord:
1879 grace_chord = musicexp.ChordEvent ()
1880 ev_chord.append_after_grace (grace_chord)
1881 elif n.is_grace ():
1882 if ev_chord.grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
1883 grace_chord = ev_chord.grace_elements.get_last_event_chord ()
1884 if not grace_chord:
1885 grace_chord = musicexp.ChordEvent ()
1886 ev_chord.append_grace (grace_chord)
1888 if hasattr (grace, 'slash') and not is_after_grace:
1889 # TODO: use grace_type = "appoggiatura" for slurred grace notes
1890 if grace.slash == "yes":
1891 ev_chord.grace_type = "acciaccatura"
1892 # now that we have inserted the chord into the grace music, insert
1893 # everything into that chord instead of the ev_chord
1894 ev_chord = grace_chord
1895 ev_chord.append (main_event)
1896 ignore_lyrics = True
1897 else:
1898 ev_chord.append (main_event)
1899 # When a note/chord has grace notes (duration==0), the duration of the
1900 # event chord is not yet known, but the event chord was already added
1901 # with duration 0. The following correct this when we hit the real note!
1902 if voice_builder.current_duration () == 0 and n._duration > 0:
1903 voice_builder.set_duration (n._duration)
1905 # if we have a figured bass, set its voice builder to the correct position
1906 # and insert the pending figures
1907 if pending_figured_bass:
1908 try:
1909 figured_bass_builder.jumpto (n._when)
1910 except NegativeSkip, neg:
1911 pass
1912 for fb in pending_figured_bass:
1913 # if a duration is given, use that, otherwise the one of the note
1914 dur = fb.real_duration
1915 if not dur:
1916 dur = ev_chord.get_length ()
1917 if not fb.duration:
1918 fb.duration = ev_chord.get_duration ()
1919 figured_bass_builder.add_music (fb, dur)
1920 pending_figured_bass = []
1922 if pending_chordnames:
1923 try:
1924 chordnames_builder.jumpto (n._when)
1925 except NegativeSkip, neg:
1926 pass
1927 for cn in pending_chordnames:
1928 # Assign the duration of the EventChord
1929 cn.duration = ev_chord.get_duration ()
1930 chordnames_builder.add_music (cn, ev_chord.get_length ())
1931 pending_chordnames = []
1934 notations_children = n.get_typed_children (musicxml.Notations)
1935 tuplet_event = None
1936 span_events = []
1938 # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
1939 # +tied | +slur | +tuplet | glissando | slide |
1940 # ornaments | technical | articulations | dynamics |
1941 # +fermata | arpeggiate | non-arpeggiate |
1942 # accidental-mark | other-notation
1943 for notations in notations_children:
1944 for tuplet_event in notations.get_tuplets():
1945 mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
1946 frac = (1,1)
1947 if mod:
1948 frac = mod.get_fraction ()
1950 tuplet_events.append ((ev_chord, tuplet_event, frac))
1952 # First, close all open slurs, only then start any new slur
1953 # TODO: Record the number of the open slur to dtermine the correct
1954 # closing slur!
1955 endslurs = [s for s in notations.get_named_children ('slur')
1956 if s.get_type () in ('stop')]
1957 if endslurs and not inside_slur:
1958 endslurs[0].message (_ ('Encountered closing slur, but no slur is open'))
1959 elif endslurs:
1960 if len (endslurs) > 1:
1961 endslurs[0].message (_ ('Cannot have two simultaneous (closing) slurs'))
1962 # record the slur status for the next note in the loop
1963 if not grace:
1964 inside_slur = False
1965 lily_ev = musicxml_spanner_to_lily_event (endslurs[0])
1966 ev_chord.append (lily_ev)
1968 startslurs = [s for s in notations.get_named_children ('slur')
1969 if s.get_type () in ('start')]
1970 if startslurs and inside_slur:
1971 startslurs[0].message (_ ('Cannot have a slur inside another slur'))
1972 elif startslurs:
1973 if len (startslurs) > 1:
1974 startslurs[0].message (_ ('Cannot have two simultaneous slurs'))
1975 # record the slur status for the next note in the loop
1976 if not grace:
1977 inside_slur = True
1978 lily_ev = musicxml_spanner_to_lily_event (startslurs[0])
1979 ev_chord.append (lily_ev)
1982 if not grace:
1983 mxl_tie = notations.get_tie ()
1984 if mxl_tie and mxl_tie.type == 'start':
1985 ev_chord.append (musicexp.TieEvent ())
1986 is_tied = True
1987 else:
1988 is_tied = False
1990 fermatas = notations.get_named_children ('fermata')
1991 for a in fermatas:
1992 ev = musicxml_fermata_to_lily_event (a)
1993 if ev:
1994 ev_chord.append (ev)
1996 arpeggiate = notations.get_named_children ('arpeggiate')
1997 for a in arpeggiate:
1998 ev = musicxml_arpeggiate_to_lily_event (a)
1999 if ev:
2000 ev_chord.append (ev)
2002 arpeggiate = notations.get_named_children ('non-arpeggiate')
2003 for a in arpeggiate:
2004 ev = musicxml_nonarpeggiate_to_lily_event (a)
2005 if ev:
2006 ev_chord.append (ev)
2008 glissandos = notations.get_named_children ('glissando')
2009 glissandos += notations.get_named_children ('slide')
2010 for a in glissandos:
2011 ev = musicxml_spanner_to_lily_event (a)
2012 if ev:
2013 ev_chord.append (ev)
2015 # accidental-marks are direct children of <notation>!
2016 for a in notations.get_named_children ('accidental-mark'):
2017 ev = musicxml_articulation_to_lily_event (a)
2018 if ev:
2019 ev_chord.append (ev)
2021 # Articulations can contain the following child elements:
2022 # accent | strong-accent | staccato | tenuto |
2023 # detached-legato | staccatissimo | spiccato |
2024 # scoop | plop | doit | falloff | breath-mark |
2025 # caesura | stress | unstress
2026 # Technical can contain the following child elements:
2027 # up-bow | down-bow | harmonic | open-string |
2028 # thumb-position | fingering | pluck | double-tongue |
2029 # triple-tongue | stopped | snap-pizzicato | fret |
2030 # string | hammer-on | pull-off | bend | tap | heel |
2031 # toe | fingernails | other-technical
2032 # Ornaments can contain the following child elements:
2033 # trill-mark | turn | delayed-turn | inverted-turn |
2034 # shake | wavy-line | mordent | inverted-mordent |
2035 # schleifer | tremolo | other-ornament, accidental-mark
2036 ornaments = notations.get_named_children ('ornaments')
2037 ornaments += notations.get_named_children ('articulations')
2038 ornaments += notations.get_named_children ('technical')
2040 for a in ornaments:
2041 for ch in a.get_all_children ():
2042 ev = musicxml_articulation_to_lily_event (ch)
2043 if ev:
2044 ev_chord.append (ev)
2046 dynamics = notations.get_named_children ('dynamics')
2047 for a in dynamics:
2048 for ch in a.get_all_children ():
2049 ev = musicxml_dynamics_to_lily_event (ch)
2050 if ev:
2051 ev_chord.append (ev)
2054 mxl_beams = [b for b in n.get_named_children ('beam')
2055 if (b.get_type () in ('begin', 'end')
2056 and b.is_primary ())]
2057 if mxl_beams and not conversion_settings.ignore_beaming:
2058 beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
2059 if beam_ev:
2060 ev_chord.append (beam_ev)
2061 if beam_ev.span_direction == -1: # beam and thus melisma starts here
2062 is_beamed = True
2063 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
2064 is_beamed = False
2066 if tuplet_event:
2067 mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
2068 frac = (1,1)
2069 if mod:
2070 frac = mod.get_fraction ()
2072 tuplet_events.append ((ev_chord, tuplet_event, frac))
2074 # Extract the lyrics
2075 if not rest and not ignore_lyrics:
2076 note_lyrics_processed = []
2077 note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
2078 for l in note_lyrics_elements:
2079 if l.get_number () < 0:
2080 for k in lyrics.keys ():
2081 lyrics[k].append (l.lyric_to_text ())
2082 note_lyrics_processed.append (k)
2083 else:
2084 lyrics[l.number].append(l.lyric_to_text ())
2085 note_lyrics_processed.append (l.number)
2086 for lnr in lyrics.keys ():
2087 if not lnr in note_lyrics_processed:
2088 lyrics[lnr].append ("\skip4")
2090 ## force trailing mm rests to be written out.
2091 voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
2093 ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
2094 ly_voice = group_repeats (ly_voice)
2096 seq_music = musicexp.SequentialMusic ()
2098 if 'drummode' in modes_found.keys ():
2099 ## \key <pitch> barfs in drummode.
2100 ly_voice = [e for e in ly_voice
2101 if not isinstance(e, musicexp.KeySignatureChange)]
2103 seq_music.elements = ly_voice
2104 for k in lyrics.keys ():
2105 return_value.lyrics_dict[k] = musicexp.Lyrics ()
2106 return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2109 if len (modes_found) > 1:
2110 error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
2112 if options.relative:
2113 v = musicexp.RelativeMusic ()
2114 v.element = seq_music
2115 v.basepitch = first_pitch
2116 seq_music = v
2118 return_value.ly_voice = seq_music
2119 for mode in modes_found.keys ():
2120 v = musicexp.ModeChangingMusicWrapper()
2121 v.element = seq_music
2122 v.mode = mode
2123 return_value.ly_voice = v
2125 # create \figuremode { figured bass elements }
2126 if figured_bass_builder.has_relevant_elements:
2127 fbass_music = musicexp.SequentialMusic ()
2128 fbass_music.elements = figured_bass_builder.elements
2129 v = musicexp.ModeChangingMusicWrapper()
2130 v.mode = 'figuremode'
2131 v.element = fbass_music
2132 return_value.figured_bass = v
2134 # create \chordmode { chords }
2135 if chordnames_builder.has_relevant_elements:
2136 cname_music = musicexp.SequentialMusic ()
2137 cname_music.elements = chordnames_builder.elements
2138 v = musicexp.ModeChangingMusicWrapper()
2139 v.mode = 'chordmode'
2140 v.element = cname_music
2141 return_value.chordnames = v
2143 return return_value
2145 def musicxml_id_to_lily (id):
2146 digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2147 'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2149 for digit in digits:
2150 d = digits.index (digit)
2151 id = re.sub ('%d' % d, digit, id)
2153 id = re.sub ('[^a-zA-Z]', 'X', id)
2154 return id
2156 def musicxml_pitch_to_lily (mxl_pitch):
2157 p = musicexp.Pitch ()
2158 p.alteration = mxl_pitch.get_alteration ()
2159 p.step = musicxml_step_to_lily (mxl_pitch.get_step ())
2160 p.octave = mxl_pitch.get_octave () - 4
2161 return p
2163 def musicxml_unpitched_to_lily (mxl_unpitched):
2164 p = None
2165 step = mxl_unpitched.get_step ()
2166 if step:
2167 p = musicexp.Pitch ()
2168 p.step = musicxml_step_to_lily (step)
2169 octave = mxl_unpitched.get_octave ()
2170 if octave and p:
2171 p.octave = octave - 4
2172 return p
2174 def musicxml_restdisplay_to_lily (mxl_rest):
2175 p = None
2176 step = mxl_rest.get_step ()
2177 if step:
2178 p = musicexp.Pitch ()
2179 p.step = musicxml_step_to_lily (step)
2180 octave = mxl_rest.get_octave ()
2181 if octave and p:
2182 p.octave = octave - 4
2183 return p
2185 def voices_in_part (part):
2186 """Return a Name -> Voice dictionary for PART"""
2187 part.interpret ()
2188 part.extract_voices ()
2189 voices = part.get_voices ()
2190 part_info = part.get_staff_attributes ()
2192 return (voices, part_info)
2194 def voices_in_part_in_parts (parts):
2195 """return a Part -> Name -> Voice dictionary"""
2196 return dict([(p.id, voices_in_part (p)) for p in parts])
2199 def get_all_voices (parts):
2200 all_voices = voices_in_part_in_parts (parts)
2202 all_ly_voices = {}
2203 all_ly_staffinfo = {}
2204 for p, (name_voice, staff_info) in all_voices.items ():
2206 part_ly_voices = {}
2207 for n, v in name_voice.items ():
2208 progress (_ ("Converting to LilyPond expressions..."))
2209 # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2210 part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
2212 all_ly_voices[p] = part_ly_voices
2213 all_ly_staffinfo[p] = staff_info
2215 return (all_ly_voices, all_ly_staffinfo)
2218 def option_parser ():
2219 p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"),
2220 description =
2221 _ ("""Convert MusicXML from FILE.xml to LilyPond input.
2222 If the given filename is -, musicxml2ly reads from the command line.
2223 """), add_help_option=False)
2225 p.add_option("-h", "--help",
2226 action="help",
2227 help=_ ("show this help and exit"))
2229 p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
2231 _ ("""Copyright (c) 2005--2008 by
2232 Han-Wen Nienhuys <hanwen@xs4all.nl>,
2233 Jan Nieuwenhuizen <janneke@gnu.org> and
2234 Reinhold Kainhofer <reinhold@kainhofer.com>
2238 This program is free software. It is covered by the GNU General Public
2239 License and you are welcome to change it and/or distribute copies of it
2240 under certain conditions. Invoke as `%s --warranty' for more
2241 information.""") % 'lilypond')
2243 p.add_option("--version",
2244 action="version",
2245 help=_ ("show version number and exit"))
2247 p.add_option ('-v', '--verbose',
2248 action = "store_true",
2249 dest = 'verbose',
2250 help = _ ("be verbose"))
2252 p.add_option ('', '--lxml',
2253 action = "store_true",
2254 default = False,
2255 dest = "use_lxml",
2256 help = _ ("use lxml.etree; uses less memory and cpu time"))
2258 p.add_option ('-z', '--compressed',
2259 action = "store_true",
2260 dest = 'compressed',
2261 default = False,
2262 help = _ ("input file is a zip-compressed MusicXML file"))
2264 p.add_option ('-r', '--relative',
2265 action = "store_true",
2266 default = True,
2267 dest = "relative",
2268 help = _ ("convert pitches in relative mode (default)"))
2270 p.add_option ('-a', '--absolute',
2271 action = "store_false",
2272 dest = "relative",
2273 help = _ ("convert pitches in absolute mode"))
2275 p.add_option ('-l', '--language',
2276 metavar = _ ("LANG"),
2277 action = "store",
2278 help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
2280 p.add_option ('--nd', '--no-articulation-directions',
2281 action = "store_false",
2282 default = True,
2283 dest = "convert_directions",
2284 help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2286 p.add_option ('--no-beaming',
2287 action = "store_false",
2288 default = True,
2289 dest = "convert_beaming",
2290 help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
2292 p.add_option ('-o', '--output',
2293 metavar = _ ("FILE"),
2294 action = "store",
2295 default = None,
2296 type = 'string',
2297 dest = 'output_name',
2298 help = _ ("set output filename to FILE, stdout if -"))
2299 p.add_option_group ('',
2300 description = (_ ("Report bugs via")
2301 + ''' http://post.gmane.org/post.php'''
2302 '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
2303 return p
2305 def music_xml_voice_name_to_lily_name (part_id, name):
2306 str = "Part%sVoice%s" % (part_id, name)
2307 return musicxml_id_to_lily (str)
2309 def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
2310 str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
2311 return musicxml_id_to_lily (str)
2313 def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
2314 str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
2315 return musicxml_id_to_lily (str)
2317 def music_xml_chordnames_name_to_lily_name (part_id, voicename):
2318 str = "Part%sVoice%sChords" % (part_id, voicename)
2319 return musicxml_id_to_lily (str)
2321 def print_voice_definitions (printer, part_list, voices):
2322 for part in part_list:
2323 part_id = part.id
2324 nv_dict = voices.get (part_id, {})
2325 for (name, voice) in nv_dict.items ():
2326 k = music_xml_voice_name_to_lily_name (part_id, name)
2327 printer.dump ('%s = ' % k)
2328 voice.ly_voice.print_ly (printer)
2329 printer.newline()
2330 if voice.chordnames:
2331 cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
2332 printer.dump ('%s = ' % cnname )
2333 voice.chordnames.print_ly (printer)
2334 printer.newline()
2335 for l in voice.lyrics_order:
2336 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
2337 printer.dump ('%s = ' % lname )
2338 voice.lyrics_dict[l].print_ly (printer)
2339 printer.newline()
2340 if voice.figured_bass:
2341 fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
2342 printer.dump ('%s = ' % fbname )
2343 voice.figured_bass.print_ly (printer)
2344 printer.newline()
2347 def uniq_list (l):
2348 return dict ([(elt,1) for elt in l]).keys ()
2350 # format the information about the staff in the form
2351 # [staffid,
2353 # [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
2354 # [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
2355 # ...
2358 # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
2359 def format_staff_info (part_id, staff_id, raw_voices):
2360 voices = []
2361 for (v, lyricsids, figured_bass, chordnames) in raw_voices:
2362 voice_name = music_xml_voice_name_to_lily_name (part_id, v)
2363 voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
2364 for l in lyricsids]
2365 figured_bass_name = ''
2366 if figured_bass:
2367 figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
2368 chordnames_name = ''
2369 if chordnames:
2370 chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
2371 voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
2372 return [staff_id, voices]
2374 def update_score_setup (score_structure, part_list, voices):
2376 for part_definition in part_list:
2377 part_id = part_definition.id
2378 nv_dict = voices.get (part_id)
2379 if not nv_dict:
2380 error_message (_ ('unknown part in part-list: %s') % part_id)
2381 continue
2383 staves = reduce (lambda x,y: x+ y,
2384 [voice.voicedata._staves.keys ()
2385 for voice in nv_dict.values ()],
2387 staves_info = []
2388 if len (staves) > 1:
2389 staves_info = []
2390 staves = uniq_list (staves)
2391 staves.sort ()
2392 for s in staves:
2393 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames)
2394 for (voice_name, voice) in nv_dict.items ()
2395 if voice.voicedata._start_staff == s]
2396 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
2397 else:
2398 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames)
2399 for (voice_name, voice) in nv_dict.items ()]
2400 staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
2401 score_structure.set_part_information (part_id, staves_info)
2403 # Set global values in the \layout block, like auto-beaming etc.
2404 def update_layout_information ():
2405 if not conversion_settings.ignore_beaming and layout_information:
2406 layout_information.set_context_item ('Score', 'autoBeaming = ##f')
2408 def print_ly_preamble (printer, filename):
2409 printer.dump_version ()
2410 printer.print_verbatim ('%% automatically converted from %s\n' % filename)
2412 def print_ly_additional_definitions (printer, filename):
2413 if needed_additional_definitions:
2414 printer.newline ()
2415 printer.print_verbatim ('%% additional definitions required by the score:')
2416 printer.newline ()
2417 for a in set(needed_additional_definitions):
2418 printer.print_verbatim (additional_definitions.get (a, ''))
2419 printer.newline ()
2420 printer.newline ()
2422 # Read in the tree from the given I/O object (either file or string) and
2423 # demarshall it using the classes from the musicxml.py file
2424 def read_xml (io_object, use_lxml):
2425 if use_lxml:
2426 import lxml.etree
2427 tree = lxml.etree.parse (io_object)
2428 mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
2429 return mxl_tree
2430 else:
2431 from xml.dom import minidom, Node
2432 doc = minidom.parse(io_object)
2433 node = doc.documentElement
2434 return musicxml.minidom_demarshal_node (node)
2435 return None
2438 def read_musicxml (filename, compressed, use_lxml):
2439 raw_string = None
2440 if compressed:
2441 if filename == "-":
2442 progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
2443 z = zipfile.ZipFile (sys.stdin)
2444 else:
2445 progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
2446 z = zipfile.ZipFile (filename, "r")
2447 container_xml = z.read ("META-INF/container.xml")
2448 if not container_xml:
2449 return None
2450 container = read_xml (StringIO.StringIO (container_xml), use_lxml)
2451 if not container:
2452 return None
2453 rootfiles = container.get_maybe_exist_named_child ('rootfiles')
2454 if not rootfiles:
2455 return None
2456 rootfile_list = rootfiles.get_named_children ('rootfile')
2457 mxml_file = None
2458 if len (rootfile_list) > 0:
2459 mxml_file = getattr (rootfile_list[0], 'full-path', None)
2460 if mxml_file:
2461 raw_string = z.read (mxml_file)
2463 if raw_string:
2464 io_object = StringIO.StringIO (raw_string)
2465 elif filename == "-":
2466 io_object = sys.stdin
2467 else:
2468 io_object = filename
2470 return read_xml (io_object, use_lxml)
2473 def convert (filename, options):
2474 if filename == "-":
2475 progress (_ ("Reading MusicXML from Standard input ...") )
2476 else:
2477 progress (_ ("Reading MusicXML from %s ...") % filename)
2479 tree = read_musicxml (filename, options.compressed, options.use_lxml)
2480 score_information = extract_score_information (tree)
2481 paper_information = extract_paper_information (tree)
2483 parts = tree.get_typed_children (musicxml.Part)
2484 (voices, staff_info) = get_all_voices (parts)
2486 score = None
2487 mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
2488 if mxl_pl:
2489 score = extract_score_structure (mxl_pl, staff_info)
2490 part_list = mxl_pl.get_named_children ("score-part")
2492 # score information is contained in the <work>, <identification> or <movement-title> tags
2493 update_score_setup (score, part_list, voices)
2494 # After the conversion, update the list of settings for the \layout block
2495 update_layout_information ()
2497 if not options.output_name:
2498 options.output_name = os.path.basename (filename)
2499 options.output_name = os.path.splitext (options.output_name)[0]
2500 elif re.match (".*\.ly", options.output_name):
2501 options.output_name = os.path.splitext (options.output_name)[0]
2504 #defs_ly_name = options.output_name + '-defs.ly'
2505 if (options.output_name == "-"):
2506 output_ly_name = 'Standard output'
2507 else:
2508 output_ly_name = options.output_name + '.ly'
2510 progress (_ ("Output to `%s'") % output_ly_name)
2511 printer = musicexp.Output_printer()
2512 #progress (_ ("Output to `%s'") % defs_ly_name)
2513 if (options.output_name == "-"):
2514 printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
2515 else:
2516 printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
2517 print_ly_preamble (printer, filename)
2518 print_ly_additional_definitions (printer, filename)
2519 if score_information:
2520 score_information.print_ly (printer)
2521 if paper_information:
2522 paper_information.print_ly (printer)
2523 if layout_information:
2524 layout_information.print_ly (printer)
2525 print_voice_definitions (printer, part_list, voices)
2527 printer.newline ()
2528 printer.dump ("% The score definition")
2529 printer.newline ()
2530 score.print_ly (printer)
2531 printer.newline ()
2533 return voices
2535 def get_existing_filename_with_extension (filename, ext):
2536 if os.path.exists (filename):
2537 return filename
2538 newfilename = filename + "." + ext
2539 if os.path.exists (newfilename):
2540 return newfilename;
2541 newfilename = filename + ext
2542 if os.path.exists (newfilename):
2543 return newfilename;
2544 return ''
2546 def main ():
2547 opt_parser = option_parser()
2549 global options
2550 (options, args) = opt_parser.parse_args ()
2551 if not args:
2552 opt_parser.print_usage()
2553 sys.exit (2)
2555 if options.language:
2556 musicexp.set_pitch_language (options.language)
2557 needed_additional_definitions.append (options.language)
2558 additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
2559 conversion_settings.ignore_beaming = not options.convert_beaming
2561 # Allow the user to leave out the .xml or xml on the filename
2562 if args[0]=="-": # Read from stdin
2563 filename="-"
2564 else:
2565 filename = get_existing_filename_with_extension (args[0], "xml")
2566 if not filename:
2567 filename = get_existing_filename_with_extension (args[0], "mxl")
2568 options.compressed = True
2569 if filename and (filename == "-" or os.path.exists (filename)):
2570 voices = convert (filename, options)
2571 else:
2572 progress (_ ("Unable to find input file %s") % args[0])
2574 if __name__ == '__main__':
2575 main()