]> git.donarmstrong.com Git - lilypond.git/blob - scripts/musicxml2ly.py
MusicXML: Implement the symbol attribute of time signatures
[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     sig = attributes.get_time_signature ()
741     if not sig:
742         return None
743     change = musicexp.TimeSignatureChange()
744     change.fractions = sig
745
746     time_elm = attributes.get_maybe_exist_named_child ('time')
747     if time_elm and hasattr (time_elm, 'symbol'):
748         change.style = { 'single-number': "'single-digit",
749                          'cut': None,
750                          'common': None,
751                          'normal': "'()"}.get (time_elm.symbol, "'()")
752     else:
753         change.style = "'()"
754
755     # TODO: Handle senza-misura measures
756     # TODO: Handle hidden time signatures (print-object="no")
757     # TODO: What shall we do if the symbol clashes with the sig? e.g. "cut" 
758     #       with 3/8 or "single-number" with (2+3)/8 or 3/8+2/4?
759
760     return change
761
762 def musicxml_key_to_lily (attributes):
763     start_pitch  = musicexp.Pitch ()
764     (fifths, mode) = attributes.get_key_signature () 
765     try:
766         (n,a) = {
767             'major'     : (0,0),
768             'minor'     : (5,0),
769             'ionian'    : (0,0),
770             'dorian'    : (1,0),
771             'phrygian'  : (2,0),
772             'lydian'    : (3,0),
773             'mixolydian': (4,0),
774             'aeolian'   : (5,0),
775             'locrian'   : (6,0),
776             }[mode]
777         start_pitch.step = n
778         start_pitch.alteration = a
779     except  KeyError:
780         error_message (_ ("unknown mode %s, expecting 'major' or 'minor'") % mode)
781
782     fifth = musicexp.Pitch()
783     fifth.step = 4
784     if fifths < 0:
785         fifths *= -1
786         fifth.step *= -1
787         fifth.normalize ()
788     
789     for x in range (fifths):
790         start_pitch = start_pitch.transposed (fifth)
791
792     start_pitch.octave = 0
793
794     change = musicexp.KeySignatureChange()
795     change.mode = mode
796     change.tonic = start_pitch
797     return change
798
799 def musicxml_transpose_to_lily (attributes):
800     transpose = attributes.get_transposition ()
801     if not transpose:
802         return None
803
804     shift = musicexp.Pitch ()
805     octave_change = transpose.get_maybe_exist_named_child ('octave-change')
806     if octave_change:
807         shift.octave = string.atoi (octave_change.get_text ())
808     chromatic_shift = string.atoi (transpose.get_named_child ('chromatic').get_text ())
809     chromatic_shift_normalized = chromatic_shift % 12;
810     (shift.step, shift.alteration) = [
811         (0,0), (0,1), (1,0), (2,-1), (2,0), 
812         (3,0), (3,1), (4,0), (5,-1), (5,0), 
813         (6,-1), (6,0)][chromatic_shift_normalized];
814     
815     shift.octave += (chromatic_shift - chromatic_shift_normalized) / 12
816
817     diatonic = transpose.get_maybe_exist_named_child ('diatonic')
818     if diatonic:
819         diatonic_step = string.atoi (diatonic.get_text ()) % 7
820         if diatonic_step != shift.step:
821             # We got the alter incorrect!
822             old_semitones = shift.semitones ()
823             shift.step = diatonic_step
824             new_semitones = shift.semitones ()
825             shift.alteration += old_semitones - new_semitones
826
827     transposition = musicexp.Transposition ()
828     transposition.pitch = musicexp.Pitch ().transposed (shift)
829     return transposition
830
831
832 def musicxml_attributes_to_lily (attrs):
833     elts = []
834     attr_dispatch =  {
835         'clef': musicxml_clef_to_lily,
836         'time': musicxml_time_to_lily,
837         'key': musicxml_key_to_lily,
838         'transpose': musicxml_transpose_to_lily,
839     }
840     for (k, func) in attr_dispatch.items ():
841         children = attrs.get_named_children (k)
842         if children:
843             ev = func (attrs)
844             if ev:
845                 elts.append (ev)
846     
847     return elts
848
849 class Marker (musicexp.Music):
850     def __init__ (self):
851         self.direction = 0
852         self.event = None
853     def print_ly (self, printer):
854         ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self)
855         pass
856     def ly_expression (self):
857         return ""
858 class RepeatMarker (Marker):
859     def __init__ (self):
860         Marker.__init__ (self)
861         self.times = 0
862 class EndingMarker (Marker):
863     pass
864
865 # Convert the <barline> element to musicxml.BarLine (for non-standard barlines)
866 # and to RepeatMarker and EndingMarker objects for repeat and
867 # alternatives start/stops
868 def musicxml_barline_to_lily (barline):
869     # retval contains all possible markers in the order:
870     # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending
871     retval = {}
872     bartype_element = barline.get_maybe_exist_named_child ("bar-style")
873     repeat_element = barline.get_maybe_exist_named_child ("repeat")
874     ending_element = barline.get_maybe_exist_named_child ("ending")
875
876     bartype = None
877     if bartype_element:
878         bartype = bartype_element.get_text ()
879
880     if repeat_element and hasattr (repeat_element, 'direction'):
881         repeat = RepeatMarker ()
882         repeat.direction = {"forward": -1, "backward": 1}.get (repeat_element.direction, 0)
883
884         if ( (repeat_element.direction == "forward" and bartype == "heavy-light") or
885              (repeat_element.direction == "backward" and bartype == "light-heavy") ):
886             bartype = None
887         if hasattr (repeat_element, 'times'):
888             try:
889                 repeat.times = int (repeat_element.times)
890             except ValueError:
891                 repeat.times = 2
892         repeat.event = barline
893         if repeat.direction == -1:
894             retval[3] = repeat
895         else:
896             retval[1] = repeat
897
898     if ending_element and hasattr (ending_element, 'type'):
899         ending = EndingMarker ()
900         ending.direction = {"start": -1, "stop": 1, "discontinue": 1}.get (ending_element.type, 0)
901         ending.event = barline
902         if ending.direction == -1:
903             retval[4] = ending
904         else:
905             retval[0] = ending
906
907     if bartype:
908         b = musicexp.BarLine ()
909         b.type = bartype
910         retval[2] = b
911
912     return retval.values ()
913
914 spanner_event_dict = {
915     'beam' : musicexp.BeamEvent,
916     'dashes' : musicexp.TextSpannerEvent,
917     'bracket' : musicexp.BracketSpannerEvent,
918     'glissando' : musicexp.GlissandoEvent,
919     'octave-shift' : musicexp.OctaveShiftEvent,
920     'pedal' : musicexp.PedalEvent,
921     'slide' : musicexp.GlissandoEvent,
922     'slur' : musicexp.SlurEvent,
923     'wavy-line' : musicexp.TrillSpanEvent,
924     'wedge' : musicexp.HairpinEvent
925 }
926 spanner_type_dict = {
927     'start': -1,
928     'begin': -1,
929     'crescendo': -1,
930     'decreschendo': -1,
931     'diminuendo': -1,
932     'continue': 0,
933     'change': 0,
934     'up': -1,
935     'down': -1,
936     'stop': 1,
937     'end' : 1
938 }
939
940 def musicxml_spanner_to_lily_event (mxl_event):
941     ev = None
942     
943     name = mxl_event.get_name()
944     func = spanner_event_dict.get (name)
945     if func:
946         ev = func()
947     else:
948         error_message (_ ('unknown span event %s') % mxl_event)
949
950
951     type = mxl_event.get_type ()
952     span_direction = spanner_type_dict.get (type)
953     # really check for None, because some types will be translated to 0, which
954     # would otherwise also lead to the unknown span warning
955     if span_direction != None:
956         ev.span_direction = span_direction
957     else:
958         error_message (_ ('unknown span type %s for %s') % (type, name))
959
960     ev.set_span_type (type)
961     ev.line_type = getattr (mxl_event, 'line-type', 'solid')
962
963     # assign the size, which is used for octave-shift, etc.
964     ev.size = mxl_event.get_size ()
965
966     return ev
967
968 def musicxml_direction_to_indicator (direction):
969     return { "above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1 }.get (direction, 0)
970
971 def musicxml_fermata_to_lily_event (mxl_event):
972     ev = musicexp.ArticulationEvent ()
973     txt = mxl_event.get_text ()
974     # The contents of the element defined the shape, possible are normal, angled and square
975     ev.type = { "angled": "shortfermata", "square": "longfermata" }.get (txt, "fermata")
976     if hasattr (mxl_event, 'type'):
977       dir = musicxml_direction_to_indicator (mxl_event.type)
978       if dir and options.convert_directions:
979         ev.force_direction = dir
980     return ev
981
982 def musicxml_arpeggiate_to_lily_event (mxl_event):
983     ev = musicexp.ArpeggioEvent ()
984     ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
985     return ev
986
987 def musicxml_nonarpeggiate_to_lily_event (mxl_event):
988     ev = musicexp.ArpeggioEvent ()
989     ev.non_arpeggiate = True
990     ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
991     return ev
992
993 def musicxml_tremolo_to_lily_event (mxl_event):
994     ev = musicexp.TremoloEvent ()
995     txt = mxl_event.get_text ()
996     if txt:
997       ev.bars = txt
998     else:
999       ev.bars = "3"
1000     return ev
1001
1002 def musicxml_falloff_to_lily_event (mxl_event):
1003     ev = musicexp.BendEvent ()
1004     ev.alter = -4
1005     return ev
1006
1007 def musicxml_doit_to_lily_event (mxl_event):
1008     ev = musicexp.BendEvent ()
1009     ev.alter = 4
1010     return ev
1011
1012 def musicxml_bend_to_lily_event (mxl_event):
1013     ev = musicexp.BendEvent ()
1014     ev.alter = mxl_event.bend_alter ()
1015     return ev
1016
1017 def musicxml_caesura_to_lily_event (mxl_event):
1018     ev = musicexp.MarkupEvent ()
1019     # FIXME: default to straight or curved caesura?
1020     ev.contents = "\\musicglyph #\"scripts.caesura.straight\""
1021     ev.force_direction = 1
1022     return ev
1023
1024 def musicxml_fingering_event (mxl_event):
1025     ev = musicexp.ShortArticulationEvent ()
1026     ev.type = mxl_event.get_text ()
1027     return ev
1028
1029 def musicxml_snappizzicato_event (mxl_event):
1030     needed_additional_definitions.append ("snappizzicato")
1031     ev = musicexp.MarkupEvent ()
1032     ev.contents = "\\snappizzicato"
1033     return ev
1034
1035 def musicxml_string_event (mxl_event):
1036     ev = musicexp.NoDirectionArticulationEvent ()
1037     ev.type = mxl_event.get_text ()
1038     return ev
1039
1040 def musicxml_accidental_mark (mxl_event):
1041     ev = musicexp.MarkupEvent ()
1042     contents = { "sharp": "\\sharp",
1043       "natural": "\\natural",
1044       "flat": "\\flat",
1045       "double-sharp": "\\doublesharp",
1046       "sharp-sharp": "\\sharp\\sharp",
1047       "flat-flat": "\\flat\\flat",
1048       "flat-flat": "\\doubleflat",
1049       "natural-sharp": "\\natural\\sharp",
1050       "natural-flat": "\\natural\\flat",
1051       "quarter-flat": "\\semiflat",
1052       "quarter-sharp": "\\semisharp",
1053       "three-quarters-flat": "\\sesquiflat",
1054       "three-quarters-sharp": "\\sesquisharp",
1055     }.get (mxl_event.get_text ())
1056     if contents:
1057         ev.contents = contents
1058         return ev
1059     else:
1060         return None
1061
1062 # translate articulations, ornaments and other notations into ArticulationEvents
1063 # possible values:
1064 #   -) string  (ArticulationEvent with that name)
1065 #   -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
1066 #   -) (class, name)  (like string, only that a different class than ArticulationEvent is used)
1067 # TODO: Some translations are missing!
1068 articulations_dict = {
1069     "accent": (musicexp.ShortArticulationEvent, ">"), # or "accent"
1070     "accidental-mark": musicxml_accidental_mark,
1071     "bend": musicxml_bend_to_lily_event,
1072     "breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"),
1073     "caesura": musicxml_caesura_to_lily_event,
1074     #"delayed-turn": "?",
1075     "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato"
1076     "doit": musicxml_doit_to_lily_event,
1077     #"double-tongue": "?",
1078     "down-bow": "downbow",
1079     "falloff": musicxml_falloff_to_lily_event,
1080     "fingering": musicxml_fingering_event,
1081     #"fingernails": "?",
1082     #"fret": "?",
1083     #"hammer-on": "?",
1084     "harmonic": "flageolet",
1085     #"heel": "?",
1086     "inverted-mordent": "prall",
1087     "inverted-turn": "reverseturn",
1088     "mordent": "mordent",
1089     "open-string": "open",
1090     #"plop": "?",
1091     #"pluck": "?",
1092     #"pull-off": "?",
1093     #"schleifer": "?",
1094     #"scoop": "?",
1095     #"shake": "?",
1096     "snap-pizzicato": musicxml_snappizzicato_event,
1097     #"spiccato": "?",
1098     "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo"
1099     "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
1100     "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
1101     #"stress": "?",
1102     "string": musicxml_string_event,
1103     "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato"
1104     #"tap": "?",
1105     "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto"
1106     "thumb-position": "thumb",
1107     #"toe": "?",
1108     "turn": "turn",
1109     "tremolo": musicxml_tremolo_to_lily_event,
1110     "trill-mark": "trill",
1111     #"triple-tongue": "?",
1112     #"unstress": "?"
1113     "up-bow": "upbow",
1114     #"wavy-line": "?",
1115 }
1116 articulation_spanners = [ "wavy-line" ]
1117
1118 def musicxml_articulation_to_lily_event (mxl_event):
1119     # wavy-line elements are treated as trill spanners, not as articulation ornaments
1120     if mxl_event.get_name () in articulation_spanners:
1121         return musicxml_spanner_to_lily_event (mxl_event)
1122
1123     tmp_tp = articulations_dict.get (mxl_event.get_name ())
1124     if not tmp_tp:
1125         return
1126
1127     if isinstance (tmp_tp, str):
1128         ev = musicexp.ArticulationEvent ()
1129         ev.type = tmp_tp
1130     elif isinstance (tmp_tp, tuple):
1131         ev = tmp_tp[0] ()
1132         ev.type = tmp_tp[1]
1133     else:
1134         ev = tmp_tp (mxl_event)
1135
1136     # Some articulations use the type attribute, other the placement...
1137     dir = None
1138     if hasattr (mxl_event, 'type') and options.convert_directions:
1139         dir = musicxml_direction_to_indicator (mxl_event.type)
1140     if hasattr (mxl_event, 'placement') and options.convert_directions:
1141         dir = musicxml_direction_to_indicator (mxl_event.placement)
1142     if dir:
1143         ev.force_direction = dir
1144     return ev
1145
1146
1147
1148 def musicxml_dynamics_to_lily_event (dynentry):
1149     dynamics_available = (
1150         "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf", 
1151         "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" )
1152     dynamicsname = dynentry.get_name ()
1153     if dynamicsname == "other-dynamics":
1154         dynamicsname = dynentry.get_text ()
1155     if not dynamicsname or dynamicsname=="#text":
1156         return
1157
1158     if not dynamicsname in dynamics_available:
1159         # Get rid of - in tag names (illegal in ly tags!)
1160         dynamicstext = dynamicsname
1161         dynamicsname = string.replace (dynamicsname, "-", "")
1162         additional_definitions[dynamicsname] = dynamicsname + \
1163               " = #(make-dynamic-script \"" + dynamicstext + "\")"
1164         needed_additional_definitions.append (dynamicsname)
1165     event = musicexp.DynamicsEvent ()
1166     event.type = dynamicsname
1167     return event
1168
1169 # Convert single-color two-byte strings to numbers 0.0 - 1.0
1170 def hexcolorval_to_nr (hex_val):
1171     try:
1172         v = int (hex_val, 16)
1173         if v == 255:
1174             v = 256
1175         return v / 256.
1176     except ValueError:
1177         return 0.
1178
1179 def hex_to_color (hex_val):
1180     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)
1181     if res:
1182         return map (lambda x: hexcolorval_to_nr (x), res.group (2,3,4))
1183     else:
1184         return None
1185
1186 def musicxml_words_to_lily_event (words):
1187     event = musicexp.TextEvent ()
1188     text = words.get_text ()
1189     text = re.sub ('^ *\n? *', '', text)
1190     text = re.sub (' *\n? *$', '', text)
1191     event.text = text
1192
1193     if hasattr (words, 'default-y') and options.convert_directions:
1194         offset = getattr (words, 'default-y')
1195         try:
1196             off = string.atoi (offset)
1197             if off > 0:
1198                 event.force_direction = 1
1199             else:
1200                 event.force_direction = -1
1201         except ValueError:
1202             event.force_direction = 0
1203
1204     if hasattr (words, 'font-weight'):
1205         font_weight = { "normal": '', "bold": '\\bold' }.get (getattr (words, 'font-weight'), '')
1206         if font_weight:
1207             event.markup += font_weight
1208
1209     if hasattr (words, 'font-size'):
1210         size = getattr (words, 'font-size')
1211         font_size = {
1212             "xx-small": '\\teeny',
1213             "x-small": '\\tiny',
1214             "small": '\\small',
1215             "medium": '',
1216             "large": '\\large',
1217             "x-large": '\\huge',
1218             "xx-large": '\\larger\\huge'
1219         }.get (size, '')
1220         if font_size:
1221             event.markup += font_size
1222
1223     if hasattr (words, 'color'):
1224         color = getattr (words, 'color')
1225         rgb = hex_to_color (color)
1226         if rgb:
1227             event.markup += "\\with-color #(rgb-color %s %s %s)" % (rgb[0], rgb[1], rgb[2])
1228
1229     if hasattr (words, 'font-style'):
1230         font_style = { "italic": '\\italic' }.get (getattr (words, 'font-style'), '')
1231         if font_style:
1232             event.markup += font_style
1233
1234     # TODO: How should I best convert the font-family attribute?
1235
1236     # TODO: How can I represent the underline, overline and line-through
1237     #       attributes in Lilypond? Values of these attributes indicate
1238     #       the number of lines
1239
1240     return event
1241
1242
1243 # convert accordion-registration to lilypond.
1244 # Since lilypond does not have any built-in commands, we need to create
1245 # the markup commands manually and define our own variables.
1246 # Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
1247 def musicxml_accordion_to_markup (mxl_event):
1248     commandname = "accReg"
1249     command = ""
1250
1251     high = mxl_event.get_maybe_exist_named_child ('accordion-high')
1252     if high:
1253         commandname += "H"
1254         command += """\\combine
1255           \\raise #2.5 \\musicglyph #\"accordion.accDot\"
1256           """
1257     middle = mxl_event.get_maybe_exist_named_child ('accordion-middle')
1258     if middle:
1259         # By default, use one dot (when no or invalid content is given). The 
1260         # MusicXML spec is quiet about this case...
1261         txt = 1
1262         try:
1263           txt = string.atoi (middle.get_text ())
1264         except ValueError:
1265             pass
1266         if txt == 3:
1267             commandname += "MMM"
1268             command += """\\combine
1269           \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1270           \\combine
1271           \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\"
1272           \\combine
1273           \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\"
1274           """
1275         elif txt == 2:
1276             commandname += "MM"
1277             command += """\\combine
1278           \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.accDot\"
1279           \\combine
1280           \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\"
1281           """
1282         elif not txt <= 0:
1283             commandname += "M"
1284             command += """\\combine
1285           \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1286           """
1287     low = mxl_event.get_maybe_exist_named_child ('accordion-low')
1288     if low:
1289         commandname += "L"
1290         command += """\\combine
1291           \\raise #0.5 \musicglyph #\"accordion.accDot\"
1292           """
1293
1294     command += "\musicglyph #\"accordion.accDiscant\""
1295     command = "\\markup { \\normalsize %s }" % command
1296     # Define the newly built command \accReg[H][MMM][L]
1297     additional_definitions[commandname] = "%s = %s" % (commandname, command)
1298     needed_additional_definitions.append (commandname)
1299     return "\\%s" % commandname
1300
1301 def musicxml_accordion_to_ly (mxl_event):
1302     txt = musicxml_accordion_to_markup (mxl_event)
1303     if txt:
1304         ev = musicexp.MarkEvent (txt)
1305         return ev
1306     return
1307
1308
1309 def musicxml_rehearsal_to_ly_mark (mxl_event):
1310     text = mxl_event.get_text ()
1311     if not text:
1312         return
1313     # default is boxed rehearsal marks!
1314     encl = "box"
1315     if hasattr (mxl_event, 'enclosure'):
1316         encl = {"none": None, "square": "box", "circle": "circle" }.get (mxl_event.enclosure, None)
1317     if encl:
1318         text = "\\%s { %s }" % (encl, text)
1319     ev = musicexp.MarkEvent ("\\markup { %s }" % text)
1320     return ev
1321
1322 def musicxml_harp_pedals_to_ly (mxl_event):
1323     count = 0
1324     result = "\\harp-pedal #\""
1325     for t in mxl_event.get_named_children ('pedal-tuning'):
1326       alter = t.get_named_child ('pedal-alter')
1327       if alter:
1328         val = int (alter.get_text ().strip ())
1329         result += {1: "v", 0: "-", -1: "^"}.get (val, "")
1330       count += 1
1331       if count == 3:
1332         result += "|"
1333     ev = musicexp.MarkupEvent ()
1334     ev.contents = result + "\""
1335     return ev
1336
1337 def musicxml_eyeglasses_to_ly (mxl_event):
1338     needed_additional_definitions.append ("eyeglasses")
1339     return musicexp.MarkEvent ("\\eyeglasses")
1340
1341 def next_non_hash_index (lst, pos):
1342     pos += 1
1343     while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
1344         pos += 1
1345     return pos
1346
1347 def musicxml_metronome_to_ly (mxl_event):
1348     children = mxl_event.get_all_children ()
1349     if not children:
1350         return
1351
1352     index = -1
1353     index = next_non_hash_index (children, index)
1354     if isinstance (children[index], musicxml.BeatUnit): 
1355         # first form of metronome-mark, using unit and beats/min or other unit
1356         ev = musicexp.TempoMark ()
1357         if hasattr (mxl_event, 'parentheses'):
1358             ev.set_parentheses (mxl_event.parentheses == "yes")
1359
1360         d = musicexp.Duration ()
1361         d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1362         index = next_non_hash_index (children, index)
1363         if isinstance (children[index], musicxml.BeatUnitDot):
1364             d.dots = 1
1365             index = next_non_hash_index (children, index)
1366         ev.set_base_duration (d)
1367         if isinstance (children[index], musicxml.BeatUnit):
1368             # Form "note = newnote"
1369             newd = musicexp.Duration ()
1370             newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1371             index = next_non_hash_index (children, index)
1372             if isinstance (children[index], musicxml.BeatUnitDot):
1373                 newd.dots = 1
1374                 index = next_non_hash_index (children, index)
1375             ev.set_new_duration (newd)
1376         elif isinstance (children[index], musicxml.PerMinute):
1377             # Form "note = bpm"
1378             try:
1379                 beats = int (children[index].get_text ())
1380                 ev.set_beats_per_minute (beats)
1381             except ValueError:
1382                 pass
1383         else:
1384             error_message (_ ("Unknown metronome mark, ignoring"))
1385             return
1386         return ev
1387     else:
1388         #TODO: Implement the other (more complex) way for tempo marks!
1389         error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
1390         return
1391
1392 # translate directions into Events, possible values:
1393 #   -) string  (MarkEvent with that command)
1394 #   -) function (function(mxl_event) needs to return a full Event-derived object
1395 #   -) (class, name)  (like string, only that a different class than MarkEvent is used)
1396 directions_dict = {
1397     'accordion-registration' : musicxml_accordion_to_ly,
1398     'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
1399 #     'damp' : ???
1400 #     'damp-all' : ???
1401     'eyeglasses': musicxml_eyeglasses_to_ly,
1402     'harp-pedals' : musicxml_harp_pedals_to_ly,
1403 #     'image' : ???
1404     'metronome' : musicxml_metronome_to_ly,
1405     'rehearsal' : musicxml_rehearsal_to_ly_mark,
1406 #     'scordatura' : ???
1407     'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
1408     'words' : musicxml_words_to_lily_event,
1409 }
1410 directions_spanners = [ 'octave-shift', 'pedal', 'wedge', 'dashes', 'bracket' ]
1411
1412 def musicxml_direction_to_lily (n):
1413     # TODO: Handle the <staff> element!
1414     res = []
1415     # placement applies to all children!
1416     dir = None
1417     if hasattr (n, 'placement') and options.convert_directions:
1418         dir = musicxml_direction_to_indicator (n.placement)
1419     dirtype_children = []
1420     # TODO: The direction-type is used for grouping (e.g. dynamics with text), 
1421     #       so we can't simply flatten them out!
1422     for dt in n.get_typed_children (musicxml.DirType):
1423         dirtype_children += dt.get_all_children ()
1424
1425     for entry in dirtype_children:
1426         # backets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
1427         if entry.get_name() in directions_spanners:
1428             event = musicxml_spanner_to_lily_event (entry)
1429             if event:
1430                 res.append (event)
1431             continue
1432
1433         # now treat all the "simple" ones, that can be translated using the dict
1434         ev = None
1435         tmp_tp = directions_dict.get (entry.get_name (), None)
1436         if isinstance (tmp_tp, str): # string means MarkEvent
1437             ev = musicexp.MarkEvent (tmp_tp)
1438         elif isinstance (tmp_tp, tuple): # tuple means (EventClass, "text")
1439             ev = tmp_tp[0] (tmp_tp[1])
1440         elif tmp_tp:
1441             ev = tmp_tp (entry)
1442         if ev:
1443             # TODO: set the correct direction! Unfortunately, \mark in ly does
1444             #       not seem to support directions!
1445             res.append (ev)
1446             continue
1447
1448         if entry.get_name () == "dynamics":
1449             for dynentry in entry.get_all_children ():
1450                 ev = musicxml_dynamics_to_lily_event (dynentry)
1451                 if ev:
1452                     res.append (ev)
1453
1454     return res
1455
1456 def musicxml_frame_to_lily_event (frame):
1457     ev = musicexp.FretEvent ()
1458     ev.strings = frame.get_strings ()
1459     ev.frets = frame.get_frets ()
1460     #offset = frame.get_first_fret () - 1
1461     barre = []
1462     for fn in frame.get_named_children ('frame-note'):
1463         fret = fn.get_fret ()
1464         if fret <= 0:
1465             fret = "o"
1466         el = [ fn.get_string (), fret ]
1467         fingering = fn.get_fingering ()
1468         if fingering >= 0:
1469             el.append (fingering)
1470         ev.elements.append (el)
1471         b = fn.get_barre ()
1472         if b == 'start':
1473             barre[0] = el[0] # start string
1474             barre[2] = el[1] # fret
1475         elif b == 'stop':
1476             barre[1] = el[0] # end string
1477     if barre:
1478         ev.barre = barre
1479     return ev
1480
1481 def musicxml_harmony_to_lily (n):
1482     res = []
1483     for f in n.get_named_children ('frame'):
1484         ev = musicxml_frame_to_lily_event (f)
1485         if ev:
1486             res.append (ev)
1487     return res
1488
1489
1490 notehead_styles_dict = {
1491     'slash': '\'slash',
1492     'triangle': '\'triangle',
1493     'diamond': '\'diamond',
1494     'square': '\'la', # TODO: Proper squared note head
1495     'cross': None, # TODO: + shaped note head
1496     'x': '\'cross',
1497     'circle-x': '\'xcircle',
1498     'inverted triangle': None, # TODO: Implement
1499     'arrow down': None, # TODO: Implement
1500     'arrow up': None, # TODO: Implement
1501     'slashed': None, # TODO: Implement
1502     'back slashed': None, # TODO: Implement
1503     'normal': None,
1504     'cluster': None, # TODO: Implement
1505     'none': '#f',
1506     'do': '\'do',
1507     're': '\'re',
1508     'mi': '\'mi',
1509     'fa': '\'fa',
1510     'so': None,
1511     'la': '\'la',
1512     'ti': '\'ti',
1513     }
1514
1515 def musicxml_notehead_to_lily (nh):
1516     styles = []
1517
1518     # Notehead style
1519     style = notehead_styles_dict.get (nh.get_text ().strip (), None)
1520     style_elm = musicexp.NotestyleEvent ()
1521     if style:
1522         style_elm.style = style
1523     if hasattr (nh, 'filled'):
1524         style_elm.filled = (getattr (nh, 'filled') == "yes")
1525     if style_elm.style or (style_elm.filled != None):
1526         styles.append (style_elm)
1527
1528     # parentheses
1529     if hasattr (nh, 'parentheses') and (nh.parentheses == "yes"):
1530         styles.append (musicexp.ParenthesizeEvent ())
1531
1532     return styles
1533
1534 def musicxml_chordpitch_to_lily (mxl_cpitch):
1535     r = musicexp.ChordPitch ()
1536     r.alteration = mxl_cpitch.get_alteration ()
1537     r.step = musicxml_step_to_lily (mxl_cpitch.get_step ())
1538     return r
1539
1540 chordkind_dict = {
1541     'major': '5',
1542     'minor': 'm5',
1543     'augmented': 'aug5',
1544     'diminished': 'dim5',
1545         # Sevenths:
1546     'dominant': '7',
1547     'major-seventh': 'maj7',
1548     'minor-seventh': 'm7',
1549     'diminished-seventh': 'dim7',
1550     'augmented-seventh': 'aug7',
1551     'half-diminished': 'dim5m7',
1552     'major-minor': 'maj7m5',
1553         # Sixths:
1554     'major-sixth': '6',
1555     'minor-sixth': 'm6',
1556         # Ninths:
1557     'dominant-ninth': '9',
1558     'major-ninth': 'maj9',
1559     'minor-ninth': 'm9',
1560         # 11ths (usually as the basis for alteration):
1561     'dominant-11th': '11',
1562     'major-11th': 'maj11',
1563     'minor-11th': 'm11',
1564         # 13ths (usually as the basis for alteration):
1565     'dominant-13th': '13.11',
1566     'major-13th': 'maj13.11',
1567     'minor-13th': 'm13',
1568         # Suspended:
1569     'suspended-second': 'sus2',
1570     'suspended-fourth': 'sus4',
1571         # Functional sixths:
1572     # TODO
1573     #'Neapolitan': '???',
1574     #'Italian': '???',
1575     #'French': '???',
1576     #'German': '???',
1577         # Other:
1578     #'pedal': '???',(pedal-point bass)
1579     'power': '5^3',
1580     #'Tristan': '???',
1581     'other': '1',
1582     'none': None,
1583 }
1584
1585 def musicxml_chordkind_to_lily (kind):
1586     res = chordkind_dict.get (kind, None)
1587     # Check for None, since a major chord is converted to ''
1588     if res == None:
1589         error_message (_ ("Unable to convert chord type %s to lilypond.") % kind)
1590     return res
1591
1592 def musicxml_harmony_to_lily_chordname (n):
1593     res = []
1594     root = n.get_maybe_exist_named_child ('root')
1595     if root:
1596         ev = musicexp.ChordNameEvent ()
1597         ev.root = musicxml_chordpitch_to_lily (root)
1598         kind = n.get_maybe_exist_named_child ('kind')
1599         if kind:
1600             ev.kind = musicxml_chordkind_to_lily (kind.get_text ())
1601             if not ev.kind:
1602                 return res
1603         bass = n.get_maybe_exist_named_child ('bass')
1604         if bass:
1605             ev.bass = musicxml_chordpitch_to_lily (bass)
1606         inversion = n.get_maybe_exist_named_child ('inversion')
1607         if inversion:
1608             # TODO: Lilypond does not support inversions, does it?
1609
1610             # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
1611             # 4. LilyPond supports the first inversion in the form of added 
1612             # bass notes.  So the first inversion of C major would be c:/g.   
1613             # To get the second inversion of C major, you would need to do 
1614             # e:6-3-^5 or e:m6-^5.  However, both of these techniques 
1615             # require you to know the chord and calculate either the fifth 
1616             # pitch (for the first inversion) or the third pitch (for the 
1617             # second inversion) so they may not be helpful for musicxml2ly.
1618             inversion_count = string.atoi (inversion.get_text ())
1619             if inversion_count == 1:
1620               # TODO: Calculate the bass note for the inversion...
1621               pass
1622             pass
1623         for deg in n.get_named_children ('degree'):
1624             d = musicexp.ChordModification ()
1625             d.type = deg.get_type ()
1626             d.step = deg.get_value ()
1627             d.alteration = deg.get_alter ()
1628             ev.add_modification (d)
1629         #TODO: convert the user-symbols attribute: 
1630             #major: a triangle, like Unicode 25B3
1631             #minor: -, like Unicode 002D
1632             #augmented: +, like Unicode 002B
1633             #diminished: (degree), like Unicode 00B0
1634             #half-diminished: (o with slash), like Unicode 00F8
1635         if ev and ev.root:
1636             res.append (ev)
1637
1638     return res
1639
1640 def musicxml_figured_bass_note_to_lily (n):
1641     res = musicexp.FiguredBassNote ()
1642     suffix_dict = { 'sharp' : "+", 
1643                     'flat' : "-", 
1644                     'natural' : "!", 
1645                     'double-sharp' : "++", 
1646                     'flat-flat' : "--", 
1647                     'sharp-sharp' : "++", 
1648                     'slash' : "/" }
1649     prefix = n.get_maybe_exist_named_child ('prefix')
1650     if prefix:
1651         res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
1652     fnumber = n.get_maybe_exist_named_child ('figure-number')
1653     if fnumber:
1654         res.set_number (fnumber.get_text ())
1655     suffix = n.get_maybe_exist_named_child ('suffix')
1656     if suffix:
1657         res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
1658     if n.get_maybe_exist_named_child ('extend'):
1659         # TODO: Implement extender lines (unfortunately, in lilypond you have 
1660         #       to use \set useBassFigureExtenders = ##t, which turns them on
1661         #       globally, while MusicXML has a property for each note...
1662         #       I'm not sure there is a proper way to implement this cleanly
1663         #n.extend
1664         pass
1665     return res
1666
1667
1668
1669 def musicxml_figured_bass_to_lily (n):
1670     if not isinstance (n, musicxml.FiguredBass):
1671         return
1672     res = musicexp.FiguredBassEvent ()
1673     for i in n.get_named_children ('figure'):
1674         note = musicxml_figured_bass_note_to_lily (i)
1675         if note:
1676             res.append (note)
1677     dur = n.get_maybe_exist_named_child ('duration')
1678     if dur:
1679         # apply the duration to res
1680         length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
1681         res.set_real_duration (length)
1682         duration = rational_to_lily_duration (length)
1683         if duration:
1684             res.set_duration (duration)
1685     if hasattr (n, 'parentheses') and n.parentheses == "yes":
1686         res.set_parentheses (True)
1687     return res
1688
1689 instrument_drumtype_dict = {
1690     'Acoustic Snare Drum': 'acousticsnare',
1691     'Side Stick': 'sidestick',
1692     'Open Triangle': 'opentriangle',
1693     'Mute Triangle': 'mutetriangle',
1694     'Tambourine': 'tambourine',
1695     'Bass Drum': 'bassdrum',
1696 }
1697
1698 def musicxml_note_to_lily_main_event (n):
1699     pitch  = None
1700     duration = None
1701     event = None
1702
1703     mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
1704     if mxl_pitch:
1705         pitch = musicxml_pitch_to_lily (mxl_pitch)
1706         event = musicexp.NoteEvent ()
1707         event.pitch = pitch
1708
1709         acc = n.get_maybe_exist_named_child ('accidental')
1710         if acc:
1711             # let's not force accs everywhere. 
1712             event.cautionary = acc.editorial
1713
1714     elif n.get_maybe_exist_typed_child (musicxml.Unpitched):
1715         # Unpitched elements have display-step and can also have
1716         # display-octave.
1717         unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
1718         event = musicexp.NoteEvent ()
1719         event.pitch = musicxml_unpitched_to_lily (unpitched)
1720         
1721     elif n.get_maybe_exist_typed_child (musicxml.Rest):
1722         # rests can have display-octave and display-step, which are
1723         # treated like an ordinary note pitch
1724         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1725         event = musicexp.RestEvent ()
1726         pitch = musicxml_restdisplay_to_lily (rest)
1727         event.pitch = pitch
1728
1729     elif n.instrument_name:
1730         event = musicexp.NoteEvent ()
1731         drum_type = instrument_drumtype_dict.get (n.instrument_name)
1732         if drum_type:
1733             event.drum_type = drum_type
1734         else:
1735             n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
1736             event.drum_type = 'acousticsnare'
1737
1738     else:
1739         n.message (_ ("cannot find suitable event"))
1740
1741     if event:
1742         event.duration = musicxml_duration_to_lily (n)
1743
1744     noteheads = n.get_named_children ('notehead')
1745     for nh in noteheads:
1746         styles = musicxml_notehead_to_lily (nh)
1747         for s in styles:
1748             event.add_associated_event (s)
1749
1750     return event
1751
1752 def musicxml_lyrics_to_text (lyrics):
1753     # TODO: Implement text styles for lyrics syllables
1754     continued = False
1755     text = ''
1756     for e in lyrics.get_all_children ():
1757         if isinstance (e, musicxml.Syllabic):
1758             continued = e.continued ()
1759         elif isinstance (e, musicxml.Text):
1760             # We need to convert soft hyphens to -, otherwise the ascii codec as well
1761             # as lilypond will barf on that character
1762             text += string.replace( e.get_text(), u'\xad', '-' )
1763         elif isinstance (e, musicxml.Elision):
1764             if text:
1765                 text += " "
1766             continued = False
1767
1768     if text == "-" and continued:
1769         return "--"
1770     elif text == "_" and continued:
1771         return "__"
1772     elif continued and text:
1773         return musicxml.escape_ly_output_string (text) + " --"
1774     elif continued:
1775         return "--"
1776     elif text:
1777         return musicxml.escape_ly_output_string (text)
1778     else:
1779         return ""
1780
1781 ## TODO
1782 class NegativeSkip:
1783     def __init__ (self, here, dest):
1784         self.here = here
1785         self.dest = dest
1786
1787 class LilyPondVoiceBuilder:
1788     def __init__ (self):
1789         self.elements = []
1790         self.pending_dynamics = []
1791         self.end_moment = Rational (0)
1792         self.begin_moment = Rational (0)
1793         self.pending_multibar = Rational (0)
1794         self.ignore_skips = False
1795         self.has_relevant_elements = False
1796         self.measure_length = Rational (4, 4)
1797
1798     def _insert_multibar (self):
1799         layout_information.set_context_item ('Score', 'skipBars = ##t')
1800         r = musicexp.MultiMeasureRest ()
1801         lenfrac = self.measure_length
1802         r.duration = rational_to_lily_duration (lenfrac)
1803         r.duration.factor *= self.pending_multibar / lenfrac
1804         self.elements.append (r)
1805         self.begin_moment = self.end_moment
1806         self.end_moment = self.begin_moment + self.pending_multibar
1807         self.pending_multibar = Rational (0)
1808
1809     def set_measure_length (self, mlen):
1810         if (mlen != self.measure_length) and self.pending_multibar:
1811             self._insert_multibar ()
1812         self.measure_length = mlen
1813
1814     def add_multibar_rest (self, duration):
1815         self.pending_multibar += duration
1816
1817     def set_duration (self, duration):
1818         self.end_moment = self.begin_moment + duration
1819     def current_duration (self):
1820         return self.end_moment - self.begin_moment
1821         
1822     def add_music (self, music, duration):
1823         assert isinstance (music, musicexp.Music)
1824         if self.pending_multibar > Rational (0):
1825             self._insert_multibar ()
1826
1827         self.has_relevant_elements = True
1828         self.elements.append (music)
1829         self.begin_moment = self.end_moment
1830         self.set_duration (duration)
1831         
1832         # Insert all pending dynamics right after the note/rest:
1833         if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
1834             for d in self.pending_dynamics:
1835                 music.append (d)
1836             self.pending_dynamics = []
1837
1838     # Insert some music command that does not affect the position in the measure
1839     def add_command (self, command):
1840         assert isinstance (command, musicexp.Music)
1841         if self.pending_multibar > Rational (0):
1842             self._insert_multibar ()
1843         self.has_relevant_elements = True
1844         self.elements.append (command)
1845     def add_barline (self, barline):
1846         # TODO: Implement merging of default barline and custom bar line
1847         self.add_music (barline, Rational (0))
1848     def add_partial (self, command):
1849         self.ignore_skips = True
1850         self.add_command (command)
1851
1852     def add_dynamics (self, dynamic):
1853         # store the dynamic item(s) until we encounter the next note/rest:
1854         self.pending_dynamics.append (dynamic)
1855
1856     def add_bar_check (self, number):
1857         # re/store has_relevant_elements, so that a barline alone does not
1858         # trigger output for figured bass, chord names
1859         has_relevant = self.has_relevant_elements
1860         b = musicexp.BarLine ()
1861         b.bar_number = number
1862         self.add_barline (b)
1863         self.has_relevant_elements = has_relevant
1864
1865     def jumpto (self, moment):
1866         current_end = self.end_moment + self.pending_multibar
1867         diff = moment - current_end
1868         
1869         if diff < Rational (0):
1870             error_message (_ ('Negative skip %s (from position %s to %s)') % 
1871                              (diff, current_end, moment))
1872             diff = Rational (0)
1873
1874         if diff > Rational (0) and not (self.ignore_skips and moment == 0):
1875             skip = musicexp.SkipEvent()
1876             duration_factor = 1
1877             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)
1878             duration_dots = 0
1879             # TODO: Use the time signature for skips, too. Problem: The skip 
1880             #       might not start at a measure boundary!
1881             if duration_log > 0: # denominator is a power of 2...
1882                 if diff.numerator () == 3:
1883                     duration_log -= 1
1884                     duration_dots = 1
1885                 else:
1886                     duration_factor = Rational (diff.numerator ())
1887             else:
1888                 # for skips of a whole or more, simply use s1*factor
1889                 duration_log = 0
1890                 duration_factor = diff
1891             skip.duration.duration_log = duration_log
1892             skip.duration.factor = duration_factor
1893             skip.duration.dots = duration_dots
1894
1895             evc = musicexp.ChordEvent ()
1896             evc.elements.append (skip)
1897             self.add_music (evc, diff)
1898
1899         if diff > Rational (0) and moment == 0:
1900             self.ignore_skips = False
1901
1902     def last_event_chord (self, starting_at):
1903
1904         value = None
1905
1906         # if the position matches, find the last ChordEvent, do not cross a bar line!
1907         at = len( self.elements ) - 1
1908         while (at >= 0 and
1909                not isinstance (self.elements[at], musicexp.ChordEvent) and
1910                not isinstance (self.elements[at], musicexp.BarLine)):
1911             at -= 1
1912
1913         if (self.elements
1914             and at >= 0
1915             and isinstance (self.elements[at], musicexp.ChordEvent)
1916             and self.begin_moment == starting_at):
1917             value = self.elements[at]
1918         else:
1919             self.jumpto (starting_at)
1920             value = None
1921         return value
1922         
1923     def correct_negative_skip (self, goto):
1924         self.end_moment = goto
1925         self.begin_moment = goto
1926         evc = musicexp.ChordEvent ()
1927         self.elements.append (evc)
1928
1929
1930 class VoiceData:
1931     def __init__ (self):
1932         self.voicename = None
1933         self.voicedata = None
1934         self.ly_voice = None
1935         self.figured_bass = None
1936         self.chordnames = None
1937         self.lyrics_dict = {}
1938         self.lyrics_order = []
1939
1940 def musicxml_step_to_lily (step):
1941     if step:
1942         return (ord (step) - ord ('A') + 7 - 2) % 7
1943     else:
1944         return None
1945
1946 def measure_length_from_attributes (attr, current_measure_length):
1947     len = attr.get_measure_length ()
1948     if not len:
1949         len = current_measure_length
1950     return len
1951
1952 def musicxml_voice_to_lily_voice (voice):
1953     tuplet_events = []
1954     modes_found = {}
1955     lyrics = {}
1956     return_value = VoiceData ()
1957     return_value.voicedata = voice
1958     
1959     # First pitch needed for relative mode (if selected in command-line options)
1960     first_pitch = None
1961
1962     # Needed for melismata detection (ignore lyrics on those notes!):
1963     inside_slur = False
1964     is_tied = False
1965     is_chord = False
1966     is_beamed = False
1967     ignore_lyrics = False
1968
1969     current_staff = None
1970     
1971     pending_figured_bass = []
1972     pending_chordnames = []
1973
1974     # Make sure that the keys in the dict don't get reordered, since
1975     # we need the correct ordering of the lyrics stanzas! By default,
1976     # a dict will reorder its keys
1977     return_value.lyrics_order = voice.get_lyrics_numbers ()
1978     for k in return_value.lyrics_order:
1979         lyrics[k] = []
1980
1981     voice_builder = LilyPondVoiceBuilder ()
1982     figured_bass_builder = LilyPondVoiceBuilder ()
1983     chordnames_builder = LilyPondVoiceBuilder ()
1984     current_measure_length = Rational (4, 4)
1985     voice_builder.set_measure_length (current_measure_length)
1986
1987     for n in voice._elements:
1988         if n.get_name () == 'forward':
1989             continue
1990         staff = n.get_maybe_exist_named_child ('staff')
1991         if staff:
1992             staff = staff.get_text ()
1993             if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'):
1994                 voice_builder.add_command (musicexp.StaffChange (staff))
1995             current_staff = staff
1996
1997         if isinstance (n, musicxml.Partial) and n.partial > 0:
1998             a = musicxml_partial_to_lily (n.partial)
1999             if a:
2000                 voice_builder.add_partial (a)
2001             continue
2002
2003         is_chord = n.get_maybe_exist_named_child ('chord')
2004         is_after_grace = (isinstance (n, musicxml.Note) and n.is_after_grace ());
2005         if not is_chord and not is_after_grace:
2006             try:
2007                 voice_builder.jumpto (n._when)
2008             except NegativeSkip, neg:
2009                 voice_builder.correct_negative_skip (n._when)
2010                 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
2011
2012         if isinstance (n, musicxml.Barline):
2013             barlines = musicxml_barline_to_lily (n)
2014             for a in barlines:
2015                 if isinstance (a, musicexp.BarLine):
2016                     voice_builder.add_barline (a)
2017                 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
2018                     voice_builder.add_command (a)
2019             continue
2020
2021         # Continue any multimeasure-rests before trying to add bar checks!
2022         # Don't handle new MM rests yet, because for them we want bar checks!
2023         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
2024         if (rest and rest.is_whole_measure ()
2025                  and voice_builder.pending_multibar > Rational (0)):
2026             voice_builder.add_multibar_rest (n._duration)
2027             continue
2028
2029
2030         # print a bar check at the beginning of each measure!
2031         if n.is_first () and n._measure_position == Rational (0) and n != voice._elements[0]:
2032             try:
2033                 num = int (n.get_parent ().number)
2034             except ValueError:
2035                 num = 0
2036             if num > 0:
2037                 voice_builder.add_bar_check (num)
2038                 figured_bass_builder.add_bar_check (num)
2039                 chordnames_builder.add_bar_check (num)
2040
2041         # Start any new multimeasure rests
2042         if (rest and rest.is_whole_measure ()):
2043             voice_builder.add_multibar_rest (n._duration)
2044             continue
2045
2046
2047         if isinstance (n, musicxml.Direction):
2048             for a in musicxml_direction_to_lily (n):
2049                 if a.wait_for_note ():
2050                     voice_builder.add_dynamics (a)
2051                 else:
2052                     voice_builder.add_command (a)
2053             continue
2054
2055         if isinstance (n, musicxml.Harmony):
2056             for a in musicxml_harmony_to_lily (n):
2057                 if a.wait_for_note ():
2058                     voice_builder.add_dynamics (a)
2059                 else:
2060                     voice_builder.add_command (a)
2061             for a in musicxml_harmony_to_lily_chordname (n):
2062                 pending_chordnames.append (a)
2063             continue
2064
2065         if isinstance (n, musicxml.FiguredBass):
2066             a = musicxml_figured_bass_to_lily (n)
2067             if a:
2068                 pending_figured_bass.append (a)
2069             continue
2070
2071         if isinstance (n, musicxml.Attributes):
2072             for a in musicxml_attributes_to_lily (n):
2073                 voice_builder.add_command (a)
2074             measure_length = measure_length_from_attributes (n, current_measure_length)
2075             if current_measure_length != measure_length:
2076                 current_measure_length = measure_length
2077                 voice_builder.set_measure_length (current_measure_length)
2078             continue
2079
2080         if not n.__class__.__name__ == 'Note':
2081             n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
2082             continue
2083         
2084         main_event = musicxml_note_to_lily_main_event (n)
2085         if main_event and not first_pitch:
2086             first_pitch = main_event.pitch
2087         # ignore lyrics for notes inside a slur, tie, chord or beam
2088         ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
2089
2090         if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
2091             modes_found['drummode'] = True
2092
2093         ev_chord = voice_builder.last_event_chord (n._when)
2094         if not ev_chord: 
2095             ev_chord = musicexp.ChordEvent()
2096             voice_builder.add_music (ev_chord, n._duration)
2097
2098         # For grace notes:
2099         grace = n.get_maybe_exist_typed_child (musicxml.Grace)
2100         if n.is_grace ():
2101             is_after_grace = ev_chord.has_elements () or n.is_after_grace ();
2102             is_chord = n.get_maybe_exist_typed_child (musicxml.Chord)
2103
2104             grace_chord = None
2105
2106             # after-graces and other graces use different lists; Depending on
2107             # whether we have a chord or not, obtain either a new ChordEvent or 
2108             # the previous one to create a chord
2109             if is_after_grace:
2110                 if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
2111                     grace_chord = ev_chord.after_grace_elements.get_last_event_chord ()
2112                 if not grace_chord:
2113                     grace_chord = musicexp.ChordEvent ()
2114                     ev_chord.append_after_grace (grace_chord)
2115             elif n.is_grace ():
2116                 if ev_chord.grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
2117                     grace_chord = ev_chord.grace_elements.get_last_event_chord ()
2118                 if not grace_chord:
2119                     grace_chord = musicexp.ChordEvent ()
2120                     ev_chord.append_grace (grace_chord)
2121
2122             if hasattr (grace, 'slash') and not is_after_grace:
2123                 # TODO: use grace_type = "appoggiatura" for slurred grace notes
2124                 if grace.slash == "yes":
2125                     ev_chord.grace_type = "acciaccatura"
2126             # now that we have inserted the chord into the grace music, insert
2127             # everything into that chord instead of the ev_chord
2128             ev_chord = grace_chord
2129             ev_chord.append (main_event)
2130             ignore_lyrics = True
2131         else:
2132             ev_chord.append (main_event)
2133             # When a note/chord has grace notes (duration==0), the duration of the
2134             # event chord is not yet known, but the event chord was already added
2135             # with duration 0. The following correct this when we hit the real note!
2136             if voice_builder.current_duration () == 0 and n._duration > 0:
2137                 voice_builder.set_duration (n._duration)
2138         
2139         # if we have a figured bass, set its voice builder to the correct position
2140         # and insert the pending figures
2141         if pending_figured_bass:
2142             try:
2143                 figured_bass_builder.jumpto (n._when)
2144             except NegativeSkip, neg:
2145                 pass
2146             for fb in pending_figured_bass:
2147                 # if a duration is given, use that, otherwise the one of the note
2148                 dur = fb.real_duration
2149                 if not dur:
2150                     dur = ev_chord.get_length ()
2151                 if not fb.duration:
2152                     fb.duration = ev_chord.get_duration ()
2153                 figured_bass_builder.add_music (fb, dur)
2154             pending_figured_bass = []
2155         
2156         if pending_chordnames:
2157             try:
2158                 chordnames_builder.jumpto (n._when)
2159             except NegativeSkip, neg:
2160                 pass
2161             for cn in pending_chordnames:
2162                 # Assign the duration of the EventChord
2163                 cn.duration = ev_chord.get_duration ()
2164                 chordnames_builder.add_music (cn, ev_chord.get_length ())
2165             pending_chordnames = []
2166
2167         notations_children = n.get_typed_children (musicxml.Notations)
2168         tuplet_event = None
2169         span_events = []
2170
2171         # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
2172         # +tied | +slur | +tuplet | glissando | slide | 
2173         #    ornaments | technical | articulations | dynamics |
2174         #    +fermata | arpeggiate | non-arpeggiate | 
2175         #    accidental-mark | other-notation
2176         for notations in notations_children:
2177             for tuplet_event in notations.get_tuplets():
2178                 time_mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
2179                 tuplet_events.append ((ev_chord, tuplet_event, time_mod))
2180
2181             # First, close all open slurs, only then start any new slur
2182             # TODO: Record the number of the open slur to dtermine the correct
2183             #       closing slur!
2184             endslurs = [s for s in notations.get_named_children ('slur')
2185                 if s.get_type () in ('stop')]
2186             if endslurs and not inside_slur:
2187                 endslurs[0].message (_ ('Encountered closing slur, but no slur is open'))
2188             elif endslurs:
2189                 if len (endslurs) > 1:
2190                     endslurs[0].message (_ ('Cannot have two simultaneous (closing) slurs'))
2191                 # record the slur status for the next note in the loop
2192                 if not grace:
2193                     inside_slur = False
2194                 lily_ev = musicxml_spanner_to_lily_event (endslurs[0])
2195                 ev_chord.append (lily_ev)
2196
2197             startslurs = [s for s in notations.get_named_children ('slur')
2198                 if s.get_type () in ('start')]
2199             if startslurs and inside_slur:
2200                 startslurs[0].message (_ ('Cannot have a slur inside another slur'))
2201             elif startslurs:
2202                 if len (startslurs) > 1:
2203                     startslurs[0].message (_ ('Cannot have two simultaneous slurs'))
2204                 # record the slur status for the next note in the loop
2205                 if not grace:
2206                     inside_slur = True
2207                 lily_ev = musicxml_spanner_to_lily_event (startslurs[0])
2208                 ev_chord.append (lily_ev)
2209
2210
2211             if not grace:
2212                 mxl_tie = notations.get_tie ()
2213                 if mxl_tie and mxl_tie.type == 'start':
2214                     ev_chord.append (musicexp.TieEvent ())
2215                     is_tied = True
2216                 else:
2217                     is_tied = False
2218
2219             fermatas = notations.get_named_children ('fermata')
2220             for a in fermatas:
2221                 ev = musicxml_fermata_to_lily_event (a)
2222                 if ev: 
2223                     ev_chord.append (ev)
2224
2225             arpeggiate = notations.get_named_children ('arpeggiate')
2226             for a in arpeggiate:
2227                 ev = musicxml_arpeggiate_to_lily_event (a)
2228                 if ev:
2229                     ev_chord.append (ev)
2230
2231             arpeggiate = notations.get_named_children ('non-arpeggiate')
2232             for a in arpeggiate:
2233                 ev = musicxml_nonarpeggiate_to_lily_event (a)
2234                 if ev:
2235                     ev_chord.append (ev)
2236
2237             glissandos = notations.get_named_children ('glissando')
2238             glissandos += notations.get_named_children ('slide')
2239             for a in glissandos:
2240                 ev = musicxml_spanner_to_lily_event (a)
2241                 if ev:
2242                     ev_chord.append (ev)
2243
2244             # accidental-marks are direct children of <notation>!
2245             for a in notations.get_named_children ('accidental-mark'):
2246                 ev = musicxml_articulation_to_lily_event (a)
2247                 if ev:
2248                     ev_chord.append (ev)
2249
2250             # Articulations can contain the following child elements:
2251             #         accent | strong-accent | staccato | tenuto |
2252             #         detached-legato | staccatissimo | spiccato |
2253             #         scoop | plop | doit | falloff | breath-mark | 
2254             #         caesura | stress | unstress
2255             # Technical can contain the following child elements:
2256             #         up-bow | down-bow | harmonic | open-string |
2257             #         thumb-position | fingering | pluck | double-tongue |
2258             #         triple-tongue | stopped | snap-pizzicato | fret |
2259             #         string | hammer-on | pull-off | bend | tap | heel |
2260             #         toe | fingernails | other-technical
2261             # Ornaments can contain the following child elements:
2262             #         trill-mark | turn | delayed-turn | inverted-turn |
2263             #         shake | wavy-line | mordent | inverted-mordent | 
2264             #         schleifer | tremolo | other-ornament, accidental-mark
2265             ornaments = notations.get_named_children ('ornaments')
2266             ornaments += notations.get_named_children ('articulations')
2267             ornaments += notations.get_named_children ('technical')
2268
2269             for a in ornaments:
2270                 for ch in a.get_all_children ():
2271                     ev = musicxml_articulation_to_lily_event (ch)
2272                     if ev:
2273                         ev_chord.append (ev)
2274
2275             dynamics = notations.get_named_children ('dynamics')
2276             for a in dynamics:
2277                 for ch in a.get_all_children ():
2278                     ev = musicxml_dynamics_to_lily_event (ch)
2279                     if ev:
2280                         ev_chord.append (ev)
2281
2282
2283         mxl_beams = [b for b in n.get_named_children ('beam')
2284                      if (b.get_type () in ('begin', 'end')
2285                          and b.is_primary ())] 
2286         if mxl_beams and not conversion_settings.ignore_beaming:
2287             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
2288             if beam_ev:
2289                 ev_chord.append (beam_ev)
2290                 if beam_ev.span_direction == -1: # beam and thus melisma starts here
2291                     is_beamed = True
2292                 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
2293                     is_beamed = False
2294
2295         # Extract the lyrics
2296         if not rest and not ignore_lyrics:
2297             note_lyrics_processed = []
2298             note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
2299             for l in note_lyrics_elements:
2300                 if l.get_number () < 0:
2301                     for k in lyrics.keys ():
2302                         lyrics[k].append (musicxml_lyrics_to_text (l))
2303                         note_lyrics_processed.append (k)
2304                 else:
2305                     lyrics[l.number].append(musicxml_lyrics_to_text (l))
2306                     note_lyrics_processed.append (l.number)
2307             for lnr in lyrics.keys ():
2308                 if not lnr in note_lyrics_processed:
2309                     lyrics[lnr].append ("\skip4")
2310
2311     ## force trailing mm rests to be written out.   
2312     voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
2313     
2314     ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
2315     ly_voice = group_repeats (ly_voice)
2316
2317     seq_music = musicexp.SequentialMusic ()
2318
2319     if 'drummode' in modes_found.keys ():
2320         ## \key <pitch> barfs in drummode.
2321         ly_voice = [e for e in ly_voice
2322                     if not isinstance(e, musicexp.KeySignatureChange)]
2323     
2324     seq_music.elements = ly_voice
2325     for k in lyrics.keys ():
2326         return_value.lyrics_dict[k] = musicexp.Lyrics ()
2327         return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2328     
2329     
2330     if len (modes_found) > 1:
2331        error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
2332        
2333     if options.relative:
2334         v = musicexp.RelativeMusic ()
2335         v.element = seq_music
2336         v.basepitch = first_pitch
2337         seq_music = v
2338
2339     return_value.ly_voice = seq_music
2340     for mode in modes_found.keys ():
2341         v = musicexp.ModeChangingMusicWrapper()
2342         v.element = seq_music
2343         v.mode = mode
2344         return_value.ly_voice = v
2345     
2346     # create \figuremode { figured bass elements }
2347     if figured_bass_builder.has_relevant_elements:
2348         fbass_music = musicexp.SequentialMusic ()
2349         fbass_music.elements = figured_bass_builder.elements
2350         v = musicexp.ModeChangingMusicWrapper()
2351         v.mode = 'figuremode'
2352         v.element = fbass_music
2353         return_value.figured_bass = v
2354     
2355     # create \chordmode { chords }
2356     if chordnames_builder.has_relevant_elements:
2357         cname_music = musicexp.SequentialMusic ()
2358         cname_music.elements = chordnames_builder.elements
2359         v = musicexp.ModeChangingMusicWrapper()
2360         v.mode = 'chordmode'
2361         v.element = cname_music
2362         return_value.chordnames = v
2363     
2364     return return_value
2365
2366 def musicxml_id_to_lily (id):
2367     digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2368               'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2369     
2370     for digit in digits:
2371         d = digits.index (digit)
2372         id = re.sub ('%d' % d, digit, id)
2373
2374     id = re.sub  ('[^a-zA-Z]', 'X', id)
2375     return id
2376
2377 def musicxml_pitch_to_lily (mxl_pitch):
2378     p = musicexp.Pitch ()
2379     p.alteration = mxl_pitch.get_alteration ()
2380     p.step = musicxml_step_to_lily (mxl_pitch.get_step ())
2381     p.octave = mxl_pitch.get_octave () - 4
2382     return p
2383
2384 def musicxml_unpitched_to_lily (mxl_unpitched):
2385     p = None
2386     step = mxl_unpitched.get_step ()
2387     if step:
2388         p = musicexp.Pitch ()
2389         p.step = musicxml_step_to_lily (step)
2390     octave = mxl_unpitched.get_octave ()
2391     if octave and p:
2392         p.octave = octave - 4
2393     return p
2394
2395 def musicxml_restdisplay_to_lily (mxl_rest):
2396     p = None
2397     step = mxl_rest.get_step ()
2398     if step:
2399         p = musicexp.Pitch ()
2400         p.step = musicxml_step_to_lily (step)
2401     octave = mxl_rest.get_octave ()
2402     if octave and p:
2403         p.octave = octave - 4
2404     return p
2405
2406 def voices_in_part (part):
2407     """Return a Name -> Voice dictionary for PART"""
2408     part.interpret ()
2409     part.extract_voices ()
2410     voices = part.get_voices ()
2411     part_info = part.get_staff_attributes ()
2412
2413     return (voices, part_info)
2414
2415 def voices_in_part_in_parts (parts):
2416     """return a Part -> Name -> Voice dictionary"""
2417     return dict([(p.id, voices_in_part (p)) for p in parts])
2418
2419
2420 def get_all_voices (parts):
2421     all_voices = voices_in_part_in_parts (parts)
2422
2423     all_ly_voices = {}
2424     all_ly_staffinfo = {}
2425     for p, (name_voice, staff_info) in all_voices.items ():
2426
2427         part_ly_voices = {}
2428         for n, v in name_voice.items ():
2429             progress (_ ("Converting to LilyPond expressions..."))
2430             # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2431             part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
2432
2433         all_ly_voices[p] = part_ly_voices
2434         all_ly_staffinfo[p] = staff_info
2435
2436     return (all_ly_voices, all_ly_staffinfo)
2437
2438
2439 def option_parser ():
2440     p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"),
2441                              description =
2442 _ ("""Convert MusicXML from FILE.xml to LilyPond input.
2443 If the given filename is -, musicxml2ly reads from the command line.
2444 """), add_help_option=False)
2445
2446     p.add_option("-h", "--help",
2447                  action="help",
2448                  help=_ ("show this help and exit"))
2449
2450     p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
2451 +
2452 _ ("""Copyright (c) 2005--2008 by
2453     Han-Wen Nienhuys <hanwen@xs4all.nl>,
2454     Jan Nieuwenhuizen <janneke@gnu.org> and
2455     Reinhold Kainhofer <reinhold@kainhofer.com>
2456 """
2457 +
2458 """
2459 This program is free software.  It is covered by the GNU General Public
2460 License and you are welcome to change it and/or distribute copies of it
2461 under certain conditions.  Invoke as `%s --warranty' for more
2462 information.""") % 'lilypond')
2463
2464     p.add_option("--version",
2465                  action="version",
2466                  help=_ ("show version number and exit"))
2467
2468     p.add_option ('-v', '--verbose',
2469                   action = "store_true",
2470                   dest = 'verbose',
2471                   help = _ ("be verbose"))
2472
2473     p.add_option ('', '--lxml',
2474                   action = "store_true",
2475                   default = False,
2476                   dest = "use_lxml",
2477                   help = _ ("use lxml.etree; uses less memory and cpu time"))
2478
2479     p.add_option ('-z', '--compressed',
2480                   action = "store_true",
2481                   dest = 'compressed',
2482                   default = False,
2483                   help = _ ("input file is a zip-compressed MusicXML file"))
2484
2485     p.add_option ('-r', '--relative',
2486                   action = "store_true",
2487                   default = True,
2488                   dest = "relative",
2489                   help = _ ("convert pitches in relative mode (default)"))
2490
2491     p.add_option ('-a', '--absolute',
2492                   action = "store_false",
2493                   dest = "relative",
2494                   help = _ ("convert pitches in absolute mode"))
2495
2496     p.add_option ('-l', '--language',
2497                   metavar = _ ("LANG"),
2498                   action = "store",
2499                   help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
2500
2501     p.add_option ('--nd', '--no-articulation-directions', 
2502                   action = "store_false",
2503                   default = True,
2504                   dest = "convert_directions",
2505                   help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2506
2507     p.add_option ('--no-beaming', 
2508                   action = "store_false",
2509                   default = True,
2510                   dest = "convert_beaming",
2511                   help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
2512
2513     p.add_option ('-o', '--output',
2514                   metavar = _ ("FILE"),
2515                   action = "store",
2516                   default = None,
2517                   type = 'string',
2518                   dest = 'output_name',
2519                   help = _ ("set output filename to FILE, stdout if -"))
2520     p.add_option_group ('',
2521                         description = (_ ("Report bugs via")
2522                                      + ''' http://post.gmane.org/post.php'''
2523                                      '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
2524     return p
2525
2526 def music_xml_voice_name_to_lily_name (part_id, name):
2527     str = "Part%sVoice%s" % (part_id, name)
2528     return musicxml_id_to_lily (str) 
2529
2530 def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
2531     str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
2532     return musicxml_id_to_lily (str) 
2533
2534 def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
2535     str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
2536     return musicxml_id_to_lily (str) 
2537
2538 def music_xml_chordnames_name_to_lily_name (part_id, voicename):
2539     str = "Part%sVoice%sChords" % (part_id, voicename)
2540     return musicxml_id_to_lily (str) 
2541
2542 def print_voice_definitions (printer, part_list, voices):
2543     for part in part_list:
2544         part_id = part.id
2545         nv_dict = voices.get (part_id, {})
2546         for (name, voice) in nv_dict.items ():
2547             k = music_xml_voice_name_to_lily_name (part_id, name)
2548             printer.dump ('%s = ' % k)
2549             voice.ly_voice.print_ly (printer)
2550             printer.newline()
2551             if voice.chordnames:
2552                 cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
2553                 printer.dump ('%s = ' % cnname )
2554                 voice.chordnames.print_ly (printer)
2555                 printer.newline()
2556             for l in voice.lyrics_order:
2557                 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
2558                 printer.dump ('%s = ' % lname )
2559                 voice.lyrics_dict[l].print_ly (printer)
2560                 printer.newline()
2561             if voice.figured_bass:
2562                 fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
2563                 printer.dump ('%s = ' % fbname )
2564                 voice.figured_bass.print_ly (printer)
2565                 printer.newline()
2566
2567
2568 def uniq_list (l):
2569     return dict ([(elt,1) for elt in l]).keys ()
2570
2571 # format the information about the staff in the form 
2572 #     [staffid,
2573 #         [
2574 #            [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
2575 #            [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
2576 #            ...
2577 #         ]
2578 #     ]
2579 # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
2580 def format_staff_info (part_id, staff_id, raw_voices):
2581     voices = []
2582     for (v, lyricsids, figured_bass, chordnames) in raw_voices:
2583         voice_name = music_xml_voice_name_to_lily_name (part_id, v)
2584         voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
2585                    for l in lyricsids]
2586         figured_bass_name = ''
2587         if figured_bass:
2588             figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
2589         chordnames_name = ''
2590         if chordnames:
2591             chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
2592         voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
2593     return [staff_id, voices]
2594
2595 def update_score_setup (score_structure, part_list, voices):
2596
2597     for part_definition in part_list:
2598         part_id = part_definition.id
2599         nv_dict = voices.get (part_id)
2600         if not nv_dict:
2601             error_message (_ ('unknown part in part-list: %s') % part_id)
2602             continue
2603
2604         staves = reduce (lambda x,y: x+ y,
2605                 [voice.voicedata._staves.keys ()
2606                  for voice in nv_dict.values ()],
2607                 [])
2608         staves_info = []
2609         if len (staves) > 1:
2610             staves_info = []
2611             staves = uniq_list (staves)
2612             staves.sort ()
2613             for s in staves:
2614                 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2615                     for (voice_name, voice) in nv_dict.items ()
2616                     if voice.voicedata._start_staff == s]
2617                 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
2618         else:
2619             thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2620                 for (voice_name, voice) in nv_dict.items ()]
2621             staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
2622         score_structure.set_part_information (part_id, staves_info)
2623
2624 # Set global values in the \layout block, like auto-beaming etc.
2625 def update_layout_information ():
2626     if not conversion_settings.ignore_beaming and layout_information:
2627         layout_information.set_context_item ('Score', 'autoBeaming = ##f')
2628
2629 def print_ly_preamble (printer, filename):
2630     printer.dump_version ()
2631     printer.print_verbatim ('%% automatically converted from %s\n' % filename)
2632
2633 def print_ly_additional_definitions (printer, filename):
2634     if needed_additional_definitions:
2635         printer.newline ()
2636         printer.print_verbatim ('%% additional definitions required by the score:')
2637         printer.newline ()
2638     for a in set(needed_additional_definitions):
2639         printer.print_verbatim (additional_definitions.get (a, ''))
2640         printer.newline ()
2641     printer.newline ()
2642
2643 # Read in the tree from the given I/O object (either file or string) and 
2644 # demarshall it using the classes from the musicxml.py file
2645 def read_xml (io_object, use_lxml):
2646     if use_lxml:
2647         import lxml.etree
2648         tree = lxml.etree.parse (io_object)
2649         mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
2650         return mxl_tree
2651     else:
2652         from xml.dom import minidom, Node
2653         doc = minidom.parse(io_object)
2654         node = doc.documentElement
2655         return musicxml.minidom_demarshal_node (node)
2656     return None
2657
2658
2659 def read_musicxml (filename, compressed, use_lxml):
2660     raw_string = None
2661     if compressed:
2662         if filename == "-":
2663              progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
2664              z = zipfile.ZipFile (sys.stdin)
2665         else:
2666             progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
2667             z = zipfile.ZipFile (filename, "r")
2668         container_xml = z.read ("META-INF/container.xml")
2669         if not container_xml:
2670             return None
2671         container = read_xml (StringIO.StringIO (container_xml), use_lxml)
2672         if not container:
2673             return None
2674         rootfiles = container.get_maybe_exist_named_child ('rootfiles')
2675         if not rootfiles:
2676             return None
2677         rootfile_list = rootfiles.get_named_children ('rootfile')
2678         mxml_file = None
2679         if len (rootfile_list) > 0:
2680             mxml_file = getattr (rootfile_list[0], 'full-path', None)
2681         if mxml_file:
2682             raw_string = z.read (mxml_file)
2683
2684     if raw_string:
2685         io_object = StringIO.StringIO (raw_string)
2686     elif filename == "-":
2687         io_object = sys.stdin
2688     else:
2689         io_object = filename
2690
2691     return read_xml (io_object, use_lxml)
2692
2693
2694 def convert (filename, options):
2695     if filename == "-":
2696         progress (_ ("Reading MusicXML from Standard input ...") )
2697     else:
2698         progress (_ ("Reading MusicXML from %s ...") % filename)
2699
2700     tree = read_musicxml (filename, options.compressed, options.use_lxml)
2701     score_information = extract_score_information (tree)
2702     paper_information = extract_paper_information (tree)
2703
2704     parts = tree.get_typed_children (musicxml.Part)
2705     (voices, staff_info) = get_all_voices (parts)
2706
2707     score = None
2708     mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
2709     if mxl_pl:
2710         score = extract_score_structure (mxl_pl, staff_info)
2711         part_list = mxl_pl.get_named_children ("score-part")
2712
2713     # score information is contained in the <work>, <identification> or <movement-title> tags
2714     update_score_setup (score, part_list, voices)
2715     # After the conversion, update the list of settings for the \layout block
2716     update_layout_information ()
2717
2718     if not options.output_name:
2719         options.output_name = os.path.basename (filename) 
2720         options.output_name = os.path.splitext (options.output_name)[0]
2721     elif re.match (".*\.ly", options.output_name):
2722         options.output_name = os.path.splitext (options.output_name)[0]
2723
2724
2725     #defs_ly_name = options.output_name + '-defs.ly'
2726     if (options.output_name == "-"):
2727       output_ly_name = 'Standard output'
2728     else:
2729       output_ly_name = options.output_name + '.ly'
2730
2731     progress (_ ("Output to `%s'") % output_ly_name)
2732     printer = musicexp.Output_printer()
2733     #progress (_ ("Output to `%s'") % defs_ly_name)
2734     if (options.output_name == "-"):
2735       printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
2736     else:
2737       printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
2738     print_ly_preamble (printer, filename)
2739     print_ly_additional_definitions (printer, filename)
2740     if score_information:
2741         score_information.print_ly (printer)
2742     if paper_information:
2743         paper_information.print_ly (printer)
2744     if layout_information:
2745         layout_information.print_ly (printer)
2746     print_voice_definitions (printer, part_list, voices)
2747     
2748     printer.newline ()
2749     printer.dump ("% The score definition")
2750     printer.newline ()
2751     score.print_ly (printer)
2752     printer.newline ()
2753
2754     return voices
2755
2756 def get_existing_filename_with_extension (filename, ext):
2757     if os.path.exists (filename):
2758         return filename
2759     newfilename = filename + "." + ext
2760     if os.path.exists (newfilename):
2761         return newfilename;
2762     newfilename = filename + ext
2763     if os.path.exists (newfilename):
2764         return newfilename;
2765     return ''
2766
2767 def main ():
2768     opt_parser = option_parser()
2769
2770     global options
2771     (options, args) = opt_parser.parse_args ()
2772     if not args:
2773         opt_parser.print_usage()
2774         sys.exit (2)
2775
2776     if options.language:
2777         musicexp.set_pitch_language (options.language)
2778         needed_additional_definitions.append (options.language)
2779         additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
2780     conversion_settings.ignore_beaming = not options.convert_beaming
2781
2782     # Allow the user to leave out the .xml or xml on the filename
2783     basefilename = args[0].decode('utf-8')
2784     if basefilename == "-": # Read from stdin
2785         basefilename = "-"
2786     else:
2787         filename = get_existing_filename_with_extension (basefilename, "xml")
2788         if not filename:
2789             filename = get_existing_filename_with_extension (basefilename, "mxl")
2790             options.compressed = True
2791     if filename and filename.endswith ("mxl"):
2792         options.compressed = True
2793
2794     if filename and (filename == "-" or os.path.exists (filename)):
2795         voices = convert (filename, options)
2796     else:
2797         progress (_ ("Unable to find input file %s") % basefilename)
2798
2799 if __name__ == '__main__':
2800     main()