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