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