11 from gettext
import gettext
as _
22 from rational
import Rational
24 # Store command-line options in a global variable, so we can access them everythwere
28 sys
.stderr
.write (str + '\n')
31 def error_message (str):
32 sys
.stderr
.write (str + '\n')
35 needed_additional_definitions
= []
36 additional_definitions
= {
37 "snappizzicato": """#(define-markup-command (snappizzicato layout props) ()
38 (interpret-markup layout props
40 (ly:stencil-translate-axis
42 (make-circle-stencil 0.7 0.1 #f)
44 (list 'draw-line 0.1 0 0.1 0 1)
45 '(-0.1 . 0.1) '(0.1 . 1)
56 def round_to_two_digits (val
):
57 return round (val
* 100) / 100
59 def extract_layout_information (tree
):
60 paper
= musicexp
.Paper ()
61 defaults
= tree
.get_maybe_exist_named_child ('defaults')
65 scaling
= defaults
.get_maybe_exist_named_child ('scaling')
67 mm
= scaling
.get_named_child ('millimeters')
68 mm
= string
.atof (mm
.get_text ())
69 tn
= scaling
.get_maybe_exist_named_child ('tenths')
70 tn
= string
.atof (tn
.get_text ())
72 paper
.global_staff_size
= mm
* 72.27 / 25.4
73 # We need the scaling (i.e. the size of staff tenths for everything!
77 def from_tenths (txt
):
78 return round_to_two_digits (string
.atof (txt
) * tenths
/ 10)
79 def set_paper_variable (varname
, parent
, element_name
):
80 el
= parent
.get_maybe_exist_named_child (element_name
)
81 if el
: # Convert to cm from tenths
82 setattr (paper
, varname
, from_tenths (el
.get_text ()))
84 pagelayout
= defaults
.get_maybe_exist_named_child ('page-layout')
86 # TODO: How can one have different margins for even and odd pages???
87 set_paper_variable ("page_height", pagelayout
, 'page-height')
88 set_paper_variable ("page_width", pagelayout
, 'page-width')
90 pmargins
= pagelayout
.get_named_children ('page-margins')
92 set_paper_variable ("left_margin", pm
, 'left-margin')
93 set_paper_variable ("right_margin", pm
, 'right-margin')
94 set_paper_variable ("bottom_margin", pm
, 'bottom-margin')
95 set_paper_variable ("top_margin", pm
, 'top-margin')
97 systemlayout
= defaults
.get_maybe_exist_named_child ('system-layout')
99 sl
= systemlayout
.get_maybe_exist_named_child ('system-margins')
101 set_paper_variable ("system_left_margin", sl
, 'left-margin')
102 set_paper_variable ("system_right_margin", sl
, 'right-margin')
103 set_paper_variable ("system_distance", systemlayout
, 'system-distance')
104 set_paper_variable ("top_system_distance", systemlayout
, 'top-system-distance')
106 stafflayout
= defaults
.get_named_children ('staff-layout')
107 for sl
in stafflayout
:
108 nr
= getattr (sl
, 'number', 1)
109 dist
= sl
.get_named_child ('staff-distance')
110 #TODO: the staff distance needs to be set in the Staff context!!!
112 # TODO: Finish appearance?, music-font?, word-font?, lyric-font*, lyric-language*
113 appearance
= defaults
.get_named_child ('appearance')
115 lws
= appearance
.get_named_children ('line-width')
117 # Possible types are: beam, bracket, dashes,
118 # enclosure, ending, extend, heavy barline, leger,
119 # light barline, octave shift, pedal, slur middle, slur tip,
120 # staff, stem, tie middle, tie tip, tuplet bracket, and wedge
122 w
= from_tenths (lw
.get_text ())
123 # TODO: Do something with these values!
124 nss
= appearance
.get_named_children ('note-size')
126 # Possible types are: cue, grace and large
128 sz
= from_tenths (ns
.get_text ())
129 # TODO: Do something with these values!
130 # <other-appearance> elements have no specified meaning
132 rawmusicfont
= defaults
.get_named_child ('music-font')
134 # TODO: Convert the font
136 rawwordfont
= defaults
.get_named_child ('word-font')
138 # TODO: Convert the font
140 rawlyricsfonts
= defaults
.get_named_children ('lyric-font')
141 for lyricsfont
in rawlyricsfonts
:
142 # TODO: Convert the font
149 # score information is contained in the <work>, <identification> or <movement-title> tags
150 # extract those into a hash, indexed by proper lilypond header attributes
151 def extract_score_information (tree
):
152 header
= musicexp
.Header ()
153 def set_if_exists (field
, value
):
155 header
.set_field (field
, musicxml
.escape_ly_output_string (value
))
157 work
= tree
.get_maybe_exist_named_child ('work')
159 set_if_exists ('title', work
.get_work_title ())
160 set_if_exists ('worknumber', work
.get_work_number ())
161 set_if_exists ('opus', work
.get_opus ())
163 movement_title
= tree
.get_maybe_exist_named_child ('movement-title')
165 set_if_exists ('title', movement_title
.get_text ())
167 identifications
= tree
.get_named_children ('identification')
168 for ids
in identifications
:
169 set_if_exists ('copyright', ids
.get_rights ())
170 set_if_exists ('composer', ids
.get_composer ())
171 set_if_exists ('arranger', ids
.get_arranger ())
172 set_if_exists ('editor', ids
.get_editor ())
173 set_if_exists ('poet', ids
.get_poet ())
175 set_if_exists ('tagline', ids
.get_encoding_software ())
176 set_if_exists ('encodingsoftware', ids
.get_encoding_software ())
177 set_if_exists ('encodingdate', ids
.get_encoding_date ())
178 set_if_exists ('encoder', ids
.get_encoding_person ())
179 set_if_exists ('encodingdescription', ids
.get_encoding_description ())
188 return len (self
.start
) + len (self
.end
) == 0
189 def add_start (self
, g
):
190 self
.start
[getattr (g
, 'number', "1")] = g
191 def add_end (self
, g
):
192 self
.end
[getattr (g
, 'number', "1")] = g
193 def print_ly (self
, printer
):
194 error_message ("Unprocessed PartGroupInfo %s encountered" % self
)
195 def ly_expression (self
):
196 error_message ("Unprocessed PartGroupInfo %s encountered" % self
)
200 def staff_attributes_to_string_tunings (mxl_attr
):
201 details
= mxl_attr
.get_maybe_exist_named_child ('staff-details')
205 staff_lines
= details
.get_maybe_exist_named_child ('staff-lines')
207 lines
= string
.atoi (staff_lines
.get_text ())
210 staff_tunings
= details
.get_named_children ('staff-tuning')
211 for i
in staff_tunings
:
215 line
= string
.atoi (i
.line
) - 1
220 step
= i
.get_named_child (u
'tuning-step')
221 step
= step
.get_text ().strip ()
222 p
.step
= (ord (step
) - ord ('A') + 7 - 2) % 7
224 octave
= i
.get_named_child (u
'tuning-octave')
225 octave
= octave
.get_text ().strip ()
226 p
.octave
= int (octave
) - 4
228 alter
= i
.get_named_child (u
'tuning-alter')
230 p
.alteration
= int (alter
.get_text ().strip ())
231 # lilypond seems to use the opposite ordering than MusicXML...
237 def staff_attributes_to_lily_staff (mxl_attr
):
239 return musicexp
.Staff ()
241 (staff_id
, attributes
) = mxl_attr
.items ()[0]
243 # distinguish by clef:
244 # percussion (percussion and rhythmic), tab, and everything else
246 clef
= attributes
.get_maybe_exist_named_child ('clef')
248 sign
= clef
.get_maybe_exist_named_child ('sign')
250 clef_sign
= {"percussion": "percussion", "TAB": "tab"}.get (sign
.get_text (), None)
253 details
= attributes
.get_maybe_exist_named_child ('staff-details')
255 staff_lines
= details
.get_maybe_exist_named_child ('staff-lines')
257 lines
= string
.atoi (staff_lines
.get_text ())
260 if clef_sign
== "percussion" and lines
== 1:
261 staff
= musicexp
.RhythmicStaff ()
262 elif clef_sign
== "percussion":
263 staff
= musicexp
.DrumStaff ()
264 # staff.drum_style_table = ???
265 elif clef_sign
== "tab":
266 staff
= musicexp
.TabStaff ()
267 staff
.string_tunings
= staff_attributes_to_string_tunings (attributes
)
268 # staff.tablature_format = ???
270 # TODO: Handle case with lines <> 5!
271 staff
= musicexp
.Staff ()
276 def extract_score_layout (part_list
, staffinfo
):
277 layout
= musicexp
.StaffGroup (None)
281 def read_score_part (el
):
282 if not isinstance (el
, musicxml
.Score_part
):
284 # Depending on the attributes of the first measure, we create different
285 # types of staves (Staff, RhythmicStaff, DrumStaff, TabStaff, etc.)
286 staff
= staff_attributes_to_lily_staff (staffinfo
.get (el
.id, None))
290 partname
= el
.get_maybe_exist_named_child ('part-name')
291 # Finale gives unnamed parts the name "MusicXML Part" automatically!
292 if partname
and partname
.get_text() != "MusicXML Part":
293 staff
.instrument_name
= partname
.get_text ()
294 if el
.get_maybe_exist_named_child ('part-abbreviation'):
295 staff
.short_instrument_name
= el
.get_maybe_exist_named_child ('part-abbreviation').get_text ()
296 # TODO: Read in the MIDI device / instrument
299 def read_score_group (el
):
300 if not isinstance (el
, musicxml
.Part_group
):
302 group
= musicexp
.StaffGroup ()
303 if hasattr (el
, 'number'):
306 #currentgroups_dict[id] = group
307 #currentgroups.append (id)
308 if el
.get_maybe_exist_named_child ('group-name'):
309 group
.instrument_name
= el
.get_maybe_exist_named_child ('group-name').get_text ()
310 if el
.get_maybe_exist_named_child ('group-abbreviation'):
311 group
.short_instrument_name
= el
.get_maybe_exist_named_child ('group-abbreviation').get_text ()
312 if el
.get_maybe_exist_named_child ('group-symbol'):
313 group
.symbol
= el
.get_maybe_exist_named_child ('group-symbol').get_text ()
314 if el
.get_maybe_exist_named_child ('group-barline'):
315 group
.spanbar
= el
.get_maybe_exist_named_child ('group-barline').get_text ()
319 parts_groups
= part_list
.get_all_children ()
321 # the start/end group tags are not necessarily ordered correctly and groups
322 # might even overlap, so we can't go through the children sequentially!
324 # 1) Replace all Score_part objects by their corresponding Staff objects,
325 # also collect all group start/stop points into one PartGroupInfo object
327 group_info
= PartGroupInfo ()
328 for el
in parts_groups
:
329 if isinstance (el
, musicxml
.Score_part
):
330 if not group_info
.is_empty ():
331 staves
.append (group_info
)
332 group_info
= PartGroupInfo ()
333 staff
= read_score_part (el
)
335 staves
.append (staff
)
336 elif isinstance (el
, musicxml
.Part_group
):
337 if el
.type == "start":
338 group_info
.add_start (el
)
339 elif el
.type == "stop":
340 group_info
.add_end (el
)
341 if not group_info
.is_empty ():
342 staves
.append (group_info
)
344 # 2) Now, detect the groups:
347 while pos
< len (staves
):
349 if isinstance (el
, PartGroupInfo
):
351 if len (group_starts
) > 0:
352 prev_start
= group_starts
[-1]
353 elif len (el
.end
) > 0: # no group to end here
355 if len (el
.end
) > 0: # closes an existing group
356 ends
= el
.end
.keys ()
357 prev_started
= staves
[prev_start
].start
.keys ()
359 intersection
= filter(lambda x
:x
in ends
, prev_started
)
360 if len (intersection
) > 0:
361 grpid
= intersection
[0]
363 # Close the last started group
364 grpid
= staves
[prev_start
].start
.keys () [0]
365 # Find the corresponding closing tag and remove it!
368 while j
< len (staves
) and not foundclosing
:
369 if isinstance (staves
[j
], PartGroupInfo
) and staves
[j
].end
.has_key (grpid
):
371 del staves
[j
].end
[grpid
]
372 if staves
[j
].is_empty ():
375 grpobj
= staves
[prev_start
].start
[grpid
]
376 group
= read_score_group (grpobj
)
377 # remove the id from both the start and end
378 if el
.end
.has_key (grpid
):
380 del staves
[prev_start
].start
[grpid
]
383 # replace the staves with the whole group
384 for j
in staves
[(prev_start
+ 1):pos
]:
386 j
.stafftype
= "InnerStaffGroup"
387 group
.append_staff (j
)
388 del staves
[(prev_start
+ 1):pos
]
389 staves
.insert (prev_start
+ 1, group
)
390 # reset pos so that we continue at the correct position
392 # remove an empty start group
393 if staves
[prev_start
].is_empty ():
394 del staves
[prev_start
]
395 group_starts
.remove (prev_start
)
397 elif len (el
.start
) > 0: # starts new part groups
398 group_starts
.append (pos
)
401 if len (staves
) == 1:
404 layout
.append_staff (i
)
409 def musicxml_duration_to_lily (mxl_note
):
410 d
= musicexp
.Duration ()
411 # if the note has no Type child, then that method spits out a warning and
412 # returns 0, i.e. a whole note
413 d
.duration_log
= mxl_note
.get_duration_log ()
415 d
.dots
= len (mxl_note
.get_typed_children (musicxml
.Dot
))
416 # Grace notes by specification have duration 0, so no time modification
417 # factor is possible. It even messes up the output with *0/1
418 if not mxl_note
.get_maybe_exist_typed_child (musicxml
.Grace
):
419 d
.factor
= mxl_note
._duration
/ d
.get_length ()
423 def rational_to_lily_duration (rational_len
):
424 d
= musicexp
.Duration ()
425 d
.duration_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)
426 d
.factor
= Rational (rational_len
.numerator ())
427 if d
.duration_log
< 0:
428 error_message ("Encountered rational duration with denominator %s, "
429 "unable to convert to lilypond duration" %
430 rational_len
.denominator ())
431 # TODO: Test the above error message
436 def musicxml_partial_to_lily (partial_len
):
438 p
= musicexp
.Partial ()
439 p
.partial
= rational_to_lily_duration (partial_len
)
444 # Detect repeats and alternative endings in the chord event list (music_list)
445 # and convert them to the corresponding musicexp objects, containing nested
447 def group_repeats (music_list
):
448 repeat_replaced
= True
451 # Walk through the list of expressions, looking for repeat structure
452 # (repeat start/end, corresponding endings). If we find one, try to find the
453 # last event of the repeat, replace the whole structure and start over again.
454 # For nested repeats, as soon as we encounter another starting repeat bar,
455 # treat that one first, and start over for the outer repeat.
456 while repeat_replaced
and i
< 100:
458 repeat_start
= -1 # position of repeat start / end
459 repeat_end
= -1 # position of repeat start / end
461 ending_start
= -1 # position of current ending start
462 endings
= [] # list of already finished endings
464 last
= len (music_list
) - 1
465 repeat_replaced
= False
467 while pos
< len (music_list
) and not repeat_replaced
:
469 repeat_finished
= False
470 if isinstance (e
, RepeatMarker
):
471 if not repeat_times
and e
.times
:
472 repeat_times
= e
.times
473 if e
.direction
== -1:
475 repeat_finished
= True
481 elif e
.direction
== 1:
487 elif isinstance (e
, EndingMarker
):
488 if e
.direction
== -1:
494 elif e
.direction
== 1:
497 endings
.append ([ending_start
, pos
])
500 elif not isinstance (e
, musicexp
.BarLine
):
501 # As soon as we encounter an element when repeat start and end
502 # is set and we are not inside an alternative ending,
503 # this whole repeat structure is finished => replace it
504 if repeat_start
>= 0 and repeat_end
> 0 and ending_start
< 0:
505 repeat_finished
= True
507 # Finish off all repeats without explicit ending bar (e.g. when
508 # we convert only one page of a multi-page score with repeats)
509 if pos
== last
and repeat_start
>= 0:
510 repeat_finished
= True
514 if ending_start
>= 0:
515 endings
.append ([ending_start
, pos
])
519 # We found the whole structure replace it!
520 r
= musicexp
.RepeatedMusic ()
521 if repeat_times
<= 0:
523 r
.repeat_count
= repeat_times
524 # don't erase the first element for "implicit" repeats (i.e. no
525 # starting repeat bars at the very beginning)
526 start
= repeat_start
+1
527 if repeat_start
== music_start
:
529 r
.set_music (music_list
[start
:repeat_end
])
530 for (start
, end
) in endings
:
531 s
= musicexp
.SequentialMusic ()
532 s
.elements
= music_list
[start
+1:end
]
534 del music_list
[repeat_start
:final_marker
+1]
535 music_list
.insert (repeat_start
, r
)
536 repeat_replaced
= True
538 # TODO: Implement repeats until the end without explicit ending bar
543 def group_tuplets (music_list
, events
):
546 """Collect Musics from
547 MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
554 for (ev_chord
, tuplet_elt
, fraction
) in events
:
555 while (j
< len (music_list
)):
556 if music_list
[j
] == ev_chord
:
559 if tuplet_elt
.type == 'start':
560 indices
.append ((j
, None, fraction
))
561 elif tuplet_elt
.type == 'stop':
562 indices
[-1] = (indices
[-1][0], j
, indices
[-1][2])
566 for (i1
, i2
, frac
) in indices
:
570 new_list
.extend (music_list
[last
:i1
])
571 seq
= musicexp
.SequentialMusic ()
573 seq
.elements
= music_list
[i1
:last
]
575 tsm
= musicexp
.TimeScaledMusic ()
578 tsm
.numerator
= frac
[0]
579 tsm
.denominator
= frac
[1]
581 new_list
.append (tsm
)
583 new_list
.extend (music_list
[last
:])
587 def musicxml_clef_to_lily (attributes
):
588 change
= musicexp
.ClefChange ()
589 (change
.type, change
.position
, change
.octave
) = attributes
.get_clef_information ()
592 def musicxml_time_to_lily (attributes
):
593 (beats
, type) = attributes
.get_time_signature ()
595 change
= musicexp
.TimeSignatureChange()
596 change
.fraction
= (beats
, type)
600 def musicxml_key_to_lily (attributes
):
601 start_pitch
= musicexp
.Pitch ()
602 (fifths
, mode
) = attributes
.get_key_signature ()
609 start_pitch
.alteration
= a
611 error_message ('unknown mode %s' % mode
)
613 fifth
= musicexp
.Pitch()
620 for x
in range (fifths
):
621 start_pitch
= start_pitch
.transposed (fifth
)
623 start_pitch
.octave
= 0
625 change
= musicexp
.KeySignatureChange()
627 change
.tonic
= start_pitch
630 def musicxml_attributes_to_lily (attrs
):
633 'clef': musicxml_clef_to_lily
,
634 'time': musicxml_time_to_lily
,
635 'key': musicxml_key_to_lily
637 for (k
, func
) in attr_dispatch
.items ():
638 children
= attrs
.get_named_children (k
)
640 elts
.append (func (attrs
))
644 class Marker (musicexp
.Music
):
648 def print_ly (self
, printer
):
649 sys
.stderr
.write ("Encountered unprocessed marker %s\n" % self
)
651 def ly_expression (self
):
653 class RepeatMarker (Marker
):
655 Marker
.__init
__ (self
)
657 class EndingMarker (Marker
):
660 # Convert the <barline> element to musicxml.BarLine (for non-standard barlines)
661 # and to RepeatMarker and EndingMarker objects for repeat and
662 # alternatives start/stops
663 def musicxml_barline_to_lily (barline
):
664 # retval contains all possible markers in the order:
665 # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending
667 bartype_element
= barline
.get_maybe_exist_named_child ("bar-style")
668 repeat_element
= barline
.get_maybe_exist_named_child ("repeat")
669 ending_element
= barline
.get_maybe_exist_named_child ("ending")
673 bartype
= bartype_element
.get_text ()
675 if repeat_element
and hasattr (repeat_element
, 'direction'):
676 repeat
= RepeatMarker ()
677 repeat
.direction
= {"forward": -1, "backward": 1}.get (repeat_element
.direction
, 0)
679 if ( (repeat_element
.direction
== "forward" and bartype
== "heavy-light") or
680 (repeat_element
.direction
== "backward" and bartype
== "light-heavy") ):
682 if hasattr (repeat_element
, 'times'):
684 repeat
.times
= int (repeat_element
.times
)
687 repeat
.event
= barline
688 if repeat
.direction
== -1:
693 if ending_element
and hasattr (ending_element
, 'type'):
694 ending
= EndingMarker ()
695 ending
.direction
= {"start": -1, "stop": 1, "discontinue": 1}.get (ending_element
.type, 0)
696 ending
.event
= barline
697 if ending
.direction
== -1:
703 b
= musicexp
.BarLine ()
707 return retval
.values ()
709 spanner_event_dict
= {
710 'slur' : musicexp
.SlurEvent
,
711 'beam' : musicexp
.BeamEvent
,
712 'glissando' : musicexp
.GlissandoEvent
,
713 'pedal' : musicexp
.PedalEvent
,
714 'wavy-line' : musicexp
.TrillSpanEvent
,
715 'octave-shift' : musicexp
.OctaveShiftEvent
,
716 'wedge' : musicexp
.HairpinEvent
718 spanner_type_dict
= {
731 def musicxml_spanner_to_lily_event (mxl_event
):
734 name
= mxl_event
.get_name()
735 func
= spanner_event_dict
.get (name
)
739 error_message ('unknown span event %s' % mxl_event
)
742 type = mxl_event
.get_type ()
743 span_direction
= spanner_type_dict
.get (type)
744 # really check for None, because some types will be translated to 0, which
745 # would otherwise also lead to the unknown span warning
746 if span_direction
!= None:
747 ev
.span_direction
= span_direction
749 error_message ('unknown span type %s for %s' % (type, name
))
751 ev
.set_span_type (type)
752 ev
.line_type
= getattr (mxl_event
, 'line-type', 'solid')
754 # assign the size, which is used for octave-shift, etc.
755 ev
.size
= mxl_event
.get_size ()
759 def musicxml_direction_to_indicator (direction
):
760 return { "above": 1, "upright": 1, "up":1, "below": -1, "downright": -1, "down": -1 }.get (direction
, 0)
762 def musicxml_fermata_to_lily_event (mxl_event
):
763 ev
= musicexp
.ArticulationEvent ()
765 if hasattr (mxl_event
, 'type'):
766 dir = musicxml_direction_to_indicator (mxl_event
.type)
767 if dir and options
.convert_directions
:
768 ev
.force_direction
= dir
772 def musicxml_arpeggiate_to_lily_event (mxl_event
):
773 ev
= musicexp
.ArpeggioEvent ()
774 ev
.direction
= musicxml_direction_to_indicator (getattr (mxl_event
, 'direction', None))
778 def musicxml_tremolo_to_lily_event (mxl_event
):
779 ev
= musicexp
.TremoloEvent ()
780 ev
.bars
= mxl_event
.get_text ()
783 def musicxml_bend_to_lily_event (mxl_event
):
784 ev
= musicexp
.BendEvent ()
785 ev
.alter
= mxl_event
.bend_alter ()
789 def musicxml_fingering_event (mxl_event
):
790 ev
= musicexp
.ShortArticulationEvent ()
791 ev
.type = mxl_event
.get_text ()
794 def musicxml_snappizzicato_event (mxl_event
):
795 needed_additional_definitions
.append ("snappizzicato")
796 ev
= musicexp
.MarkupEvent ()
797 ev
.contents
= "\\snappizzicato"
800 def musicxml_string_event (mxl_event
):
801 ev
= musicexp
.NoDirectionArticulationEvent ()
802 ev
.type = mxl_event
.get_text ()
805 def musicxml_accidental_mark (mxl_event
):
806 ev
= musicexp
.MarkupEvent ()
807 contents
= { "sharp": "\\sharp",
808 "natural": "\\natural",
810 "double-sharp": "\\doublesharp",
811 "sharp-sharp": "\\sharp\\sharp",
812 "flat-flat": "\\flat\\flat",
813 "flat-flat": "\\doubleflat",
814 "natural-sharp": "\\natural\\sharp",
815 "natural-flat": "\\natural\\flat",
816 "quarter-flat": "\\semiflat",
817 "quarter-sharp": "\\semisharp",
818 "three-quarters-flat": "\\sesquiflat",
819 "three-quarters-sharp": "\\sesquisharp",
820 }.get (mxl_event
.get_text ())
822 ev
.contents
= contents
827 # translate articulations, ornaments and other notations into ArticulationEvents
829 # -) string (ArticulationEvent with that name)
830 # -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
831 # -) (class, name) (like string, only that a different class than ArticulationEvent is used)
832 # TODO: Some translations are missing!
833 articulations_dict
= {
834 "accent": (musicexp
.ShortArticulationEvent
, ">"), # or "accent"
835 "accidental-mark": musicxml_accidental_mark
,
836 "bend": musicxml_bend_to_lily_event
,
837 "breath-mark": (musicexp
.NoDirectionArticulationEvent
, "breathe"),
838 #"caesura": "caesura",
839 #"delayed-turn": "?",
840 "detached-legato": (musicexp
.ShortArticulationEvent
, "_"), # or "portato"
842 #"double-tongue": "",
843 "down-bow": "downbow",
845 "fingering": musicxml_fingering_event
,
849 "harmonic": "flageolet",
851 "inverted-mordent": "prall",
852 "inverted-turn": "reverseturn",
853 "mordent": "mordent",
854 "open-string": "open",
861 "snap-pizzicato": musicxml_snappizzicato_event
,
863 "staccatissimo": (musicexp
.ShortArticulationEvent
, "|"), # or "staccatissimo"
864 "staccato": (musicexp
.ShortArticulationEvent
, "."), # or "staccato"
865 "stopped": (musicexp
.ShortArticulationEvent
, "+"), # or "stopped"
867 "string": musicxml_string_event
,
868 "strong-accent": (musicexp
.ShortArticulationEvent
, "^"), # or "marcato"
870 "tenuto": (musicexp
.ShortArticulationEvent
, "-"), # or "tenuto"
871 #"thumb-position": "",
874 "tremolo": musicxml_tremolo_to_lily_event
,
875 "trill-mark": "trill",
876 #"triple-tongue": "",
881 articulation_spanners
= [ "wavy-line" ]
883 def musicxml_articulation_to_lily_event (mxl_event
):
884 # wavy-line elements are treated as trill spanners, not as articulation ornaments
885 if mxl_event
.get_name () in articulation_spanners
:
886 return musicxml_spanner_to_lily_event (mxl_event
)
888 tmp_tp
= articulations_dict
.get (mxl_event
.get_name ())
892 if isinstance (tmp_tp
, str):
893 ev
= musicexp
.ArticulationEvent ()
895 elif isinstance (tmp_tp
, tuple):
899 ev
= tmp_tp (mxl_event
)
901 # Some articulations use the type attribute, other the placement...
903 if hasattr (mxl_event
, 'type') and options
.convert_directions
:
904 dir = musicxml_direction_to_indicator (mxl_event
.type)
905 if hasattr (mxl_event
, 'placement') and options
.convert_directions
:
906 dir = musicxml_direction_to_indicator (mxl_event
.placement
)
908 ev
.force_direction
= dir
912 def musicxml_dynamics_to_lily_event (dynentry
):
913 dynamics_available
= ( "p", "pp", "ppp", "pppp", "ppppp", "pppppp",
914 "f", "ff", "fff", "ffff", "fffff", "ffffff",
915 "mp", "mf", "sf", "sfp", "sfpp", "fp",
916 "rf", "rfz", "sfz", "sffz", "fz" )
917 if not dynentry
.get_name() in dynamics_available
:
919 event
= musicexp
.DynamicsEvent ()
920 event
.type = dynentry
.get_name ()
923 # Convert single-color two-byte strings to numbers 0.0 - 1.0
924 def hexcolorval_to_nr (hex_val
):
926 v
= int (hex_val
, 16)
933 def hex_to_color (hex_val
):
934 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
)
936 return map (lambda x
: hexcolorval_to_nr (x
), res
.group (2,3,4))
940 def musicxml_words_to_lily_event (words
):
941 event
= musicexp
.TextEvent ()
942 text
= words
.get_text ()
943 text
= re
.sub ('^ *\n? *', '', text
)
944 text
= re
.sub (' *\n? *$', '', text
)
947 if hasattr (words
, 'default-y') and options
.convert_directions
:
948 offset
= getattr (words
, 'default-y')
950 off
= string
.atoi (offset
)
952 event
.force_direction
= 1
954 event
.force_direction
= -1
956 event
.force_direction
= 0
958 if hasattr (words
, 'font-weight'):
959 font_weight
= { "normal": '', "bold": '\\bold' }.get (getattr (words
, 'font-weight'), '')
961 event
.markup
+= font_weight
963 if hasattr (words
, 'font-size'):
964 size
= getattr (words
, 'font-size')
966 "xx-small": '\\teeny',
972 "xx-large": '\\bigger\\huge'
975 event
.markup
+= font_size
977 if hasattr (words
, 'color'):
978 color
= getattr (words
, 'color')
979 rgb
= hex_to_color (color
)
981 event
.markup
+= "\\with-color #(rgb-color %s %s %s)" % (rgb
[0], rgb
[1], rgb
[2])
983 if hasattr (words
, 'font-style'):
984 font_style
= { "italic": '\\italic' }.get (getattr (words
, 'font-style'), '')
986 event
.markup
+= font_style
988 # TODO: How should I best convert the font-family attribute?
990 # TODO: How can I represent the underline, overline and line-through
991 # attributes in Lilypond? Values of these attributes indicate
992 # the number of lines
997 direction_spanners
= [ 'octave-shift', 'pedal', 'wedge' ]
999 def musicxml_direction_to_lily (n
):
1000 # TODO: Handle the <staff> element!
1002 dirtype_children
= []
1003 for dt
in n
.get_typed_children (musicxml
.DirType
):
1004 dirtype_children
+= dt
.get_all_children ()
1006 for entry
in dirtype_children
:
1008 if entry
.get_name () == "dynamics":
1009 for dynentry
in entry
.get_all_children ():
1010 ev
= musicxml_dynamics_to_lily_event (dynentry
)
1014 if entry
.get_name () == "words":
1015 ev
= musicxml_words_to_lily_event (entry
)
1019 # octave shifts. pedal marks, hairpins etc. are spanners:
1020 if entry
.get_name() in direction_spanners
:
1021 event
= musicxml_spanner_to_lily_event (entry
)
1028 def musicxml_frame_to_lily_event (frame
):
1029 ev
= musicexp
.FretEvent ()
1030 ev
.strings
= frame
.get_strings ()
1031 ev
.frets
= frame
.get_frets ()
1032 #offset = frame.get_first_fret () - 1
1034 for fn
in frame
.get_named_children ('frame-note'):
1035 fret
= fn
.get_fret ()
1038 el
= [ fn
.get_string (), fret
]
1039 fingering
= fn
.get_fingering ()
1041 el
.append (fingering
)
1042 ev
.elements
.append (el
)
1045 barre
[0] = el
[0] # start string
1046 barre
[2] = el
[1] # fret
1048 barre
[1] = el
[0] # end string
1053 def musicxml_harmony_to_lily (n
):
1055 for f
in n
.get_named_children ('frame'):
1056 ev
= musicxml_frame_to_lily_event (f
)
1062 instrument_drumtype_dict
= {
1063 'Acoustic Snare Drum': 'acousticsnare',
1064 'Side Stick': 'sidestick',
1065 'Open Triangle': 'opentriangle',
1066 'Mute Triangle': 'mutetriangle',
1067 'Tambourine': 'tambourine',
1068 'Bass Drum': 'bassdrum',
1071 def musicxml_note_to_lily_main_event (n
):
1075 mxl_pitch
= n
.get_maybe_exist_typed_child (musicxml
.Pitch
)
1078 pitch
= musicxml_pitch_to_lily (mxl_pitch
)
1079 event
= musicexp
.NoteEvent()
1082 acc
= n
.get_maybe_exist_named_child ('accidental')
1084 # let's not force accs everywhere.
1085 event
.cautionary
= acc
.editorial
1087 elif n
.get_maybe_exist_typed_child (musicxml
.Rest
):
1088 # rests can have display-octave and display-step, which are
1089 # treated like an ordinary note pitch
1090 rest
= n
.get_maybe_exist_typed_child (musicxml
.Rest
)
1091 event
= musicexp
.RestEvent()
1092 pitch
= musicxml_restdisplay_to_lily (rest
)
1094 elif n
.instrument_name
:
1095 event
= musicexp
.NoteEvent ()
1096 drum_type
= instrument_drumtype_dict
.get (n
.instrument_name
)
1098 event
.drum_type
= drum_type
1100 n
.message ("drum %s type unknown, please add to instrument_drumtype_dict" % n
.instrument_name
)
1101 event
.drum_type
= 'acousticsnare'
1104 n
.message ("cannot find suitable event")
1106 event
.duration
= musicxml_duration_to_lily (n
)
1112 def __init__ (self
, here
, dest
):
1116 class LilyPondVoiceBuilder
:
1117 def __init__ (self
):
1119 self
.pending_dynamics
= []
1120 self
.end_moment
= Rational (0)
1121 self
.begin_moment
= Rational (0)
1122 self
.pending_multibar
= Rational (0)
1123 self
.ignore_skips
= False
1125 def _insert_multibar (self
):
1126 r
= musicexp
.MultiMeasureRest ()
1127 r
.duration
= musicexp
.Duration()
1128 r
.duration
.duration_log
= 0
1129 r
.duration
.factor
= self
.pending_multibar
1130 self
.elements
.append (r
)
1131 self
.begin_moment
= self
.end_moment
1132 self
.end_moment
= self
.begin_moment
+ self
.pending_multibar
1133 self
.pending_multibar
= Rational (0)
1135 def add_multibar_rest (self
, duration
):
1136 self
.pending_multibar
+= duration
1138 def set_duration (self
, duration
):
1139 self
.end_moment
= self
.begin_moment
+ duration
1140 def current_duration (self
):
1141 return self
.end_moment
- self
.begin_moment
1143 def add_music (self
, music
, duration
):
1144 assert isinstance (music
, musicexp
.Music
)
1145 if self
.pending_multibar
> Rational (0):
1146 self
._insert
_multibar
()
1148 self
.elements
.append (music
)
1149 self
.begin_moment
= self
.end_moment
1150 self
.set_duration (duration
)
1152 # Insert all pending dynamics right after the note/rest:
1153 if isinstance (music
, musicexp
.EventChord
) and self
.pending_dynamics
:
1154 for d
in self
.pending_dynamics
:
1156 self
.pending_dynamics
= []
1158 # Insert some music command that does not affect the position in the measure
1159 def add_command (self
, command
):
1160 assert isinstance (command
, musicexp
.Music
)
1161 if self
.pending_multibar
> Rational (0):
1162 self
._insert
_multibar
()
1163 self
.elements
.append (command
)
1164 def add_barline (self
, barline
):
1165 # TODO: Implement merging of default barline and custom bar line
1166 self
.add_music (barline
, Rational (0))
1167 def add_partial (self
, command
):
1168 self
.ignore_skips
= True
1169 self
.add_command (command
)
1171 def add_dynamics (self
, dynamic
):
1172 # store the dynamic item(s) until we encounter the next note/rest:
1173 self
.pending_dynamics
.append (dynamic
)
1175 def add_bar_check (self
, number
):
1176 b
= musicexp
.BarLine ()
1177 b
.bar_number
= number
1178 self
.add_barline (b
)
1180 def jumpto (self
, moment
):
1181 current_end
= self
.end_moment
+ self
.pending_multibar
1182 diff
= moment
- current_end
1184 if diff
< Rational (0):
1185 error_message ('Negative skip %s' % diff
)
1188 if diff
> Rational (0) and not (self
.ignore_skips
and moment
== 0):
1189 skip
= musicexp
.SkipEvent()
1191 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)
1193 if duration_log
> 0: # denominator is a power of 2...
1194 if diff
.numerator () == 3:
1198 duration_factor
= Rational (diff
.numerator ())
1201 duration_factor
= diff
1202 skip
.duration
.duration_log
= duration_log
1203 skip
.duration
.factor
= duration_factor
1204 skip
.duration
.dots
= duration_dots
1206 evc
= musicexp
.EventChord ()
1207 evc
.elements
.append (skip
)
1208 self
.add_music (evc
, diff
)
1210 if diff
> Rational (0) and moment
== 0:
1211 self
.ignore_skips
= False
1213 def last_event_chord (self
, starting_at
):
1217 # if the position matches, find the last EventChord, do not cross a bar line!
1218 at
= len( self
.elements
) - 1
1220 not isinstance (self
.elements
[at
], musicexp
.EventChord
) and
1221 not isinstance (self
.elements
[at
], musicexp
.BarLine
)):
1226 and isinstance (self
.elements
[at
], musicexp
.EventChord
)
1227 and self
.begin_moment
== starting_at
):
1228 value
= self
.elements
[at
]
1230 self
.jumpto (starting_at
)
1234 def correct_negative_skip (self
, goto
):
1235 self
.end_moment
= goto
1236 self
.begin_moment
= goto
1237 evc
= musicexp
.EventChord ()
1238 self
.elements
.append (evc
)
1242 def __init__ (self
):
1243 self
.voicedata
= None
1244 self
.ly_voice
= None
1245 self
.lyrics_dict
= {}
1246 self
.lyrics_order
= []
1248 def musicxml_voice_to_lily_voice (voice
):
1252 return_value
= VoiceData ()
1253 return_value
.voicedata
= voice
1255 # First pitch needed for relative mode (if selected in command-line options)
1258 # Needed for melismata detection (ignore lyrics on those notes!):
1262 ignore_lyrics
= False
1264 current_staff
= None
1266 # Make sure that the keys in the dict don't get reordered, since
1267 # we need the correct ordering of the lyrics stanzas! By default,
1268 # a dict will reorder its keys
1269 return_value
.lyrics_order
= voice
.get_lyrics_numbers ()
1270 for k
in return_value
.lyrics_order
:
1273 voice_builder
= LilyPondVoiceBuilder()
1275 for n
in voice
._elements
:
1276 if n
.get_name () == 'forward':
1278 staff
= n
.get_maybe_exist_named_child ('staff')
1280 staff
= staff
.get_text ()
1281 if current_staff
and staff
<> current_staff
and not n
.get_maybe_exist_named_child ('chord'):
1282 voice_builder
.add_command (musicexp
.StaffChange (staff
))
1283 current_staff
= staff
1285 if isinstance (n
, musicxml
.Partial
) and n
.partial
> 0:
1286 a
= musicxml_partial_to_lily (n
.partial
)
1288 voice_builder
.add_partial (a
)
1291 if isinstance (n
, musicxml
.Direction
):
1292 for a
in musicxml_direction_to_lily (n
):
1293 if a
.wait_for_note ():
1294 voice_builder
.add_dynamics (a
)
1296 voice_builder
.add_command (a
)
1299 if isinstance (n
, musicxml
.Harmony
):
1300 for a
in musicxml_harmony_to_lily (n
):
1301 if a
.wait_for_note ():
1302 voice_builder
.add_dynamics (a
)
1304 voice_builder
.add_command (a
)
1307 is_chord
= n
.get_maybe_exist_named_child ('chord')
1310 voice_builder
.jumpto (n
._when
)
1311 except NegativeSkip
, neg
:
1312 voice_builder
.correct_negative_skip (n
._when
)
1313 n
.message ("Negative skip? from %s to %s, diff %s" % (neg
.here
, neg
.dest
, neg
.dest
- neg
.here
))
1315 if isinstance (n
, musicxml
.Attributes
):
1316 if n
.is_first () and n
._measure
_position
== Rational (0):
1318 number
= int (n
.get_parent ().number
)
1322 voice_builder
.add_bar_check (number
)
1324 for a
in musicxml_attributes_to_lily (n
):
1325 voice_builder
.add_command (a
)
1328 if isinstance (n
, musicxml
.Barline
):
1329 barlines
= musicxml_barline_to_lily (n
)
1331 if isinstance (a
, musicexp
.BarLine
):
1332 voice_builder
.add_barline (a
)
1333 elif isinstance (a
, RepeatMarker
) or isinstance (a
, EndingMarker
):
1334 voice_builder
.add_command (a
)
1337 if not n
.__class
__.__name
__ == 'Note':
1338 error_message ('not a Note or Attributes? %s' % n
)
1341 rest
= n
.get_maybe_exist_typed_child (musicxml
.Rest
)
1343 and rest
.is_whole_measure ()):
1345 voice_builder
.add_multibar_rest (n
._duration
)
1348 if n
.is_first () and n
._measure
_position
== Rational (0):
1350 num
= int (n
.get_parent ().number
)
1354 voice_builder
.add_bar_check (num
)
1356 main_event
= musicxml_note_to_lily_main_event (n
)
1357 if main_event
and not first_pitch
:
1358 first_pitch
= main_event
.pitch
1359 ignore_lyrics
= inside_slur
or is_tied
or is_chord
1361 if hasattr (main_event
, 'drum_type') and main_event
.drum_type
:
1362 modes_found
['drummode'] = True
1365 ev_chord
= voice_builder
.last_event_chord (n
._when
)
1367 ev_chord
= musicexp
.EventChord()
1368 voice_builder
.add_music (ev_chord
, n
._duration
)
1370 grace
= n
.get_maybe_exist_typed_child (musicxml
.Grace
)
1373 if n
.get_maybe_exist_typed_child (musicxml
.Chord
) and ev_chord
.grace_elements
:
1374 grace_chord
= ev_chord
.grace_elements
.get_last_event_chord ()
1376 grace_chord
= musicexp
.EventChord ()
1377 ev_chord
.append_grace (grace_chord
)
1378 if hasattr (grace
, 'slash'):
1379 # TODO: use grace_type = "appoggiatura" for slurred grace notes
1380 if grace
.slash
== "yes":
1381 ev_chord
.grace_type
= "acciaccatura"
1382 elif grace
.slash
== "no":
1383 ev_chord
.grace_type
= "grace"
1384 # now that we have inserted the chord into the grace music, insert
1385 # everything into that chord instead of the ev_chord
1386 ev_chord
= grace_chord
1387 ev_chord
.append (main_event
)
1388 ignore_lyrics
= True
1390 ev_chord
.append (main_event
)
1391 # When a note/chord has grace notes (duration==0), the duration of the
1392 # event chord is not yet known, but the event chord was already added
1393 # with duration 0. The following correct this when we hit the real note!
1394 if voice_builder
.current_duration () == 0 and n
._duration
> 0:
1395 voice_builder
.set_duration (n
._duration
)
1397 notations_children
= n
.get_typed_children (musicxml
.Notations
)
1401 # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
1402 # +tied | +slur | +tuplet | glissando | slide |
1403 # ornaments | technical | articulations | dynamics |
1404 # +fermata | arpeggiate | non-arpeggiate |
1405 # accidental-mark | other-notation
1406 for notations
in notations_children
:
1407 for tuplet_event
in notations
.get_tuplets():
1408 mod
= n
.get_maybe_exist_typed_child (musicxml
.Time_modification
)
1411 frac
= mod
.get_fraction ()
1413 tuplet_events
.append ((ev_chord
, tuplet_event
, frac
))
1415 slurs
= [s
for s
in notations
.get_named_children ('slur')
1416 if s
.get_type () in ('start','stop')]
1419 error_message ('more than 1 slur?')
1420 # record the slur status for the next note in the loop
1422 if slurs
[0].get_type () == 'start':
1424 elif slurs
[0].get_type () == 'stop':
1426 lily_ev
= musicxml_spanner_to_lily_event (slurs
[0])
1427 ev_chord
.append (lily_ev
)
1430 mxl_tie
= notations
.get_tie ()
1431 if mxl_tie
and mxl_tie
.type == 'start':
1432 ev_chord
.append (musicexp
.TieEvent ())
1437 fermatas
= notations
.get_named_children ('fermata')
1439 ev
= musicxml_fermata_to_lily_event (a
)
1441 ev_chord
.append (ev
)
1443 arpeggiate
= notations
.get_named_children ('arpeggiate')
1444 for a
in arpeggiate
:
1445 ev
= musicxml_arpeggiate_to_lily_event (a
)
1447 ev_chord
.append (ev
)
1449 glissandos
= notations
.get_named_children ('glissando')
1450 for a
in glissandos
:
1451 ev
= musicxml_spanner_to_lily_event (a
)
1453 ev_chord
.append (ev
)
1455 # Articulations can contain the following child elements:
1456 # accent | strong-accent | staccato | tenuto |
1457 # detached-legato | staccatissimo | spiccato |
1458 # scoop | plop | doit | falloff | breath-mark |
1459 # caesura | stress | unstress
1460 # Technical can contain the following child elements:
1461 # up-bow | down-bow | harmonic | open-string |
1462 # thumb-position | fingering | pluck | double-tongue |
1463 # triple-tongue | stopped | snap-pizzicato | fret |
1464 # string | hammer-on | pull-off | bend | tap | heel |
1465 # toe | fingernails | other-technical
1466 # Ornaments can contain the following child elements:
1467 # trill-mark | turn | delayed-turn | inverted-turn |
1468 # shake | wavy-line | mordent | inverted-mordent |
1469 # schleifer | tremolo | other-ornament, accidental-mark
1470 ornaments
= notations
.get_named_children ('ornaments')
1472 for ch
in a
.get_named_children ('tremolo'):
1473 ev
= musicxml_tremolo_to_lily_event (ch
)
1475 ev_chord
.append (ev
)
1477 ornaments
+= notations
.get_named_children ('articulations')
1478 ornaments
+= notations
.get_named_children ('technical')
1481 for ch
in a
.get_all_children ():
1482 ev
= musicxml_articulation_to_lily_event (ch
)
1484 ev_chord
.append (ev
)
1486 dynamics
= notations
.get_named_children ('dynamics')
1488 for ch
in a
.get_all_children ():
1489 ev
= musicxml_dynamics_to_lily_event (ch
)
1491 ev_chord
.append (ev
)
1493 # Extract the lyrics
1494 if not rest
and not ignore_lyrics
:
1495 note_lyrics_processed
= []
1496 note_lyrics_elements
= n
.get_typed_children (musicxml
.Lyric
)
1497 for l
in note_lyrics_elements
:
1498 if l
.get_number () < 0:
1499 for k
in lyrics
.keys ():
1500 lyrics
[k
].append (l
.lyric_to_text ())
1501 note_lyrics_processed
.append (k
)
1503 lyrics
[l
.number
].append(l
.lyric_to_text ())
1504 note_lyrics_processed
.append (l
.number
)
1505 for lnr
in lyrics
.keys ():
1506 if not lnr
in note_lyrics_processed
:
1507 lyrics
[lnr
].append ("\skip4")
1510 mxl_beams
= [b
for b
in n
.get_named_children ('beam')
1511 if (b
.get_type () in ('begin', 'end')
1512 and b
.is_primary ())]
1514 beam_ev
= musicxml_spanner_to_lily_event (mxl_beams
[0])
1516 ev_chord
.append (beam_ev
)
1519 mod
= n
.get_maybe_exist_typed_child (musicxml
.Time_modification
)
1522 frac
= mod
.get_fraction ()
1524 tuplet_events
.append ((ev_chord
, tuplet_event
, frac
))
1526 ## force trailing mm rests to be written out.
1527 voice_builder
.add_music (musicexp
.EventChord (), Rational (0))
1529 ly_voice
= group_tuplets (voice_builder
.elements
, tuplet_events
)
1530 ly_voice
= group_repeats (ly_voice
)
1532 seq_music
= musicexp
.SequentialMusic ()
1534 if 'drummode' in modes_found
.keys ():
1535 ## \key <pitch> barfs in drummode.
1536 ly_voice
= [e
for e
in ly_voice
1537 if not isinstance(e
, musicexp
.KeySignatureChange
)]
1539 seq_music
.elements
= ly_voice
1540 for k
in lyrics
.keys ():
1541 return_value
.lyrics_dict
[k
] = musicexp
.Lyrics ()
1542 return_value
.lyrics_dict
[k
].lyrics_syllables
= lyrics
[k
]
1545 if len (modes_found
) > 1:
1546 error_message ('Too many modes found %s' % modes_found
.keys ())
1548 if options
.relative
:
1549 v
= musicexp
.RelativeMusic ()
1550 v
.element
= seq_music
1551 v
.basepitch
= first_pitch
1554 return_value
.ly_voice
= seq_music
1555 for mode
in modes_found
.keys ():
1556 v
= musicexp
.ModeChangingMusicWrapper()
1557 v
.element
= seq_music
1559 return_value
.ly_voice
= v
1564 def musicxml_id_to_lily (id):
1565 digits
= ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
1566 'Six', 'Seven', 'Eight', 'Nine', 'Ten']
1568 for digit
in digits
:
1569 d
= digits
.index (digit
)
1570 id = re
.sub ('%d' % d
, digit
, id)
1572 id = re
.sub ('[^a-zA-Z]', 'X', id)
1576 def musicxml_pitch_to_lily (mxl_pitch
):
1577 p
= musicexp
.Pitch()
1578 p
.alteration
= mxl_pitch
.get_alteration ()
1579 p
.step
= (ord (mxl_pitch
.get_step ()) - ord ('A') + 7 - 2) % 7
1580 p
.octave
= mxl_pitch
.get_octave () - 4
1583 def musicxml_restdisplay_to_lily (mxl_rest
):
1585 step
= mxl_rest
.get_step ()
1587 p
= musicexp
.Pitch()
1588 p
.step
= (ord (step
) - ord ('A') + 7 - 2) % 7
1589 octave
= mxl_rest
.get_octave ()
1591 p
.octave
= octave
- 4
1594 def voices_in_part (part
):
1595 """Return a Name -> Voice dictionary for PART"""
1597 part
.extract_voices ()
1598 voices
= part
.get_voices ()
1599 part_info
= part
.get_staff_attributes ()
1601 return (voices
, part_info
)
1603 def voices_in_part_in_parts (parts
):
1604 """return a Part -> Name -> Voice dictionary"""
1605 return dict([(p
.id, voices_in_part (p
)) for p
in parts
])
1608 def get_all_voices (parts
):
1609 all_voices
= voices_in_part_in_parts (parts
)
1612 all_ly_staffinfo
= {}
1613 for p
, (name_voice
, staff_info
) in all_voices
.items ():
1616 for n
, v
in name_voice
.items ():
1617 progress ("Converting to LilyPond expressions...")
1618 # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
1619 part_ly_voices
[n
] = musicxml_voice_to_lily_voice (v
)
1621 all_ly_voices
[p
] = part_ly_voices
1622 all_ly_staffinfo
[p
] = staff_info
1624 return (all_ly_voices
, all_ly_staffinfo
)
1627 def option_parser ():
1628 p
= ly
.get_option_parser(usage
=_ ("musicxml2ly [options] FILE.xml"),
1629 version
=('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
1631 _ ("""This program is free software. It is covered by the GNU General Public
1632 License and you are welcome to change it and/or distribute copies of it
1633 under certain conditions. Invoke as `%s --warranty' for more
1634 information.""") % 'lilypond'
1636 Copyright (c) 2005--2007 by
1637 Han-Wen Nienhuys <hanwen@xs4all.nl>,
1638 Jan Nieuwenhuizen <janneke@gnu.org> and
1639 Reinhold Kainhofer <reinhold@kainhofer.com>
1641 description
=_ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n")
1642 p
.add_option ('-v', '--verbose',
1643 action
="store_true",
1645 help=_ ("be verbose"))
1647 p
.add_option ('', '--lxml',
1648 action
="store_true",
1651 help=_ ("Use lxml.etree; uses less memory and cpu time."))
1653 p
.add_option ('-z', '--compressed',
1654 action
= "store_true",
1655 dest
= 'compressed',
1657 help = _ ("Input file is a zip-compressed MusicXML file."))
1659 p
.add_option ('-r', '--relative',
1660 action
= "store_true",
1662 help = _ ("Convert pitches in relative mode."))
1664 p
.add_option ('-l', '--language',
1666 help = _ ("Use a different language file, e.g. 'deutsch' for deutsch.ly."))
1668 p
.add_option ('--no-articulation-directions', '--nd',
1669 action
= "store_false",
1671 dest
= "convert_directions",
1672 help = _ ("Do not convert directions (^, _ or -) for articulations."))
1674 p
.add_option ('-o', '--output',
1680 help=_ ("set output filename to FILE"))
1681 p
.add_option_group ('bugs',
1682 description
=(_ ("Report bugs via")
1683 + ''' http://post.gmane.org/post.php'''
1684 '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
1687 def music_xml_voice_name_to_lily_name (part_id
, name
):
1688 str = "Part%sVoice%s" % (part_id
, name
)
1689 return musicxml_id_to_lily (str)
1691 def music_xml_lyrics_name_to_lily_name (part_id
, name
, lyricsnr
):
1692 str = "Part%sVoice%sLyrics%s" % (part_id
, name
, lyricsnr
)
1693 return musicxml_id_to_lily (str)
1695 def print_voice_definitions (printer
, part_list
, voices
):
1696 for part
in part_list
:
1698 nv_dict
= voices
.get (part_id
, {})
1699 for (name
, voice
) in nv_dict
.items ():
1700 k
= music_xml_voice_name_to_lily_name (part_id
, name
)
1701 printer
.dump ('%s = ' % k
)
1702 voice
.ly_voice
.print_ly (printer
)
1704 for l
in voice
.lyrics_order
:
1705 lname
= music_xml_lyrics_name_to_lily_name (part_id
, name
, l
)
1706 printer
.dump ('%s = ' %lname
)
1707 voice
.lyrics_dict
[l
].print_ly (printer
)
1712 return dict ([(elt
,1) for elt
in l
]).keys ()
1714 # format the information about the staff in the form
1717 # [voiceid1, [lyricsid11, lyricsid12,...] ...],
1718 # [voiceid2, [lyricsid21, lyricsid22,...] ...],
1722 # raw_voices is of the form [(voicename, lyricsids)*]
1723 def format_staff_info (part_id
, staff_id
, raw_voices
):
1725 for (v
, lyricsids
) in raw_voices
:
1726 voice_name
= music_xml_voice_name_to_lily_name (part_id
, v
)
1727 voice_lyrics
= [music_xml_lyrics_name_to_lily_name (part_id
, v
, l
)
1729 voices
.append ([voice_name
, voice_lyrics
])
1730 return [staff_id
, voices
]
1732 def update_score_setup (score_structure
, part_list
, voices
):
1734 for part_definition
in part_list
:
1735 part_id
= part_definition
.id
1736 nv_dict
= voices
.get (part_id
)
1738 error_message ('unknown part in part-list: %s' % part_id
)
1741 staves
= reduce (lambda x
,y
: x
+ y
,
1742 [voice
.voicedata
._staves
.keys ()
1743 for voice
in nv_dict
.values ()],
1746 if len (staves
) > 1:
1748 staves
= uniq_list (staves
)
1751 thisstaff_raw_voices
= [(voice_name
, voice
.lyrics_order
)
1752 for (voice_name
, voice
) in nv_dict
.items ()
1753 if voice
.voicedata
._start
_staff
== s
]
1754 staves_info
.append (format_staff_info (part_id
, s
, thisstaff_raw_voices
))
1756 thisstaff_raw_voices
= [(voice_name
, voice
.lyrics_order
)
1757 for (voice_name
, voice
) in nv_dict
.items ()]
1758 staves_info
.append (format_staff_info (part_id
, None, thisstaff_raw_voices
))
1759 score_structure
.set_part_information (part_id
, staves_info
)
1761 def print_ly_preamble (printer
, filename
):
1762 printer
.dump_version ()
1763 printer
.print_verbatim ('%% automatically converted from %s\n' % filename
)
1765 def print_ly_additional_definitions (printer
, filename
):
1766 if needed_additional_definitions
:
1768 printer
.print_verbatim ('%% additional definitions required by the score:')
1770 for a
in set(needed_additional_definitions
):
1771 printer
.print_verbatim (additional_definitions
.get (a
, ''))
1774 # Read in the tree from the given I/O object (either file or string) and
1775 # demarshall it using the classes from the musicxml.py file
1776 def read_xml (io_object
, use_lxml
):
1779 tree
= lxml
.etree
.parse (io_object
)
1780 mxl_tree
= musicxml
.lxml_demarshal_node (tree
.getroot ())
1783 from xml
.dom
import minidom
, Node
1784 doc
= minidom
.parse(io_object
)
1785 node
= doc
.documentElement
1786 return musicxml
.minidom_demarshal_node (node
)
1790 def read_musicxml (filename
, compressed
, use_lxml
):
1793 progress ("Input file %s is compressed, extracting raw MusicXML data" % filename
)
1794 z
= zipfile
.ZipFile (filename
, "r")
1795 container_xml
= z
.read ("META-INF/container.xml")
1796 if not container_xml
:
1798 container
= read_xml (StringIO
.StringIO (container_xml
), use_lxml
)
1801 rootfiles
= container
.get_maybe_exist_named_child ('rootfiles')
1804 rootfile_list
= rootfiles
.get_named_children ('rootfile')
1806 if len (rootfile_list
) > 0:
1807 mxml_file
= getattr (rootfile_list
[0], 'full-path', None)
1809 raw_string
= z
.read (mxml_file
)
1811 io_object
= filename
1813 io_object
= StringIO
.StringIO (raw_string
)
1815 return read_xml (io_object
, use_lxml
)
1818 def convert (filename
, options
):
1819 progress ("Reading MusicXML from %s ..." % filename
)
1821 tree
= read_musicxml (filename
, options
.compressed
, options
.use_lxml
)
1822 parts
= tree
.get_typed_children (musicxml
.Part
)
1823 (voices
, staff_info
) = get_all_voices (parts
)
1825 score_structure
= None
1826 mxl_pl
= tree
.get_maybe_exist_typed_child (musicxml
.Part_list
)
1828 score_structure
= extract_score_layout (mxl_pl
, staff_info
)
1829 part_list
= mxl_pl
.get_named_children ("score-part")
1831 # score information is contained in the <work>, <identification> or <movement-title> tags
1832 score_information
= extract_score_information (tree
)
1833 layout_information
= extract_layout_information (tree
)
1834 update_score_setup (score_structure
, part_list
, voices
)
1836 if not options
.output_name
:
1837 options
.output_name
= os
.path
.basename (filename
)
1838 options
.output_name
= os
.path
.splitext (options
.output_name
)[0]
1839 elif re
.match (".*\.ly", options
.output_name
):
1840 options
.output_name
= os
.path
.splitext (options
.output_name
)[0]
1843 defs_ly_name
= options
.output_name
+ '-defs.ly'
1844 driver_ly_name
= options
.output_name
+ '.ly'
1846 printer
= musicexp
.Output_printer()
1847 progress ("Output to `%s'" % defs_ly_name
)
1848 printer
.set_file (codecs
.open (defs_ly_name
, 'wb', encoding
='utf-8'))
1850 print_ly_preamble (printer
, filename
)
1851 print_ly_additional_definitions (printer
, filename
)
1852 if score_information
:
1853 score_information
.print_ly (printer
)
1854 if layout_information
:
1855 layout_information
.print_ly (printer
)
1856 print_voice_definitions (printer
, part_list
, voices
)
1861 progress ("Output to `%s'" % driver_ly_name
)
1862 printer
= musicexp
.Output_printer()
1863 printer
.set_file (codecs
.open (driver_ly_name
, 'wb', encoding
='utf-8'))
1864 print_ly_preamble (printer
, filename
)
1865 printer
.dump (r
'\include "%s"' % os
.path
.basename (defs_ly_name
))
1866 score_structure
.print_ly (printer
)
1871 def get_existing_filename_with_extension (filename
, ext
):
1872 if os
.path
.exists (filename
):
1874 newfilename
= filename
+ "." + ext
1875 if os
.path
.exists (newfilename
):
1877 newfilename
= filename
+ ext
1878 if os
.path
.exists (newfilename
):
1883 opt_parser
= option_parser()
1886 (options
, args
) = opt_parser
.parse_args ()
1888 opt_parser
.print_usage()
1891 if options
.language
:
1892 musicexp
.set_pitch_language (options
.language
)
1893 needed_additional_definitions
.append (options
.language
)
1894 additional_definitions
[options
.language
] = "\\include \"%s.ly\"\n" % options
.language
1896 # Allow the user to leave out the .xml or xml on the filename
1897 filename
= get_existing_filename_with_extension (args
[0], "xml")
1899 filename
= get_existing_filename_with_extension (args
[0], "mxl")
1900 options
.compressed
= True
1901 if filename
and os
.path
.exists (filename
):
1902 voices
= convert (filename
, options
)
1904 progress ("Unable to find input file %s" % args
[0])
1906 if __name__
== '__main__':