Glossary: tweak snippets for 1.54 Clef.
[lilypond.git] / scripts / musicxml2ly.py
blob0a100d3932f55cb86bb1fd5a151d76170424e792
1 #!@TARGET_PYTHON@
2 # -*- coding: utf-8 -*-
3 import optparse
4 import sys
5 import re
6 import os
7 import string
8 import codecs
9 import zipfile
10 import StringIO
12 """
13 @relocate-preamble@
14 """
16 import lilylib as ly
17 _ = ly._
19 import musicxml
20 import musicexp
22 from rational import Rational
24 # Store command-line options in a global variable, so we can access them everythwere
25 options = None
27 class Conversion_Settings:
28 def __init__(self):
29 self.ignore_beaming = False
31 conversion_settings = Conversion_Settings ()
32 # Use a global variable to store the setting needed inside a \layout block.
33 # whenever we need to change a setting or add/remove an engraver, we can access
34 # this layout and add the corresponding settings
35 layout_information = musicexp.Layout ()
37 def progress (str):
38 ly.stderr_write (str + '\n')
39 sys.stderr.flush ()
41 def error_message (str):
42 ly.stderr_write (str + '\n')
43 sys.stderr.flush ()
45 needed_additional_definitions = []
46 additional_definitions = {
48 "snappizzicato": """#(define-markup-command (snappizzicato layout props) ()
49 (interpret-markup layout props
50 (markup #:stencil
51 (ly:stencil-translate-axis
52 (ly:stencil-add
53 (make-circle-stencil 0.7 0.1 #f)
54 (ly:make-stencil
55 (list 'draw-line 0.1 0 0.1 0 1)
56 '(-0.1 . 0.1) '(0.1 . 1)))
57 0.7 X))))""",
59 "eyeglasses": """eyeglassesps = #"0.15 setlinewidth
60 -0.9 0 translate
61 1.1 1.1 scale
62 1.2 0.7 moveto
63 0.7 0.7 0.5 0 361 arc
64 stroke
65 2.20 0.70 0.50 0 361 arc
66 stroke
67 1.45 0.85 0.30 0 180 arc
68 stroke
69 0.20 0.70 moveto
70 0.80 2.00 lineto
71 0.92 2.26 1.30 2.40 1.15 1.70 curveto
72 stroke
73 2.70 0.70 moveto
74 3.30 2.00 lineto
75 3.42 2.26 3.80 2.40 3.65 1.70 curveto
76 stroke"
77 eyeglasses = \markup { \with-dimensions #'(0 . 4.4) #'(0 . 2.5) \postscript #eyeglassesps }""",
79 "tuplet-note-wrapper": """ % a formatter function, which is simply a wrapper around an existing
80 % tuplet formatter function. It takes the value returned by the given
81 % function and appends a note of given length.
82 #(define-public ((tuplet-number::append-note-wrapper function note) grob)
83 (let* ((txt (if function (function grob) #f)))
84 (if txt
85 (markup txt #:fontsize -5 #:note note UP)
86 (markup #:fontsize -5 #:note note UP)
89 )""",
91 "tuplet-non-default-denominator": """#(define ((tuplet-number::non-default-tuplet-denominator-text denominator) grob)
92 (number->string (if denominator
93 denominator
94 (ly:event-property (event-cause grob) 'denominator))))
95 """,
97 "tuplet-non-default-fraction": """#(define ((tuplet-number::non-default-tuplet-fraction-text denominator numerator) grob)
98 (let* ((ev (event-cause grob))
99 (den (if denominator denominator (ly:event-property ev 'denominator)))
100 (num (if numerator numerator (ly:event-property ev 'numerator))))
101 (format "~a:~a" den num)))
102 """,
104 "compound-time-signature": """%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105 % Formatting of (possibly complex) compound time signatures
106 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
108 #(define-public (insert-markups l m)
109 (let* ((ll (reverse l)))
110 (let join-markups ((markups (list (car ll)))
111 (remaining (cdr ll)))
112 (if (pair? remaining)
113 (join-markups (cons (car remaining) (cons m markups)) (cdr remaining))
114 markups))))
116 % Use a centered-column inside a left-column, because the centered column
117 % moves its reference point to the center, which the left-column undoes.
118 % The center-column also aligns its contented centered, which is not undone...
119 #(define-public (format-time-fraction time-sig-fraction)
120 (let* ((revargs (reverse (map number->string time-sig-fraction)))
121 (den (car revargs))
122 (nums (reverse (cdr revargs))))
123 (make-override-markup '(baseline-skip . 0)
124 (make-number-markup
125 (make-left-column-markup (list
126 (make-center-column-markup (list
127 (make-line-markup (insert-markups nums "+"))
128 den))))))))
130 #(define-public (format-complex-compound-time time-sig)
131 (let* ((sigs (map format-time-fraction time-sig)))
132 (make-override-markup '(baseline-skip . 0)
133 (make-number-markup
134 (make-line-markup
135 (insert-markups sigs (make-vcenter-markup "+")))))))
137 #(define-public (format-compound-time time-sig)
138 (cond
139 ((not (pair? time-sig)) (null-markup))
140 ((pair? (car time-sig)) (format-complex-compound-time time-sig))
141 (else (format-time-fraction time-sig))))
144 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
145 % Measure length calculation of (possibly complex) compound time signatures
146 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
148 #(define-public (calculate-time-fraction time-sig-fraction)
149 (let* ((revargs (reverse time-sig-fraction))
150 (den (car revargs))
151 (nums (cdr revargs)))
152 (ly:make-moment (apply + nums) den)))
154 #(define-public (calculate-complex-compound-time time-sig)
155 (let* ((sigs (map calculate-time-fraction time-sig)))
156 (let add-moment ((moment ZERO-MOMENT)
157 (remaining sigs))
158 (if (pair? remaining)
159 (add-moment (ly:moment-add moment (car remaining)) (cdr remaining))
160 moment))))
162 #(define-public (calculate-compound-measure-length time-sig)
163 (cond
164 ((not (pair? time-sig)) (ly:make-moment 4 4))
165 ((pair? (car time-sig)) (calculate-complex-compound-time time-sig))
166 (else (calculate-time-fraction time-sig))))
169 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
170 % Base beat lenth
171 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
173 #(define-public (calculate-compound-base-beat-full time-sig)
174 (let* ((den (map last time-sig)))
175 (apply max den)))
177 #(define-public (calculate-compound-base-beat time-sig)
178 (ly:make-moment 1 (cond
179 ((not (pair? time-sig)) 4)
180 ((pair? (car time-sig)) (calculate-compound-base-beat-full time-sig))
181 (else (calculate-compound-base-beat-full (list time-sig))))))
184 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
185 % The music function to set the complex time signature
186 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
188 compoundMeter =
189 #(define-music-function (parser location args) (pair?)
190 (let ((mlen (calculate-compound-measure-length args))
191 (beat (calculate-compound-base-beat args)))
193 \once \override Staff.TimeSignature #'stencil = #ly:text-interface::print
194 \once \override Staff.TimeSignature #'text = #(format-compound-time $args)
195 % \set Staff.beatGrouping = #(reverse (cdr (reverse $args)))
196 \set Timing.measureLength = $mlen
197 \set Timing.timeSignatureFraction = #(cons (ly:moment-main-numerator $mlen)
198 (ly:moment-main-denominator $mlen))
199 \set Timing.beatLength = $beat
201 % TODO: Implement beatGrouping and auto-beam-settings!!!
202 #} ))
206 def round_to_two_digits (val):
207 return round (val * 100) / 100
209 def extract_paper_information (tree):
210 paper = musicexp.Paper ()
211 defaults = tree.get_maybe_exist_named_child ('defaults')
212 if not defaults:
213 return None
214 tenths = -1
215 scaling = defaults.get_maybe_exist_named_child ('scaling')
216 if scaling:
217 mm = scaling.get_named_child ('millimeters')
218 mm = string.atof (mm.get_text ())
219 tn = scaling.get_maybe_exist_named_child ('tenths')
220 tn = string.atof (tn.get_text ())
221 tenths = mm / tn
222 paper.global_staff_size = mm * 72.27 / 25.4
223 # We need the scaling (i.e. the size of staff tenths for everything!
224 if tenths < 0:
225 return None
227 def from_tenths (txt):
228 return round_to_two_digits (string.atof (txt) * tenths / 10)
229 def set_paper_variable (varname, parent, element_name):
230 el = parent.get_maybe_exist_named_child (element_name)
231 if el: # Convert to cm from tenths
232 setattr (paper, varname, from_tenths (el.get_text ()))
234 pagelayout = defaults.get_maybe_exist_named_child ('page-layout')
235 if pagelayout:
236 # TODO: How can one have different margins for even and odd pages???
237 set_paper_variable ("page_height", pagelayout, 'page-height')
238 set_paper_variable ("page_width", pagelayout, 'page-width')
240 pmargins = pagelayout.get_named_children ('page-margins')
241 for pm in pmargins:
242 set_paper_variable ("left_margin", pm, 'left-margin')
243 set_paper_variable ("right_margin", pm, 'right-margin')
244 set_paper_variable ("bottom_margin", pm, 'bottom-margin')
245 set_paper_variable ("top_margin", pm, 'top-margin')
247 systemlayout = defaults.get_maybe_exist_named_child ('system-layout')
248 if systemlayout:
249 sl = systemlayout.get_maybe_exist_named_child ('system-margins')
250 if sl:
251 set_paper_variable ("system_left_margin", sl, 'left-margin')
252 set_paper_variable ("system_right_margin", sl, 'right-margin')
253 set_paper_variable ("system_distance", systemlayout, 'system-distance')
254 set_paper_variable ("top_system_distance", systemlayout, 'top-system-distance')
256 stafflayout = defaults.get_named_children ('staff-layout')
257 for sl in stafflayout:
258 nr = getattr (sl, 'number', 1)
259 dist = sl.get_named_child ('staff-distance')
260 #TODO: the staff distance needs to be set in the Staff context!!!
262 # TODO: Finish appearance?, music-font?, word-font?, lyric-font*, lyric-language*
263 appearance = defaults.get_named_child ('appearance')
264 if appearance:
265 lws = appearance.get_named_children ('line-width')
266 for lw in lws:
267 # Possible types are: beam, bracket, dashes,
268 # enclosure, ending, extend, heavy barline, leger,
269 # light barline, octave shift, pedal, slur middle, slur tip,
270 # staff, stem, tie middle, tie tip, tuplet bracket, and wedge
271 tp = lw.type
272 w = from_tenths (lw.get_text ())
273 # TODO: Do something with these values!
274 nss = appearance.get_named_children ('note-size')
275 for ns in nss:
276 # Possible types are: cue, grace and large
277 tp = ns.type
278 sz = from_tenths (ns.get_text ())
279 # TODO: Do something with these values!
280 # <other-appearance> elements have no specified meaning
282 rawmusicfont = defaults.get_named_child ('music-font')
283 if rawmusicfont:
284 # TODO: Convert the font
285 pass
286 rawwordfont = defaults.get_named_child ('word-font')
287 if rawwordfont:
288 # TODO: Convert the font
289 pass
290 rawlyricsfonts = defaults.get_named_children ('lyric-font')
291 for lyricsfont in rawlyricsfonts:
292 # TODO: Convert the font
293 pass
295 return paper
299 # score information is contained in the <work>, <identification> or <movement-title> tags
300 # extract those into a hash, indexed by proper lilypond header attributes
301 def extract_score_information (tree):
302 header = musicexp.Header ()
303 def set_if_exists (field, value):
304 if value:
305 header.set_field (field, musicxml.escape_ly_output_string (value))
307 work = tree.get_maybe_exist_named_child ('work')
308 if work:
309 set_if_exists ('title', work.get_work_title ())
310 set_if_exists ('worknumber', work.get_work_number ())
311 set_if_exists ('opus', work.get_opus ())
312 else:
313 movement_title = tree.get_maybe_exist_named_child ('movement-title')
314 if movement_title:
315 set_if_exists ('title', movement_title.get_text ())
317 identifications = tree.get_named_children ('identification')
318 for ids in identifications:
319 set_if_exists ('copyright', ids.get_rights ())
320 set_if_exists ('composer', ids.get_composer ())
321 set_if_exists ('arranger', ids.get_arranger ())
322 set_if_exists ('editor', ids.get_editor ())
323 set_if_exists ('poet', ids.get_poet ())
325 set_if_exists ('tagline', ids.get_encoding_software ())
326 set_if_exists ('encodingsoftware', ids.get_encoding_software ())
327 set_if_exists ('encodingdate', ids.get_encoding_date ())
328 set_if_exists ('encoder', ids.get_encoding_person ())
329 set_if_exists ('encodingdescription', ids.get_encoding_description ())
331 set_if_exists ('texidoc', ids.get_file_description ());
333 # Finally, apply the required compatibility modes
334 # Some applications created wrong MusicXML files, so we need to
335 # apply some compatibility mode, e.g. ignoring some features/tags
336 # in those files
337 software = ids.get_encoding_software_list ()
339 # Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin
340 # is missing all beam ends => ignore all beaming information
341 if "Dolet 3.4 for Sibelius" in software:
342 conversion_settings.ignore_beaming = True
343 progress (_ ("Encountered file created by Dolet 3.4 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
344 if "Noteworthy Composer" in software:
345 conversion_settings.ignore_beaming = True
346 progress (_ ("Encountered file created by Noteworthy Composer's nwc2xml, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
347 # TODO: Check for other unsupported features
349 return header
351 class PartGroupInfo:
352 def __init__ (self):
353 self.start = {}
354 self.end = {}
355 def is_empty (self):
356 return len (self.start) + len (self.end) == 0
357 def add_start (self, g):
358 self.start[getattr (g, 'number', "1")] = g
359 def add_end (self, g):
360 self.end[getattr (g, 'number', "1")] = g
361 def print_ly (self, printer):
362 error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
363 def ly_expression (self):
364 error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
365 return ''
367 def staff_attributes_to_string_tunings (mxl_attr):
368 details = mxl_attr.get_maybe_exist_named_child ('staff-details')
369 if not details:
370 return []
371 lines = 6
372 staff_lines = details.get_maybe_exist_named_child ('staff-lines')
373 if staff_lines:
374 lines = string.atoi (staff_lines.get_text ())
376 tunings = [0]*lines
377 staff_tunings = details.get_named_children ('staff-tuning')
378 for i in staff_tunings:
379 p = musicexp.Pitch()
380 line = 0
381 try:
382 line = string.atoi (i.line) - 1
383 except ValueError:
384 pass
385 tunings[line] = p
387 step = i.get_named_child (u'tuning-step')
388 step = step.get_text ().strip ()
389 p.step = musicxml_step_to_lily (step)
391 octave = i.get_named_child (u'tuning-octave')
392 octave = octave.get_text ().strip ()
393 p.octave = int (octave) - 4
395 alter = i.get_named_child (u'tuning-alter')
396 if alter:
397 p.alteration = int (alter.get_text ().strip ())
398 # lilypond seems to use the opposite ordering than MusicXML...
399 tunings.reverse ()
401 return tunings
404 def staff_attributes_to_lily_staff (mxl_attr):
405 if not mxl_attr:
406 return musicexp.Staff ()
408 (staff_id, attributes) = mxl_attr.items ()[0]
410 # distinguish by clef:
411 # percussion (percussion and rhythmic), tab, and everything else
412 clef_sign = None
413 clef = attributes.get_maybe_exist_named_child ('clef')
414 if clef:
415 sign = clef.get_maybe_exist_named_child ('sign')
416 if sign:
417 clef_sign = {"percussion": "percussion", "TAB": "tab"}.get (sign.get_text (), None)
419 lines = 5
420 details = attributes.get_named_children ('staff-details')
421 for d in details:
422 staff_lines = d.get_maybe_exist_named_child ('staff-lines')
423 if staff_lines:
424 lines = string.atoi (staff_lines.get_text ())
426 staff = None
427 if clef_sign == "percussion" and lines == 1:
428 staff = musicexp.RhythmicStaff ()
429 elif clef_sign == "percussion":
430 staff = musicexp.DrumStaff ()
431 # staff.drum_style_table = ???
432 elif clef_sign == "tab":
433 staff = musicexp.TabStaff ()
434 staff.string_tunings = staff_attributes_to_string_tunings (attributes)
435 # staff.tablature_format = ???
436 else:
437 # TODO: Handle case with lines <> 5!
438 staff = musicexp.Staff ()
440 return staff
443 def extract_score_structure (part_list, staffinfo):
444 score = musicexp.Score ()
445 structure = musicexp.StaffGroup (None)
446 score.set_contents (structure)
448 if not part_list:
449 return structure
451 def read_score_part (el):
452 if not isinstance (el, musicxml.Score_part):
453 return
454 # Depending on the attributes of the first measure, we create different
455 # types of staves (Staff, RhythmicStaff, DrumStaff, TabStaff, etc.)
456 staff = staff_attributes_to_lily_staff (staffinfo.get (el.id, None))
457 if not staff:
458 return None
459 staff.id = el.id
460 partname = el.get_maybe_exist_named_child ('part-name')
461 # Finale gives unnamed parts the name "MusicXML Part" automatically!
462 if partname and partname.get_text() != "MusicXML Part":
463 staff.instrument_name = partname.get_text ()
464 if el.get_maybe_exist_named_child ('part-abbreviation'):
465 staff.short_instrument_name = el.get_maybe_exist_named_child ('part-abbreviation').get_text ()
466 # TODO: Read in the MIDI device / instrument
467 return staff
469 def read_score_group (el):
470 if not isinstance (el, musicxml.Part_group):
471 return
472 group = musicexp.StaffGroup ()
473 if hasattr (el, 'number'):
474 id = el.number
475 group.id = id
476 #currentgroups_dict[id] = group
477 #currentgroups.append (id)
478 if el.get_maybe_exist_named_child ('group-name'):
479 group.instrument_name = el.get_maybe_exist_named_child ('group-name').get_text ()
480 if el.get_maybe_exist_named_child ('group-abbreviation'):
481 group.short_instrument_name = el.get_maybe_exist_named_child ('group-abbreviation').get_text ()
482 if el.get_maybe_exist_named_child ('group-symbol'):
483 group.symbol = el.get_maybe_exist_named_child ('group-symbol').get_text ()
484 if el.get_maybe_exist_named_child ('group-barline'):
485 group.spanbar = el.get_maybe_exist_named_child ('group-barline').get_text ()
486 return group
489 parts_groups = part_list.get_all_children ()
491 # the start/end group tags are not necessarily ordered correctly and groups
492 # might even overlap, so we can't go through the children sequentially!
494 # 1) Replace all Score_part objects by their corresponding Staff objects,
495 # also collect all group start/stop points into one PartGroupInfo object
496 staves = []
497 group_info = PartGroupInfo ()
498 for el in parts_groups:
499 if isinstance (el, musicxml.Score_part):
500 if not group_info.is_empty ():
501 staves.append (group_info)
502 group_info = PartGroupInfo ()
503 staff = read_score_part (el)
504 if staff:
505 staves.append (staff)
506 elif isinstance (el, musicxml.Part_group):
507 if el.type == "start":
508 group_info.add_start (el)
509 elif el.type == "stop":
510 group_info.add_end (el)
511 if not group_info.is_empty ():
512 staves.append (group_info)
514 # 2) Now, detect the groups:
515 group_starts = []
516 pos = 0
517 while pos < len (staves):
518 el = staves[pos]
519 if isinstance (el, PartGroupInfo):
520 prev_start = 0
521 if len (group_starts) > 0:
522 prev_start = group_starts[-1]
523 elif len (el.end) > 0: # no group to end here
524 el.end = {}
525 if len (el.end) > 0: # closes an existing group
526 ends = el.end.keys ()
527 prev_started = staves[prev_start].start.keys ()
528 grpid = None
529 intersection = filter(lambda x:x in ends, prev_started)
530 if len (intersection) > 0:
531 grpid = intersection[0]
532 else:
533 # Close the last started group
534 grpid = staves[prev_start].start.keys () [0]
535 # Find the corresponding closing tag and remove it!
536 j = pos + 1
537 foundclosing = False
538 while j < len (staves) and not foundclosing:
539 if isinstance (staves[j], PartGroupInfo) and staves[j].end.has_key (grpid):
540 foundclosing = True
541 del staves[j].end[grpid]
542 if staves[j].is_empty ():
543 del staves[j]
544 j += 1
545 grpobj = staves[prev_start].start[grpid]
546 group = read_score_group (grpobj)
547 # remove the id from both the start and end
548 if el.end.has_key (grpid):
549 del el.end[grpid]
550 del staves[prev_start].start[grpid]
551 if el.is_empty ():
552 del staves[pos]
553 # replace the staves with the whole group
554 for j in staves[(prev_start + 1):pos]:
555 group.append_staff (j)
556 del staves[(prev_start + 1):pos]
557 staves.insert (prev_start + 1, group)
558 # reset pos so that we continue at the correct position
559 pos = prev_start
560 # remove an empty start group
561 if staves[prev_start].is_empty ():
562 del staves[prev_start]
563 group_starts.remove (prev_start)
564 pos -= 1
565 elif len (el.start) > 0: # starts new part groups
566 group_starts.append (pos)
567 pos += 1
569 if len (staves) == 1:
570 return staves[0]
571 for i in staves:
572 structure.append_staff (i)
573 return score
576 def musicxml_duration_to_lily (mxl_note):
577 # if the note has no Type child, then that method returns None. In that case,
578 # use the <duration> tag instead. If that doesn't exist, either -> Error
579 dur = mxl_note.get_duration_info ()
580 if dur:
581 d = musicexp.Duration ()
582 d.duration_log = dur[0]
583 d.dots = dur[1]
584 # Grace notes by specification have duration 0, so no time modification
585 # factor is possible. It even messes up the output with *0/1
586 if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
587 d.factor = mxl_note._duration / d.get_length ()
588 return d
590 else:
591 if mxl_note._duration > 0:
592 return rational_to_lily_duration (mxl_note._duration)
593 else:
594 mxl_note.message (_ ("Encountered note at %s without type and duration (=%s)") % (mxl_note.start, mxl_note._duration) )
595 return None
598 def rational_to_lily_duration (rational_len):
599 d = musicexp.Duration ()
601 rational_len.normalize_self ()
602 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)
604 # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration
605 if (d_log >= 0 and rational_len.numerator() in (1,3,5,7) ):
606 # account for the dots!
607 d.dots = (rational_len.numerator()-1)/2
608 d.duration_log = d_log - d.dots
609 elif (d_log >= 0):
610 d.duration_log = d_log
611 d.factor = Rational (rational_len.numerator ())
612 else:
613 error_message (_ ("Encountered rational duration with denominator %s, "
614 "unable to convert to lilypond duration") %
615 rational_len.denominator ())
616 # TODO: Test the above error message
617 return None
619 return d
621 def musicxml_partial_to_lily (partial_len):
622 if partial_len > 0:
623 p = musicexp.Partial ()
624 p.partial = rational_to_lily_duration (partial_len)
625 return p
626 else:
627 return Null
629 # Detect repeats and alternative endings in the chord event list (music_list)
630 # and convert them to the corresponding musicexp objects, containing nested
631 # music
632 def group_repeats (music_list):
633 repeat_replaced = True
634 music_start = 0
635 i = 0
636 # Walk through the list of expressions, looking for repeat structure
637 # (repeat start/end, corresponding endings). If we find one, try to find the
638 # last event of the repeat, replace the whole structure and start over again.
639 # For nested repeats, as soon as we encounter another starting repeat bar,
640 # treat that one first, and start over for the outer repeat.
641 while repeat_replaced and i < 100:
642 i += 1
643 repeat_start = -1 # position of repeat start / end
644 repeat_end = -1 # position of repeat start / end
645 repeat_times = 0
646 ending_start = -1 # position of current ending start
647 endings = [] # list of already finished endings
648 pos = 0
649 last = len (music_list) - 1
650 repeat_replaced = False
651 final_marker = 0
652 while pos < len (music_list) and not repeat_replaced:
653 e = music_list[pos]
654 repeat_finished = False
655 if isinstance (e, RepeatMarker):
656 if not repeat_times and e.times:
657 repeat_times = e.times
658 if e.direction == -1:
659 if repeat_end >= 0:
660 repeat_finished = True
661 else:
662 repeat_start = pos
663 repeat_end = -1
664 ending_start = -1
665 endings = []
666 elif e.direction == 1:
667 if repeat_start < 0:
668 repeat_start = 0
669 if repeat_end < 0:
670 repeat_end = pos
671 final_marker = pos
672 elif isinstance (e, EndingMarker):
673 if e.direction == -1:
674 if repeat_start < 0:
675 repeat_start = 0
676 if repeat_end < 0:
677 repeat_end = pos
678 ending_start = pos
679 elif e.direction == 1:
680 if ending_start < 0:
681 ending_start = 0
682 endings.append ([ending_start, pos])
683 ending_start = -1
684 final_marker = pos
685 elif not isinstance (e, musicexp.BarLine):
686 # As soon as we encounter an element when repeat start and end
687 # is set and we are not inside an alternative ending,
688 # this whole repeat structure is finished => replace it
689 if repeat_start >= 0 and repeat_end > 0 and ending_start < 0:
690 repeat_finished = True
692 # Finish off all repeats without explicit ending bar (e.g. when
693 # we convert only one page of a multi-page score with repeats)
694 if pos == last and repeat_start >= 0:
695 repeat_finished = True
696 final_marker = pos
697 if repeat_end < 0:
698 repeat_end = pos
699 if ending_start >= 0:
700 endings.append ([ending_start, pos])
701 ending_start = -1
703 if repeat_finished:
704 # We found the whole structure replace it!
705 r = musicexp.RepeatedMusic ()
706 if repeat_times <= 0:
707 repeat_times = 2
708 r.repeat_count = repeat_times
709 # don't erase the first element for "implicit" repeats (i.e. no
710 # starting repeat bars at the very beginning)
711 start = repeat_start+1
712 if repeat_start == music_start:
713 start = music_start
714 r.set_music (music_list[start:repeat_end])
715 for (start, end) in endings:
716 s = musicexp.SequentialMusic ()
717 s.elements = music_list[start+1:end]
718 r.add_ending (s)
719 del music_list[repeat_start:final_marker+1]
720 music_list.insert (repeat_start, r)
721 repeat_replaced = True
722 pos += 1
723 # TODO: Implement repeats until the end without explicit ending bar
724 return music_list
727 # Extract the settings for tuplets from the <notations><tuplet> and the
728 # <time-modification> elements of the note:
729 def musicxml_tuplet_to_lily (tuplet_elt, time_modification):
730 tsm = musicexp.TimeScaledMusic ()
731 fraction = (1,1)
732 if time_modification:
733 fraction = time_modification.get_fraction ()
734 tsm.numerator = fraction[0]
735 tsm.denominator = fraction[1]
738 normal_type = tuplet_elt.get_normal_type ()
739 if not normal_type and time_modification:
740 normal_type = time_modification.get_normal_type ()
741 if not normal_type and time_modification:
742 note = time_modification.get_parent ()
743 if note:
744 normal_type = note.get_duration_info ()
745 if normal_type:
746 normal_note = musicexp.Duration ()
747 (normal_note.duration_log, normal_note.dots) = normal_type
748 tsm.normal_type = normal_note
750 actual_type = tuplet_elt.get_actual_type ()
751 if actual_type:
752 actual_note = musicexp.Duration ()
753 (actual_note.duration_log, actual_note.dots) = normal_type
754 tsm.actual_type = actual_note
756 # Obtain non-default nrs of notes from the tuplet object!
757 tsm.display_numerator = tuplet_elt.get_normal_nr ()
758 tsm.display_denominator = tuplet_elt.get_actual_nr ()
761 if hasattr (tuplet_elt, 'bracket') and tuplet_elt.bracket == "no":
762 tsm.display_bracket = None
763 elif hasattr (tuplet_elt, 'line-shape') and getattr (tuplet_elt, 'line-shape') == "curved":
764 tsm.display_bracket = "curved"
765 else:
766 tsm.display_bracket = "bracket"
768 display_values = {"none": None, "actual": "actual", "both": "both"}
769 if hasattr (tuplet_elt, "show-number"):
770 tsm.display_number = display_values.get (getattr (tuplet_elt, "show-number"), "actual")
771 if tsm.display_number == "actual" and tsm.display_denominator:
772 needed_additional_definitions.append ("tuplet-non-default-denominator")
773 elif tsm.display_number == "both" and (tsm.display_numerator or tsm.display_denominator):
774 needed_additional_definitions.append ("tuplet-non-default-fraction")
776 if hasattr (tuplet_elt, "show-type"):
777 if getattr (tuplet_elt, "show-type") == "actual":
778 needed_additional_definitions.append ("tuplet-note-wrapper")
779 tsm.display_type = display_values.get (getattr (tuplet_elt, "show-type"), None)
781 return tsm
784 def group_tuplets (music_list, events):
787 """Collect Musics from
788 MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
792 indices = []
793 brackets = {}
795 j = 0
796 for (ev_chord, tuplet_elt, time_modification) in events:
797 while (j < len (music_list)):
798 if music_list[j] == ev_chord:
799 break
800 j += 1
801 nr = 0
802 if hasattr (tuplet_elt, 'number'):
803 nr = getattr (tuplet_elt, 'number')
804 if tuplet_elt.type == 'start':
805 tuplet_object = musicxml_tuplet_to_lily (tuplet_elt, time_modification)
806 tuplet_info = [j, None, tuplet_object]
807 indices.append (tuplet_info)
808 brackets[nr] = tuplet_info
809 elif tuplet_elt.type == 'stop':
810 bracket_info = brackets.get (nr, None)
811 if bracket_info:
812 bracket_info[1] = j # Set the ending position to j
813 del brackets[nr]
815 new_list = []
816 last = 0
817 for (i1, i2, tsm) in indices:
818 if i1 > i2:
819 continue
821 new_list.extend (music_list[last:i1])
822 seq = musicexp.SequentialMusic ()
823 last = i2 + 1
824 seq.elements = music_list[i1:last]
826 tsm.element = seq
828 new_list.append (tsm)
829 #TODO: Handle nested tuplets!!!!
831 new_list.extend (music_list[last:])
832 return new_list
835 def musicxml_clef_to_lily (attributes):
836 change = musicexp.ClefChange ()
837 (change.type, change.position, change.octave) = attributes.get_clef_information ()
838 return change
840 def musicxml_time_to_lily (attributes):
841 sig = attributes.get_time_signature ()
842 if not sig:
843 return None
844 change = musicexp.TimeSignatureChange()
845 change.fractions = sig
846 if (len(sig) != 2) or isinstance (sig[0], list):
847 needed_additional_definitions.append ("compound-time-signature")
849 time_elm = attributes.get_maybe_exist_named_child ('time')
850 if time_elm and hasattr (time_elm, 'symbol'):
851 change.style = { 'single-number': "'single-digit",
852 'cut': None,
853 'common': None,
854 'normal': "'()"}.get (time_elm.symbol, "'()")
855 else:
856 change.style = "'()"
858 # TODO: Handle senza-misura measures
859 # TODO: Handle hidden time signatures (print-object="no")
860 # TODO: What shall we do if the symbol clashes with the sig? e.g. "cut"
861 # with 3/8 or "single-number" with (2+3)/8 or 3/8+2/4?
863 return change
865 def musicxml_key_to_lily (attributes):
866 key_sig = attributes.get_key_signature ()
867 if not key_sig or not (isinstance (key_sig, list) or isinstance (key_sig, tuple)):
868 error_message (_ ("Unable to extract key signature!"))
869 return None
871 change = musicexp.KeySignatureChange()
873 if len (key_sig) == 2 and not isinstance (key_sig[0], list):
874 # standard key signature, (fifths, mode)
875 (fifths, mode) = key_sig
876 change.mode = mode
878 start_pitch = musicexp.Pitch ()
879 start_pitch.octave = 0
880 try:
881 (n,a) = {
882 'major' : (0,0),
883 'minor' : (5,0),
884 'ionian' : (0,0),
885 'dorian' : (1,0),
886 'phrygian' : (2,0),
887 'lydian' : (3,0),
888 'mixolydian': (4,0),
889 'aeolian' : (5,0),
890 'locrian' : (6,0),
891 }[mode]
892 start_pitch.step = n
893 start_pitch.alteration = a
894 except KeyError:
895 error_message (_ ("unknown mode %s, expecting 'major' or 'minor' "
896 "or a church mode!") % mode)
898 fifth = musicexp.Pitch()
899 fifth.step = 4
900 if fifths < 0:
901 fifths *= -1
902 fifth.step *= -1
903 fifth.normalize ()
904 for x in range (fifths):
905 start_pitch = start_pitch.transposed (fifth)
906 change.tonic = start_pitch
908 else:
909 # Non-standard key signature of the form [[step,alter<,octave>],...]
910 change.non_standard_alterations = key_sig
911 return change
913 def musicxml_transpose_to_lily (attributes):
914 transpose = attributes.get_transposition ()
915 if not transpose:
916 return None
918 shift = musicexp.Pitch ()
919 octave_change = transpose.get_maybe_exist_named_child ('octave-change')
920 if octave_change:
921 shift.octave = string.atoi (octave_change.get_text ())
922 chromatic_shift = string.atoi (transpose.get_named_child ('chromatic').get_text ())
923 chromatic_shift_normalized = chromatic_shift % 12;
924 (shift.step, shift.alteration) = [
925 (0,0), (0,1), (1,0), (2,-1), (2,0),
926 (3,0), (3,1), (4,0), (5,-1), (5,0),
927 (6,-1), (6,0)][chromatic_shift_normalized];
929 shift.octave += (chromatic_shift - chromatic_shift_normalized) / 12
931 diatonic = transpose.get_maybe_exist_named_child ('diatonic')
932 if diatonic:
933 diatonic_step = string.atoi (diatonic.get_text ()) % 7
934 if diatonic_step != shift.step:
935 # We got the alter incorrect!
936 old_semitones = shift.semitones ()
937 shift.step = diatonic_step
938 new_semitones = shift.semitones ()
939 shift.alteration += old_semitones - new_semitones
941 transposition = musicexp.Transposition ()
942 transposition.pitch = musicexp.Pitch ().transposed (shift)
943 return transposition
946 def musicxml_attributes_to_lily (attrs):
947 elts = []
948 attr_dispatch = {
949 'clef': musicxml_clef_to_lily,
950 'time': musicxml_time_to_lily,
951 'key': musicxml_key_to_lily,
952 'transpose': musicxml_transpose_to_lily,
954 for (k, func) in attr_dispatch.items ():
955 children = attrs.get_named_children (k)
956 if children:
957 ev = func (attrs)
958 if ev:
959 elts.append (ev)
961 return elts
963 class Marker (musicexp.Music):
964 def __init__ (self):
965 self.direction = 0
966 self.event = None
967 def print_ly (self, printer):
968 ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self)
969 pass
970 def ly_expression (self):
971 return ""
972 class RepeatMarker (Marker):
973 def __init__ (self):
974 Marker.__init__ (self)
975 self.times = 0
976 class EndingMarker (Marker):
977 pass
979 # Convert the <barline> element to musicxml.BarLine (for non-standard barlines)
980 # and to RepeatMarker and EndingMarker objects for repeat and
981 # alternatives start/stops
982 def musicxml_barline_to_lily (barline):
983 # retval contains all possible markers in the order:
984 # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending
985 retval = {}
986 bartype_element = barline.get_maybe_exist_named_child ("bar-style")
987 repeat_element = barline.get_maybe_exist_named_child ("repeat")
988 ending_element = barline.get_maybe_exist_named_child ("ending")
990 bartype = None
991 if bartype_element:
992 bartype = bartype_element.get_text ()
994 if repeat_element and hasattr (repeat_element, 'direction'):
995 repeat = RepeatMarker ()
996 repeat.direction = {"forward": -1, "backward": 1}.get (repeat_element.direction, 0)
998 if ( (repeat_element.direction == "forward" and bartype == "heavy-light") or
999 (repeat_element.direction == "backward" and bartype == "light-heavy") ):
1000 bartype = None
1001 if hasattr (repeat_element, 'times'):
1002 try:
1003 repeat.times = int (repeat_element.times)
1004 except ValueError:
1005 repeat.times = 2
1006 repeat.event = barline
1007 if repeat.direction == -1:
1008 retval[3] = repeat
1009 else:
1010 retval[1] = repeat
1012 if ending_element and hasattr (ending_element, 'type'):
1013 ending = EndingMarker ()
1014 ending.direction = {"start": -1, "stop": 1, "discontinue": 1}.get (ending_element.type, 0)
1015 ending.event = barline
1016 if ending.direction == -1:
1017 retval[4] = ending
1018 else:
1019 retval[0] = ending
1021 if bartype:
1022 b = musicexp.BarLine ()
1023 b.type = bartype
1024 retval[2] = b
1026 return retval.values ()
1028 spanner_event_dict = {
1029 'beam' : musicexp.BeamEvent,
1030 'dashes' : musicexp.TextSpannerEvent,
1031 'bracket' : musicexp.BracketSpannerEvent,
1032 'glissando' : musicexp.GlissandoEvent,
1033 'octave-shift' : musicexp.OctaveShiftEvent,
1034 'pedal' : musicexp.PedalEvent,
1035 'slide' : musicexp.GlissandoEvent,
1036 'slur' : musicexp.SlurEvent,
1037 'wavy-line' : musicexp.TrillSpanEvent,
1038 'wedge' : musicexp.HairpinEvent
1040 spanner_type_dict = {
1041 'start': -1,
1042 'begin': -1,
1043 'crescendo': -1,
1044 'decreschendo': -1,
1045 'diminuendo': -1,
1046 'continue': 0,
1047 'change': 0,
1048 'up': -1,
1049 'down': -1,
1050 'stop': 1,
1051 'end' : 1
1054 def musicxml_spanner_to_lily_event (mxl_event):
1055 ev = None
1057 name = mxl_event.get_name()
1058 func = spanner_event_dict.get (name)
1059 if func:
1060 ev = func()
1061 else:
1062 error_message (_ ('unknown span event %s') % mxl_event)
1065 type = mxl_event.get_type ()
1066 span_direction = spanner_type_dict.get (type)
1067 # really check for None, because some types will be translated to 0, which
1068 # would otherwise also lead to the unknown span warning
1069 if span_direction != None:
1070 ev.span_direction = span_direction
1071 else:
1072 error_message (_ ('unknown span type %s for %s') % (type, name))
1074 ev.set_span_type (type)
1075 ev.line_type = getattr (mxl_event, 'line-type', 'solid')
1077 # assign the size, which is used for octave-shift, etc.
1078 ev.size = mxl_event.get_size ()
1080 return ev
1082 def musicxml_direction_to_indicator (direction):
1083 return { "above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1 }.get (direction, 0)
1085 def musicxml_fermata_to_lily_event (mxl_event):
1086 ev = musicexp.ArticulationEvent ()
1087 txt = mxl_event.get_text ()
1088 # The contents of the element defined the shape, possible are normal, angled and square
1089 ev.type = { "angled": "shortfermata", "square": "longfermata" }.get (txt, "fermata")
1090 if hasattr (mxl_event, 'type'):
1091 dir = musicxml_direction_to_indicator (mxl_event.type)
1092 if dir and options.convert_directions:
1093 ev.force_direction = dir
1094 return ev
1096 def musicxml_arpeggiate_to_lily_event (mxl_event):
1097 ev = musicexp.ArpeggioEvent ()
1098 ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
1099 return ev
1101 def musicxml_nonarpeggiate_to_lily_event (mxl_event):
1102 ev = musicexp.ArpeggioEvent ()
1103 ev.non_arpeggiate = True
1104 ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
1105 return ev
1107 def musicxml_tremolo_to_lily_event (mxl_event):
1108 ev = musicexp.TremoloEvent ()
1109 txt = mxl_event.get_text ()
1110 if txt:
1111 ev.bars = txt
1112 else:
1113 ev.bars = "3"
1114 return ev
1116 def musicxml_falloff_to_lily_event (mxl_event):
1117 ev = musicexp.BendEvent ()
1118 ev.alter = -4
1119 return ev
1121 def musicxml_doit_to_lily_event (mxl_event):
1122 ev = musicexp.BendEvent ()
1123 ev.alter = 4
1124 return ev
1126 def musicxml_bend_to_lily_event (mxl_event):
1127 ev = musicexp.BendEvent ()
1128 ev.alter = mxl_event.bend_alter ()
1129 return ev
1131 def musicxml_caesura_to_lily_event (mxl_event):
1132 ev = musicexp.MarkupEvent ()
1133 # FIXME: default to straight or curved caesura?
1134 ev.contents = "\\musicglyph #\"scripts.caesura.straight\""
1135 ev.force_direction = 1
1136 return ev
1138 def musicxml_fingering_event (mxl_event):
1139 ev = musicexp.ShortArticulationEvent ()
1140 ev.type = mxl_event.get_text ()
1141 return ev
1143 def musicxml_snappizzicato_event (mxl_event):
1144 needed_additional_definitions.append ("snappizzicato")
1145 ev = musicexp.MarkupEvent ()
1146 ev.contents = "\\snappizzicato"
1147 return ev
1149 def musicxml_string_event (mxl_event):
1150 ev = musicexp.NoDirectionArticulationEvent ()
1151 ev.type = mxl_event.get_text ()
1152 return ev
1154 def musicxml_accidental_mark (mxl_event):
1155 ev = musicexp.MarkupEvent ()
1156 contents = { "sharp": "\\sharp",
1157 "natural": "\\natural",
1158 "flat": "\\flat",
1159 "double-sharp": "\\doublesharp",
1160 "sharp-sharp": "\\sharp\\sharp",
1161 "flat-flat": "\\flat\\flat",
1162 "flat-flat": "\\doubleflat",
1163 "natural-sharp": "\\natural\\sharp",
1164 "natural-flat": "\\natural\\flat",
1165 "quarter-flat": "\\semiflat",
1166 "quarter-sharp": "\\semisharp",
1167 "three-quarters-flat": "\\sesquiflat",
1168 "three-quarters-sharp": "\\sesquisharp",
1169 }.get (mxl_event.get_text ())
1170 if contents:
1171 ev.contents = contents
1172 return ev
1173 else:
1174 return None
1176 # translate articulations, ornaments and other notations into ArticulationEvents
1177 # possible values:
1178 # -) string (ArticulationEvent with that name)
1179 # -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
1180 # -) (class, name) (like string, only that a different class than ArticulationEvent is used)
1181 # TODO: Some translations are missing!
1182 articulations_dict = {
1183 "accent": (musicexp.ShortArticulationEvent, ">"), # or "accent"
1184 "accidental-mark": musicxml_accidental_mark,
1185 "bend": musicxml_bend_to_lily_event,
1186 "breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"),
1187 "caesura": musicxml_caesura_to_lily_event,
1188 #"delayed-turn": "?",
1189 "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato"
1190 "doit": musicxml_doit_to_lily_event,
1191 #"double-tongue": "?",
1192 "down-bow": "downbow",
1193 "falloff": musicxml_falloff_to_lily_event,
1194 "fingering": musicxml_fingering_event,
1195 #"fingernails": "?",
1196 #"fret": "?",
1197 #"hammer-on": "?",
1198 "harmonic": "flageolet",
1199 #"heel": "?",
1200 "inverted-mordent": "prall",
1201 "inverted-turn": "reverseturn",
1202 "mordent": "mordent",
1203 "open-string": "open",
1204 #"plop": "?",
1205 #"pluck": "?",
1206 #"pull-off": "?",
1207 #"schleifer": "?",
1208 #"scoop": "?",
1209 #"shake": "?",
1210 "snap-pizzicato": musicxml_snappizzicato_event,
1211 #"spiccato": "?",
1212 "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo"
1213 "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
1214 "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
1215 #"stress": "?",
1216 "string": musicxml_string_event,
1217 "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato"
1218 #"tap": "?",
1219 "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto"
1220 "thumb-position": "thumb",
1221 #"toe": "?",
1222 "turn": "turn",
1223 "tremolo": musicxml_tremolo_to_lily_event,
1224 "trill-mark": "trill",
1225 #"triple-tongue": "?",
1226 #"unstress": "?"
1227 "up-bow": "upbow",
1228 #"wavy-line": "?",
1230 articulation_spanners = [ "wavy-line" ]
1232 def musicxml_articulation_to_lily_event (mxl_event):
1233 # wavy-line elements are treated as trill spanners, not as articulation ornaments
1234 if mxl_event.get_name () in articulation_spanners:
1235 return musicxml_spanner_to_lily_event (mxl_event)
1237 tmp_tp = articulations_dict.get (mxl_event.get_name ())
1238 if not tmp_tp:
1239 return
1241 if isinstance (tmp_tp, str):
1242 ev = musicexp.ArticulationEvent ()
1243 ev.type = tmp_tp
1244 elif isinstance (tmp_tp, tuple):
1245 ev = tmp_tp[0] ()
1246 ev.type = tmp_tp[1]
1247 else:
1248 ev = tmp_tp (mxl_event)
1250 # Some articulations use the type attribute, other the placement...
1251 dir = None
1252 if hasattr (mxl_event, 'type') and options.convert_directions:
1253 dir = musicxml_direction_to_indicator (mxl_event.type)
1254 if hasattr (mxl_event, 'placement') and options.convert_directions:
1255 dir = musicxml_direction_to_indicator (mxl_event.placement)
1256 if dir:
1257 ev.force_direction = dir
1258 return ev
1262 def musicxml_dynamics_to_lily_event (dynentry):
1263 dynamics_available = (
1264 "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf",
1265 "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" )
1266 dynamicsname = dynentry.get_name ()
1267 if dynamicsname == "other-dynamics":
1268 dynamicsname = dynentry.get_text ()
1269 if not dynamicsname or dynamicsname=="#text":
1270 return
1272 if not dynamicsname in dynamics_available:
1273 # Get rid of - in tag names (illegal in ly tags!)
1274 dynamicstext = dynamicsname
1275 dynamicsname = string.replace (dynamicsname, "-", "")
1276 additional_definitions[dynamicsname] = dynamicsname + \
1277 " = #(make-dynamic-script \"" + dynamicstext + "\")"
1278 needed_additional_definitions.append (dynamicsname)
1279 event = musicexp.DynamicsEvent ()
1280 event.type = dynamicsname
1281 return event
1283 # Convert single-color two-byte strings to numbers 0.0 - 1.0
1284 def hexcolorval_to_nr (hex_val):
1285 try:
1286 v = int (hex_val, 16)
1287 if v == 255:
1288 v = 256
1289 return v / 256.
1290 except ValueError:
1291 return 0.
1293 def hex_to_color (hex_val):
1294 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)
1295 if res:
1296 return map (lambda x: hexcolorval_to_nr (x), res.group (2,3,4))
1297 else:
1298 return None
1300 def musicxml_words_to_lily_event (words):
1301 event = musicexp.TextEvent ()
1302 text = words.get_text ()
1303 text = re.sub ('^ *\n? *', '', text)
1304 text = re.sub (' *\n? *$', '', text)
1305 event.text = text
1307 if hasattr (words, 'default-y') and options.convert_directions:
1308 offset = getattr (words, 'default-y')
1309 try:
1310 off = string.atoi (offset)
1311 if off > 0:
1312 event.force_direction = 1
1313 else:
1314 event.force_direction = -1
1315 except ValueError:
1316 event.force_direction = 0
1318 if hasattr (words, 'font-weight'):
1319 font_weight = { "normal": '', "bold": '\\bold' }.get (getattr (words, 'font-weight'), '')
1320 if font_weight:
1321 event.markup += font_weight
1323 if hasattr (words, 'font-size'):
1324 size = getattr (words, 'font-size')
1325 font_size = {
1326 "xx-small": '\\teeny',
1327 "x-small": '\\tiny',
1328 "small": '\\small',
1329 "medium": '',
1330 "large": '\\large',
1331 "x-large": '\\huge',
1332 "xx-large": '\\larger\\huge'
1333 }.get (size, '')
1334 if font_size:
1335 event.markup += font_size
1337 if hasattr (words, 'color'):
1338 color = getattr (words, 'color')
1339 rgb = hex_to_color (color)
1340 if rgb:
1341 event.markup += "\\with-color #(rgb-color %s %s %s)" % (rgb[0], rgb[1], rgb[2])
1343 if hasattr (words, 'font-style'):
1344 font_style = { "italic": '\\italic' }.get (getattr (words, 'font-style'), '')
1345 if font_style:
1346 event.markup += font_style
1348 # TODO: How should I best convert the font-family attribute?
1350 # TODO: How can I represent the underline, overline and line-through
1351 # attributes in Lilypond? Values of these attributes indicate
1352 # the number of lines
1354 return event
1357 # convert accordion-registration to lilypond.
1358 # Since lilypond does not have any built-in commands, we need to create
1359 # the markup commands manually and define our own variables.
1360 # Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
1361 def musicxml_accordion_to_markup (mxl_event):
1362 commandname = "accReg"
1363 command = ""
1365 high = mxl_event.get_maybe_exist_named_child ('accordion-high')
1366 if high:
1367 commandname += "H"
1368 command += """\\combine
1369 \\raise #2.5 \\musicglyph #\"accordion.accDot\"
1371 middle = mxl_event.get_maybe_exist_named_child ('accordion-middle')
1372 if middle:
1373 # By default, use one dot (when no or invalid content is given). The
1374 # MusicXML spec is quiet about this case...
1375 txt = 1
1376 try:
1377 txt = string.atoi (middle.get_text ())
1378 except ValueError:
1379 pass
1380 if txt == 3:
1381 commandname += "MMM"
1382 command += """\\combine
1383 \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1384 \\combine
1385 \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\"
1386 \\combine
1387 \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\"
1389 elif txt == 2:
1390 commandname += "MM"
1391 command += """\\combine
1392 \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.accDot\"
1393 \\combine
1394 \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\"
1396 elif not txt <= 0:
1397 commandname += "M"
1398 command += """\\combine
1399 \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1401 low = mxl_event.get_maybe_exist_named_child ('accordion-low')
1402 if low:
1403 commandname += "L"
1404 command += """\\combine
1405 \\raise #0.5 \musicglyph #\"accordion.accDot\"
1408 command += "\musicglyph #\"accordion.accDiscant\""
1409 command = "\\markup { \\normalsize %s }" % command
1410 # Define the newly built command \accReg[H][MMM][L]
1411 additional_definitions[commandname] = "%s = %s" % (commandname, command)
1412 needed_additional_definitions.append (commandname)
1413 return "\\%s" % commandname
1415 def musicxml_accordion_to_ly (mxl_event):
1416 txt = musicxml_accordion_to_markup (mxl_event)
1417 if txt:
1418 ev = musicexp.MarkEvent (txt)
1419 return ev
1420 return
1423 def musicxml_rehearsal_to_ly_mark (mxl_event):
1424 text = mxl_event.get_text ()
1425 if not text:
1426 return
1427 # default is boxed rehearsal marks!
1428 encl = "box"
1429 if hasattr (mxl_event, 'enclosure'):
1430 encl = {"none": None, "square": "box", "circle": "circle" }.get (mxl_event.enclosure, None)
1431 if encl:
1432 text = "\\%s { %s }" % (encl, text)
1433 ev = musicexp.MarkEvent ("\\markup { %s }" % text)
1434 return ev
1436 def musicxml_harp_pedals_to_ly (mxl_event):
1437 count = 0
1438 result = "\\harp-pedal #\""
1439 for t in mxl_event.get_named_children ('pedal-tuning'):
1440 alter = t.get_named_child ('pedal-alter')
1441 if alter:
1442 val = int (alter.get_text ().strip ())
1443 result += {1: "v", 0: "-", -1: "^"}.get (val, "")
1444 count += 1
1445 if count == 3:
1446 result += "|"
1447 ev = musicexp.MarkupEvent ()
1448 ev.contents = result + "\""
1449 return ev
1451 def musicxml_eyeglasses_to_ly (mxl_event):
1452 needed_additional_definitions.append ("eyeglasses")
1453 return musicexp.MarkEvent ("\\eyeglasses")
1455 def next_non_hash_index (lst, pos):
1456 pos += 1
1457 while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
1458 pos += 1
1459 return pos
1461 def musicxml_metronome_to_ly (mxl_event):
1462 children = mxl_event.get_all_children ()
1463 if not children:
1464 return
1466 index = -1
1467 index = next_non_hash_index (children, index)
1468 if isinstance (children[index], musicxml.BeatUnit):
1469 # first form of metronome-mark, using unit and beats/min or other unit
1470 ev = musicexp.TempoMark ()
1471 if hasattr (mxl_event, 'parentheses'):
1472 ev.set_parentheses (mxl_event.parentheses == "yes")
1474 d = musicexp.Duration ()
1475 d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1476 index = next_non_hash_index (children, index)
1477 if isinstance (children[index], musicxml.BeatUnitDot):
1478 d.dots = 1
1479 index = next_non_hash_index (children, index)
1480 ev.set_base_duration (d)
1481 if isinstance (children[index], musicxml.BeatUnit):
1482 # Form "note = newnote"
1483 newd = musicexp.Duration ()
1484 newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1485 index = next_non_hash_index (children, index)
1486 if isinstance (children[index], musicxml.BeatUnitDot):
1487 newd.dots = 1
1488 index = next_non_hash_index (children, index)
1489 ev.set_new_duration (newd)
1490 elif isinstance (children[index], musicxml.PerMinute):
1491 # Form "note = bpm"
1492 try:
1493 beats = int (children[index].get_text ())
1494 ev.set_beats_per_minute (beats)
1495 except ValueError:
1496 pass
1497 else:
1498 error_message (_ ("Unknown metronome mark, ignoring"))
1499 return
1500 return ev
1501 else:
1502 #TODO: Implement the other (more complex) way for tempo marks!
1503 error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
1504 return
1506 # translate directions into Events, possible values:
1507 # -) string (MarkEvent with that command)
1508 # -) function (function(mxl_event) needs to return a full Event-derived object
1509 # -) (class, name) (like string, only that a different class than MarkEvent is used)
1510 directions_dict = {
1511 'accordion-registration' : musicxml_accordion_to_ly,
1512 'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
1513 # 'damp' : ???
1514 # 'damp-all' : ???
1515 'eyeglasses': musicxml_eyeglasses_to_ly,
1516 'harp-pedals' : musicxml_harp_pedals_to_ly,
1517 # 'image' : ???
1518 'metronome' : musicxml_metronome_to_ly,
1519 'rehearsal' : musicxml_rehearsal_to_ly_mark,
1520 # 'scordatura' : ???
1521 'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
1522 'words' : musicxml_words_to_lily_event,
1524 directions_spanners = [ 'octave-shift', 'pedal', 'wedge', 'dashes', 'bracket' ]
1526 def musicxml_direction_to_lily (n):
1527 # TODO: Handle the <staff> element!
1528 res = []
1529 # placement applies to all children!
1530 dir = None
1531 if hasattr (n, 'placement') and options.convert_directions:
1532 dir = musicxml_direction_to_indicator (n.placement)
1533 dirtype_children = []
1534 # TODO: The direction-type is used for grouping (e.g. dynamics with text),
1535 # so we can't simply flatten them out!
1536 for dt in n.get_typed_children (musicxml.DirType):
1537 dirtype_children += dt.get_all_children ()
1539 for entry in dirtype_children:
1540 # backets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
1541 if entry.get_name() in directions_spanners:
1542 event = musicxml_spanner_to_lily_event (entry)
1543 if event:
1544 res.append (event)
1545 continue
1547 # now treat all the "simple" ones, that can be translated using the dict
1548 ev = None
1549 tmp_tp = directions_dict.get (entry.get_name (), None)
1550 if isinstance (tmp_tp, str): # string means MarkEvent
1551 ev = musicexp.MarkEvent (tmp_tp)
1552 elif isinstance (tmp_tp, tuple): # tuple means (EventClass, "text")
1553 ev = tmp_tp[0] (tmp_tp[1])
1554 elif tmp_tp:
1555 ev = tmp_tp (entry)
1556 if ev:
1557 # TODO: set the correct direction! Unfortunately, \mark in ly does
1558 # not seem to support directions!
1559 res.append (ev)
1560 continue
1562 if entry.get_name () == "dynamics":
1563 for dynentry in entry.get_all_children ():
1564 ev = musicxml_dynamics_to_lily_event (dynentry)
1565 if ev:
1566 res.append (ev)
1568 return res
1570 def musicxml_frame_to_lily_event (frame):
1571 ev = musicexp.FretEvent ()
1572 ev.strings = frame.get_strings ()
1573 ev.frets = frame.get_frets ()
1574 #offset = frame.get_first_fret () - 1
1575 barre = []
1576 for fn in frame.get_named_children ('frame-note'):
1577 fret = fn.get_fret ()
1578 if fret <= 0:
1579 fret = "o"
1580 el = [ fn.get_string (), fret ]
1581 fingering = fn.get_fingering ()
1582 if fingering >= 0:
1583 el.append (fingering)
1584 ev.elements.append (el)
1585 b = fn.get_barre ()
1586 if b == 'start':
1587 barre[0] = el[0] # start string
1588 barre[2] = el[1] # fret
1589 elif b == 'stop':
1590 barre[1] = el[0] # end string
1591 if barre:
1592 ev.barre = barre
1593 return ev
1595 def musicxml_harmony_to_lily (n):
1596 res = []
1597 for f in n.get_named_children ('frame'):
1598 ev = musicxml_frame_to_lily_event (f)
1599 if ev:
1600 res.append (ev)
1601 return res
1604 notehead_styles_dict = {
1605 'slash': '\'slash',
1606 'triangle': '\'triangle',
1607 'diamond': '\'diamond',
1608 'square': '\'la', # TODO: Proper squared note head
1609 'cross': None, # TODO: + shaped note head
1610 'x': '\'cross',
1611 'circle-x': '\'xcircle',
1612 'inverted triangle': None, # TODO: Implement
1613 'arrow down': None, # TODO: Implement
1614 'arrow up': None, # TODO: Implement
1615 'slashed': None, # TODO: Implement
1616 'back slashed': None, # TODO: Implement
1617 'normal': None,
1618 'cluster': None, # TODO: Implement
1619 'none': '#f',
1620 'do': '\'do',
1621 're': '\'re',
1622 'mi': '\'mi',
1623 'fa': '\'fa',
1624 'so': None,
1625 'la': '\'la',
1626 'ti': '\'ti',
1629 def musicxml_notehead_to_lily (nh):
1630 styles = []
1632 # Notehead style
1633 style = notehead_styles_dict.get (nh.get_text ().strip (), None)
1634 style_elm = musicexp.NotestyleEvent ()
1635 if style:
1636 style_elm.style = style
1637 if hasattr (nh, 'filled'):
1638 style_elm.filled = (getattr (nh, 'filled') == "yes")
1639 if style_elm.style or (style_elm.filled != None):
1640 styles.append (style_elm)
1642 # parentheses
1643 if hasattr (nh, 'parentheses') and (nh.parentheses == "yes"):
1644 styles.append (musicexp.ParenthesizeEvent ())
1646 return styles
1648 def musicxml_chordpitch_to_lily (mxl_cpitch):
1649 r = musicexp.ChordPitch ()
1650 r.alteration = mxl_cpitch.get_alteration ()
1651 r.step = musicxml_step_to_lily (mxl_cpitch.get_step ())
1652 return r
1654 chordkind_dict = {
1655 'major': '5',
1656 'minor': 'm5',
1657 'augmented': 'aug5',
1658 'diminished': 'dim5',
1659 # Sevenths:
1660 'dominant': '7',
1661 'major-seventh': 'maj7',
1662 'minor-seventh': 'm7',
1663 'diminished-seventh': 'dim7',
1664 'augmented-seventh': 'aug7',
1665 'half-diminished': 'dim5m7',
1666 'major-minor': 'maj7m5',
1667 # Sixths:
1668 'major-sixth': '6',
1669 'minor-sixth': 'm6',
1670 # Ninths:
1671 'dominant-ninth': '9',
1672 'major-ninth': 'maj9',
1673 'minor-ninth': 'm9',
1674 # 11ths (usually as the basis for alteration):
1675 'dominant-11th': '11',
1676 'major-11th': 'maj11',
1677 'minor-11th': 'm11',
1678 # 13ths (usually as the basis for alteration):
1679 'dominant-13th': '13.11',
1680 'major-13th': 'maj13.11',
1681 'minor-13th': 'm13',
1682 # Suspended:
1683 'suspended-second': 'sus2',
1684 'suspended-fourth': 'sus4',
1685 # Functional sixths:
1686 # TODO
1687 #'Neapolitan': '???',
1688 #'Italian': '???',
1689 #'French': '???',
1690 #'German': '???',
1691 # Other:
1692 #'pedal': '???',(pedal-point bass)
1693 'power': '5^3',
1694 #'Tristan': '???',
1695 'other': '1',
1696 'none': None,
1699 def musicxml_chordkind_to_lily (kind):
1700 res = chordkind_dict.get (kind, None)
1701 # Check for None, since a major chord is converted to ''
1702 if res == None:
1703 error_message (_ ("Unable to convert chord type %s to lilypond.") % kind)
1704 return res
1706 def musicxml_harmony_to_lily_chordname (n):
1707 res = []
1708 root = n.get_maybe_exist_named_child ('root')
1709 if root:
1710 ev = musicexp.ChordNameEvent ()
1711 ev.root = musicxml_chordpitch_to_lily (root)
1712 kind = n.get_maybe_exist_named_child ('kind')
1713 if kind:
1714 ev.kind = musicxml_chordkind_to_lily (kind.get_text ())
1715 if not ev.kind:
1716 return res
1717 bass = n.get_maybe_exist_named_child ('bass')
1718 if bass:
1719 ev.bass = musicxml_chordpitch_to_lily (bass)
1720 inversion = n.get_maybe_exist_named_child ('inversion')
1721 if inversion:
1722 # TODO: Lilypond does not support inversions, does it?
1724 # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
1725 # 4. LilyPond supports the first inversion in the form of added
1726 # bass notes. So the first inversion of C major would be c:/g.
1727 # To get the second inversion of C major, you would need to do
1728 # e:6-3-^5 or e:m6-^5. However, both of these techniques
1729 # require you to know the chord and calculate either the fifth
1730 # pitch (for the first inversion) or the third pitch (for the
1731 # second inversion) so they may not be helpful for musicxml2ly.
1732 inversion_count = string.atoi (inversion.get_text ())
1733 if inversion_count == 1:
1734 # TODO: Calculate the bass note for the inversion...
1735 pass
1736 pass
1737 for deg in n.get_named_children ('degree'):
1738 d = musicexp.ChordModification ()
1739 d.type = deg.get_type ()
1740 d.step = deg.get_value ()
1741 d.alteration = deg.get_alter ()
1742 ev.add_modification (d)
1743 #TODO: convert the user-symbols attribute:
1744 #major: a triangle, like Unicode 25B3
1745 #minor: -, like Unicode 002D
1746 #augmented: +, like Unicode 002B
1747 #diminished: (degree), like Unicode 00B0
1748 #half-diminished: (o with slash), like Unicode 00F8
1749 if ev and ev.root:
1750 res.append (ev)
1752 return res
1754 def musicxml_figured_bass_note_to_lily (n):
1755 res = musicexp.FiguredBassNote ()
1756 suffix_dict = { 'sharp' : "+",
1757 'flat' : "-",
1758 'natural' : "!",
1759 'double-sharp' : "++",
1760 'flat-flat' : "--",
1761 'sharp-sharp' : "++",
1762 'slash' : "/" }
1763 prefix = n.get_maybe_exist_named_child ('prefix')
1764 if prefix:
1765 res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
1766 fnumber = n.get_maybe_exist_named_child ('figure-number')
1767 if fnumber:
1768 res.set_number (fnumber.get_text ())
1769 suffix = n.get_maybe_exist_named_child ('suffix')
1770 if suffix:
1771 res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
1772 if n.get_maybe_exist_named_child ('extend'):
1773 # TODO: Implement extender lines (unfortunately, in lilypond you have
1774 # to use \set useBassFigureExtenders = ##t, which turns them on
1775 # globally, while MusicXML has a property for each note...
1776 # I'm not sure there is a proper way to implement this cleanly
1777 #n.extend
1778 pass
1779 return res
1783 def musicxml_figured_bass_to_lily (n):
1784 if not isinstance (n, musicxml.FiguredBass):
1785 return
1786 res = musicexp.FiguredBassEvent ()
1787 for i in n.get_named_children ('figure'):
1788 note = musicxml_figured_bass_note_to_lily (i)
1789 if note:
1790 res.append (note)
1791 dur = n.get_maybe_exist_named_child ('duration')
1792 if dur:
1793 # apply the duration to res
1794 length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
1795 res.set_real_duration (length)
1796 duration = rational_to_lily_duration (length)
1797 if duration:
1798 res.set_duration (duration)
1799 if hasattr (n, 'parentheses') and n.parentheses == "yes":
1800 res.set_parentheses (True)
1801 return res
1803 instrument_drumtype_dict = {
1804 'Acoustic Snare Drum': 'acousticsnare',
1805 'Side Stick': 'sidestick',
1806 'Open Triangle': 'opentriangle',
1807 'Mute Triangle': 'mutetriangle',
1808 'Tambourine': 'tambourine',
1809 'Bass Drum': 'bassdrum',
1812 def musicxml_note_to_lily_main_event (n):
1813 pitch = None
1814 duration = None
1815 event = None
1817 mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
1818 if mxl_pitch:
1819 pitch = musicxml_pitch_to_lily (mxl_pitch)
1820 event = musicexp.NoteEvent ()
1821 event.pitch = pitch
1823 acc = n.get_maybe_exist_named_child ('accidental')
1824 if acc:
1825 # let's not force accs everywhere.
1826 event.cautionary = acc.editorial
1828 elif n.get_maybe_exist_typed_child (musicxml.Unpitched):
1829 # Unpitched elements have display-step and can also have
1830 # display-octave.
1831 unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
1832 event = musicexp.NoteEvent ()
1833 event.pitch = musicxml_unpitched_to_lily (unpitched)
1835 elif n.get_maybe_exist_typed_child (musicxml.Rest):
1836 # rests can have display-octave and display-step, which are
1837 # treated like an ordinary note pitch
1838 rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1839 event = musicexp.RestEvent ()
1840 pitch = musicxml_restdisplay_to_lily (rest)
1841 event.pitch = pitch
1843 elif n.instrument_name:
1844 event = musicexp.NoteEvent ()
1845 drum_type = instrument_drumtype_dict.get (n.instrument_name)
1846 if drum_type:
1847 event.drum_type = drum_type
1848 else:
1849 n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
1850 event.drum_type = 'acousticsnare'
1852 else:
1853 n.message (_ ("cannot find suitable event"))
1855 if event:
1856 event.duration = musicxml_duration_to_lily (n)
1858 noteheads = n.get_named_children ('notehead')
1859 for nh in noteheads:
1860 styles = musicxml_notehead_to_lily (nh)
1861 for s in styles:
1862 event.add_associated_event (s)
1864 return event
1866 def musicxml_lyrics_to_text (lyrics):
1867 # TODO: Implement text styles for lyrics syllables
1868 continued = False
1869 extended = False
1870 text = ''
1871 for e in lyrics.get_all_children ():
1872 if isinstance (e, musicxml.Syllabic):
1873 continued = e.continued ()
1874 elif isinstance (e, musicxml.Text):
1875 # We need to convert soft hyphens to -, otherwise the ascii codec as well
1876 # as lilypond will barf on that character
1877 text += string.replace( e.get_text(), u'\xad', '-' )
1878 elif isinstance (e, musicxml.Elision):
1879 if text:
1880 text += " "
1881 continued = False
1882 extended = False
1883 elif isinstance (e, musicxml.Extend):
1884 if text:
1885 text += " "
1886 extended = True
1888 if text == "-" and continued:
1889 return "--"
1890 elif text == "_" and extended:
1891 return "__"
1892 elif continued and text:
1893 return musicxml.escape_ly_output_string (text) + " --"
1894 elif continued:
1895 return "--"
1896 elif extended and text:
1897 return musicxml.escape_ly_output_string (text) + " __"
1898 elif extended:
1899 return "__"
1900 elif text:
1901 return musicxml.escape_ly_output_string (text)
1902 else:
1903 return ""
1905 ## TODO
1906 class NegativeSkip:
1907 def __init__ (self, here, dest):
1908 self.here = here
1909 self.dest = dest
1911 class LilyPondVoiceBuilder:
1912 def __init__ (self):
1913 self.elements = []
1914 self.pending_dynamics = []
1915 self.end_moment = Rational (0)
1916 self.begin_moment = Rational (0)
1917 self.pending_multibar = Rational (0)
1918 self.ignore_skips = False
1919 self.has_relevant_elements = False
1920 self.measure_length = Rational (4, 4)
1922 def _insert_multibar (self):
1923 layout_information.set_context_item ('Score', 'skipBars = ##t')
1924 r = musicexp.MultiMeasureRest ()
1925 lenfrac = self.measure_length
1926 r.duration = rational_to_lily_duration (lenfrac)
1927 r.duration.factor *= self.pending_multibar / lenfrac
1928 self.elements.append (r)
1929 self.begin_moment = self.end_moment
1930 self.end_moment = self.begin_moment + self.pending_multibar
1931 self.pending_multibar = Rational (0)
1933 def set_measure_length (self, mlen):
1934 if (mlen != self.measure_length) and self.pending_multibar:
1935 self._insert_multibar ()
1936 self.measure_length = mlen
1938 def add_multibar_rest (self, duration):
1939 self.pending_multibar += duration
1941 def set_duration (self, duration):
1942 self.end_moment = self.begin_moment + duration
1943 def current_duration (self):
1944 return self.end_moment - self.begin_moment
1946 def add_music (self, music, duration):
1947 assert isinstance (music, musicexp.Music)
1948 if self.pending_multibar > Rational (0):
1949 self._insert_multibar ()
1951 self.has_relevant_elements = True
1952 self.elements.append (music)
1953 self.begin_moment = self.end_moment
1954 self.set_duration (duration)
1956 # Insert all pending dynamics right after the note/rest:
1957 if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
1958 for d in self.pending_dynamics:
1959 music.append (d)
1960 self.pending_dynamics = []
1962 # Insert some music command that does not affect the position in the measure
1963 def add_command (self, command):
1964 assert isinstance (command, musicexp.Music)
1965 if self.pending_multibar > Rational (0):
1966 self._insert_multibar ()
1967 self.has_relevant_elements = True
1968 self.elements.append (command)
1969 def add_barline (self, barline):
1970 # TODO: Implement merging of default barline and custom bar line
1971 self.add_music (barline, Rational (0))
1972 def add_partial (self, command):
1973 self.ignore_skips = True
1974 self.add_command (command)
1976 def add_dynamics (self, dynamic):
1977 # store the dynamic item(s) until we encounter the next note/rest:
1978 self.pending_dynamics.append (dynamic)
1980 def add_bar_check (self, number):
1981 # re/store has_relevant_elements, so that a barline alone does not
1982 # trigger output for figured bass, chord names
1983 has_relevant = self.has_relevant_elements
1984 b = musicexp.BarLine ()
1985 b.bar_number = number
1986 self.add_barline (b)
1987 self.has_relevant_elements = has_relevant
1989 def jumpto (self, moment):
1990 current_end = self.end_moment + self.pending_multibar
1991 diff = moment - current_end
1993 if diff < Rational (0):
1994 error_message (_ ('Negative skip %s (from position %s to %s)') %
1995 (diff, current_end, moment))
1996 diff = Rational (0)
1998 if diff > Rational (0) and not (self.ignore_skips and moment == 0):
1999 skip = musicexp.SkipEvent()
2000 duration_factor = 1
2001 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)
2002 duration_dots = 0
2003 # TODO: Use the time signature for skips, too. Problem: The skip
2004 # might not start at a measure boundary!
2005 if duration_log > 0: # denominator is a power of 2...
2006 if diff.numerator () == 3:
2007 duration_log -= 1
2008 duration_dots = 1
2009 else:
2010 duration_factor = Rational (diff.numerator ())
2011 else:
2012 # for skips of a whole or more, simply use s1*factor
2013 duration_log = 0
2014 duration_factor = diff
2015 skip.duration.duration_log = duration_log
2016 skip.duration.factor = duration_factor
2017 skip.duration.dots = duration_dots
2019 evc = musicexp.ChordEvent ()
2020 evc.elements.append (skip)
2021 self.add_music (evc, diff)
2023 if diff > Rational (0) and moment == 0:
2024 self.ignore_skips = False
2026 def last_event_chord (self, starting_at):
2028 value = None
2030 # if the position matches, find the last ChordEvent, do not cross a bar line!
2031 at = len( self.elements ) - 1
2032 while (at >= 0 and
2033 not isinstance (self.elements[at], musicexp.ChordEvent) and
2034 not isinstance (self.elements[at], musicexp.BarLine)):
2035 at -= 1
2037 if (self.elements
2038 and at >= 0
2039 and isinstance (self.elements[at], musicexp.ChordEvent)
2040 and self.begin_moment == starting_at):
2041 value = self.elements[at]
2042 else:
2043 self.jumpto (starting_at)
2044 value = None
2045 return value
2047 def correct_negative_skip (self, goto):
2048 self.end_moment = goto
2049 self.begin_moment = goto
2050 evc = musicexp.ChordEvent ()
2051 self.elements.append (evc)
2054 class VoiceData:
2055 def __init__ (self):
2056 self.voicename = None
2057 self.voicedata = None
2058 self.ly_voice = None
2059 self.figured_bass = None
2060 self.chordnames = None
2061 self.lyrics_dict = {}
2062 self.lyrics_order = []
2064 def musicxml_step_to_lily (step):
2065 if step:
2066 return (ord (step) - ord ('A') + 7 - 2) % 7
2067 else:
2068 return None
2070 def measure_length_from_attributes (attr, current_measure_length):
2071 len = attr.get_measure_length ()
2072 if not len:
2073 len = current_measure_length
2074 return len
2076 def musicxml_voice_to_lily_voice (voice):
2077 tuplet_events = []
2078 modes_found = {}
2079 lyrics = {}
2080 return_value = VoiceData ()
2081 return_value.voicedata = voice
2083 # First pitch needed for relative mode (if selected in command-line options)
2084 first_pitch = None
2086 # Needed for melismata detection (ignore lyrics on those notes!):
2087 inside_slur = False
2088 is_tied = False
2089 is_chord = False
2090 is_beamed = False
2091 ignore_lyrics = False
2093 current_staff = None
2095 pending_figured_bass = []
2096 pending_chordnames = []
2098 # Make sure that the keys in the dict don't get reordered, since
2099 # we need the correct ordering of the lyrics stanzas! By default,
2100 # a dict will reorder its keys
2101 return_value.lyrics_order = voice.get_lyrics_numbers ()
2102 for k in return_value.lyrics_order:
2103 lyrics[k] = []
2105 voice_builder = LilyPondVoiceBuilder ()
2106 figured_bass_builder = LilyPondVoiceBuilder ()
2107 chordnames_builder = LilyPondVoiceBuilder ()
2108 current_measure_length = Rational (4, 4)
2109 voice_builder.set_measure_length (current_measure_length)
2111 for n in voice._elements:
2112 if n.get_name () == 'forward':
2113 continue
2114 staff = n.get_maybe_exist_named_child ('staff')
2115 if staff:
2116 staff = staff.get_text ()
2117 if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'):
2118 voice_builder.add_command (musicexp.StaffChange (staff))
2119 current_staff = staff
2121 if isinstance (n, musicxml.Partial) and n.partial > 0:
2122 a = musicxml_partial_to_lily (n.partial)
2123 if a:
2124 voice_builder.add_partial (a)
2125 continue
2127 is_chord = n.get_maybe_exist_named_child ('chord')
2128 is_after_grace = (isinstance (n, musicxml.Note) and n.is_after_grace ());
2129 if not is_chord and not is_after_grace:
2130 try:
2131 voice_builder.jumpto (n._when)
2132 except NegativeSkip, neg:
2133 voice_builder.correct_negative_skip (n._when)
2134 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
2136 if isinstance (n, musicxml.Barline):
2137 barlines = musicxml_barline_to_lily (n)
2138 for a in barlines:
2139 if isinstance (a, musicexp.BarLine):
2140 voice_builder.add_barline (a)
2141 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
2142 voice_builder.add_command (a)
2143 continue
2145 # Continue any multimeasure-rests before trying to add bar checks!
2146 # Don't handle new MM rests yet, because for them we want bar checks!
2147 rest = n.get_maybe_exist_typed_child (musicxml.Rest)
2148 if (rest and rest.is_whole_measure ()
2149 and voice_builder.pending_multibar > Rational (0)):
2150 voice_builder.add_multibar_rest (n._duration)
2151 continue
2154 # print a bar check at the beginning of each measure!
2155 if n.is_first () and n._measure_position == Rational (0) and n != voice._elements[0]:
2156 try:
2157 num = int (n.get_parent ().number)
2158 except ValueError:
2159 num = 0
2160 if num > 0:
2161 voice_builder.add_bar_check (num)
2162 figured_bass_builder.add_bar_check (num)
2163 chordnames_builder.add_bar_check (num)
2165 # Start any new multimeasure rests
2166 if (rest and rest.is_whole_measure ()):
2167 voice_builder.add_multibar_rest (n._duration)
2168 continue
2171 if isinstance (n, musicxml.Direction):
2172 for a in musicxml_direction_to_lily (n):
2173 if a.wait_for_note ():
2174 voice_builder.add_dynamics (a)
2175 else:
2176 voice_builder.add_command (a)
2177 continue
2179 if isinstance (n, musicxml.Harmony):
2180 for a in musicxml_harmony_to_lily (n):
2181 if a.wait_for_note ():
2182 voice_builder.add_dynamics (a)
2183 else:
2184 voice_builder.add_command (a)
2185 for a in musicxml_harmony_to_lily_chordname (n):
2186 pending_chordnames.append (a)
2187 continue
2189 if isinstance (n, musicxml.FiguredBass):
2190 a = musicxml_figured_bass_to_lily (n)
2191 if a:
2192 pending_figured_bass.append (a)
2193 continue
2195 if isinstance (n, musicxml.Attributes):
2196 for a in musicxml_attributes_to_lily (n):
2197 voice_builder.add_command (a)
2198 measure_length = measure_length_from_attributes (n, current_measure_length)
2199 if current_measure_length != measure_length:
2200 current_measure_length = measure_length
2201 voice_builder.set_measure_length (current_measure_length)
2202 continue
2204 if not n.__class__.__name__ == 'Note':
2205 n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
2206 continue
2208 main_event = musicxml_note_to_lily_main_event (n)
2209 if main_event and not first_pitch:
2210 first_pitch = main_event.pitch
2211 # ignore lyrics for notes inside a slur, tie, chord or beam
2212 ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
2214 if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
2215 modes_found['drummode'] = True
2217 ev_chord = voice_builder.last_event_chord (n._when)
2218 if not ev_chord:
2219 ev_chord = musicexp.ChordEvent()
2220 voice_builder.add_music (ev_chord, n._duration)
2222 # For grace notes:
2223 grace = n.get_maybe_exist_typed_child (musicxml.Grace)
2224 if n.is_grace ():
2225 is_after_grace = ev_chord.has_elements () or n.is_after_grace ();
2226 is_chord = n.get_maybe_exist_typed_child (musicxml.Chord)
2228 grace_chord = None
2230 # after-graces and other graces use different lists; Depending on
2231 # whether we have a chord or not, obtain either a new ChordEvent or
2232 # the previous one to create a chord
2233 if is_after_grace:
2234 if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
2235 grace_chord = ev_chord.after_grace_elements.get_last_event_chord ()
2236 if not grace_chord:
2237 grace_chord = musicexp.ChordEvent ()
2238 ev_chord.append_after_grace (grace_chord)
2239 elif n.is_grace ():
2240 if ev_chord.grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
2241 grace_chord = ev_chord.grace_elements.get_last_event_chord ()
2242 if not grace_chord:
2243 grace_chord = musicexp.ChordEvent ()
2244 ev_chord.append_grace (grace_chord)
2246 if hasattr (grace, 'slash') and not is_after_grace:
2247 # TODO: use grace_type = "appoggiatura" for slurred grace notes
2248 if grace.slash == "yes":
2249 ev_chord.grace_type = "acciaccatura"
2250 # now that we have inserted the chord into the grace music, insert
2251 # everything into that chord instead of the ev_chord
2252 ev_chord = grace_chord
2253 ev_chord.append (main_event)
2254 ignore_lyrics = True
2255 else:
2256 ev_chord.append (main_event)
2257 # When a note/chord has grace notes (duration==0), the duration of the
2258 # event chord is not yet known, but the event chord was already added
2259 # with duration 0. The following correct this when we hit the real note!
2260 if voice_builder.current_duration () == 0 and n._duration > 0:
2261 voice_builder.set_duration (n._duration)
2263 # if we have a figured bass, set its voice builder to the correct position
2264 # and insert the pending figures
2265 if pending_figured_bass:
2266 try:
2267 figured_bass_builder.jumpto (n._when)
2268 except NegativeSkip, neg:
2269 pass
2270 for fb in pending_figured_bass:
2271 # if a duration is given, use that, otherwise the one of the note
2272 dur = fb.real_duration
2273 if not dur:
2274 dur = ev_chord.get_length ()
2275 if not fb.duration:
2276 fb.duration = ev_chord.get_duration ()
2277 figured_bass_builder.add_music (fb, dur)
2278 pending_figured_bass = []
2280 if pending_chordnames:
2281 try:
2282 chordnames_builder.jumpto (n._when)
2283 except NegativeSkip, neg:
2284 pass
2285 for cn in pending_chordnames:
2286 # Assign the duration of the EventChord
2287 cn.duration = ev_chord.get_duration ()
2288 chordnames_builder.add_music (cn, ev_chord.get_length ())
2289 pending_chordnames = []
2291 notations_children = n.get_typed_children (musicxml.Notations)
2292 tuplet_event = None
2293 span_events = []
2295 # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
2296 # +tied | +slur | +tuplet | glissando | slide |
2297 # ornaments | technical | articulations | dynamics |
2298 # +fermata | arpeggiate | non-arpeggiate |
2299 # accidental-mark | other-notation
2300 for notations in notations_children:
2301 for tuplet_event in notations.get_tuplets():
2302 time_mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
2303 tuplet_events.append ((ev_chord, tuplet_event, time_mod))
2305 # First, close all open slurs, only then start any new slur
2306 # TODO: Record the number of the open slur to dtermine the correct
2307 # closing slur!
2308 endslurs = [s for s in notations.get_named_children ('slur')
2309 if s.get_type () in ('stop')]
2310 if endslurs and not inside_slur:
2311 endslurs[0].message (_ ('Encountered closing slur, but no slur is open'))
2312 elif endslurs:
2313 if len (endslurs) > 1:
2314 endslurs[0].message (_ ('Cannot have two simultaneous (closing) slurs'))
2315 # record the slur status for the next note in the loop
2316 if not grace:
2317 inside_slur = False
2318 lily_ev = musicxml_spanner_to_lily_event (endslurs[0])
2319 ev_chord.append (lily_ev)
2321 startslurs = [s for s in notations.get_named_children ('slur')
2322 if s.get_type () in ('start')]
2323 if startslurs and inside_slur:
2324 startslurs[0].message (_ ('Cannot have a slur inside another slur'))
2325 elif startslurs:
2326 if len (startslurs) > 1:
2327 startslurs[0].message (_ ('Cannot have two simultaneous slurs'))
2328 # record the slur status for the next note in the loop
2329 if not grace:
2330 inside_slur = True
2331 lily_ev = musicxml_spanner_to_lily_event (startslurs[0])
2332 ev_chord.append (lily_ev)
2335 if not grace:
2336 mxl_tie = notations.get_tie ()
2337 if mxl_tie and mxl_tie.type == 'start':
2338 ev_chord.append (musicexp.TieEvent ())
2339 is_tied = True
2340 else:
2341 is_tied = False
2343 fermatas = notations.get_named_children ('fermata')
2344 for a in fermatas:
2345 ev = musicxml_fermata_to_lily_event (a)
2346 if ev:
2347 ev_chord.append (ev)
2349 arpeggiate = notations.get_named_children ('arpeggiate')
2350 for a in arpeggiate:
2351 ev = musicxml_arpeggiate_to_lily_event (a)
2352 if ev:
2353 ev_chord.append (ev)
2355 arpeggiate = notations.get_named_children ('non-arpeggiate')
2356 for a in arpeggiate:
2357 ev = musicxml_nonarpeggiate_to_lily_event (a)
2358 if ev:
2359 ev_chord.append (ev)
2361 glissandos = notations.get_named_children ('glissando')
2362 glissandos += notations.get_named_children ('slide')
2363 for a in glissandos:
2364 ev = musicxml_spanner_to_lily_event (a)
2365 if ev:
2366 ev_chord.append (ev)
2368 # accidental-marks are direct children of <notation>!
2369 for a in notations.get_named_children ('accidental-mark'):
2370 ev = musicxml_articulation_to_lily_event (a)
2371 if ev:
2372 ev_chord.append (ev)
2374 # Articulations can contain the following child elements:
2375 # accent | strong-accent | staccato | tenuto |
2376 # detached-legato | staccatissimo | spiccato |
2377 # scoop | plop | doit | falloff | breath-mark |
2378 # caesura | stress | unstress
2379 # Technical can contain the following child elements:
2380 # up-bow | down-bow | harmonic | open-string |
2381 # thumb-position | fingering | pluck | double-tongue |
2382 # triple-tongue | stopped | snap-pizzicato | fret |
2383 # string | hammer-on | pull-off | bend | tap | heel |
2384 # toe | fingernails | other-technical
2385 # Ornaments can contain the following child elements:
2386 # trill-mark | turn | delayed-turn | inverted-turn |
2387 # shake | wavy-line | mordent | inverted-mordent |
2388 # schleifer | tremolo | other-ornament, accidental-mark
2389 ornaments = notations.get_named_children ('ornaments')
2390 ornaments += notations.get_named_children ('articulations')
2391 ornaments += notations.get_named_children ('technical')
2393 for a in ornaments:
2394 for ch in a.get_all_children ():
2395 ev = musicxml_articulation_to_lily_event (ch)
2396 if ev:
2397 ev_chord.append (ev)
2399 dynamics = notations.get_named_children ('dynamics')
2400 for a in dynamics:
2401 for ch in a.get_all_children ():
2402 ev = musicxml_dynamics_to_lily_event (ch)
2403 if ev:
2404 ev_chord.append (ev)
2407 mxl_beams = [b for b in n.get_named_children ('beam')
2408 if (b.get_type () in ('begin', 'end')
2409 and b.is_primary ())]
2410 if mxl_beams and not conversion_settings.ignore_beaming:
2411 beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
2412 if beam_ev:
2413 ev_chord.append (beam_ev)
2414 if beam_ev.span_direction == -1: # beam and thus melisma starts here
2415 is_beamed = True
2416 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
2417 is_beamed = False
2419 # Extract the lyrics
2420 if not rest and not ignore_lyrics:
2421 note_lyrics_processed = []
2422 note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
2423 for l in note_lyrics_elements:
2424 if l.get_number () < 0:
2425 for k in lyrics.keys ():
2426 lyrics[k].append (musicxml_lyrics_to_text (l))
2427 note_lyrics_processed.append (k)
2428 else:
2429 lyrics[l.number].append(musicxml_lyrics_to_text (l))
2430 note_lyrics_processed.append (l.number)
2431 for lnr in lyrics.keys ():
2432 if not lnr in note_lyrics_processed:
2433 lyrics[lnr].append ("\skip4")
2435 ## force trailing mm rests to be written out.
2436 voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
2438 ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
2439 ly_voice = group_repeats (ly_voice)
2441 seq_music = musicexp.SequentialMusic ()
2443 if 'drummode' in modes_found.keys ():
2444 ## \key <pitch> barfs in drummode.
2445 ly_voice = [e for e in ly_voice
2446 if not isinstance(e, musicexp.KeySignatureChange)]
2448 seq_music.elements = ly_voice
2449 for k in lyrics.keys ():
2450 return_value.lyrics_dict[k] = musicexp.Lyrics ()
2451 return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2454 if len (modes_found) > 1:
2455 error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
2457 if options.relative:
2458 v = musicexp.RelativeMusic ()
2459 v.element = seq_music
2460 v.basepitch = first_pitch
2461 seq_music = v
2463 return_value.ly_voice = seq_music
2464 for mode in modes_found.keys ():
2465 v = musicexp.ModeChangingMusicWrapper()
2466 v.element = seq_music
2467 v.mode = mode
2468 return_value.ly_voice = v
2470 # create \figuremode { figured bass elements }
2471 if figured_bass_builder.has_relevant_elements:
2472 fbass_music = musicexp.SequentialMusic ()
2473 fbass_music.elements = figured_bass_builder.elements
2474 v = musicexp.ModeChangingMusicWrapper()
2475 v.mode = 'figuremode'
2476 v.element = fbass_music
2477 return_value.figured_bass = v
2479 # create \chordmode { chords }
2480 if chordnames_builder.has_relevant_elements:
2481 cname_music = musicexp.SequentialMusic ()
2482 cname_music.elements = chordnames_builder.elements
2483 v = musicexp.ModeChangingMusicWrapper()
2484 v.mode = 'chordmode'
2485 v.element = cname_music
2486 return_value.chordnames = v
2488 return return_value
2490 def musicxml_id_to_lily (id):
2491 digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2492 'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2494 for digit in digits:
2495 d = digits.index (digit)
2496 id = re.sub ('%d' % d, digit, id)
2498 id = re.sub ('[^a-zA-Z]', 'X', id)
2499 return id
2501 def musicxml_pitch_to_lily (mxl_pitch):
2502 p = musicexp.Pitch ()
2503 p.alteration = mxl_pitch.get_alteration ()
2504 p.step = musicxml_step_to_lily (mxl_pitch.get_step ())
2505 p.octave = mxl_pitch.get_octave () - 4
2506 return p
2508 def musicxml_unpitched_to_lily (mxl_unpitched):
2509 p = None
2510 step = mxl_unpitched.get_step ()
2511 if step:
2512 p = musicexp.Pitch ()
2513 p.step = musicxml_step_to_lily (step)
2514 octave = mxl_unpitched.get_octave ()
2515 if octave and p:
2516 p.octave = octave - 4
2517 return p
2519 def musicxml_restdisplay_to_lily (mxl_rest):
2520 p = None
2521 step = mxl_rest.get_step ()
2522 if step:
2523 p = musicexp.Pitch ()
2524 p.step = musicxml_step_to_lily (step)
2525 octave = mxl_rest.get_octave ()
2526 if octave and p:
2527 p.octave = octave - 4
2528 return p
2530 def voices_in_part (part):
2531 """Return a Name -> Voice dictionary for PART"""
2532 part.interpret ()
2533 part.extract_voices ()
2534 voices = part.get_voices ()
2535 part_info = part.get_staff_attributes ()
2537 return (voices, part_info)
2539 def voices_in_part_in_parts (parts):
2540 """return a Part -> Name -> Voice dictionary"""
2541 return dict([(p.id, voices_in_part (p)) for p in parts])
2544 def get_all_voices (parts):
2545 all_voices = voices_in_part_in_parts (parts)
2547 all_ly_voices = {}
2548 all_ly_staffinfo = {}
2549 for p, (name_voice, staff_info) in all_voices.items ():
2551 part_ly_voices = {}
2552 for n, v in name_voice.items ():
2553 progress (_ ("Converting to LilyPond expressions..."))
2554 # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2555 part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
2557 all_ly_voices[p] = part_ly_voices
2558 all_ly_staffinfo[p] = staff_info
2560 return (all_ly_voices, all_ly_staffinfo)
2563 def option_parser ():
2564 p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"),
2565 description =
2566 _ ("""Convert MusicXML from FILE.xml to LilyPond input.
2567 If the given filename is -, musicxml2ly reads from the command line.
2568 """), add_help_option=False)
2570 p.add_option("-h", "--help",
2571 action="help",
2572 help=_ ("show this help and exit"))
2574 p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
2576 _ ("""Copyright (c) 2005--2008 by
2577 Han-Wen Nienhuys <hanwen@xs4all.nl>,
2578 Jan Nieuwenhuizen <janneke@gnu.org> and
2579 Reinhold Kainhofer <reinhold@kainhofer.com>
2583 This program is free software. It is covered by the GNU General Public
2584 License and you are welcome to change it and/or distribute copies of it
2585 under certain conditions. Invoke as `%s --warranty' for more
2586 information.""") % 'lilypond')
2588 p.add_option("--version",
2589 action="version",
2590 help=_ ("show version number and exit"))
2592 p.add_option ('-v', '--verbose',
2593 action = "store_true",
2594 dest = 'verbose',
2595 help = _ ("be verbose"))
2597 p.add_option ('', '--lxml',
2598 action = "store_true",
2599 default = False,
2600 dest = "use_lxml",
2601 help = _ ("use lxml.etree; uses less memory and cpu time"))
2603 p.add_option ('-z', '--compressed',
2604 action = "store_true",
2605 dest = 'compressed',
2606 default = False,
2607 help = _ ("input file is a zip-compressed MusicXML file"))
2609 p.add_option ('-r', '--relative',
2610 action = "store_true",
2611 default = True,
2612 dest = "relative",
2613 help = _ ("convert pitches in relative mode (default)"))
2615 p.add_option ('-a', '--absolute',
2616 action = "store_false",
2617 dest = "relative",
2618 help = _ ("convert pitches in absolute mode"))
2620 p.add_option ('-l', '--language',
2621 metavar = _ ("LANG"),
2622 action = "store",
2623 help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
2625 p.add_option ('--nd', '--no-articulation-directions',
2626 action = "store_false",
2627 default = True,
2628 dest = "convert_directions",
2629 help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2631 p.add_option ('--no-beaming',
2632 action = "store_false",
2633 default = True,
2634 dest = "convert_beaming",
2635 help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
2637 p.add_option ('-o', '--output',
2638 metavar = _ ("FILE"),
2639 action = "store",
2640 default = None,
2641 type = 'string',
2642 dest = 'output_name',
2643 help = _ ("set output filename to FILE, stdout if -"))
2644 p.add_option_group ('',
2645 description = (
2646 _ ("Report bugs via %s")
2647 % 'http://post.gmane.org/post.php'
2648 '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
2649 return p
2651 def music_xml_voice_name_to_lily_name (part_id, name):
2652 str = "Part%sVoice%s" % (part_id, name)
2653 return musicxml_id_to_lily (str)
2655 def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
2656 str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
2657 return musicxml_id_to_lily (str)
2659 def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
2660 str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
2661 return musicxml_id_to_lily (str)
2663 def music_xml_chordnames_name_to_lily_name (part_id, voicename):
2664 str = "Part%sVoice%sChords" % (part_id, voicename)
2665 return musicxml_id_to_lily (str)
2667 def print_voice_definitions (printer, part_list, voices):
2668 for part in part_list:
2669 part_id = part.id
2670 nv_dict = voices.get (part_id, {})
2671 for (name, voice) in nv_dict.items ():
2672 k = music_xml_voice_name_to_lily_name (part_id, name)
2673 printer.dump ('%s = ' % k)
2674 voice.ly_voice.print_ly (printer)
2675 printer.newline()
2676 if voice.chordnames:
2677 cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
2678 printer.dump ('%s = ' % cnname )
2679 voice.chordnames.print_ly (printer)
2680 printer.newline()
2681 for l in voice.lyrics_order:
2682 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
2683 printer.dump ('%s = ' % lname )
2684 voice.lyrics_dict[l].print_ly (printer)
2685 printer.newline()
2686 if voice.figured_bass:
2687 fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
2688 printer.dump ('%s = ' % fbname )
2689 voice.figured_bass.print_ly (printer)
2690 printer.newline()
2693 def uniq_list (l):
2694 return dict ([(elt,1) for elt in l]).keys ()
2696 # format the information about the staff in the form
2697 # [staffid,
2699 # [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
2700 # [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
2701 # ...
2704 # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
2705 def format_staff_info (part_id, staff_id, raw_voices):
2706 voices = []
2707 for (v, lyricsids, figured_bass, chordnames) in raw_voices:
2708 voice_name = music_xml_voice_name_to_lily_name (part_id, v)
2709 voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
2710 for l in lyricsids]
2711 figured_bass_name = ''
2712 if figured_bass:
2713 figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
2714 chordnames_name = ''
2715 if chordnames:
2716 chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
2717 voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
2718 return [staff_id, voices]
2720 def update_score_setup (score_structure, part_list, voices):
2722 for part_definition in part_list:
2723 part_id = part_definition.id
2724 nv_dict = voices.get (part_id)
2725 if not nv_dict:
2726 error_message (_ ('unknown part in part-list: %s') % part_id)
2727 continue
2729 staves = reduce (lambda x,y: x+ y,
2730 [voice.voicedata._staves.keys ()
2731 for voice in nv_dict.values ()],
2733 staves_info = []
2734 if len (staves) > 1:
2735 staves_info = []
2736 staves = uniq_list (staves)
2737 staves.sort ()
2738 for s in staves:
2739 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames)
2740 for (voice_name, voice) in nv_dict.items ()
2741 if voice.voicedata._start_staff == s]
2742 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
2743 else:
2744 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames)
2745 for (voice_name, voice) in nv_dict.items ()]
2746 staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
2747 score_structure.set_part_information (part_id, staves_info)
2749 # Set global values in the \layout block, like auto-beaming etc.
2750 def update_layout_information ():
2751 if not conversion_settings.ignore_beaming and layout_information:
2752 layout_information.set_context_item ('Score', 'autoBeaming = ##f')
2754 def print_ly_preamble (printer, filename):
2755 printer.dump_version ()
2756 printer.print_verbatim ('%% automatically converted from %s\n' % filename)
2758 def print_ly_additional_definitions (printer, filename):
2759 if needed_additional_definitions:
2760 printer.newline ()
2761 printer.print_verbatim ('%% additional definitions required by the score:')
2762 printer.newline ()
2763 for a in set(needed_additional_definitions):
2764 printer.print_verbatim (additional_definitions.get (a, ''))
2765 printer.newline ()
2766 printer.newline ()
2768 # Read in the tree from the given I/O object (either file or string) and
2769 # demarshall it using the classes from the musicxml.py file
2770 def read_xml (io_object, use_lxml):
2771 if use_lxml:
2772 import lxml.etree
2773 tree = lxml.etree.parse (io_object)
2774 mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
2775 return mxl_tree
2776 else:
2777 from xml.dom import minidom, Node
2778 doc = minidom.parse(io_object)
2779 node = doc.documentElement
2780 return musicxml.minidom_demarshal_node (node)
2781 return None
2784 def read_musicxml (filename, compressed, use_lxml):
2785 raw_string = None
2786 if compressed:
2787 if filename == "-":
2788 progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
2789 z = zipfile.ZipFile (sys.stdin)
2790 else:
2791 progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
2792 z = zipfile.ZipFile (filename, "r")
2793 container_xml = z.read ("META-INF/container.xml")
2794 if not container_xml:
2795 return None
2796 container = read_xml (StringIO.StringIO (container_xml), use_lxml)
2797 if not container:
2798 return None
2799 rootfiles = container.get_maybe_exist_named_child ('rootfiles')
2800 if not rootfiles:
2801 return None
2802 rootfile_list = rootfiles.get_named_children ('rootfile')
2803 mxml_file = None
2804 if len (rootfile_list) > 0:
2805 mxml_file = getattr (rootfile_list[0], 'full-path', None)
2806 if mxml_file:
2807 raw_string = z.read (mxml_file)
2809 if raw_string:
2810 io_object = StringIO.StringIO (raw_string)
2811 elif filename == "-":
2812 io_object = sys.stdin
2813 else:
2814 io_object = filename
2816 return read_xml (io_object, use_lxml)
2819 def convert (filename, options):
2820 if filename == "-":
2821 progress (_ ("Reading MusicXML from Standard input ...") )
2822 else:
2823 progress (_ ("Reading MusicXML from %s ...") % filename)
2825 tree = read_musicxml (filename, options.compressed, options.use_lxml)
2826 score_information = extract_score_information (tree)
2827 paper_information = extract_paper_information (tree)
2829 parts = tree.get_typed_children (musicxml.Part)
2830 (voices, staff_info) = get_all_voices (parts)
2832 score = None
2833 mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
2834 if mxl_pl:
2835 score = extract_score_structure (mxl_pl, staff_info)
2836 part_list = mxl_pl.get_named_children ("score-part")
2838 # score information is contained in the <work>, <identification> or <movement-title> tags
2839 update_score_setup (score, part_list, voices)
2840 # After the conversion, update the list of settings for the \layout block
2841 update_layout_information ()
2843 if not options.output_name:
2844 options.output_name = os.path.basename (filename)
2845 options.output_name = os.path.splitext (options.output_name)[0]
2846 elif re.match (".*\.ly", options.output_name):
2847 options.output_name = os.path.splitext (options.output_name)[0]
2850 #defs_ly_name = options.output_name + '-defs.ly'
2851 if (options.output_name == "-"):
2852 output_ly_name = 'Standard output'
2853 else:
2854 output_ly_name = options.output_name + '.ly'
2856 progress (_ ("Output to `%s'") % output_ly_name)
2857 printer = musicexp.Output_printer()
2858 #progress (_ ("Output to `%s'") % defs_ly_name)
2859 if (options.output_name == "-"):
2860 printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
2861 else:
2862 printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
2863 print_ly_preamble (printer, filename)
2864 print_ly_additional_definitions (printer, filename)
2865 if score_information:
2866 score_information.print_ly (printer)
2867 if paper_information:
2868 paper_information.print_ly (printer)
2869 if layout_information:
2870 layout_information.print_ly (printer)
2871 print_voice_definitions (printer, part_list, voices)
2873 printer.newline ()
2874 printer.dump ("% The score definition")
2875 printer.newline ()
2876 score.print_ly (printer)
2877 printer.newline ()
2879 return voices
2881 def get_existing_filename_with_extension (filename, ext):
2882 if os.path.exists (filename):
2883 return filename
2884 newfilename = filename + "." + ext
2885 if os.path.exists (newfilename):
2886 return newfilename;
2887 newfilename = filename + ext
2888 if os.path.exists (newfilename):
2889 return newfilename;
2890 return ''
2892 def main ():
2893 opt_parser = option_parser()
2895 global options
2896 (options, args) = opt_parser.parse_args ()
2897 if not args:
2898 opt_parser.print_usage()
2899 sys.exit (2)
2901 if options.language:
2902 musicexp.set_pitch_language (options.language)
2903 needed_additional_definitions.append (options.language)
2904 additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
2905 conversion_settings.ignore_beaming = not options.convert_beaming
2907 # Allow the user to leave out the .xml or xml on the filename
2908 basefilename = args[0].decode('utf-8')
2909 if basefilename == "-": # Read from stdin
2910 basefilename = "-"
2911 else:
2912 filename = get_existing_filename_with_extension (basefilename, "xml")
2913 if not filename:
2914 filename = get_existing_filename_with_extension (basefilename, "mxl")
2915 options.compressed = True
2916 if filename and filename.endswith ("mxl"):
2917 options.compressed = True
2919 if filename and (filename == "-" or os.path.exists (filename)):
2920 voices = convert (filename, options)
2921 else:
2922 progress (_ ("Unable to find input file %s") % basefilename)
2924 if __name__ == '__main__':
2925 main()