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